首页 > 解决方案 > 当 observablecollection ItemSource 更改 WPF 时,通过 MVVM 以编程方式关注行中的特定单元格

问题描述

我正在尝试将焦点设置在 DataGrid 中一行中的特定单元格上。数据网格绑定到 X 的可观察集合。当我点击特定按钮时,它会导致将新的 X 插入到可观察集合中,但是数据网格方面没有焦点,我想聚焦光标所以您可以输入新创建的行(行中的特定单元格)。这可能会走 MVVM 路线吗?我的虚拟机有可观察的集合,所以不太确定从这里去哪里......

标签: c#wpf

解决方案


我想出了一个简单的例子

1) MVVM 辅助类(用于可见性):

public class NotifyPropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
    public void Execute(object parameter) => _execute(parameter);
}

2) 标记

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" Margin="5">
        <Button Content="Add row" Command="{Binding AddRowCommand}"/>
    </StackPanel>
    <DataGrid ItemsSource="{Binding ItemsList}" Grid.Row="1" AutoGenerateColumns="False" LoadingRow="DataGrid_LoadingRow" CanUserAddRows="False" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Column 1" Binding="{Binding Column1}"/>
            <DataGridTextColumn Header="Column 2" Binding="{Binding Column2}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

3) ViewModel 和数据

public class MainViewModel : NotifyPropertyChanged
{
    private ObservableCollection<DataItem> _itemsList;
    private ICommand _addRowCommand;
    public ObservableCollection<DataItem> ItemsList
    {
        get => _itemsList;
        set
        {
            _itemsList = value;
            OnPropertyChanged();
        }
    }
    public ICommand AddRowCommand => _addRowCommand ?? (_addRowCommand = new RelayCommand(parameter =>
    {
        ItemsList.Add(new DataItem());
    }));
    public MainViewModel()
    {
        ItemsList = new ObservableCollection<DataItem>
        {
            new DataItem { Column1 = "aaa", Column2 = "bbb" },
            new DataItem { Column1 = "ccc", Column2 = "ddd" }
        };
    }
}

public class DataItem : NotifyPropertyChanged
{
    private string _column1;
    private string _column2;

    public string Column1
    {
        get => _column1;
        set
        {
            _column1 = value;
            OnPropertyChanged();
        }
    }
    public string Column2
    {
        get => _column2;
        set
        {
            _column2 = value;
            OnPropertyChanged();
        }
    }
}

4)和代码隐藏类中的事件处理程序

private void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
    DataGrid dataGrid = sender as DataGrid;

    // condition avoids execution when DataGrid initially loading
    // and case when DataGrid adding an empty row if UserCanAddRows is enabled
    if (dataGrid.IsLoaded && e.Row.Item is DataItem)
    {
        dataGrid.Focus(); // moving focus from button to DataGrid
        dataGrid.SelectedIndex = e.Row.GetIndex(); // highlighting current row as selected, just a visual thing, you may remove it
        dataGrid.CurrentCell = new DataGridCellInfo(e.Row.Item, dataGrid.Columns[0]); // set current cell
        Dispatcher.BeginInvoke((Action)(() =>
        {
            dataGrid.BeginEdit(); // to avoid focus issue I'm running this through Dispatcher asynchronously
        }));
    }
}

这不会破坏 MVVM,因为事件处理程序不与数据交互而只检查其类型。也许有一种更安全的方法来检查它是否NewItemPlaceholder不在e.Row.Item. 或者您可以删除类型检查以防万一CanUserAddRows设置为 false。

DataGrid在添加行时编辑单元格


推荐阅读