我在WPF应用程序中有一个数据网格。
我的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事件,因为我想将 Enter 作为 TAB 。也用于焦点目的。你会理解它。
实际上,我的DataGrid包含3个自动生成的列。当我在任何行的第三个单元格中输入数据,并按 Enter 时,应添加一个新行,因为默认情况下CanUsersAddRows设置为true。但是第三个的焦点是新创建的行的第三个单元格。但我希望焦点集中在新创建的行的第一个单元格上。我已经通过在上面提到的代码中使用PreviewKeyDown事件成功实现了这一目标。
我也尝试过以下的事情。当焦点位于最后一行的第一个单元格时,如果我将该单元格保留为空,并且如果我按 Enter ,则焦点应该放在Grid外部声明的按钮上。我部分地完成了这件事。为什么我使用了部分????为此,请参阅下面的问题部分。
我在上面提到的代码中使用了GotFocus和LostFocus事件,因为我想在DataGrid失去焦点时删除DataGrid末尾的自动生成的行。我也已经解决了这个问题。
问题:
当我在DataGrid中输入一些数据时,例如:如果我在DataGrid的三行中输入一些数据。那时DataGrid的第三行第三个单元有焦点。现在,当我按 Enter 时,会创建一个新行。称之为第四排。现在重点关注第四排的第一个单元格。现在,如果我将该单元格留空并按 Enter ,则焦点应该转到DataGrid外部的Button,并且预计将删除第四行(自动生成的行)。到目前为止,我的应用程序工作正常。
现在,DataGrid外部的按钮会被聚焦。现在,如果我按下箭头键,焦点将转到第一行的第三个单元格。简而言之,DataGrid获得焦点,因此由于DataGrid的GotFocus事件中的代码,CanUserAddRows变为true。现在,如果我到达第四行的第一个单元格,多次按 Enter ,我预计会出现与上一段中提到的相同的行为。现在如果按 Enter 将第四行的第一个单元格留空,则焦点将转到第四行的第二个单元格。但是我希望它能够进入Button Outside DataGrid。
调试时发现的内容:
当第四行的第一个单元格第二次获得焦点时,就在此之前,一个新的空Person被添加到People Collection中。所以DataGrid不再将第四行视为AutoGenerated。我不知道如何阻止People集合添加该行。
示例以及如何重现该问题:
Here是我的示例项目。
要清楚地了解我的问题,请按照以下步骤操作:
将一些数据添加到DataGrid,如下所示:
A 输入
B 输入
C 输入
D 输入
E 输入
F 输入
G 输入
H 输入
我 输入
输入
焦点将转到按钮。按向下箭头键。现在再次按 Enter ,到达第四行的第一个单元格。由于第四行的第一个单元格为空,因此预期焦点将转到按钮,但它会继续到网格的下一个单元格。我试图解决过去15个小时左右的问题。但我没有成功。请给我一些关于这个问题的指导。
答案 0 :(得分:2)
首先,问题中所写的行为存在一些差异,并在样本中提供:
我在第一行的第一个单元格中输入了一些数据并按Enter键, 而不是焦点移动到第一行的第二个单元格,它移动到第一个 第二排的细胞。
要解决此问题,请删除此else语句:
else
{
if (ExtractBoundValue(row, dep as DataGridCell) == null
|| ExtractBoundValue(row, dep as DataGridCell).ToString().Trim() == "")
{
return;
}
}
当在任何行的第一个单元格上按下enter时,此语句停止第二个单元格获得焦点。
现在,针对您的实际问题。问题是当您在第三行的最后一个单元格上按Enter键时,第四行的第一个单元格进入编辑模式。因此,导致创建Person对象的实例。在将行置于编辑模式之前,首先检查行是否为 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();
}