Dynamic columns within the XamDataGrid

Before I start this post, let me just say I’m using an old version of the Infragistics XamDataGrid for this post, version 10.3. Hence this may have been changed in subsequent releases, but as I have a legacy application to support, that’s the version they’re using.

In the past few posts I’ve been looking at implementing a hierarchical data model and displaying it using the XamDataGrid is a similar way to a tree view might work. For the project I’m currently working on we also have the requirement to display an unknown number of columns.

If we take the previous examples where we had the following view model

public class EmployeeViewModel
{
   public EmployeeViewModel()
   {
      Manages = new ObservableCollection<EmployeeViewModel>();
   }

   public string Name { get; set; }
   public ObservableCollection<EmployeeViewModel> Manages { get; private set; }
}

Let’s assume each employee will need to take an unspecified number of courses each year. Some employees might need to take one group of courses whilst others may take another group of courses but we want to see them all listed against each employee.

Ofcourse we could simply add an ObservableCollection with every available course stored for each employee within this list, but this may be neither feasible or desirable.

Another alternative might be to use a DataTable and obviously populate this as necessary.

But I’m going to implement this functionality all inside a our XamDataGrid derived class. As we’re already handling similar changes in code for the hierarchical data, I don’t feel too dirty writing more code.

Let’s see the code

The view model will looks as follows

public class CourseViewModel
{
   public string Name { get; set; }
   public double Score { get; set; }
}

public class EmployeeViewModel
{
   public EmployeeViewModel()
   {
      Manages = new ObservableCollection<EmployeeViewModel>();
      Courses = new ObservableCollection<CourseViewModel>();
   }

   public string Name { get; set; }
   public ObservableCollection<EmployeeViewModel> Manages { get; private set; }
   public ObservableCollection<CourseViewModel> Courses { get; private set; }

   public double? this[string course]
   {
      get
      {
         var result = Courses.FirstOrDefault(c => c.Name == course);
         return result == null ? (double?)null : result.Score;
      }
   }
}

The key additions to the previous implementation of the EmployeeViewModel (from previous posts) is the addition of the Courses collection, but more interesting is the addition of the indexer. This will be used when the XamDataGrid is after the Score for the Course for the specific column of data.

In the code behind, we’ll declare the following (at the class level)

private readonly IList<Field> dynamicParentFields = new List<Field>();
private readonly IList<Field> dynamicChildFields = new List<Field>();

private readonly List<string> courses = new List<string>();

private readonly IValueConverter courseValueConverter;

and within the constructor of the code behind class we’ll have the following (after the DataContext has been assigned)

courseValueConverter = new CourseValueConverter();

TraverseCourses((IList<EmployeeViewModel>) DataContext);
courses.Sort();

and here’s the rest of the methods in the code behind

private void TraverseCourses(IEnumerable<EmployeeViewModel> employees)
{
   if (employees == null)
      return;

   foreach (var employee in employees)
   {
      TraverseCourses(employee.Manages);
      foreach (var course in employee.Courses)
      {
         if (courses.FirstOrDefault(c => c == course.Name) == null)
         {
            courses.Add(course.Name);
         }
      }
   }			
}

private void AddDynamicFields(FieldLayout layout, IList<Field> fields)
{
   foreach (var field in fields)
   {
      layout.Fields.Remove(field);
   }
   fields.Clear();

   var rootPropertyPath = new PropertyPath("");

   foreach (var course in courses)
   {
      var field = CreateDynamicField(rootPropertyPath, course);
      layout.Fields.Add(field);
      fields.Add(field);
   }
}

private UnboundField CreateDynamicField(PropertyPath rootPropertyPath, string course)
{
   return new UnboundField
   {
      Name = course,
      Label = course,
      DataType = typeof(object),
      Binding = new Binding
      {
         Path = rootPropertyPath,
         Mode = BindingMode.OneWay,
         Converter = courseValueConverter,
         ConverterParameter = course
      },
      Settings =
      {
         AllowEdit = false,
         LabelTextAlignment = TextAlignment.Center,
         EditorType = typeof(XamTextEditor)
      },
   };
}

