WPF数据表格设置选定行

25

如何使用Datagrid.SelectedItem在程序中自动选择一行?

我首先需要创建一个DataGridRow对象的IEnumerable,然后将匹配的行传递给此SelectedItem属性吗?还是有其他方法呢?

编辑:

在选择行之前,我需要先匹配第一列单元格的单元格内容与TextBox.Text


你正在使用 WPF Toolkit 的 DataGrid 吗? - jsmith
@jsmith 是的,那就是我正在使用的。 - Tony The Lion
8个回答

42

我的代码遍历datagrid的第一列单元格,检查单元格内容是否等于textbox.text的值,并选择该行。

for (int i = 0; i < dataGrid.Items.Count; i++)
{
    DataGridRow row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(i);
    TextBlock cellContent = dataGrid.Columns[0].GetCellContent(row) as TextBlock;
    if (cellContent != null && cellContent.Text.Equals(textBox1.Text))
    {
        object item = dataGrid.Items[i];
        dataGrid.SelectedItem = item;
        dataGrid.ScrollIntoView(item);
        row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        break;
    }
}

2
在某些情况下,你可能会发现在dataGrid.ScrollIntoView()之前添加dataGrid.UpdateLayout()非常有用-正如MSDN所指出的那样:当ItemsSource集合的内容发生变化时,特别是如果向集合中添加或删除了许多项,你可能需要在调用ScrollIntoView来滚动指定项到视口之前调用UpdateLayout - jwaliszko
1
我尝试过这个,但它在我这里崩溃了,但上下文是好的。 我发现如果可视网格区域只有5行,但你有20行数据,ContainerFromIndex(i)会失败并返回一个行,如果不检查null就会抛出错误。 - DRapp
这只对用户可见的行起作用,是可能的吗?尽管在我的情况下 DataGrid.Items.Count 远远超过 1000,但 ContainerFromIndex(i) 在前 7 个元素之后开始返回 null。或者我漏掉了什么吗? - Boern

27

您不需要遍历 DataGrid 的行,可以使用更简单的解决方案实现您的目标。 为了匹配您的行,您可以通过遍历绑定到 DataGrid.ItemsSource 属性的集合,然后在程序中将此项分配给您的 DataGrid.SelectedItem 属性,或者如果要允许用户选择多行,则可以将其添加到 DataGrid.SelectedItems 集合中。请参见下面的代码:

<Window x:Class="ProgGridSelection.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" Loaded="OnWindowLoaded">
<StackPanel>
    <DataGrid Name="empDataGrid" ItemsSource="{Binding}" Height="200"/>
    <TextBox Name="empNameTextBox"/>
    <Button Content="Click" Click="OnSelectionButtonClick" />
</StackPanel>

public partial class MainWindow : Window
{
    public class Employee
    {
        public string Code { get; set; }
        public string Name { get; set; }
    }

    private ObservableCollection<Employee> _empCollection;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void OnWindowLoaded(object sender, RoutedEventArgs e)
    {
        // Generate test data
        _empCollection =
            new ObservableCollection<Employee>
                {
                    new Employee {Code = "E001", Name = "Mohammed A. Fadil"},
                    new Employee {Code = "E013", Name = "Ahmed Yousif"},
                    new Employee {Code = "E431", Name = "Jasmin Kamal"},
                };

        /* Set the Window.DataContext, alternatively you can set your
         * DataGrid DataContext property to the employees collection.
         * on the other hand, you you have to bind your DataGrid
         * DataContext property to the DataContext (see the XAML code)
         */
        DataContext = _empCollection;
    }

    private void OnSelectionButtonClick(object sender, RoutedEventArgs e)
    {
        /* select the employee that his name matches the
         * name on the TextBox
         */
        var emp = (from i in _empCollection
                   where i.Name == empNameTextBox.Text.Trim()
                   select i).FirstOrDefault();

        /* Now, to set the selected item on the DataGrid you just need
         * assign the matched employee to your DataGrid SeletedItem
         * property, alternatively you can add it to your DataGrid
         * SelectedItems collection if you want to allow the user
         * to select more than one row, e.g.:
         *    empDataGrid.SelectedItems.Add(emp);
         */
        if (emp != null)
            empDataGrid.SelectedItem = emp;
    }
}

@Jim,为什么?你尝试复制代码并在新项目中运行它作为概念验证了吗? - Mohammed A. Fadil

9

我曾经搜索过类似问题的解决方案,也许我的方法可以帮助您和任何遇到这个问题的人。

我在XAML DataGrid定义中使用了SelectedValuePath="id",并且编程时我只需要将DataGrid.SelectedValue设置为所需的值。

我知道这种解决方案有利有弊,但在特定情况下,它是快速且简单的。

最好的问候

Marcin


你好,很抱歉回复晚了。Marcin,能否分享一下拥有此问题的代码?这会对我很有帮助。我正在面临类似的问题。 - Indhi
这是最简单的解决方案! - Abbas

8
你正在尝试做的事情比我想象中更加棘手,因为你不能直接将 DataGrid 绑定到 DataTable。当你将 DataGrid.ItemsSource 绑定到 DataTable 时,实际上是将它绑定到默认的 DataView,而不是表本身。这就是为什么,例如,当你单击列标题时,DataGrid 不需要进行任何操作即可对行进行排序 - 因为该功能已经集成在 DataView 中,并且 DataGrid 知道如何通过 IBindingList 接口访问它。 DataView 实现了 IEnumerable<DataRowView>(或多或少),而 DataGrid 通过迭代来填充其项。这意味着当你将 DataGrid.ItemsSource 绑定到 DataTable 时,其 SelectedItem 属性将是一个 DataRowView,而不是一个 DataRow
如果你知道所有这些,那么构建一个包装器类来公开可以绑定的属性非常简单。有三个关键属性:
- Table,即 DataTable, - Row,一个双向可绑定的类型为 DataRowView 的属性,以及 - SearchText,一个字符串属性,当设置它时,将在表的默认视图中查找第一个匹配的 DataRowView,设置 Row 属性,并引发 PropertyChanged
它看起来像这样:
public class DataTableWrapper : INotifyPropertyChanged
{
    private DataRowView _Row;

