如何使用 MVVM 应用程序在 WPF 中以编程方式设置 DataGrid 的选定项?

Posted

技术标签:

【中文标题】如何使用 MVVM 应用程序在 WPF 中以编程方式设置 DataGrid 的选定项?【英文标题】:How to set selected item of a DataGrid programmatically in WPF with MVVM application? 【发布时间】:2013-03-27 09:48:52 【问题描述】:

我已将DataTable 绑定到DataGrid 控件。如何以编程方式设置所选项目?

例子

在我的view model 中,我有一个 DataTable 类型的属性来绑定 DataGrid

 private DataTable sizeQuantityTable;

 public DataTable SizeQuantityTable
 
        get
        
            return sizeQuantityTable;
        
        set
        
            sizeQuantityTable = value;
            NotifyPropertyChanged("SizeQuantityTable");
        
  

我的XAML

<DataGrid 
            ItemsSource="Binding SizeQuantityTable"
            AutoGenerateColumns="True" 
            Margin="0,0,0,120" />

视图模型的constructor(分配虚拟值)

this.SizeQuantityTable = new DataTable();

DataColumn sizeQuantityColumn = new DataColumn();
sizeQuantityColumn.ColumnName = "Size Quantity";
this.SizeQuantityTable.Columns.Add(sizeQuantityColumn);

DataColumn sColumn = new DataColumn();
sColumn.ColumnName = "S";
this.SizeQuantityTable.Columns.Add(sColumn);

DataColumn mColumn = new DataColumn();
mColumn.ColumnName = "M";
this.SizeQuantityTable.Columns.Add(mColumn);

DataRow row1 = this.SizeQuantityTable.NewRow();
row1[sizeQuantityColumn] = "Blue";
row1[sColumn] = "12";
row1[mColumn] = "15";
this.SizeQuantityTable.Rows.Add(row1);

DataRow row2 = this.SizeQuantityTable.NewRow();
row2[sizeQuantityColumn] = "Red";
row2[sColumn] = "18";
row2[mColumn] = "21";
this.SizeQuantityTable.Rows.Add(row2);

DataRow row3 = this.SizeQuantityTable.NewRow();
row3[sizeQuantityColumn] = "Green";
row3[sColumn] = "24";
row3[mColumn] = "27";
this.SizeQuantityTable.Rows.Add(row3);

好的。我创建了三列,即sizeQuantityColumnsColumnmColumn,并添加了三行,即row1row2row2

所以,假设我想将选定的项目设置为row2(所以在视图中,第二行应该突出显示)。

我该怎么做?

编辑

我将 DataGrid 的 SelectedIndex 硬编码为 1。(所以应该选择第二行)。在design time 中,它显示为选中。但不是在运行时。您可以在下面的快照中看到它。

所以最终问题是没有突出显示该行。

【问题讨论】:

【参考方案1】:

有几种方法可以选择DataGrid 中的项目。这只是取决于哪一个最适合这种情况

首先也是最基本的是SelectedIndex,这只会选择DataGrid中该索引处的行

 <DataGrid SelectedIndex="Binding SelectedIndex" />

private int _selectedIndex;
public int SelectedIndex

    get  return _selectedIndex; 
    set  _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); 


SelectedIndex = 2;

SelectedItem 将选择与您设置的行匹配的行

<DataGrid SelectedItem="Binding SelectedRow" />

private DataRow _selectedRow;
public DataRow SelectedRow

    get  return _selectedRow; 
    set  _selectedRow = value; NotifyPropertyChanged("SelectedRow");


SelectedRow = items.First(x => x.whatever == something);

最常见的是SelectedValueSelectedValuePath 设置,在这种情况下你设置你要选择的列,然后可以通过设置相应的值来选择行

<DataGrid SelectedValuePath="Size Quantity" SelectedValue="Binding SelectionValue" 

private string _selectedValue
public string SelectionValue 

    get  return _selectedValue; 
    set  _selectedValue = value; NotifyPropertyChanged("SelectionValue"); 


SelectionValue = "Blue";

编辑:

这是我的测试,它突出显示很好

代码:

