Monthly Archives: May 2007

Improvements for the SortableBindingList (and TypedList)

I found out that the sorting didn’t work for ‘Expression’ properties. My first thought was to add another switch to the logic of the already existing code:

object value1 = t1;
object value2 = t2;

if (prop.Name.StartsWith("||"))
{
 // do something to find the 'ExpressionProperty' values
}
else
{
 foreach (string property in prop.Name.Split('.'))
 {
  // navigate through the relations
  PropertyInfo propertyInfo = value1.GetType().GetProperty(property);
  value1 = propertyInfo.GetValue(value1, null);
  value2 = propertyInfo.GetValue(value2, null);
 }
}

Since i already have a PropertyDescriptor it seems a lot smarter to use it’s GetValue instead:

object value1 = prop.GetValue(t1);
object value2 = prop.GetValue(t2);

At Matthieu MEZIL‘s blog i found a suggestion to use Comparer<T>. This allowed me to reduce:

IComparable comparable = value1 as IComparable;
if (comparable != null)
{
 return reverse * comparable.CompareTo(value2);
}
else
{
 comparable = value2 as IComparable;
 if (comparable != null)
 {
  return -1 * reverse * comparable.CompareTo(value1);
 }
 else
 {
  return 0;
 }
}

with:

// Notice that this requires that atleast value1 or value2 are an instance of a type that implements IComparable
return reverse * Comparer.Default.Compare(value1, value2);

Feel free to get yet another version of TypedList.zip.

About .Net events in Belgium

Earlier this evening i visited “Visual C++ ‘Orcas’ and a Microsoft C++ Strategy”, an MSDN Belux event. Since the events are for free, time would be the only thing you can loose…. But as always with these events, they are worth the time. I even liked the jokes this time ;) ) The speaker, Ronald Laeremans, now the Program Unit Manager for Visual C++ talked and kept talking about his passion :) 30 minutes after his session Tom eventually found a way to stop him.. I can only recommend that you visit these events too… Scheduled:

Presenting the ExpressionDescriptor

A couple of days ago i presented you the TypedList which supports navigation through subproperties. Another common feature request is the possibility to add a column that has a value based on other values in the row (like a DataColumn with it’s Expression property set). With the plumbing code i’ve written it’s as simple as implementing the following interface:

public interface IExpressionProvider<componentType, PropertyType>
{
 /// <summary>
 /// Gets the name of the property
 /// </summary>
 string Name { get; }

 /// <summary>
 /// Returns the value of the expression for the given component
 /// </summary>
 /// <param name="component"></param>
 /// <returns></returns>
 PropertyType GetValue(ComponentType component);
}

An example implementation could be an expression that represents the duration of an Appointment:

public class DurationExpressionProvider : IExpressionProvider<appointment, TimeSpan>
{
 #region IExpressionProvider<appointment,TimeSpan> Members

 public string Name { get { return "||Duration"; } }

 public TimeSpan GetValue(Appointment component)
 {
  return component.DateTimeRange.End - component.DateTimeRange.Start;
 }

 #endregion
}

I’ve changed the constructor of TypedList a bit so that it accepts an enumeration of PropertyDescriptors. In my example you can initialise the list as following:

List<propertyDescriptor> propertyDescriptors = new List<propertyDescriptor>();

// create the subpropertydescriptors
string[] propertyNames = new string[] { "Patient.Name", "Patient.Address.Municipality", "DateTimeRange.Start", "DateTimeRange.End" };
propertyDescriptors.AddRange(Array.ConvertAll<string, SubPropertyDescriptor<appointment>>(propertyNames, delegate(string propertyName) { return new SubPropertyDescriptor<appointment>(propertyName); }));

// add an expressiondescriptor
IExpressionProvider<appointment, TimeSpan> expressionProvider = new DurationExpressionProvider();
ExpressionDescriptor<appointment, TimeSpan> durationDescriptor = new ExpressionDescriptor<appointment, TimeSpan>(expressionProvider);
propertyDescriptors.Add(durationDescriptor);

TypedBindingList<appointment> appointments = new TypedBindingList<appointment>(propertyDescriptors);

And this is how it looks like at runtime:

