WPF如何遍历DataGrid?

21

使用 WPF C#.NET4.5 和 Visual Studio 2012 Ultimate。

旧的 WinForms 代码:

foreach (DataGridViewRow paretoRow in ParetoGrid.Rows)
{
       if ((Convert.ToInt32(paretoRow.Cells["CurrentPareto"].Value) < (Convert.ToInt32(paretoRow.Cells["NewPareto"].Value))))
       {
              paretoRow.Cells["pNew"].Value = downArrow
       }
}

正如您所看到的,我循环遍历每一行并检查特定单元格,如果为 true,则填充另一个单元格。这是我以前经常使用的好老的 winforms 代码... 但是,转换到 WPF 比我之前想象的要不同得多。

DataGrid 不包含 Row 属性。相反,我认为您需要使用:

DataGridRow paretoRow in paretogrid.Items

但我仍然不知道如何获取单元格。

所以我的问题是,是否需要进行语法更改?如果需要,应该在哪里进行更改?或者,我开始相信在WPF中,datagrids更多地使用对象而不是winforms,因此不需要使用名为“row”的属性。如果是这种情况,我应该在这个例子中知道什么逻辑/语法?

感谢大家的耐心等待,我想当我回家过银行假期时,会对WPF进行一些挖掘,看看它实际上有多不同。


1
这个应该有帮助:https://dev59.com/rmvXa4cB1Zd3GeqPFAU9 - Ian Gilroy
10个回答

32

人们似乎把这个问题想得太复杂了,下面是我的解决方法:

foreach (System.Data.DataRowView dr in yourDataGrid.ItemsSource)
{
     MessageBox.Show(dr[0].ToString());
}

2
我认为人们喜欢把事情复杂化。 - Yusha
我建议使用yourDataGrid.Items而不是yourDataGrid.ItemsSource。这将保留基于排序列的任何用户应用的排序方向。 - Jackson Enix
1
无法工作,显示错误无法将对象强制转换为System.Data.DataRowView。 - abdou_dev
这对我来说是一个奇迹般的发现。应该有一个网站专门提供常见问题的KISS解决方案,比如“最小代码解决方案”。 - Paul Wichtendahl
就像他们说的那样,简单中蕴含着光辉 :-) - Tiyyob

28

我认为你想要做的第一件事是获取你的 DataGrid 的所有行:

public IEnumerable<Microsoft.Windows.Controls.DataGridRow> GetDataGridRows(Microsoft.Windows.Controls.DataGrid grid)
{
    var itemsSource = grid.ItemsSource as IEnumerable;
    if (null == itemsSource) yield return null;
    foreach (var item in itemsSource)
    {
        var row = grid.ItemContainerGenerator.ContainerFromItem(item) as Microsoft.Windows.Controls.DataGridRow;
        if (null != row) yield return row;
    }
}

然后迭代您的网格:

var rows = GetDataGridRows(nameofyordatagrid); 

