WPF中DataGrid自动编辑单元格后Enter一键聚焦TextBox问题
WPF DataGrid自动编辑后按Enter需两次才能返回TextBox的问题
实现功能:用户在TextBox输入ID字符串,加载数据填充DataGrid,自动触发指定行的目标单元格进入编辑状态;用户在该单元格输入值后按Enter键,焦点需回到TextBox。
问题:自动触发编辑后,需按两次Enter键才能回到TextBox;手动用鼠标选择单元格编辑时,一次Enter即可正常返回焦点。自动单元格编辑通过附加属性监听CurrentCellChanged事件并触发DataGrid.BeginEdit实现,尝试在XAML后台的PreviewKeyDown方法中提交/取消编辑无效,怀疑需修改CurrentCellChanged的实现逻辑。
XAML代码
<Window x:Class="TestChangingEditedCellFocus.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:TestChangingEditedCellFocus" d:DataContext="{d:DesignInstance Type=local:ViewModel}" mc:Ignorable="d" FocusManager.FocusedElement="{Binding ElementName=idfield}" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <ResourceDictionary> <CollectionViewSource x:Key="srModels" Source="{Binding Models}"/> </ResourceDictionary> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="40"/> <RowDefinition/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <TextBox x:Name="idfield" Width="100" Height="30" Text="{Binding Path=ModelId, UpdateSourceTrigger=PropertyChanged}"/> <Button IsDefault="True" Command="{Binding Path=Command}" Content="Get Row" Height="30"/> </StackPanel> <DataGrid Grid.Row="1" x:Name="modelgrid" local:DataGridAutoEdit.AutoEditColumn="2" ItemsSource="{Binding Source={StaticResource srModels}}" SelectedItem="{Binding CurrentModel, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow" PreviewKeyDown="modelgrid_PreviewKeyDown" > <DataGrid.Columns> <DataGridTemplateColumn Header="ID" Width="Auto" IsReadOnly="True"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Label Content="{Binding Id}" HorizontalContentAlignment="Right"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn Header="Name" Width="Auto" IsReadOnly="True"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Label Content="{Binding Name}" HorizontalContentAlignment="Right"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn Header="Value" Width="Auto"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" VerticalContentAlignment="Center" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Right" FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </Grid> </Window>
后台代码
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new ViewModel(); } private void modelgrid_PreviewKeyDown(object sender, KeyEventArgs e) { if ((e.Key == Key.Enter) || (e.Key == Key.Return)) { //DataGrid grid = sender as DataGrid; //grid.CancelEdit(); idfield.Focus(); idfield.SelectAll(); } } }
ViewModel代码
public class ViewModel : ObservableObject { private ObservableCollection<model> _models; private model _currentModel; private string _modelId; private ICommand _command; public ObservableCollection<model> Models { get { if (_models is null) { _models = new ObservableCollection<model>(); OnPropertyChanged("Models"); } return _models; } } public model CurrentModel { get { return _currentModel; } set { _currentModel = value; OnPropertyChanged("CurrentModel"); } } public string ModelId { get { return _modelId; } set { _modelId = value; OnPropertyChanged("ModelId"); } } public ICommand Command { get { if (_command == null) { _command = new RelayCommand(param=>ExcecuteCommand(), pred=> ModelId is not null); } return _command; } } public void ExcecuteCommand() { Models.Clear(); if (Models.Count == 0) { Models.Add(new model() { Id = 1, Name = "name1" }); Models.Add(new model() { Id = 2, Name = "name2" }); Models.Add(new model() { Id = 3, Name = "name3" }); Models.Add(new model() { Id = 4, Name = "name4" }); } foreach (model model in Models) { System.Diagnostics.Debug.WriteLine("Model: " + model.Name); int id = int.Parse(ModelId); if(id == model.Id) { CurrentModel = model; } } ModelId = null; } }
DataGrid自动编辑辅助类
class DataGridAutoEdit { public static readonly DependencyProperty AutoEditColumnProperty = DependencyProperty.RegisterAttached("AutoEditColumn", typeof(int), typeof(DataGridAutoEdit), new PropertyMetadata(default(int), AutoEditColumnChangedCallback)); public static void SetAutoEditColumn(DependencyObject element, int value) { element.SetValue(AutoEditColumnProperty, value); } public static int GetAutoEditColumn(DependencyObject element) { return (int)element.GetValue(AutoEditColumnProperty); } private static void AutoEditColumnChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not DataGrid dataGrid) return; GetAutoEditColumnHelper(dataGrid)?.Dispose(); if (e.NewValue is int columnIndex) { SetAutoEditColumnHelper(d, new AutoEditColumnHelper(dataGrid, columnIndex)); } else { d.ClearValue(AutoEditColumnHelperProperty); } } private static readonly DependencyProperty AutoEditColumnHelperProperty = DependencyProperty.RegisterAttached("AutoEditColumnHelper", typeof(AutoEditColumnHelper), typeof(DataGridAutoEdit), new PropertyMetadata(default(AutoEditColumnHelper))); private static void SetAutoEditColumnHelper(DependencyObject element, AutoEditColumnHelper value) { element.SetValue(AutoEditColumnHelperProperty, value); } private static AutoEditColumnHelper? GetAutoEditColumnHelper(DependencyObject element) { return element.GetValue(AutoEditColumnHelperProperty) as AutoEditColumnHelper; } private class AutoEditColumnHelper : IDisposable { private readonly DataGrid _dataGrid; private readonly int _columnIndex; private object? _lastItem; public AutoEditColumnHelper(DataGrid dataGrid, int columnIndex) { _dataGrid = dataGrid; _columnIndex = columnIndex; _dataGrid.CurrentCellChanged += OnCurrentCellChanged; } public void Dispose() { _dataGrid.CurrentCellChanged -= OnCurrentCellChanged; } private void OnCurrentCellChanged(object? sender, EventArgs e) { DataGridCellInfo currentCell = _dataGrid.CurrentCell; if (!currentCell.IsValid || Equals(currentCell.Item, _lastItem)) { return; } DataGridColumn autoEditColumn = GetAutoEditColumn(); if (autoEditColumn is null) { return; } _dataGrid.Dispatcher.BeginInvoke(() => { _lastItem = _dataGrid.SelectedItem; _dataGrid.CurrentCell = new DataGridCellInfo(_lastItem, autoEditColumn); _dataGrid.BeginEdit(); }); } private DataGridColumn? GetAutoEditColumn() { return _columnIndex < 0 || _columnIndex > _dataGrid.Columns.Count ? null : _dataGrid.Columns[_columnIndex]; } } }
问题原因与解决方法
原因分析
自动触发BeginEdit时,DataGrid的编辑状态层级与手动编辑不同:第一次Enter键仅用于结束单元格的编辑状态,并未触发PreviewKeyDown事件;第二次Enter才会触发事件并执行焦点切换逻辑。而手动编辑时,单元格编辑完成后Enter键会直接触发PreviewKeyDown。
解决方法
1. 修改PreviewKeyDown事件处理逻辑
确保先提交DataGrid的编辑状态,再切换焦点,并标记事件已处理:
private void modelgrid_PreviewKeyDown(object sender, KeyEventArgs e) { if ((e.Key == Key.Enter) || (e.Key == Key.Return)) { var grid = sender as DataGrid; // 提交单元格编辑,确保数据更新 grid.CommitEdit(DataGridEditingUnit.Cell, true); // 结束DataGrid的整体编辑状态 grid.CommitEdit(); idfield.Focus(); idfield.SelectAll(); // 标记事件已处理,避免后续默认逻辑干扰 e.Handled = true; } }
2. 优化自动编辑的焦点定位
在辅助类的BeginEdit后,主动将焦点设置到单元格内的TextBox,确保编辑状态的焦点层级正确:
- 先给XAML中Value列的TextBox命名:
<TextBox x:Name="valueTextBox" Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" VerticalContentAlignment="Center" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" HorizontalContentAlignment="Right" FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>
- 修改辅助类的
OnCurrentCellChanged方法:
_dataGrid.Dispatcher.BeginInvoke(() => { _lastItem = _dataGrid.SelectedItem; _dataGrid.CurrentCell = new DataGridCellInfo(_lastItem, autoEditColumn); if (_dataGrid.BeginEdit()) { // 获取单元格内的TextBox并设置焦点 var cellContent = _dataGrid.CurrentCell.Column.GetCellContent(_lastItem); var textBox = cellContent?.FindName("valueTextBox") as TextBox; textBox?.Focus(); } });
通过以上两种方式结合,即可实现自动编辑后按一次Enter键就回到TextBox的需求。
内容的提问来源于stack exchange,提问作者Hulikoi
相关产品推荐
相关产品推荐

