通过DataSource为DatagridView绑定数据时为DataGridViewRow.Tag设置值

Posted

技术标签:

【中文标题】通过DataSource为DatagridView绑定数据时为DataGridViewRow.Tag设置值【英文标题】:Setting value for DataGridViewRow.Tag when data binding by DataSource for DatagridView 【发布时间】:2021-08-04 02:16:57 【问题描述】:

我正在尝试在数据绑定时为DataGridViewRow.Tag设置一个值,但我不知道该怎么做?

我试过DataRow row = table.NewRow(); 但是行没有标签。

binding DataGridView 到表时如何设置 DataGridViewRow.Tag 的值(例如)?还是不可能?

编辑1:

这是我正在使用的代码:

            var table = new DataTable();
            table.Columns.Add("Title", typeof(string));
            table.Columns.Add("URL", typeof(string));
            table.Columns.Add("Read Later", typeof(bool));
            foreach (XElement node in nodes)
            
                Helper.CheckNode(node);

                var id = node.Attribute("id").Value;
                var url = node.Element("path").Value;
                var comment = node.Element("title").Value;
                var readlater = node.Attribute("readLater")?.Value.ToString() == "1";

                var row = table.NewRow();
                row.ItemArray = new object[]  url, comment, readlater ;
                
                table.Rows.Add(row);//Edit2
            
            dataGridView1.DataSource = table;

我正在尝试为该行设置一个标签以在 CellClick 事件中使用它:

       var cRow = dataGridView1.Rows[e.RowIndex];
       var id = cRow.Tag.ToString();

【问题讨论】:

如果它没有Tag 属性,那么它是不可能的。也许如果您解释了您要做什么,我们可以提供帮助。 @ŇɏssaPøngjǣrdenlarp 请检查我的编辑1 我没有按照您的要求进行操作,但是,看起来行没有被添加到 table. 代码行中……var row = table.NewRow(); 将给出一个新行,但是,它不会“添加”到table。我很确定如果您想将该行“添加”到table,您需要执行类似... table.Rows.Add(row); @JohnG 是的,我忘了在表格中添加行。当从表中的 DataSource 填充 DataGridView 时,我试图找到一种方法来制作 DataGridViewRow have a tag value。因为我需要在单击单元格时访问标签。在这个项目中,我正在努力使行的标签仅具有项目的 id,但我正在处理的其他项目我希望行的标签具有许多数据作为对象类。 我可能会误会,因为我对 Tag 属性并不熟悉。但是,根据我的小研究,您的代码似乎必须遍历每个网格行并设置每行的 Tag 属性。当设置网格DataSource 时,我看不到任何会“自动”设置每一行的Tag 值的东西。再说一次,我可能错了。我不明白您希望 Tag 包含哪些值。如果该行与某个其他对象相关联,您能否在单击单元格时不“抓取”相关对象,而不是将对象添加为Tag?我错过了什么吗? 【参考方案1】:

将数据与显示方式分开

使用 DataGridView 时,直接访问单元格和列很少是个好主意。使用DataGridView.DataSource 更容易。

在现代编程中,倾向于将数据(= 模型)与数据的显示方式(= 视图)分开。要将这两个项目粘合在一起,需要一个适配器类,通常称为视图模型。这三项简称为MVVM。

使用DataGridView.DataSource显示数据

显然,如果操作员点击一个单元格,你想读取该单元格所在行的标签的值,以获得一些额外的信息,一些Id。

这个显示的行是一些数据的显示。显然,此数据的部分功能是访问此 ID。你不应该把这些信息放在视图中,你应该把它放在模型中。

class MyWebPage                        // TODO: invent proper identifier

    public int Id get; set;
    public string Title get; set;
    public string Url get; set;
    public bool ReadLater get; set;

    ... // other properties

显然,您有一种方法可以从nodes 序列中获取要显示的数据。将获取此数据(=模型)与显示(=视图)分开:

IEnumerable<MyWebPage> FetchWebPages(...)

    ...
    foreach (XElement node in nodes)
    
        Helper.CheckNode(node);

        bool readLater = this.CreateReadLater(node);
        yield return new MyWebPage
        
            Id = node.Attribute("id").Value,
            Url = node.Element("path").Value,
            Title = node.Element("title").Value,
            ReadLater = this.CreateReadLater(node),
        ;
    

我不知道节点“ReadLater”中有什么,显然您知道如何将其转换为布尔值。

bool CreateReadLater(XElement node)

     // TODO: implement, if null return true; if not null ...
     // out of scope of this question

为每个要显示的属性创建一个 DataGridViewColumn。属性DataPropertyName 定义应在列中显示的属性。如果标准 ToString 不足以正确显示该值,例如定义小数点后的位数或将负值着色为红色,请使用 DefaultCellStyle。

您可以使用 Visual Studio 设计器执行此操作,也可以在构造函数中执行此操作:

public MyForm

    InitializeComponents();
    this.dataGridViewColumnTitle.DataPropertyName = nameof(MyWebPage.Title);
    this.dataGridViewColumnUrl.DataPropertyName = nameof(MyWebPage.Url);
    ...

您不想显示 Id,因此没有此列。

现在要显示数据,您所要做的就是将列表分配给数据源:

this.dataGrieViewWebPages.DataSource = this.FetchWebPages().ToList();

这只是显示。如果操作员可以更改显示的值,并且您想访问更改后的值,则应将项目放在实现接口IBindingList 的对象中,例如,使用类(惊喜!)BindingList&lt;T&gt;

private BindingList<MyWebPage> DisplayedWebPages

    get => (BindingList<MyWebPage>)this.dataGrieViewWebPages.DataSource;
    set => this.dataGrieViewWebPages.DataSource = value;

初始化:

private void DisplayWebPages()

    this.DisplayedWebPages = new BindingList<MyWebPage>(this.FetchWebPages.ToList());

然后转眼!显示所有网页。操作员所做的每一项更改:添加/删除/编辑行都会在 DisplayedWebPages 中自动更新。

如果要访问当前选中的网页:

private MyWebPage CurrentWebPage =>(MyWebPage)this.dataGrieViewWebPages.CurrentRow?.DataBoundItem;

private IEnumerable<MyWebPage> SelectedWebPages =>
    this.dataGrieViewWebPages.SelectedRows
    .Cast<DataGridViewRow>()
    .Select(row => row.DataBoundItem)
    .Cast<MyWebPage>();

现在显然,每当操作员单击一个单元格时,您都希望对显示在单元格行中的网页的 Id 执行某些操作。

查看:显示的单元格和行 ViewModel:当操作员单击单元格时做出反应 模型必须做的动作

对单元格点击做出反应:获取 Id

我们已经处理了上面的视图。 ViewModel 是事件处理程序:

void OnDatGridViewCellClicked(object sender, DataGridViewCellEventArgs e)

    // use the eventArgs to fetch the row, and thus the WebPage:
    MyWebPage webPage = (MyWebPage)this.dataGridViewWebPages.Rows[e.RowIndow].DataBoundItem;
    this.ProcessWebPage(webPage);

ProcessWebPage 通常是 Model 类中的一个方法:

public void ProcessWebPage(MyWebPage webPage)

     // Do what you need to do if the operator clicks the cell, for example:
     int webPageId = webPage.Id;
     ...

结论:模型与视图分离的优点

顺便问一下,您是否看到所有 ViewModel 方法都是单行的?只有您的 Model 方法 FetchWebPages 和 ProcessWebPage 包含几行。

由于您将视图与模型分开,因此对模型或视图的更改将相当简单:

如果您想将数据存储为 Json 格式,或者存储在数据库中而不是 XML 中,您的视图不会改变 如果您不想在单击单元格时做出反应,但在单击“确定”按钮时,您的模型不会改变。如果您决定显示更多或更少的列,您的模型也不必更改 由于您将模型与视图分开,因此可以在没有表单的情况下对模型进行单元测试。您还可以使用仅填充测试值的模型来测试视图。

【讨论】:

非常感谢您的帮助,我今天学到了一些新东西。我有一个问题,当我在我的 XML(或数据库)中删除/添加/或编辑一行时,我仍然需要调用此行来显示更改,对吗? DisplayWebPages() 当我尝试删除我的 XML 文件中的一个条目时,它仍然在我的 DataGridView 的行中。 我可以从我的测试中看到,当我编辑列表中的一个项目并调用DisplayWebPages() 以在列表中显示编辑时,它会滚动到顶部。就像我清除了所有行并添加了新行一样。当我使用dataSource = table 时,我没有看到这个问题 datagridview 发生变化并保持在当前状态而不滚动。当我编辑一行时,我能做些什么来防止滚动?我像这样编辑它var row = ((BindingList&lt;WebPage&gt;)dataGridView1.DataSource).OfType&lt;WebPage&gt;().Where(w =&gt; w.Url == url).Cast&lt;WebPage&gt;().FirstOrDefault(); 然后row.Title = title; 如果只更改/添加/删除绑定列表的几个项目,不要创建新的,只需添加/删除/更改项目。显示会自动更新。

以上是关于通过DataSource为DatagridView绑定数据时为DataGridViewRow.Tag设置值的主要内容,如果未能解决你的问题,请参考以下文章

将 Datagridview 的 DataSource 转换为 List?

将 DataSource 设置为 Datagridview 后添加一行

设置DataSource后DateGridView不显示的问题

c#中datagridview中datasource反复赋值没有变化?

DataGridView 问题,不可见行仍然可见,尽管有 DataSource,但 DataGridView 为空

SqlDataAdapter.Fill(DataGridView.DataSource) 复制所有行