Sunday, March 21, 2021

Get DataGridView Current Cell Value Using BindingSource

A couple of months ago, I ran into a post on the Forums about how to get the value of the currently selected cell of a DataGridView, from the grid's BindingSource. The person asking the question didn't specify what kind of object was specified as the BindingSource's DataSource.

Since I work a lot with DataSets/DataTables, I assumed he was binding his DataGridView to a DataTable, and provided him with this answer to his question:

// To get the PropertyName of the data in the current cell of the DataGridView
string colName = this.dataGridView1.CurrentCell.OwningColumn.DataPropertyName;

// Use an object for the value of the DataRow column
object colValue = null;

// Now, let's find the value that's in that cell, where this.bs is the BindingSource:
if (this.bs.DataSource is DataTable)
colValue = ((DataTable)this.bs.DataSource).Rows[this.bs.Position][colName];
else if (this.bs.DataSource is DataSet)
{
DataTable dt = ((DataSet)this.bs.DataSource).Tables[this.bs.DataMember];
colValue = dt.Rows[this.bs.Position][colName];
}

You can find the Type of the bound column by using this line of code if you need to know that as well:

Type colType = this.dataGridView1.CurrentCell.OwningColumn.ValueType;

The nice thing about using DataTables for databinding grids with a BindingSource, is that it is automatically two-way databinding. If you make a change in the grid's cell, it populates the column in the row of the DataTable. Likewise, if the underlying data in the DataTable is changed programmatically, it will immediately show in the grid's cell.

OK, so what about other kinds of DataSources? Let's take a look:

BindingList

If you have a List of a particular class, say a Person class, you'll want to use a BindingList for your DataGridView's DataSource. In order to be sure that your Person class will support two-way databinding, your class will need to implement the INotifyPropertyChanged interface. Here is a sample Person class with the interface implemented (be sure to add a "using System.Runtime.CompilerServices" directive ... it is needed for the INotifyPropertyChanged implementation) :