and one more thing in the code behind, we need to alter the AssigningFieldLayoutToItem method that we created a event handler off of in the previous posts, it will now look like this

private void grid_AssigningFieldLayoutToItem(object sender, 
            AssigningFieldLayoutToItemEventArgs e)
{
   if (e.Item != null)
   {
      AddDynamicFields(grid.FieldLayouts["parent"], dynamicParentFields);
      AddDynamicFields(grid.FieldLayouts["child"], dynamicChildFields);

      e.FieldLayout = 
          e.ParentExpandableFieldRecord == null ? 
          grid.FieldLayouts["parent"] : 
          grid.FieldLayouts["child"];
   }
}

The final piece of the jigsaw is the CourseValueConverter which looks like this

public class CourseValueConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      if (value is EmployeeViewModel && parameter is string)
      {
         var result = value as EmployeeViewModel;
         var course = (string)parameter;
         return result[course];
      }
      return null;
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new NotImplementedException();
   }
}

How does this work

So basically at some point after the model has been created, we traverse the EmployeeViewModel tree and get all distinct course names. Obviously in a production environment we’ll also need to handle data changes on the DataContext to ensure we refresh this list as needed.

During the AssigningFieldLayoutToItem event we populate the field layouts (in this sample we’ve restricted the code to the two known field layouts, ofcourse you may need to handle this more generically).

The AddDynamicField method clears any dynamic fields that we’ve created (we keep track of those fields in the dynamicParentFields and dynamicChildFields lists), then we create new Unbound fields for each dynamic field, we assign a converter and pass in the field name (in this case the course name).

The converter does the real work. It’s passed the current EmployeeViewModel by the XamDataGrid, from this it calls the EmployeeViewModel indexer with the parameter (the course name) assigned to the field. Ofcourse we could replace the indexer with a method if we preferred.

Finally the instance of the EmployeeViewModel then checks it’s courses collection for the supplied course and if it’s got it, returns a value, in this case the score.

If our data looked as follows

public static class EmployeeViewModelFactory
{
   public static ObservableCollection<EmployeeViewModel> Create()
   {
      var employees = new ObservableCollection<EmployeeViewModel>();

      var bob = new EmployeeViewModel { Name = "Bob" };
      var bill = new EmployeeViewModel { Name = "Bill" };
      var fred = new EmployeeViewModel { Name = "Fred" };
      var alfred = new EmployeeViewModel { Name = "Alfred" };
      var jim = new EmployeeViewModel { Name = "Jim" };
      var jeff = new EmployeeViewModel { Name = "Jeff" };
      var craig = new EmployeeViewModel { Name = "Craig" };

      bob.Courses.Add(new CourseViewModel { Name = "C2", Score = 100 });
      bob.Courses.Add(new CourseViewModel { Name = "C4", Score = 85 });

      craig.Courses.Add(new CourseViewModel { Name = "C1", Score = 98 });
      craig.Courses.Add(new CourseViewModel { Name = "C4", Score = 65 });

      jeff.Courses.Add(new CourseViewModel { Name = "C3", Score = 70 });

      bob.Manages.Add(bill);
      bob.Manages.Add(fred);

      alfred.Manages.Add(jim);

      jim.Manages.Add(jeff);

      employees.Add(bob);
      employees.Add(alfred);
      employees.Add(craig);

     return employees;
   }
}

our view will now look like this

Dynamically added columns

Addendum

Whilst the above code works, at this point I would tend towards creating my own XamDataGrid derived class and implementing most/all the functionality in that class instead of the code behind. There may be alternate ways to achieve this, I’ve heard of people creating behaviors for the XamDataGrid to handle the creation of the dynamic columns.