prwijerathne Ответов: 2

Как выбрать и установить фокус строки datagrid программно с помощью шаблона проектирования MVVM в WPF


I have a simple WPF application that has a data grid. What I want is to select and set focus data grid row once I click a button. When the row is selected, I need to change the selected (focused) row using keyboard up/down arrow keys. Most impotent thing is, I want to do this in MVVM design pattern. My application is as below.

My Item.cs class is as below:


public class Item
{
    public string ItemCode { get; set; }
    public string ItemName { get; set; }
    public double ItemPrice { get; set; }

    public Item(string itemCode, string itemName, double itemPrice)
    {
        this.ItemCode = itemCode;
        this.ItemName = itemName;
        this.ItemPrice = itemPrice;
    }
}


ItemViewModel.cs is as below:


public class ItemsViewModel : INotifyPropertyChanged
{
    private List<Item> _items;

    public List<Item> ItemsCollection
    {
        get { return this._items; }
        set
        {
            _items = value;
            OnPropertyChanged(nameof(ItemsCollection));
        }
    }

    public ItemsViewModel()
    {
        this.ItemsCollection = new List<Item>();
        //Add default items
        this.ItemsCollection.Add(new Item("I001", "Text Book", 10));
        this.ItemsCollection.Add(new Item("I002", "Pencil", 20));
        this.ItemsCollection.Add(new Item("I003", "Bag", 15));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}


MainWindowViewModel.cs as below:


public class MainWindowViewModel : INotifyPropertyChanged
{
    public ICommand SelectRow { get; private set; }

    public MainWindowViewModel()
    {
        this.SelectRow = new RelayCommand(this.SelectGridRow, o => true);
    }

    private void SelectGridRow(object param)
    {
        //TODO: Code should goes here
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}


I have written following RelayCommand.cs to handle the command binding


public class RelayCommand : ICommand
{
    #region Fields

    /// <summary>
    /// Encapsulated the execute action
    /// </summary>
    private Action<object> execute;

    /// <summary>
    /// Encapsulated the representation for the validation of the execute method
    /// </summary>
    private Predicate<object> canExecute;

    #endregion // Fields

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the RelayCommand class
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<object> execute)
        : this(execute, DefaultCanExecute)
    {
    }

    /// <summary>
    /// Initializes a new instance of the RelayCommand class
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }

        if (canExecute == null)
        {
            throw new ArgumentNullException("canExecute");
        }

        this.execute = execute;
        this.canExecute = canExecute;
    }

    #endregion // Constructors

    #region ICommand Members

    /// <summary>
    /// An event to raise when the CanExecute value is changed
    /// </summary>
    /// <remarks>
    /// Any subscription to this event will automatically subscribe to both 
    /// the local OnCanExecuteChanged method AND
    /// the CommandManager RequerySuggested event
    /// </remarks>
    public event EventHandler CanExecuteChanged
    {
        add
        {
            CommandManager.RequerySuggested += value;
            this.CanExecuteChangedInternal += value;
        }

        remove
        {
            CommandManager.RequerySuggested -= value;
            this.CanExecuteChangedInternal -= value;
        }
    }

    /// <summary>
    /// An event to allow the CanExecuteChanged event to be raised manually
    /// </summary>
    private event EventHandler CanExecuteChangedInternal;

    /// <summary>
    /// Defines if command can be executed
    /// </summary>
    /// <param name="parameter">the parameter that represents the validation method</param>
    /// <returns>true if the command can be executed</returns>
    public bool CanExecute(object parameter)
    {
        return this.canExecute != null && this.canExecute(parameter);
    }

    /// <summary>
    /// Execute the encapsulated command
    /// </summary>
    /// <param name="parameter">the parameter that represents the execution method</param>
    public void Execute(object parameter)
    {
        this.execute(parameter);
    }

    #endregion // ICommand Members

    /// <summary>
    /// Raises the can execute changed.
    /// </summary>
    public void OnCanExecuteChanged()
    {
        EventHandler handler = this.CanExecuteChangedInternal;
        if (handler != null)
        {
            //DispatcherHelper.BeginInvokeOnUIThread(() => handler.Invoke(this, EventArgs.Empty));
            handler.Invoke(this, EventArgs.Empty);
        }
    }

    /// <summary>
    /// Destroys this instance.
    /// </summary>
    public void Destroy()
    {
        this.canExecute = _ => false;
        this.execute = _ => { return; };
    }

    /// <summary>
    /// Defines if command can be executed (default behaviour)
    /// </summary>
    /// <param name="parameter">The parameter.</param>
    /// <returns>Always true</returns>
    private static bool DefaultCanExecute(object parameter)
    {
        return true;
    }
}


I have a ItemView.xaml User control as below:


