【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;
        }
    }

posted @   宋桓公  阅读(489)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示