foreach (DataGridRow row in rows)  
{  
  DataRowView rowView = (DataRowView)row.Item;
  foreach (DataGridColumn column in nameofyordatagrid.Columns)
  {
      if (column.GetCellContent(row) is TextBlock)
      {
          TextBlock cellContent = column.GetCellContent(row) as TextBlock;
          MessageBox.Show(cellContent.Text);
      }
  } 

那么我如何获取所有行呢?看着我的foreach循环,我曾经使用DataGridViewRow,但是在WPF中它不起作用。你会如何获取所有行呢? - lemunk
谢谢提供代码,我会试试。但是一开始看起来比WinForms繁琐得多,一个简单的三行变得更加复杂了。但我想这是对我目前方法的修正,应该是正确的做法。我的想法对吗? - lemunk
在 var itemsource = grid.itemsource as IEnumerable 下出现了语法错误,错误是别名“IEnumerable”提示“需要1个类型参数”。 - lemunk
你需要确保在你的表格中有ItemSource。 - COLD TOLD
1
你需要确保在你的表格中有ItemSource。这是什么意思?请耐心等待我哈哈。 - lemunk
1
非常有用,但自从2009年3月Silverlight工具包发布以来,命名空间microsoft.windows.controls已更改为system.windows.controls:http://silverlight.codeplex.com/wikipage?title=Silverlight%20Toolkit%20March%202009%20change%20list代码会有一些变化,你需要添加以下内容: using System.Collections.Generic; using System.Collections;解决"IEnumerable"错误说"需要1个类型参数"的问题。 - Mr Rubix

1
我甚至不理解为什么在数据网格中获取行及其值如此复杂。感觉像是地狱一样难找。API甚至会给出有趣的事件名称,这也不是很直接。为什么人们不能专注于基线并提供确切所需内容,而不是各种没有用处和意义的不同选项。我的意思是,要吃饭只需要一把勺子和叉子对吧。自从10万年前以来就从未改变过。这是我的代码,感谢那位提到有些人试图过度复杂化事情并浪费你的时间的人。
    private void dtaResultGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        ActivateTestDatagridAccess();
    }

    public async void ActivateTestDatagridAccess()
    {
        try
        {
            await Task.Delay(500);
            foreach (System.Data.DataRowView dr in dtaResultGrid.ItemsSource)
            {
                for (int j = 0; j < dtaResultGrid.Columns.Count; j++)
                {
                    Console.WriteLine(dr[j].ToString());
                }
                Console.Write(Environment.NewLine);
            }
        }
        catch (Exception exrr)
        {
            Console.WriteLine(exrr.ToString());
        }
    }

1

从Charles那里得到的“最简单”的答案对我很有用。但我使用了Items而不是ItemsSource

现在,对于遇到此错误的人:

System.InvalidCastException
Unable to cast object of type 'MS.Internal.NamedObject' to type 'System.Data.DataRowView'.

对我有用的是禁用DataGrid的属性CanUserAddRows。这将删除新行的占位符行,从而删除占位符对象(它不是DataRowView,而是其他东西)。如果您已经禁用了此选项,则我不知道。

由于我想循环遍历每行的每个元素,所以我添加了另一个foreach

foreach (System.Data.DataRowView dr in nameofyourgrid.Items)
{
    foreach (var item in dr.Row.ItemArray)
    {
        MessageBox.Show(item.ToString());
    }
}

1

是的,你说得对。WPF DataGrid 的建立是为了更好地支持对象的使用。

您可以使用类似以下的 ViewModel。将它们全部构建到一个集合中,然后将该集合设置为您的 ItemsSource。如果您想要显示图像而不是勾选标记以表示 pNew 是 true/false,则还需要使用 ValueConverter。

public class FooViewModel : INotifyPropertyChanged
{
    private int currentPareto;
    public int CurrentPareto 
    {
        get
        {
           return currentPareto;
        }
        set
        { 
            if (currentPareto == value)
                return;

            currentPareto = value;
            OnPropertyChanged("CurrentPareto");
            OnPropertyChanged("pNew");
        }
    }

    private int newPareto;
    public int NewPareto 
    {
        get
        {
           return newPareto;
        }
        set
        { 
            if (newPareto == value)
                return;

            newPareto = value;
            OnPropertyChanged("NewPareto");
            OnPropertyChanged("pNew");
        }
    }