<UserControl x:Class="DataGrid_FocusRow.ItemView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:DataGrid_FocusRow"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
<Grid>
    <StackPanel Orientation="Vertical">
        <DataGrid x:Name="grdItems" ItemsSource="{Binding ItemsCollection}" AutoGenerateColumns="False" ColumnWidth="*">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Item Code" Binding="{Binding ItemCode}" />
                <DataGridTextColumn Header="Item Name" Binding="{Binding ItemName}" />
                <DataGridTextColumn Header="Item Price" Binding="{Binding ItemPrice}" />
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Grid></UserControl>


Мой MainWindow.xaml выглядит следующим образом:

<Window x:Class="DataGrid_FocusRow.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:DataGrid_FocusRow"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<StackPanel>
    <local:ItemView/>

    <Button Command="{Binding SelectRow}">Select Row</Button>
</StackPanel></Window>


Что я уже пробовал:

I've already tried by setting "SelectedIndex" and "SelectedItem". It selects the row in the data grid. But it does not set focus to the particular row. Because of that I cannot change the selection by UP/DOWN keys in my keyboard.

Gerry Schmitz

"Самое главное" - это то, что он работает ... ваши "паттерны" вторичны.

2 Ответов

Рейтинг:
1

George Swan

Насколько я понимаю, методы, реализующие функциональность пользовательского элемента управления, должны находиться в коде, лежащем за xaml пользовательского элемента управления, а не определяться в ViewModel. Модель представления должна быть отделена от представления и ничего не знать о нем. Мое предложение состоит в том, чтобы добавить кнопку "выбрать строку" к UserControl и подпишитесь на его событие нажатия кнопки в коде позади. Установите DataContext в xaml привязка SelectedItem собственность компании DataGrid К SelectedItem свойство, определенное в ViewModel следующим образом.

<UserControl x:Class="DataGrid_FocusRow.ItemView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:DataGrid_FocusRow"
         mc:Ignorable="d" 
         d:DesignHeight="450" d:DesignWidth="800">
  <UserControl.DataContext>
        <local:ItemsViewModel/>
    </UserControl.DataContext>
    <Grid>
        <StackPanel Orientation="Vertical">
            <DataGrid x:Name="grdItems" ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" AutoGenerateColumns="False" ColumnWidth="*">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Item Code" Binding="{Binding ItemCode}" />
                    <DataGridTextColumn Header="Item Name" Binding="{Binding ItemName}" />
                    <DataGridTextColumn Header="Item Price" Binding="{Binding ItemPrice}" />
                </DataGrid.Columns>
            </DataGrid>
            <Button Click="Button_Click">Select Row</Button>
        </StackPanel>
    </Grid>
</UserControl>

Код позади должен содержать следующее частичное определение класса, которое добавляет функциональность, необходимую при нажатии кнопки.
public partial class ItemView : UserControl
   {
       public ItemView()
       {
           InitializeComponent();
       }
       private void Button_Click(object sender, RoutedEventArgs e)
       {
           if (grdItems.Items.Count == 0) return;
           object item = grdItems.Items[0];
           grdItems.SelectedItem= item;
           grdItems.ScrollIntoView(item);
          var row = (DataGridRow)grdItems.ItemContainerGenerator.ContainerFromIndex(grdItems.SelectedIndex);
           row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        }
   }

