【wpf】DataGrid的使用
DataGrid动态生成
简单方式
动态生成实现需要设置AutoGenerateColumns="True",
<DataGrid Name="dataGrid" AutoGenerateColumns="True" />
后台dataGrid.ItemsSource = infoList;
infoList,就是一个类似Json的数组结构数据:
[
{
"columnChName": "孔宽1",
"nominalDim": 17.28,
"tolMax": 0.02,
"tolMin": 0.02,
"usl": 17.3,
"lsl": 17.26
},
{
"columnChName": "孔从中心偏移3",
"nominalDim": 17.28,
"tolMax": 0.02,
"tolMin": 0.02,
"usl": 17.3,
"lsl": 17.26
},
{
"columnChName": "孔从中心偏移6",
"nominalDim": 17.28,
"tolMax": 0.02,
"tolMin": 0.02,
"usl": 17.3,
"lsl": 17.26
},
{
"columnChName": "孔从中心偏移5",
"nominalDim": 13.84,
"tolMax": 0.05,
"tolMin": 0.05,
"usl": 13.89,
"lsl": 13.79
},
{
"columnChName": "孔从中心偏移2",
"nominalDim": 13.84,
"tolMax": 0.05,
"tolMin": 0.05,
"usl": 13.89,
"lsl": 13.79
},
{
"columnChName": "孔长3",
"nominalDim": 52.94,
"tolMax": 0.02,
"tolMin": 0.02,
"usl": 52.96,
"lsl": 52.92
},
{
"columnChName": "孔从中心偏移7",
"nominalDim": 49.75,
"tolMax": 0.02,
"tolMin": 0.02,
"usl": 49.77,
"lsl": 49.73
}
]
固定不变的key会自动变成表格的列标题。
那界面直接就会出现这样的结果,十分方便:
通用的方式
这样的简单是简单,但是受到的数据格式的限制,而且无法对表格进行更精细的设置,下面介绍一下更通用的做法。
数据源DataTable
首先我们新建一个DataTable ,DataTable dt = new DataTable(); 让 dt 作为我们DataGrid的数据源,好处是DataTable作为数据源之后,数据源的变换可以直接通知到界面(类似ObservableCollection)。
数据关联
将 DataGrid 的数据源与 DataTable 关联起来:
datagrid.ItemsSource = dt.AsDataView();
标题头部分构造(示例代码)
foreach (var item in _data_model.result.titleColumn)
{
dt.Columns.Add(item.columnChName);
}
内容构造(示例代码)
添加一行的伪代码:
//添加一行的伪代码
DataRow row = dt.NewRow();
foreach (var cd in (JsonObject)r.contentData)
{
try
{
row[new_key] = cd.Value.ToString();
}
catch (ArgumentException ex)
{
logger.Info("ArgumentException:" + ex.Message);
}
}
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对象
<DataGrid x:Name="testDateGrid" CanUserSortColumns="False" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="LoadingRow">
<prism:InvokeCommandAction Command="{Binding EventLoadingRow}"/>
</i:EventTrigger>
<i:EventTrigger EventName="UnloadingRow">
<prism:InvokeCommandAction Command="{Binding EventUnloadingRow}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
这里为了实现MVVM我用prism的方式添加了两个事件LoadingRow,UnloadingRow。如果是普通的事件添加是自动传入sender的,也就是DataGrid对象本身。而通过prism的方式,只会传参数e。
后台代码如下,这里只能传参数DataGridRowEventArgs:
public DelegateCommand<DataGridRowEventArgs> EventLoadingRow { get; set; }
不过我们有办法通过e来获取sender,不够稍微有点麻烦:
testDateGrid = Tool.GetChildObjectWithName<DataGrid>((DependencyObject)e.OriginalSource, "testDateGrid");
"testDateGrid"这个是我在前台xaml中配置的名字,然后通过GetChildObjectWithName这个方法获取到DateGrid对象。方法的具体实现,我放到最后的 附录 中吧。
有了DateGrid对象,如何获取DateGrid的每一行,以及每一行中的每个单元格呢?这里我们为DataGrid添加两个扩展方法,分别用来获取DataGridRow和DataGridCell:
namespace BaseLibrary.MyExtensions
{
public static class DataGridExtensions
{
public static DataGridCell GetCell(this DataGrid grid, DataGridRow row, int columnIndex = 0)
{
if (row == null) return null;
var presenter = Tools.Tool.GetChildObjectFirst<DataGridCellsPresenter>(row);
if (presenter == null) return null;
var cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
if (cell != null) return cell;
// now try to bring into view and retreive the cell
grid.ScrollIntoView(row, grid.Columns[columnIndex]);
cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(columnIndex);
return cell;
}
/// <summary>
/// 获取DataGrid的行
/// </summary>
/// <param name="dataGrid">DataGrid控件</param>
/// <param name="rowIndex">DataGrid行号</param>
/// <returns>指定的行号</returns>
public static DataGridRow GetRow(this DataGrid dataGrid, int rowIndex)
{
DataGridRow rowContainer = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex);
if (rowContainer == null)
{
dataGrid.UpdateLayout();
dataGrid.ScrollIntoView(dataGrid.Items[rowIndex]);
rowContainer = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(rowIndex);
}
return rowContainer;
}
}
}
4 事件LoadingRow
这里还有一个问题,testDateGrid中的Row我们是动态添加的,而且是通过变更数据源DataTable实现的,那我们需要一个事件:每添加一行之后就会触发。但是DateGrid并么有提供这样一个事件,只提供了LoadingRow,它在添加一行之前触发。这样的话,就没办法在事件中获取到当前新增的行对象。那边我们可以利用prism中的事件,利用LoadingRow构建 “LoadedRow” 事件
我接下来的操作会借助prism的事件,当然你可以替换成其他操作。prism的事件可参见博客:【Prism系列】Prism事件聚合器_code bean的博客-CSDN博客_prism 事件聚合器
namespace BaseLibrary.MyEvent
{
public class WaitEvent : PubSubEvent
{
}
}
//行增加事件
EventLoadingRow = new DelegateCommand<DataGridRowEventArgs>((e) => {
_eventAggregator.GetEvent<WaitEvent>().Publish();
});
//确保在行增加之后发生:
_eventAggregator.GetEvent<WaitEvent>().Subscribe(() => {
//------省略代码
}, ThreadOption.UIThread);
思路就是,在LoadingRow再发布一个事件,然后订阅这个事件的时候,使用ThreadOption.UIThread,这样就可以将 LoadingRow 变为 “LoadedRow”
5 最后的设置
到这里,所以的准备工作就绪了,我得到了对象DateGrid testDateGrid,于是通过扩展方法GetRow就能得到行对象:
//获取每一行
var rowContainer = testDateGrid.GetRow(dtTest.Rows.Count - 1);
有了行对象,我们就能得到具体的cell:
// 根据每一行获取每一列
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用于行被删除时序号的重新排列:
//用于散出后的重新排序
EventUnloadingRow = new DelegateCommand<DataGridRowEventArgs>((e) => {
if (testDateGrid.Items != null)
{
for (int i = 0; i < testDateGrid.Items.Count; i++)
{
try
{
DataGridRow row = testDateGrid.ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow;
if (row != null)
{
row.Header = $"[{ (i + 1)}]" ;
}
}
catch { }
}
}
});
//行增加事件
EventLoadingRow = new DelegateCommand<DataGridRowEventArgs>((e) => {
e.Row.Header = $"[{e.Row.GetIndex() + 1}]";
});
效果展示
过程中涉及动态的添加和删除,注意观察序号的变化:
2022年11月2日,内容更新
如何实现Cell单元格的不可以编辑
需要要求有的cell可编辑,有个不可以,当获取到cell对象后,可直接这种cell.IsEnabled = false
这样整个cell确实不可以编辑了,但是这样的话,整个cell不可用,会有一些副作用。
比如,即使你设置了cell.ToolTip,也无法显示ToolTip提示了。
你可能会想到isReadOnly属性,但整个数据本身就是只读的不可设置!(我TM也奇了怪了)
最后,发现有个属性能满足我的需求就是Focusable(cell.Focusable = false;)
既能实现不可编辑,又不妨碍ToolTip显示。
附录
public class Tool
{
/// <summary>
/// 根据类型查找子元素
/// 调用形式: List<StackPanel> initToolBarWeChatUserSp = GetChildObjects<StackPanel>(name, typeof(StackPanel));
/// </summary>
/// <typeparam name="T">查找类型</typeparam>
/// <param name="obj">查询对象</param>
/// <returns></returns>
static public List<T> GetChildObjects<T>(DependencyObject obj) where T : FrameworkElement
{
DependencyObject child = null;
List<T> childList = new List<T>();
Type typename = typeof(T);
for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
{
child = VisualTreeHelper.GetChild(obj, i);
if (child is T && (((T)child).GetType() == typename))
{
childList.Add((T)child);
}
childList.AddRange(GetChildObjects<T>(child));
}
return childList;
}
static public T GetChildObjectFirst<T>(DependencyObject obj) where T : FrameworkElement
{
List<T> childList = new List<T>();
childList = GetChildObjects<T>(obj);
if (childList.Count > 0)
{
return childList[0];
}
else
{
return null;
}
}
/// <summary>
/// 获取父可视对象中第一个指定类型的子可视对象
/// </summary>
/// <typeparam name="T">可视对象类型</typeparam>
/// <param name="parent">父可视对象</param>
/// <returns>第一个指定类型的子可视对象</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;
}
static public T GetChildObjectWithName<T>(DependencyObject obj, string name) where T : FrameworkElement
{
List<T> childList = new List<T>();
childList = GetChildObjects<T>(obj);
foreach (var item in childList)
{
if (item.Name == name)
{
return item;
}
}
return null;
}
}
作者:宋桓公
出处:http://www.cnblogs.com/douzi2/
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?