当前位置:   article > 正文

【wpf】DataGrid的使用_wpf dataview

wpf dataview

DataGrid动态生成

简单方式

动态生成实现需要设置AutoGenerateColumns="True",

<DataGrid Name="dataGrid" AutoGenerateColumns="True" />

后台dataGrid.ItemsSource = infoList;

infoList,就是一个类似Json的数组结构数据:

  1. [
  2. {
  3. "columnChName": "孔宽1",
  4. "nominalDim": 17.28,
  5. "tolMax": 0.02,
  6. "tolMin": 0.02,
  7. "usl": 17.3,
  8. "lsl": 17.26
  9. },
  10. {
  11. "columnChName": "孔从中心偏移3",
  12. "nominalDim": 17.28,
  13. "tolMax": 0.02,
  14. "tolMin": 0.02,
  15. "usl": 17.3,
  16. "lsl": 17.26
  17. },
  18. {
  19. "columnChName": "孔从中心偏移6",
  20. "nominalDim": 17.28,
  21. "tolMax": 0.02,
  22. "tolMin": 0.02,
  23. "usl": 17.3,
  24. "lsl": 17.26
  25. },
  26. {
  27. "columnChName": "孔从中心偏移5",
  28. "nominalDim": 13.84,
  29. "tolMax": 0.05,
  30. "tolMin": 0.05,
  31. "usl": 13.89,
  32. "lsl": 13.79
  33. },
  34. {
  35. "columnChName": "孔从中心偏移2",
  36. "nominalDim": 13.84,
  37. "tolMax": 0.05,
  38. "tolMin": 0.05,
  39. "usl": 13.89,
  40. "lsl": 13.79
  41. },
  42. {
  43. "columnChName": "孔长3",
  44. "nominalDim": 52.94,
  45. "tolMax": 0.02,
  46. "tolMin": 0.02,
  47. "usl": 52.96,
  48. "lsl": 52.92
  49. },
  50. {
  51. "columnChName": "孔从中心偏移7",
  52. "nominalDim": 49.75,
  53. "tolMax": 0.02,
  54. "tolMin": 0.02,
  55. "usl": 49.77,
  56. "lsl": 49.73
  57. }
  58. ]

固定不变的key会自动变成表格的列标题。

那界面直接就会出现这样的结果,十分方便:

如果不想动态生成,要自己定义标题头,

首先需要 AutoGenerateColumns="False" ,然后:

  1. <Window>
  2. <Grid>
  3. <DataGrid ItemsSource="{Binding Customers}">
  4. <DataGrid.Columns>
  5. <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
  6. <DataGridTextColumn Header="Age" Binding="{Binding Age}" />
  7. <DataGridTextColumn Header="Address" Binding="{Binding Address}" />
  8. </DataGrid.Columns>
  9. </DataGrid>
  10. </Grid>
  11. </Window>

这样也是可以的~~~~

通用的方式

这样的简单是简单,但是受到的数据格式的限制,而且无法对表格进行更精细的设置,下面介绍一下更通用的做法。

数据源DataTable

首先我们新建一个DataTable ,DataTable dt = new DataTable(); 让 dt 作为我们DataGrid的数据源,好处是DataTable作为数据源之后,数据源的变换可以直接通知到界面(类似ObservableCollection)。

数据关联

将 DataGrid 的数据源与 DataTable 关联起来:

datagrid.ItemsSource = dt.AsDataView();

标题头部分构造(示例代码)

  1. foreach (var item in _data_model.result.titleColumn)
  2. {
  3. dt.Columns.Add(item.columnChName);
  4. }

内容构造(示例代码)

添加一行的伪代码:

  1. //添加一行的伪代码
  2. DataRow row = dt.NewRow();
  3. foreach (var cd in (JsonObject)r.contentData)
  4. {
  5. try
  6. {
  7. row[new_key] = cd.Value.ToString();
  8. }
  9. catch (ArgumentException ex)
  10. {
  11. logger.Info("ArgumentException:" + ex.Message);
  12. }
  13. }
  14. dt.Rows.Add(row);

我们需要注意这里的new_key,必须是标题头中包含的内容,不然是会报ArgumentException的异常。new_key对应的就是一个列标题的内容

如何动态的配置每个单元格

在阐述这个话题之前,我需要做一些铺垫。

1 DataGrid如何获取当前列数

testDateGrid.Columns.Count

 还有一个方法,DataTable dt 是DataGrid的数据源,所以通过dt.Columns.Count获取到列数

2 DataGrid如何获取当前行数

 之前一直找Rows这个属性,所以一直找不到如何获取行数,其实这里的Items就相当于Rows

testDateGrid.Items.Count

同理,DataTable dt 是DataGrid的数据源,所以通过 dt.Rows.Count可以获取到行数