Если вы добавите SelectedItem свойство в ViewModel, он будет привязан к SelectedItem в DataGrid.
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace DataGrid_FocusRow
{
    public class ItemsViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<Item> _items;

        public ObservableCollection<Item> ItemsCollection
        {
            get { return _items; }
            set
            {
                _items = value;
                OnPropertyChanged();
            }
        }
        private Item selectedItem;
        public Item SelectedItem
        {
            get
            {
                return selectedItem;
            }
            set
            {
                selectedItem = value;
                OnPropertyChanged();
            }
        }

        public ItemsViewModel()
        {
            this.ItemsCollection = new ObservableCollection<Item>
            {
                //Add default items
                new Item("I001", "Text Book", 10),
                new Item("I002", "Pencil", 20),
                new Item("I003", "Bag", 15)
            };
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

}

Я должен добавить, что код для установки фокуса на a DataGrid Row это не оригинал, я сохранил его как фрагмент в прошлом, но не смог найти исходный источник.


Fehr Benjamin

Это событие нажатия кнопки в код позади не против шаблона MVVM? Кроме того, я придерживаюсь мнения, что пользователь должен выбрать только строку, чтобы получить значения внутри текстовых полей. Дополнительное нажатие кнопки не требуется.

George Swan

Спасибо за комментарий. Это вопрос личного выбора, я чувствую, что методы, реализующие функциональность пользовательского элемента управления, должны находиться в коде, стоящем за xaml пользовательского элемента управления, а не определяться в ViewModel. ViewModel не должен ничего знать о представлении. Я принимаю вашу точку зрения по поводу нажатия кнопки. Наилучшие пожелания.

Рейтинг:
1

Fehr Benjamin

На самом деле я решаю его без какого-либо кода позади и события нажатия кнопки.

модель представления:

 public ICommand SaveCommand { get; private set; }
        public ICommand UpdateCommand { get; private set; }
        public ICommand DeleteCommand { get; private set; }
        public ICommand PrintCommand { get; private set; }
        public ICommand ShowAdvCommand { get; private set; }
        public ICommand SelectionChangedCommand { get; set; }

        //observable collection for machine model
        private ObservableCollection<Machine> _dataContext;
        public ObservableCollection<Machine> DataContext
        {
            get { return _dataContext; }
            set { _dataContext = value; OnPropertyChanged(); }
        }

        private Machine machineSelectedItem;
        public Machine MachineSelectedItem
        {
            get { return machineSelectedItem; }
            set { machineSelectedItem = value; OnPropertyChanged(); }
        }
        public object MachineDataGrid { get; set; }

        //PRWContext for general use
        private PRWContext context = new PRWContext();

        public MachineViewModel()
        {
            //Commands for save, update, delete and print
            SaveCommand = new RelayCommand(() => ExecuteSaveCommand());
            UpdateCommand = new RelayCommand(() => ExecuteUpdateCommand());
            DeleteCommand = new RelayCommand(() => ExecuteDeleteCommand());
            PrintCommand = new RelayCommand(() => ExecutePrintCommand());
            SelectionChangedCommand = new RelayCommand(() => ExecuteSelectionChangedCommand());

            //Load the data from PRW Database to datagrid
            LoadData();
        }

        //execute save
        private void ExecuteSaveCommand()
        {
            Machine machine = new Machine
            {
                //Machine data
                MachineID = MachineID,
                CustomerID = CustomerID,
                CustomerName = CustomerName,
                City = City,
                Country = Country,

                //Serial data
                SpindleC1 = SpindleC1,
                SpindleC2 = SpindleC2,
                HoningHead = HoningHead,

                //Softwareversion data
                NCVersion = NCVersion,
                HMIVersion = HMIVersion,
                HRIVersion = HRIVersion,
                AHSVersion = AHSVersion
            };

            context.Machines.Add(machine);
            context.SaveChanges();

            ClearText();
        }

        //execute update
        private void ExecuteUpdateCommand()
        {
            Machine machine = context.Machines.FirstOrDefault(w => w.MachineID == MachineID);

            machine.CustomerID = CustomerID;
            machine.CustomerName = CustomerName;

            context.SaveChanges();
            ClearText();
        }

        //execute delete
        private void ExecuteDeleteCommand()
        {
            throw new NotImplementedException();
        }

        //execute print 
        private void ExecutePrintCommand()
        {
            throw new NotImplementedException();
        }

        // Execute selection changed
        private void ExecuteSelectionChangedCommand()
        {
            MachineID = machineSelectedItem.MachineID ? .ToString() ?? "";
            CustomerID = machineSelectedItem.CustomerID ? .ToString() ?? "";
        }
        //Load data from database to grid
        private void LoadData()
        {
            context.Machines.Load();
            this.DataContext = context.Machines.Local;
        }

        //Clear textboxes
        private void ClearText()
        {
            MachineID = string.Empty;
            CustomerID = string.Empty;
            CustomerName = string.Empty;
            City = string.Empty;
            Country = string.Empty;
            SpindleC1 = string.Empty;
            SpindleC2 = string.Empty;
            HoningHead = string.Empty;
            NCVersion = string.Empty;
            HMIVersion = string.Empty;
            HRIVersion = string.Empty;
            AHSVersion = string.Empty;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}


Смотреть:

<DataGrid x:Name="MachineDataGrid" AutoGenerateColumns="False" MaxHeight="750" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" ItemsSource="{Binding DataContext, Mode=TwoWay}" SelectedItem="{Binding Path=MachineSelectedItem, Mode=TwoWay}">
                    <ie:Interaction.Triggers>
                        <ie:EventTrigger EventName="SelectionChanged">
                            <ie:InvokeCommandAction Command="{Binding SelectionChangedCommand}"  CommandParameter="{Binding ElementName=MachineDataGrid, Path=SelectedItem}"/>
                        </ie:EventTrigger>
                    </ie:Interaction.Triggers>
                    <DataGrid.Columns>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgMachineID}" Binding="{Binding MachineID, Mode=TwoWay}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgCustomerId}" Binding="{Binding CustomerID}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgCustomerName}" Binding="{Binding CustomerName}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgCity}" Binding="{Binding City}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgCountry}" Binding="{Binding Country}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgSpindleC1}" Binding="{Binding SpindleC1}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgSpindleC2}" Binding="{Binding SpindleC2}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgHoningHead}" Binding="{Binding HoningHead}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgNCVersion}" Binding="{Binding NCVersion}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgHMIVersion}" Binding="{Binding HMIVersion}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgHRIVersion}" Binding="{Binding HRIVersion}"/>
                        <materialDesign:DataGridTextColumn Header="{x:Static language:Lang.DgAHSVersion}" Binding="{Binding AHSVersion}"/>
                    </DataGrid.Columns>
                </DataGrid>