You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 20:45:32