public partial class MainWindow : Window, INotifyPropertyChanged

    public MainWindow()
    
        InitializeComponent();

        this.SizeQuantityTable = new DataTable();
        DataColumn sizeQuantityColumn = new DataColumn();
        sizeQuantityColumn.ColumnName = "Size Quantity";
        ...................
        ........

    

    private string _selectedValue;
    public string SelectionValue 
    
        get  return _selectedValue; 
        set  _selectedValue = value; NotifyPropertyChanged("SelectionValue"); 
    

    private int _selectedIndex;
    public int SelectedIndex
    
        get  return _selectedIndex; 
        set  _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); 
    

    private DataTable sizeQuantityTable;
    public DataTable SizeQuantityTable
    
        get  return sizeQuantityTable; 
        set  sizeQuantityTable = value; NotifyPropertyChanged("SizeQuantityTable"); 
    

    private void Button_Click_1(object sender, RoutedEventArgs e)
    
        SelectedIndex = 2;
    

    private void Button_Click_2(object sender, RoutedEventArgs e)
    
        SelectionValue = "Blue";
    

    private void NotifyPropertyChanged(string p)
    
        if (PropertyChanged != null)
        
            PropertyChanged(this, new PropertyChangedEventArgs(p));
        
    

Xaml:

<Window x:Class="WpfApplication21.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="202" Width="232" Name="UI">

    <Grid DataContext="Binding ElementName=UI">
        <DataGrid SelectedValuePath="Size Quantity"        
                  SelectedValue="Binding SelectionValue" 
                  SelectedIndex="Binding SelectedIndex"
                  ItemsSource="Binding SizeQuantityTable"
                  AutoGenerateColumns="True" 
                  Margin="0,0,0,41" />
        <StackPanel Orientation="Horizontal" Height="37" VerticalAlignment="Bottom" >
            <Button Content="SelectedIndex" Height="26"  Width="107" Click="Button_Click_1"/>
            <Button Content="SelectedValue" Height="26"  Width="107" Click="Button_Click_2"/>
        </StackPanel>
    </Grid>
</Window>

结果:

【讨论】:

非常感谢。我试图绑定SelectedItemSelectedValue 属性。但我无法完成我的任务。然后按照您的建议,我尝试使用SelectedIndex。然后在设计时,它显示它是被选中的。但是在运行时它不会显示为选中状态。问题是即使它已被选中,该行也未突出显示 我已经更新了这个问题。所以现在的问题是选中的行没有高亮 您是否使用Test 按钮设置SelectedIndex 号..号..Test按钮做nothing。 (我只用它来debug 目的) 确保在加载完所有内容后设置选择,尝试将选择代码放在测试按钮中,它应该会突出显示,如果有帮助,我还发布了我的测试应用程序:)【参考方案2】:

您始终可以使用SelectedItem 属性并将其绑定到行,如下所示:

SelectedItem="Binding ActiveRow"

ViewModel 做:

ActiveRow = secondRow;

【讨论】:

我也试过这种方式。我定义了一个DataRowView 类型的属性并绑定到DataGrid 控件的SelectedItem 属性。这样我就可以get选中的项目了。但是我couldn't set选中的项目programmaticaly 也许你忘了使用双向绑定。 SelectedItem="Binding ActiveRow, Mode=TwoWay" 怎么样?【参考方案3】:

在您的 DataGrid 中添加 SelectedItem、SelectedValue。

<DataGrid 
        ItemsSource="Binding SizeQuantityTable"
        AutoGenerateColumns="True" 
        SelectedValue ="Binding SelectedValue"
        Margin="0,0,0,120" />

在你的视图模型中

private string _selectedValue;
public string SelectedValue

    get
    
        return _selectedValue;
    
    set
    
        _selectedValue = value;
        NotifyPropertyChanged("SelectedValue");
    


private DataTable sizeQuantityTable;
public DataTable SizeQuantityTable

    get
    
        return sizeQuantityTable;
    
    set
    
        sizeQuantityTable = value;
        NotifyPropertyChanged("SizeQuantityTable");
    

您也可以使用 SelectedItem,这是首选。

【讨论】:

【参考方案4】:

我也遇到了同样的问题。 我看到在设计时正确选择了数据网格的项目,但在运行时没有。 (顺便说一下,我在 xaml 中创建了视图模型的实例)。

我可以通过移动代码以编程方式将所选项目从视图模型构造函数设置到视图模型中的不同方法,然后在窗口(或用户控件)的加载事件中调用此方法来解决此问题。

很明显,当调用视图模型构造函数时,视图本身并没有完全完成初始化。 这可以通过不在视图模型构造函数中编码来避免。

查看(xaml):

<Window x:Class="MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Test" 
        xmlns:viewModel="clr-namespace:ViewModels" Loaded="Window_Loaded">
    <Window.DataContext>
        <viewModel:ExampleViewModel/>
    </Window.DataContext>

查看(后面的代码):

    private void Window_Loaded(object sender, RoutedEventArgs e)
    
        ((ExampleViewModel)this.DataContext).LoadData();
    