3 基于prism的事件时如何获取sender对象

  1. <DataGrid x:Name="testDateGrid" CanUserSortColumns="False" >
  2. <i:Interaction.Triggers>
  3. <i:EventTrigger EventName="LoadingRow">
  4. <prism:InvokeCommandAction Command="{Binding EventLoadingRow}"/>
  5. </i:EventTrigger>
  6. <i:EventTrigger EventName="UnloadingRow">
  7. <prism:InvokeCommandAction Command="{Binding EventUnloadingRow}"/>
  8. </i:EventTrigger>
  9. </i:Interaction.Triggers>
  10. </DataGrid>

这里为了实现MVVM我用prism的方式添加了两个事件LoadingRow,UnloadingRow。如果是普通的事件添加是自动传入sender的,也就是DataGrid对象本身。而通过prism的方式,只会传参数e。

后台代码如下,这里只能传参数DataGridRowEventArgs:

public DelegateCommand<DataGridRowEventArgs> EventLoadingRow { get; set; }

不过我们有办法通过e来获取sender,不够稍微有点麻烦:

  1. testDateGrid = Tool.GetChildObjectWithName<DataGrid>((DependencyObject)e.OriginalSource, "testDateGrid");

"testDateGrid"这个是我在前台xaml中配置的名字,然后通过GetChildObjectWithName这个方法获取到DateGrid对象。方法的具体实现,我放到最后的 附录 中吧。

有了DateGrid对象,如何获取DateGrid的每一行,以及每一行中的每个单元格呢?这里我们为DataGrid添加两个扩展方法,分别用来获取DataGridRow和DataGridCell:

  1. namespace BaseLibrary.MyExtensions
  2. {
  3. public static class DataGridExtensions
  4. {
  5. public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int columnIndex = 0)
  6. {
  7. if (row == null) return null;
  8. var presenter = Tools.Tool.GetChildObjectFirst<DataGridCellsPresenter>(row);
  9. if (presenter == null) return null;
  10. var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
  11. if (cell != null) return cell;
  12. // now try to bring into view and retreive the cell
  13. grid.ScrollIntoView(row, grid.Columns[columnIndex]);
  14. cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
  15. return cell;
  16. }
  17. /// <summary>
  18. /// 获取DataGrid的行
  19. /// </summary>
  20. /// <param name="dataGrid">DataGrid控件</param>
  21. /// <param name="rowIndex">DataGrid行号</param>
  22. /// <returns>指定的行号</returns>
  23. public static DataGridRow GetRow(this DataGrid dataGrid, int rowIndex)
  24. {
  25. DataGridRow rowContainer = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex);
  26. if (rowContainer == null)
  27. {
  28. dataGrid.UpdateLayout();
  29. dataGrid.ScrollIntoView(dataGrid.Items[rowIndex]);
  30. rowContainer = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex);
  31. }
  32. return rowContainer;
  33. }
  34. }
  35. }

4 事件LoadingRow

这里还有一个问题,testDateGrid中的Row我们是动态添加的,而且是通过变更数据源DataTable实现的,那我们需要一个事件:每添加一行之后就会触发。但是DateGrid并么有提供这样一个事件,只提供了LoadingRow,它在添加一行之前触发。这样的话,就没办法在事件中获取到当前新增的行对象。那边我们可以利用prism中的事件,利用LoadingRow构建 “LoadedRow” 事件

 我接下来的操作会借助prism的事件,当然你可以替换成其他操作。prism的事件可参见博客:【Prism系列】Prism事件聚合器_code bean的博客-CSDN博客_prism 事件聚合器

  1. namespace BaseLibrary.MyEvent
  2. {
  3. public class WaitEvent : PubSubEvent
  4. {
  5. }
  6. }
  7. //行增加事件
  8. EventLoadingRow = new DelegateCommand<DataGridRowEventArgs>((e) => {
  9. _eventAggregator.GetEvent<WaitEvent>().Publish();
  10. });
  11. //确保在行增加之后发生:
  12. _eventAggregator.GetEvent<WaitEvent>().Subscribe(() => {
  13. //------省略代码
  14. }, ThreadOption.UIThread);

思路就是,在LoadingRow再发布一个事件,然后订阅这个事件的时候,使用ThreadOption.UIThread,这样就可以将 LoadingRow 变为 “LoadedRow

5 最后的设置

到这里,所以的准备工作就绪了,我得到了对象DateGrid testDateGrid,于是通过扩展方法GetRow就能得到行对象:

  1. //获取每一行
  2. var rowContainer = testDateGrid.GetRow(dtTest.Rows.Count - 1);

有了行对象,我们就能得到具体的cell:

  1. // 根据每一行获取每一列
  2. DataGridCell cell = testDateGrid.GetCell(rowContainer, i);

有了cell对象,我们就可以对每个cell进行设置:

比如,设置某些cell不可编辑:cell.IsEnabled = false;

比如,设置某些cell背景或者前景颜色:cell.Foreground = new SolidColorBrush(Colors.Green);

还有一点需要注意的是:这些设置会在表格排序后失效,所以我们一般需要禁用DateGrid的排序功能(将属性  CanUserSortColumns="False")。

DateGrid添加行自增序号

动态添加行的时候,如何给DateGrid添加行自增序号呢?这里我介绍一种简单的方法。