    private string _SearchText;

    public DataTableWrapper()
    {
        // using a parameterless constructor lets you create it directly in XAML
        DataTable t = new DataTable();
        t.Columns.Add("id", typeof (int));
        t.Columns.Add("text", typeof (string));

        // let's acquire some sample data
        t.Rows.Add(new object[] { 1, "Tower"});
        t.Rows.Add(new object[] { 2, "Luxor" });
        t.Rows.Add(new object[] { 3, "American" });
        t.Rows.Add(new object[] { 4, "Festival" });
        t.Rows.Add(new object[] { 5, "Worldwide" });
        t.Rows.Add(new object[] { 6, "Continental" });
        t.Rows.Add(new object[] { 7, "Imperial" });

        Table = t;

    }

    // you should have this defined as a code snippet if you work with WPF
    private void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler h = PropertyChanged;
        if (h != null)
        {
            h(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    // SelectedItem gets bound to this two-way
    public DataRowView Row
    {
        get { return _Row; }
        set
        {
            if (_Row != value)
            {
                _Row = value;
                OnPropertyChanged("Row");
            }
        }
    }

    // the search TextBox is bound two-way to this
    public string SearchText
    {
        get { return _SearchText; }
        set
        {
            if (_SearchText != value)
            {
                _SearchText = value;
                Row = Table.DefaultView.OfType<DataRowView>()
                    .Where(x => x.Row.Field<string>("text").Contains(_SearchText))
                    .FirstOrDefault();
            }
        }
    }

    public DataTable Table { get; private set; }
}

以下是使用它的XAML代码:

<Window x:Class="DataGridSelectionDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
        xmlns:DataGridSelectionDemo="clr-namespace:DataGridSelectionDemo" 
        Title="DataGrid selection demo" 
        Height="350" 
        Width="525">
    <Window.DataContext>
        <DataGridSelectionDemo:DataTableWrapper />
    </Window.DataContext>
    <DockPanel>
        <Grid DockPanel.Dock="Top">
        <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Label>Text</Label>
            <TextBox Grid.Column="1" 
                     Text="{Binding SearchText, Mode=TwoWay}" />
        </Grid>
        <dg:DataGrid DockPanel.Dock="Top"
                     ItemsSource="{Binding Table}"
                     SelectedItem="{Binding Row, Mode=TwoWay}" />
    </DockPanel>
</Window>

2
尽管其他帖子似乎获得了更多的赞,但这篇文章似乎是讨论如何以最佳方式将数据集绑定到XML控件以充分利用工具功能的文章。 - Jeff Dege

2

// 一般情况下访问所有行 //

foreach (var item in dataGrid1.Items)
{
    string str = ((DataRowView)dataGrid1.Items[1]).Row["ColumnName"].ToString();
}

//访问选定的行 //

private void dataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    try
    {
        string str = ((DataRowView)dataGrid1.SelectedItem).Row["ColumnName"].ToString();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

1

我修改了serge_gubenko的代码,现在它运行得更好了

for (int i = 0; i < dataGrid.Items.Count; i++)
{
    string txt = searchTxt.Text;
    dataGrid.ScrollIntoView(dataGrid.Items[i]);
    DataGridRow row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(i);
    TextBlock cellContent = dataGrid.Columns[1].GetCellContent(row) as TextBlock;
    if (cellContent != null && cellContent.Text.ToLower().Equals(txt.ToLower()))
    {
        object item = dataGrid.Items[i];
        dataGrid.SelectedItem = item;
        dataGrid.ScrollIntoView(item);
        row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        break;
    }
}

1
我发现了一篇相对较新的TechNet文章(与问题的年龄相比),其中包括了我在这个主题上能找到的最佳技术:

WPF: Programmatically Selecting and Focusing a Row or Cell in a DataGrid

它包含了应该覆盖大多数要求的详细信息。重要的是要记住,如果为某些行指定了DataGridRow的自定义模板,则这些行将没有DataGridCells,然后网格的正常选择机制将无法工作。

您需要更具体地说明您已经为网格提供了什么数据源,以回答您问题的第一部分,就像其他人所说的那样。


FindVisualChild 技巧在 TechNet 文章中早已描述,并由 Andre Luus 友情提及,非常完美。 感谢 Andre 提供的链接。我记得早在 2013 年之前就使用了 Visual Child 的搜索,但时间过去了,你会忘记与非 WPF 技术一起工作的问题。 我以为现在,经过这么多年,WPF 开发人员必须改进库,并且像集中 DataGrid 行这样简单的任务可以用一两行代码解决。 但是,没有,我只能将解决方案“仅”压缩到 36 行。 太棒了!与 JS 方法相比较。 - Alexander Chernosvitov

0
如果有人在这里遇到了在OnSelectionChanged之后发生内部网格选择问题的情况 - 在尝试了数小时的所有选择器设置后,唯一有效的方法是重新加载和重新填充DataGrid以及所选项目。这并不优雅,但现在我不确定在我的情况下是否存在更好的解决方案。
datagrid.ItemsSource = null
datagrid.ItemsSource = items;
datagrid.SelectedItem = selectedItem;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接