image of the typedlist

As always, feel free to download the code: TypedList.zip.

Presenting the TypedList<T>

A while ago i presented the SortableBindingList. One of the nice features you get with DataSets is that you can use relations to navigate through the data. Business Objects don’t give you this functionality by default. Today i implemented a BindingList that supports navigation through relations. First i’ll present you the Business Objects:

screenshot of business objects

We would like to create an overview of the appointments using a datagridview:

screenshot of wanted ui

I drag a datagridview on the designer form, add columns, and then i set the datapropertynames as following: (Notice how i use a . to navigate the relations)

this.dataGridView1.AutoGenerateColumns = false;
this.ColumnId.DataPropertyName = "Id";
this.ColumnPatient.DataPropertyName = "Patient.Name";
this.ColumnMunicipality.DataPropertyName = "Patient.Address.Municipality";
this.ColumnStart.DataPropertyName = "DateTimeRange.Start";
this.ColumnEnd.DataPropertyName = "DateTimeRange.End";

First we need to implement a method that allows us to find a PropertyInfo for the given property name:

public static PropertyInfo Resolve(string propertyName)
{
 Type t = typeof(T);
 PropertyInfo propertyInfo = null;

 string[] subPropertyNames = propertyName.Split('.');
 if (subPropertyNames.Length == 1)
 {
  // a regular property
  propertyInfo = t.GetProperty(propertyName);
 }
 else
 {
  // navigate through the subproperties
  for (int i = 0; i < subPropertyNames.Length - 1; ++i)
  {
   propertyInfo = t.GetProperty(subPropertyNames[i]);
   t = propertyInfo.PropertyType;
  }
 }

 return propertyInfo;
}

Now we are ready to implement the ITypedList.GetItemProperties method in our TypedList:

public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
 PropertyDescriptorCollection propertyDescriptors = new PropertyDescriptorCollection(listAccessors);

 // add the regular property descriptors T has
 foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(T)))
 {
  propertyDescriptors.Add(propertyDescriptor);
 }

 // add the subproperties
 foreach (string subPropertyName in this.subPropertyNames)
 {
  propertyDescriptors.Add(new SubPropertyDescriptor<t>(subPropertyName));
 }

 return propertyDescriptors;
}

Using this class is as simple as:

// create a TypedList that holds Appointments
TypedBindingList<appointment> appointments = new TypedBindingList<appointment>(new string[] { "Patient.Name", "Patient.Address.Municipality", "DateTimeRange.Start", "DateTimeRange.End" });

// Initialise two patients
Patient patient = new Patient(1, "Tim", new Address(1, "MyStreet", 1820, "Melsbroek"));
Patient patient2 = new Patient(2, "John", new Address(2, "His Street", 3000, "Leuven"));

// Add appointsments to the list
appointments.Add(new Appointment(1, patient, new DateTimeRange(new DateTime(2007, 5, 3, 15, 0, 0), new DateTime(2007, 5, 3, 16, 0, 0))));
appointments.Add(new Appointment(2, patient2, new DateTimeRange(new DateTime(2007, 5, 4, 15, 0, 0), new DateTime(2007, 5, 4, 16, 0, 0))));
appointments.Add(new Appointment(3, patient, new DateTimeRange(new DateTime(2007, 5, 5, 15, 0, 0), new DateTime(2007, 5, 5, 16, 0, 0))));
appointments.Add(new Appointment(4, patient, new DateTimeRange(new DateTime(2007, 5, 6, 15, 0, 0), new DateTime(2007, 5, 6, 16, 0, 0))));
appointments.Add(new Appointment(5, patient2, new DateTimeRange(new DateTime(2007, 5, 7, 15, 0, 0), new DateTime(2007, 5, 7, 16, 0, 0))));
appointments.Add(new Appointment(6, patient, new DateTimeRange(new DateTime(2007, 5, 7, 17, 0, 0), new DateTime(2007, 5, 7, 17, 15, 0))));

// Assign this list to the datagridview datasource
this.dataGridView1.DataSource = appointments;

Pretty cool, don’t you think? As always, feel free to download the code: TypedList.zip