    public bool pNew
    {
        get
        {
            return CurrentPareto < NewPareto;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

编辑

为了简化代码,您可以使用基础ViewModel类并使用PropertyChanged编织。 代码将简化为以下内容:

public class FooViewModel : ViewModelBase
{
    public int CurrentPareto { get; set; }
    public int NewPareto { get; set; }
    public bool pNew { get { return CurrentPareto < NewPareto; } }
}

0

在WPF中,您可以更加动态和面向对象地进行操作。您可以将“pNew”列绑定到放置在DataGrid中的元素的属性上,该属性返回下箭头。 如果值发生更改,则可以引发PropertyChanged事件(接口INotifyPropertyChanged),并重新评估绑定的属性。

对于初学WPF也很有趣的是DataTemplateControlTemplateConverter。 当调用属性时,转换器将属性值更改为WPF可用的值(例如BoolToVisibility)。 DataTemplateControlTemplate可用于更改控件的外观。

有许多好的WPF教程可供选择。我还建议研究MVVM模式,作为业务对象和WPF控件之间的中间层,特别是处理您在此处尝试做的事情。


谢谢您的建议,这周末我一定会仔细研究。目前我还在寻找一种“黑客”式的解决方案,只是想看看如何使用WinForms风格的方法来实现它。 - lemunk

0
如果您使用类的实例(例如struct_class)填充datagridview行,则这将是最快的方式来进行foreach循环。
foreach (struct_class row in dgv.Items)
{
    MessageBox.Show(row.name);
}

0
如果您的DataGrid绑定到一个自定义对象集合(例如List<MyViewItem>),那么DataGridRow迭代显然不起作用。只有当您使用DataTable/DataGridView作为DataGrid的ItemsSource时才有效。
您可以遍历您的自定义DataGrid集合,但只有在视图中的行才具有已评估的绑定。
为了解决这个问题,我使用了BindingPropertyHelper解决方案作为备选方案。
public static class PropertyPathHelper
{
    public static object GetValue(object obj, string propertyPath)
    {
        Binding binding = new Binding(propertyPath);
        binding.Mode = BindingMode.OneTime;
        binding.Source = obj;
        BindingOperations.SetBinding(_dummy, Dummy.ValueProperty, binding);
        return _dummy.GetValue(Dummy.ValueProperty);
    }

    private static readonly Dummy _dummy = new Dummy();

    private class Dummy : DependencyObject
    {
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(Dummy), new UIPropertyMetadata(null));
    }
}

以下代码将所有DataGrid行转换为一个新的(可迭代的)DataTable:
DataTable dt = new DataTable();
for (int i = 0; i < dataGrid.Columns.Count; i++)
{
    var c = dataGrid.Columns[i];

    if (c.Header != null)
        dt.Columns.Add(new DataColumn(c.Header.ToString()));
    else
        dt.Columns.Add();
}

for (int rowIndex = 0; rowIndex < dataGrid.Items.Count; rowIndex++)
{
    var item = dataGrid.Items[rowIndex];

    var newRow = dt.NewRow();

    bool written = false;
    for (int columnIndex = 0; columnIndex < dataGrid.Columns.Count; columnIndex++)
    {
        DataGridColumn column = dataGrid.Columns[columnIndex];

        FrameworkElement feColumn = column.GetCellContent(item);
        if (feColumn is TextBlock tb)
        {
            // binding loaded (in view)
            newRow[columnIndex] = tb.Text;
            written = true;
        }
        else if (column is DataGridTextColumn textColumn
            && textColumn.Binding is Binding bind)
        {
            // falback and evaluate binding explicit (out of view)
            object value = PropertyPathHelper.GetValue(item, bind.Path.Path);
            newRow[columnIndex] = value;
            written = true;
        }
    }

    if (written == false)
    {
        throw new InvalidOperationException($"Could not read row index {rowIndex} of DataGrid; Source item type: {item?.GetType()?.FullName}");
    }

    dt.Rows.Add(newRow);
}

0
你可以使用以下扩展来轻松地获取DataGrid中的选定行、所需单元格、所需行、选定行。无需被冗长的代码行吓到。最初,我从这里受益,并根据我的问题进行了调整。
/// <summary>
/// Extension methods for DataGrid
/// </summary>
public static class DataGridHelper
{
    public static T FindElementByName<T>(FrameworkElement element, string sChildName) where T : FrameworkElement
    {
        T childElement = null;
        var nChildCount = VisualTreeHelper.GetChildrenCount(element);
        for (int i = 0; i < nChildCount; i++)
        {
            FrameworkElement child = VisualTreeHelper.GetChild(element, i) as FrameworkElement;

            if (child == null)
                continue;

            if (child is T && child.Name.Equals(sChildName))
            {
                childElement = (T)child;
                break;
            }

            childElement = FindElementByName<T>(child, sChildName);

            if (childElement != null)
                break;
        }
        return childElement;
    }

    /// <summary>
    /// Gets the visual child of an element
    /// </summary>
    /// <typeparam name="T">Expected type</typeparam>
    /// <param name="parent">The parent of the expected element</param>
    /// <returns>A visual child</returns>
    public static T GetVisualChild<T>(Visual parent) where T : Visual
    {
        T child = default(T);
        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }



    /// <summary>
    /// Gets the specified cell of the DataGrid
    /// </summary>
    /// <param name="grid">The DataGrid instance</param>
    /// <param name="row">The row of the cell</param>
    /// <param name="column">The column index of the cell</param>
    /// <returns>A cell of the DataGrid</returns>
    public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int column)
    {
        if (row != null)
        {
            DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(row);

            if (presenter == null)
            {
                grid.ScrollIntoView(row, grid.Columns[column]);
                presenter = GetVisualChild<DataGridCellsPresenter>(row);
            }

            DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column);

            return cell;
        }
        return null;
    }