记得之前,我给DateGrid添加了两个事件吗?LoadingRow 和 UnloadingRow。

LoadingRow用于行数增加时,序号的递增。UnloadingRow用于行被删除时序号的重新排列:

  1. //用于散出后的重新排序
  2. EventUnloadingRow = new DelegateCommand<DataGridRowEventArgs>((e) => {
  3. if (testDateGrid.Items != null)
  4. {
  5. for (int i = 0; i < testDateGrid.Items.Count; i++)
  6. {
  7. try
  8. {
  9. DataGridRow row = testDateGrid.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow;
  10. if (row != null)
  11. {
  12. row.Header = $"[{ (i + 1)}]" ;
  13. }
  14. }
  15. catch { }
  16. }
  17. }
  18. });
  19. //行增加事件
  20. EventLoadingRow = new DelegateCommand<DataGridRowEventArgs>((e) => {
  21. e.Row.Header = $"[{e.Row.GetIndex() + 1}]";
  22. });

效果展示

过程中涉及动态的添加和删除,注意观察序号的变化:

2022年11月2日,内容更新

如何实现Cell单元格的不可以编辑

需要要求有的cell可编辑,有个不可以,当获取到cell对象后,可直接这种cell.IsEnabled = false

这样整个cell确实不可以编辑了,但是这样的话,整个cell不可用,会有一些副作用。

比如,即使你设置了cell.ToolTip,也无法显示ToolTip提示了

你可能会想到isReadOnly属性,但整个数据本身就是只读的不可设置!(我TM也奇了怪了)

最后,发现有个属性能满足我的需求就是Focusable(cell.Focusable = false;)

既能实现不可编辑,又不妨碍ToolTip显示。

2022年12月30日,内容更新:

编辑单元格时获取单元格的行和列

CellEditEnding事件中,本来这样可以的:

var d = sender as DataGrid;
int row_num = d.SelectedIndex;
int colum_num = e.Column.DisplayIndex;

但是,当DataGrid支持多选之后,SelectedIndex就永远为-1了(testDateGrid.SelectionMode = DataGridSelectionMode.Extended; //支持单元格多选)

找了很多属性,发现就是没有此时行对应的属性!!!!!

结果只能这样了:

 就是新建行的时候把序号保存到row的Tag属性中!

最后就变成在CellEditEnding事件中:

int row_num = (int)e.Row.Tag;
int colum_num = e.Column.DisplayIndex;

附录

  1. public class Tool
  2. {
  3. /// <summary>
  4. /// 根据类型查找子元素
  5. /// 调用形式: List<StackPanel> initToolBarWeChatUserSp = GetChildObjects<StackPanel>(name, typeof(StackPanel));
  6. /// </summary>
  7. /// <typeparam name="T">查找类型</typeparam>
  8. /// <param name="obj">查询对象</param>
  9. /// <returns></returns>
  10. static public List<T> GetChildObjects<T>(DependencyObject obj) where T : FrameworkElement
  11. {
  12. DependencyObject child = null;
  13. List<T> childList = new List<T>();
  14. Type typename = typeof(T);
  15. for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
  16. {
  17. child = VisualTreeHelper.GetChild(obj, i);
  18. if (child is T && (((T)child).GetType() == typename))
  19. {
  20. childList.Add((T)child);
  21. }
  22. childList.AddRange(GetChildObjects<T>(child));
  23. }
  24. return childList;
  25. }
  26. static public T GetChildObjectFirst<T>(DependencyObject obj) where T : FrameworkElement
  27. {
  28. List<T> childList = new List<T>();
  29. childList = GetChildObjects<T>(obj);
  30. if (childList.Count > 0)
  31. {
  32. return childList[0];
  33. }
  34. else
  35. {
  36. return null;
  37. }
  38. }
  39. /// <summary>
  40. /// 获取父可视对象中第一个指定类型的子可视对象
  41. /// </summary>
  42. /// <typeparam name="T">可视对象类型</typeparam>
  43. /// <param name="parent">父可视对象</param>
  44. /// <returns>第一个指定类型的子可视对象</returns>
  45. public static T GetVisualChild<T>(Visual parent) where T : Visual
  46. {
  47. T child = default(T);
  48. int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
  49. for (int i = 0; i < numVisuals; i++)
  50. {
  51. Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
  52. child = v as T;
  53. if (child == null)
  54. {
  55. child = GetVisualChild<T>(v);
  56. }
  57. if (child != null)
  58. {
  59. break;
  60. }
  61. }
  62. return child;
  63. }
  64. static public T GetChildObjectWithName<T>(DependencyObject obj, string name) where T : FrameworkElement
  65. {
  66. List<T> childList = new List<T>();
  67. childList = GetChildObjects<T>(obj);
  68. foreach (var item in childList)
  69. {
  70. if (item.Name == name)
  71. {
  72. return item;
  73. }
  74. }
  75. return null;
  76. }
  77. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/153213
推荐阅读
  

闽ICP备14008679号