Setting value for DataGridViewRow.Tag when data binding by DataSource for DatagridView

683 Views Asked by At

I am trying to set a value for DataGridViewRow.Tag when data binding but I don't know how to do it?

I tried with DataRow row = table.NewRow(); But row doesn't have tag.

How to set a value for DataGridViewRow.Tag when binding DataGridView to table (for example)? Or it isn't possible?

Edit1:

Here is the code i am using:

            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;

I am trying to set a tag for the row to use it in CellClick event:

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

There are 1 best solutions below

3
On BEST ANSWER

Separate the data from how it is displayed

When using a DataGridView, it is seldom a good idea to access the cells and the columns directly. It is way more easier to use DataGridView.DataSource.

In modern programming there is a tendency to separate your data (= model) from the way your data is displayed (= view). To glue these two items together an adapter class is needed, which is usually called the viewmodel. Abbreviated these three items are called MVVM.

Use DataGridView.DataSource to display the data

Apparently, if the operator clicks a cell, you want to read the value of the tag of the row of the cell, to get some extra information, some Id.

This displayed row is the display of some data. Apparently part of the functionality of this data is access to this Id. You should not put this information in the view, you should put it in the model.

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
}

Apparently you have a method to fetch the data that you want to display from a sequence of nodes. Separate fetching this data (=model) from displaying it (= view):

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),
        };
    }
}

I don't know what is in node "ReadLater", apparently you know how to convert it to a Boolean.

bool CreateReadLater(XElement node)
{
     // TODO: implement, if null return true; if not null ...
     // out of scope of this question
}

For every property that you want to display you create a DataGridViewColumn. Property DataPropertyName defines which property should be shown in the column. Use DefaultCellStyle if a standard ToString is not enough to display the value properly, for instance, to define the number of digits after the decimal point, or to color negative values red.

You can do this using the visual studio designer, or you can do this in the constructor:

public MyForm
{
    InitializeComponents();
    this.dataGridViewColumnTitle.DataPropertyName = nameof(MyWebPage.Title);
    this.dataGridViewColumnUrl.DataPropertyName = nameof(MyWebPage.Url);
    ...
}

You don't want to display the Id, so there is no column for this.

Now to display the data, all you have to do is assign the list to the datasource:

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

This is display only. If the operator can change the displayed values, and you want to access the changed values, you should put the items in an object that implements interface IBindingList, for instance, using class (surprise!) BindingList<T>:

private BindingList<MyWebPage> DisplayedWebPages
{
    get => (BindingList<MyWebPage>)this.dataGrieViewWebPages.DataSource;
    set => this.dataGrieViewWebPages.DataSource = value;
}

Initialization:

private void DisplayWebPages()
{
    this.DisplayedWebPages = new BindingList<MyWebPage>(this.FetchWebPages.ToList());
}

And presto! All webpages are displayed. Every change that the operator makes: add / remove / edit rows are automatically updated in the DisplayedWebPages.

If you want to access the currently selected WebPages:

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

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

Now apparently whenever the operator clicks a cell, you want to do something with the Id of the WebPage that is displayed in the Row of the cell.

  • View: Displayed Cell and Row
  • ViewModel: React when operator clicks a cell
  • Model Action that must be done

React on Cell Click: get the Id

We've handled the View above. ViewModel is the event handler:

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 is typically a method in your Model class:

public void ProcessWebPage(MyWebPage webPage)
{
     // Do what you need to do if the operator clicks the cell, for example:
     int webPageId = webPage.Id;
     ...
}

Conclusion: advantages of separating model from view

By the way, did you see that all ViewModel methods are one-liners? Only your Model methods FetchWebPages and ProcessWebPage contain several lines.

Because you separated the View from the Model, changes to your Model or your View will be fairly simple:

  • If you want to store your data in Json format, or in a database instead of in an XML, your View won't change
  • If you don't want to react on cell click, but on Button OK click, then your Model won't change. Your model also doesn't have to change if you decide to show more or less columns
  • Because you separated your Model from your View, the Model can be unit tested without a form. You can also test the View with a Model filled with only test values.