    /// <summary>
    /// Gets the specified cell of the DataGrid
    /// </summary>
    /// <param name="grid">The DataGrid instance</param>
    /// <param name="row">The row index of the cell</param>
    /// <param name="column">The column index of the cell</param>
    /// <returns>A cell of the DataGrid</returns>
    public static DataGridCell GetCell(this DataGrid grid, int row, int column)
    {
        DataGridRow rowContainer = grid.GetRow(row);
        return grid.GetCell(rowContainer, column);
    }

    /// <summary>
    /// Gets the specified row of the DataGrid
    /// </summary>
    /// <param name="grid">The DataGrid instance</param>
    /// <param name="index">The index of the row</param>
    /// <returns>A row of the DataGrid</returns>
    public static DataGridRow GetRow(this DataGrid grid, int index)
    {
        DataGridRow row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
        if (row == null)
        {
            // May be virtualized, bring into view and try again.
            grid.UpdateLayout();
            grid.ScrollIntoView(grid.Items[index]);
            row = (DataGridRow)grid.ItemContainerGenerator.ContainerFromIndex(index);
        }
        return row;
    }

    /// <summary>
    /// Gets the selected row of the DataGrid
    /// </summary>
    /// <param name="grid">The DataGrid instance</param>
    /// <returns></returns>
    public static DataGridRow GetSelectedRow(this DataGrid grid)
    {
        return (DataGridRow)grid.ItemContainerGenerator.ContainerFromItem(grid.SelectedItem);
    }
}

只需复制并粘贴为新类。然后像这样使用它:
这是在使用上述扩展获取单元格后访问单元格内容(如按钮、文本框、组合框等)的方法。 如果您使用列或数据模板,这将非常有用。
    private FrameworkElement GetComponentOfCell(int row, int column, string nameOfComponent)
    {
        DataGridCell cell = dgOperationList.GetCell(row, column);
        return DataGridHelper.FindElementByName<FrameworkElement>(cell, nameOfComponent);
    }
    
    GetComponentOfCell(row_index, column, nameOfControl) as ComboBox;

使用以下方法来迭代遍历DataGrid:
for (int row = 0; row < dgOperationList.Items.Count - 1; row++)
{
    DataGridRow dgr = dgOperationList.GetRow(row);
    dgOperationList.GetCell(dgr,/*your_colum_number goes here*/);
    GetComponentOfCell(row,/*your_colum_number goes here*/,/*your component name if you use template*/)
}

重要提示:如果您在窗口或用户控件加载事件之前尝试使用这些扩展,即使DataGrid的itemsource已经绑定,您可能会遇到空值或对象实例异常。

-4
为什么你不能只使用这个属性来获取行数,然后使用一个For循环来遍历呢?
dataGridView1.Rows.Count

你的回答是正确的(所以我给了它一个赞)。也许大多数投票者想要更多的示例代码?(我希望投票需要评论。) - Jeff
3
这个答案不正确,因为WPF DataGrid没有名为Rows的成员。实际上,在此线程中先前就提到了这一点。但我也希望在点踩时需要添加评论。 - Dennis

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