private class Person : IPerson, INotifyPropertyChanged
{
// Fields and Properties
private string m_FirstName = "";
private string m_LastName = "";

public string FirstName
{ get { return this.m_FirstName; }
set
{
if (value != this.m_FirstName)
{
this.m_FirstName = value;
this.NotifyPropertyChanged();
}
}
}
public string LastName
{ get { return this.m_LastName; }
set
{
if (value != this.m_LastName)
{
this.m_LastName = value;
this.NotifyPropertyChanged();
}
}
}

// Constructors
public Person()
{
FirstName = "";
LastName = "";
}
public Person(string first, string last)
{
FirstName = first;
LastName = last;
}


// PropertyChanged stuff
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Next, I populated a List<Person> for this example:

//populate a List<Person> for testing
List<Person> listPerson = new List<Person>();
Person pers;
for (int i = 0; i < 5; i++)
{
pers = new Person();
pers.FirstName = i.ToString();
pers.LastName = "L" + i.ToString();
listPerson.Add(pers);
}

And last, create the binding stuff:

//create a BindingList and a BindingSource and set the grid's DataSource to the BindingSource
BindingList<Person> bindingList = new BindingList<Person>(listPerson);
BindingSource bsB = new BindingSource();
bsB.DataSource = bindingList;
this.dataGridView1.DataSource = bsB;

Finding the value in that cell using only the colName and BindingSource will be a little trickier than it was for a DataTable. But we can make use of the PropertyInfo class and reflection to get that very easily.

// First, Get the PropertyName of the current cell of the DataGridView (same as before)
string colName = this.dataGridView1.CurrentCell.OwningColumn.DataPropertyName;

// And find the value that is in that cell, using the BindingSource
BindingSource bs = (BindingSource)this.dataGridView1.DataSource;
BindingList<Person> bl = (BindingList<Person>)bs.DataSource;

// PropertyInfo and reflection
PropertyInfo pi = bl[bs.Position].GetType().GetProperty(colName);
object colValue = pi.GetAccessors()[0].Invoke(bl[bs.Position], null);

Before I go any further, you're probably wondering if it's necessary to use a BindingSource *and* a BindingList. Well, not really. If you use the BindingList as the grid's DataSource directly, everything works fine (meaning, you will have two-way databinding), but if you don't use a BindingSource, you don't have the benefit of knowing what position (row) contains the CurrentCell. That's not a problem if you don't need it, you can use the CurrentCell.RowIndex for that. And, for that matter, you can use the CurrentCell.Value and not have to worry about any of this!! However, for the question I was answering on the forums, the requirement was to find the value using the BindingSource. So, there you go.  ;0)

Here is a blog post I wrote about 5 years ago explaining the reflection methodology:

https://geek-goddess-bonnie.blogspot.com/2016/07/pemstatus-more-with-reflection-in-net.html

Taking an example from that blog post, let's use a method to make this easier. Here's the method:

public object GetPropertyValue(object o, string name)
{
PropertyInfo pi = o.GetType().GetProperty(name);
if (pi != null)
return pi.GetAccessors()[0].Invoke(o, null);
else
return null;
}

Use it like this:

string colName = this.dataGridView1.CurrentCell.OwningColumn.DataPropertyName;
BindingSource bs = (BindingSource)this.dataGridView1.DataSource;
BindingList<Person> bl = (BindingList<Person>)bs.DataSource;

object colValue = GetPropertyValue(bl[bs.Position], colName);

How about we do something even cooler! Let's make a generic method that will do all the heavy lifting. At first, I thought that I'd have to use a method signature with generics, like this (where, in this case, T would be Person when the method is called):

public object GetValueFromBindingSource<T>(BindingSource bs, string propertyName, T listClass)

But, as it turned out, that's not necessary. We can cast the bs.DataSource to an IBindingList and it all works perfectly. So <T> generic methods are not necessary! We get this instead:

public object GetValueFromBindingSource(BindingSource bs, string propertyName)
{
object objValue = null;
if (bs.DataSource is DataTable)
objValue = ((DataTable)bs.DataSource).Rows[bs.Position][propertyName];
else if (bs.DataSource is DataSet)
{
DataTable dtNW = ((DataSet)bs.DataSource).Tables[bs.DataMember];
objValue = dtNW.Rows[bs.Position][propertyName];
}
else if (bs.DataSource is IBindingList)
{
IBindingList bl = (IBindingList)bs.DataSource;
objValue = GetPropertyValue(bl[bs.Position], propertyName);
}
return currValue;
}


The above method will work for any DataGridView.DataSource, whether it's a DataSet, DataTable or any kind of BindingList.. And, you'd call it like this:

string colName = this.dataGridView1.CurrentCell.OwningColumn.DataPropertyName;
BindingSource bs = (BindingSource)this.dataGridView1.DataSource;

object colValue = GetValueFromBindingSource(bs, colName);

ObservableCollection

One more thing worth mentioning is that the BindingList can also be used with an ObservableCollection. In fact, you *have* to use a BindingList for your ObservableCollection,  because otherwise you only get one-way databinding (grid-to-collection). The reason that the ObservableCollection doesn't work two-way as a BindingSource is because it doesn't implement the INotifyPropertyChanged interface, only the INotifyChanged, which doesn't help in this situation. But, stick it in a BindingList and that'll work fine (the syntax is the same as above):

ObservableCollection<Person> collPerson = new ObservableCollection<Person>();
// Fill your collection, I won't bother with coding that
// Then set your BindingList. As you can see, it's the same syntax as adding the listPerson:
BindingList<Person> bl = new BindingList<Person>(collPerson);

That's it for now. Happy Coding!  =0)

Saturday, January 30, 2021

Wayback Machine

You may be familiar with the Wayback Machine from old "Mr. Peabody and Sherman" segments on the "Rocky and Bullwinkle" cartoon series (1959 to 1964). If not, I just discovered that there was an animated "Mr. Peabody and Sherman" movie in 2014 and another animated TV series in 2015. If you're not familiar with any of this, the Wayback Machine is basically a time machine that Mr. Peabody (who is a very smart dog) invented.

Now, what does this have to do with software development? Well, nothing specifically, unless you are trying to find a website that you *knew* existed once upon a time, and you even have a link for it ... but it's either gone or hijacked or, even worse, has been taken over by malware! That is what happened to a link I had used on a blog post in October (see https://geek-goddess-bonnie.blogspot.com/2020/10/configure-msdtc-for-distributed.html ) and I had to scramble to be able to provide the information to write that blog post. 

At the time I wrote that post, I had totally forgotten about using the web version (as opposed to the cartoon version) of the Wayback Machine (https://web.archive.org/ ). I recently searched for the link that I had wanted to use in that October post and found many versions of it, because the web archive crawls many websites and takes snapshots of them at intervals, so one site can have many entries. Case in point, the blog post entry I was interested in was crawled 105 times between May 2015 and February 2020 (sometime after that point in time, it was compromised).

Anyway, here's a link to the archived post (and, I just updated my old October blog post with this link as well):

Have fun exploring the Wayback Machine!  =0)