Edit: Today, May 8th 2007, i discovered a but in SubPropertyDescriptor.SetValue and uploaded a newer version of the code.

Exploring DataGridViewComboBoxColumn databinding (part2)

A while ago i wrote about Exploring DataGridViewComboBoxColumn databinding using Business Objects. Some people asked me to give an example using DataSets…

I’ll start with creating a DataSet, add two DataTables, and create a relation on PersonType.Id (int32). In the designer this looks like:

screenshot of dataset designer displaying person and persontype

Next i create a DataSetDac that will return an instance of a Filled PersonDataSet (In real life you would probably use a TableAdapter and get the data from a database) The code is as simple as:

public static PersonsDataSet Find()
{
 PersonsDataSet personsDataSet = new PersonsDataSet();

 personsDataSet.PersonType.Rows.Add(new object[] { 0, "A geeky person" });
 personsDataSet.PersonType.Rows.Add(new object[] { 1, "A coward" });
 personsDataSet.PersonType.Rows.Add(new object[] { 2, "Feeling hot hot hot" });
 personsDataSet.PersonType.Rows.Add(new object[] { -1, "(None)" });

 personsDataSet.Person.Rows.Add(new object[] { "Timvw", 0 });
 personsDataSet.Person.Rows.Add(new object[] { "John Doe", 1 });
 personsDataSet.Person.Rows.Add(new object[] { "An Onymous", 1 });
 personsDataSet.Person.Rows.Add(new object[] { "Jenna Jameson", 2 });
 personsDataSet.Person.Rows.Add(new object[] { "Null Able", -1 });

 return personsDataSet;
}

And now comes the important stuff: Bind the data to the DataGridView and DataGridViewComboBoxColumn:

public Form1()
{
 InitializeComponent();

 PersonsDataSet personsDataSet = DataSetDac.Find();

 this.dataGridView1.AutoGenerateColumns = false;
 this.ColumnName.DataPropertyName = "Name";
 this.ColumnPersonTypeCode.DataPropertyName = "PersonTypeId";

 this.ColumnPersonTypeCode.DisplayMember = "Label";
 this.ColumnPersonTypeCode.ValueMember = "Id";
 this.ColumnPersonTypeCode.DataSource = personsDataSet.PersonType;

 BindingSource bindingSource = new BindingSource();
 bindingSource.DataSource = personsDataSet.Person;
 this.dataGridView1.DataSource = bindingSource;
}

Geeks are doomed to stay Geeks forever. When you try change a Person that is a Geek, the values in ComboBox should be limited to Geek and (None). The following code takes care of that:

void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
 if (this.dataGridView1.CurrentCell.ColumnIndex == this.ColumnPersonTypeCode.Index)
 {
  BindingSource bindingSource = this.dataGridView1.DataSource as BindingSource;
  PersonsDataSet.PersonRow person = (bindingSource.Current as DataRowView).Row as PersonsDataSet.PersonRow;

  // this method returns the allowed persontypes for the given person
  PersonsDataSet.PersonTypeDataTable personTypeDataTable = DataSetDac.FindPersonTypes(person);

  DataGridViewComboBoxEditingControl comboBox = e.Control as DataGridViewComboBoxEditingControl;
  comboBox.DataSource = personTypeDataTable;

  comboBox.SelectionChangeCommitted -= this.comboBox_SelectionChangeCommitted;
  comboBox.SelectionChangeCommitted += this.comboBox_SelectionChangeCommitted;
 }
}

public static PersonsDataSet.PersonTypeDataTable FindPersonTypes(PersonsDataSet.PersonRow person)
{
 // by default, all persons simply have one of the available persontypecodes
 PersonsDataSet personsDataSet = Find();

 if (person.PersonTypeId.Equals(0))
 {
  // geeks are doomed to stay geeks
  personsDataSet.PersonType.Rows.RemoveAt(2);
  personsDataSet.PersonType.Rows.RemoveAt(1);
 }

 return personsDataSet.PersonType;
}

void comboBox_SelectionChangeCommitted(object sender, EventArgs e)
{
 this.dataGridView1.EndEdit();
}

As always, feel free to download DataGridViewComboBoxBinding2.zip.