Tab/Enter/箭头键和控件焦点问题与WPF数据网格和相邻控件



我在WPF应用程序中有一个数据网格。

My DataGrid绑定到一个名为People的Person集合,如下面的代码所示:

Person.cs

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string City { get; set; }
}

MainWindowViewModel.cs

public class MainWindowViewModel : INotifyPropertyChanged
{
    public MainWindowViewModel()
    {
        People = new ObservableCollection<Person>();
    }
    private ObservableCollection<Person> _people;
    public ObservableCollection<Person> People
    {
        get
        {
            return _people;
        }
        set
        {
            _people = value;
            OnPropertyChanged("People");
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MainWindow.xaml

<Window .......
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="65*"/>
            <ColumnDefinition Width="9*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="93*"/>
            <RowDefinition Height="14*"/>
        </Grid.RowDefinitions>
        <DataGrid x:Name="maindg" AutoGenerateColumns="True" ItemsSource="{Binding People}" Margin="0,0,0.4,-0.2" Grid.RowSpan="2" Grid.ColumnSpan="2" 
                  PreviewKeyDown="DataGrid_KeyDown_1" SelectedIndex="0" GridLinesVisibility="Vertical" 
                  SelectionMode="Single" SelectionUnit="CellOrRowHeader" GotFocus="maindg_GotFocus" LostFocus="maindg_LostFocus" />
        <Button Height="20" Width="50" Content="Save" Grid.Column="1" Grid.Row="1" x:Name="btnSave" />
    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    private int FindRowIndex(DataGridRow row)
    {
        DataGrid dataGrid =
            ItemsControl.ItemsControlFromItemContainer(row)
            as DataGrid;
        int index = dataGrid.ItemContainerGenerator.
            IndexFromContainer(row);
        return index;
    }
    private object ExtractBoundValue(DataGridRow row,
                             DataGridCell cell)
    {
        // find the column that this cell belongs to
        DataGridBoundColumn col =
           cell.Column as DataGridBoundColumn;
        // find the property that this column is bound to
        Binding binding = col.Binding as Binding;
        string boundPropertyName = binding.Path.Path;
        // find the object that is related to this row
        object data = row.Item;
        // extract the property value
        PropertyDescriptorCollection properties =
            TypeDescriptor.GetProperties(data);
        PropertyDescriptor property = properties[boundPropertyName];
        if (property != null)
        {
            object value = property.GetValue(data);
            return value;
        }
        return null;
    }
    private void DataGrid_KeyDown_1(object sender, KeyEventArgs e)
    {
        if (e.Key != Key.Enter) return;
        DependencyObject dep = (DependencyObject)e.OriginalSource;
        //here we just find the cell got focused ...
        //then we can use the cell key down or key up
        // iteratively traverse the visual tree
        while ((dep != null) && !(dep is DataGridCell) && !(dep is DataGridColumnHeader))
        {
            dep = VisualTreeHelper.GetParent(dep);
        }
        if (dep == null)
            return;
        if (dep is DataGridCell)
        {
            try
            {
                //cancel if datagrid in edit mode
                maindg.CommitEdit();

                //Check if selected cell is on first column and last row
                if (maindg.CurrentColumn.DisplayIndex == 0)
                {
                    DependencyObject dep1 = dep;
                    while ((dep1 != null) && !(dep1 is DataGridRow))
                    {
                        dep1 = VisualTreeHelper.GetParent(dep1);
                    }
                    DataGridRow row = dep1 as DataGridRow;
                    if (FindRowIndex(row) == maindg.Items.Count - 1)
                    {
                        if (ExtractBoundValue(row, dep as DataGridCell) == null || ExtractBoundValue(row, dep as DataGridCell).ToString().Trim() == "")
                        {
                            btnSave.Focus();
                            return;
                        }
                    }
                    else
                    {
                        if (ExtractBoundValue(row, dep as DataGridCell) == null || ExtractBoundValue(row, dep as DataGridCell).ToString().Trim() == "")
                        {
                            return;
                        }
                    }
                }
            }
            catch
            {
                maindg.CancelEdit();
            }
            //get current cell
            DataGridCell cell = dep as DataGridCell;
            //deselect current cell
            cell.IsSelected = false;
            //find next right cell
            var nextCell = cell.PredictFocus(FocusNavigationDirection.Right);
            //if next right cell null go for find next ro first cell
            if (nextCell == null)
            {
                DependencyObject nextRowCell;
                nextRowCell = cell.PredictFocus(FocusNavigationDirection.Down);
                //if next row is null so we have no more row Return;
                if (nextRowCell == null)
                {
                    nextRowCell = dep;
                    while ((nextRowCell as DataGridCell).PredictFocus(FocusNavigationDirection.Left) != null)
                        nextRowCell = (nextRowCell as DataGridCell).PredictFocus(FocusNavigationDirection.Left);
                    //change current cell
                    maindg.CurrentCell = new DataGridCellInfo(nextRowCell as DataGridCell);
                    //change selected cell
                    (nextRowCell as DataGridCell).IsSelected = true;
                    return;
                }
                //we do this because we cant use FocusNavigationDirection.Next for function PredictFocus
                //so we have to find it this way
                while ((nextRowCell as DataGridCell).PredictFocus(FocusNavigationDirection.Left) != null)
                    nextRowCell = (nextRowCell as DataGridCell).PredictFocus(FocusNavigationDirection.Left);
                //set new cell as next cell
                nextCell = nextRowCell;
            }
            //change current cell
            maindg.CurrentCell = new DataGridCellInfo(nextCell as DataGridCell);
            //change selected cell
            (nextCell as DataGridCell).IsSelected = true;
            // start edit mode
            maindg.BeginEdit();
        }
        //handl the default action of keydown
        e.Handled = true;
    }
    private void maindg_GotFocus(object sender, RoutedEventArgs e)
    {
        if (!maindg.CanUserAddRows)
        {
            maindg.CanUserAddRows = true;
        }
    }
    private void maindg_LostFocus(object sender, RoutedEventArgs e)
    {
        if (!maindg.IsKeyboardFocusWithin && maindg.CanUserAddRows)
        {
            maindg.CanUserAddRows = false;
        }
    }
}

正如你在上面的代码中看到的,我已经使用了DataGrid的三个事件,即:PreviewKeyDown, GotFocus和LostFocus。

我已经使用PreviewKeyDown事件,因为我想把我的输入作为TAB。也是为了聚焦的目的。你很快就会明白的。

实际上,我的DataGrid包含3个自动生成的列。当我在任何一行的第三个单元格中输入数据时,按输入应添加新行,因为CanUsersAddRows默认设置为true。但是第三个的焦点放在新创建行的第三个单元格上。但我希望焦点放在新创建行的第一个单元格上。我已经通过在上面提到的代码中使用PreviewKeyDown事件成功地实现了这一点。

我还试图实现以下事情。当焦点在最后一行的第一个单元格上时,如果我将该单元格保留为空,并且如果我按Enter,焦点应该在Grid外部声明的按钮上。我已经部分实现了这个目标。为什么我用了部分????这个词

我在上面提到的代码中使用了GotFocus和LostFocus事件,因为我想在DataGrid失去焦点时删除DataGrid末尾自动生成的行。我也找到了解决那个问题的方法。

问题:

当我在DataGrid中输入一些数据时,例如:如果我在DataGrid的三行中输入一些数据。此时DataGrid的第三行第三个单元格成为焦点。现在,当我按Enter时,将创建一个新行。称之为第四行。现在重点放在第四行的第一个单元格上。现在,如果我将该单元格保留为空并按Enter,则焦点预计将转到DataGrid外部的Button,并且预计将删除第四行(自动生成行)。到目前为止,我的应用程序运行良好。

现在,DataGrid外面的按钮被聚焦了。现在如果我按下箭头键,焦点就会移到第一行的第三个单元格。简而言之,DataGrid获得焦点,因此CanUserAddRows由于DataGrid的GotFocus事件中的代码而变为true。现在,如果我到达第四行的第一个单元格,通过多次按Enter,我期望与上段中提到的相同的行为。现在,如果我按Enter,使第四行的第一个单元格为空,则焦点将放在第四行的第二个单元格上。但是我希望它去Button Outside DataGrid。

调试时发现:

当第四行的第一个单元格第二次获得焦点时,就在此之前,一个新的空Person被添加到People Collection中。因此DataGrid不再将第四行视为自动生成的。我不知道如何阻止People集合添加那一行。

示例和如何重现问题:

这是我的项目示例。

要清楚地了解我的问题,请按照以下步骤:

  1. 下载示例应用程序并运行它。
  2. 按如下方式向DataGrid添加一些数据:

    输入

    输入

    C 输入

    D

    输入

    E

    输入

    F

    输入

    <

    G/kbd> 输入

    H 输入

    输入

    输入

    焦点将转到按钮。按下箭头键。现在,再次按,输入的次数与到达第四行的第一个单元格的次数一样多。由于第四行的第一个单元格是空的,因此焦点应该转到按钮上,但它会继续转到网格的下一个单元格。我正在努力解决过去15个小时左右的问题。但是我没有成功。

首先,问题中所写的行为与样本中提供的行为有些不一致:

我在第一行的第一个单元格中输入了一些数据并按下Enter,焦点不是移动到第一行的第二个单元格,而是移动到第一个单元格第二行单元格

要解决这个问题,请删除else语句:

else
{
   if (ExtractBoundValue(row, dep as DataGridCell) == null
       || ExtractBoundValue(row, dep as DataGridCell).ToString().Trim() == "")
   {
       return;
   }
}

当在任意一行的第一个单元格上按enter键时,此语句停止第二个单元格以获取焦点。


现在,谈谈你的实际问题。问题是当你按下第三行的最后一个单元格,第四行的第一个单元格进入编辑模式。因此,导致创建Person对象的实例。在将行置于编辑模式之前,首先检查row是否为 NewItemPlaceholder

替换最后一行

// start edit mode
maindg.BeginEdit();

使用这个检查 NewItemPlaceholder ,如果没有,则只将行置于编辑模式:

DependencyObject selectedRow = nextCell;
while ((selectedRow != null) && !(selectedRow is DataGridRow))
{
    selectedRow = VisualTreeHelper.GetParent(selectedRow);
}
if ((selectedRow as DataGridRow).Item.ToString() != "{NewItemPlaceholder}")
{
    // start edit mode
    maindg.BeginEdit();
}

最新更新