如果您不喜欢在后面的代码中设置Loaded 事件,您也可以在 xaml 中进行(需要引用“Microsoft.Expression.Interactions”和“System.Windows.Interactivity”):

    <Window x:Class="MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Test" 
        xmlns:viewModel="clr-namespace:ViewModels">
    <Window.DataContext>
        <viewModel:ExampleViewModel/>
    </Window.DataContext>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <ei:CallMethodAction TargetObject="Binding" MethodName="LoadData"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

在每种情况下,您都调用 ViewModel 中的 LoadData 方法:

public class ExampleViewModel

    /// <summary>
    /// Constructor.
    /// </summary>
    public ExampleViewModel()
    
        // Do NOT set selected item here
    


    public void LoadData()
    
        // Set selected item here
    

【讨论】:

【参考方案5】:

对于使用 Observable Collections 的任何人,您可能会发现此解决方案很有用。

SelectedModelIndex 属性仅返回 ItemSource 集合中 SelectedModel 的索引。我发现设置 SelectedIndexSelectedItem 突出显示 DataGrid 中的行。

    private ObservableCollection<Model> _itemSource
    public ObservableCollection<Model> ItemSource
    
        get  return _itemSource; 
        set 
         
            _itemSource = value; 
            OnPropertyChanged("ItemSource"); 
        
    

    // Binding must be set to One-Way for read-only properties
    public int SelectedModelIndex
    
        get 
        
            if (ItemSource != null && ItemSource.Count > 0)
                return ItemSource.IndexOf(SelectedModel);
            else
                return -1;
                    
    

    private Model _selectedModel;
    public Model SelectedModel
    
        get  return _selectedModel; 
        set 
        
            _selectedModel = value;
            OnPropertyChanged("SelectedModel"); 
            OnPropertyChanged("SelectedModelIndex");               
                        
    

【讨论】:

【参考方案6】:

xaml:

<DataGrid  SelectionUnit="FullRow" >
</DataGrid>

代码

SelectRowByIndex(yourDataGridViewName, 0);

public static void SelectRowByIndex(DataGrid dataGrid, int rowIndex)
        
            if (!dataGrid.SelectionUnit.Equals(DataGridSelectionUnit.FullRow))
                throw new ArgumentException("The SelectionUnit of the DataGrid must be set to FullRow.");

            if (rowIndex < 0 || rowIndex > (dataGrid.Items.Count - 1))
                throw new ArgumentException(string.Format("0 is an invalid row index.", rowIndex));

            dataGrid.SelectedItems.Clear();
            /* set the SelectedItem property */
            object item = dataGrid.Items[rowIndex]; // = Product X
            dataGrid.SelectedItem = item;

            DataGridRow row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
            if (row == null)
            
                /* bring the data item (Product object) into view
                 * in case it has been virtualized away */
                dataGrid.ScrollIntoView(item);
                row = dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex) as DataGridRow;
            
            //TODO: Retrieve and focus a DataGridCell object
            if (row != null)
            
                DataGridCell cell = GetCell(dataGrid, row, 0);
                if (cell != null)
                    cell.Focus();
            
        


 public static DataGridCell GetCell(DataGrid dataGrid, DataGridRow rowContainer, int column)
    
        if (rowContainer != null)
        
            DataGridCellsPresenter presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
            if (presenter == null)
            
                /* if the row has been virtualized away, call its ApplyTemplate() method
                 * to build its visual tree in order for the DataGridCellsPresenter
                 * and the DataGridCells to be created */
                rowContainer.ApplyTemplate();
                presenter = FindVisualChild<DataGridCellsPresenter>(rowContainer);
            
            if (presenter != null)
            
                DataGridCell cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
                if (cell == null)
                
                    /* bring the column into view
                     * in case it has been virtualized away */
                    dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]);
                    cell = presenter.ItemContainerGenerator.ContainerFromIndex(column) as DataGridCell;
                
                return cell;
            
        
        return null;
    
    public static childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
    
        foreach (childItem child in FindVisualChildren<childItem>(obj))
        
            return child;
        

        return null;
    
    public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj)
   where T : DependencyObject
    
        if (depObj != null)
        
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                
                    yield return (T)child;
                

                foreach (T childOfChild in FindVisualChildren<T>(child))
                
                    yield return childOfChild;
                
            
        
     

【讨论】:

以上是关于如何使用 MVVM 应用程序在 WPF 中以编程方式设置 DataGrid 的选定项?的主要内容,如果未能解决你的问题,请参考以下文章

如何从数据网格中以特定格式创建 txt 文件?

WPF经典编程模式-MVVM示例讲解

如何在 WPF/MVVM 应用程序中处理依赖注入

WPF:如何使用 MVVM 将命令绑定到 ListBoxItem?

WPF MVVM

如何使用 MVVM 模式“禁用”WPF 中的按钮?