Musings by Generator

Development, Life and everything else in S.A.

Custom Columns for the DataGrid in Silverlight

We are developing a silverlight front-end for a client, and hit a problem where we didn’t want to specify the columns or write custom converters to get the text descriptions from lists for ID’s etc. We just wanted to say to the grid, here is your data, now bind. So a custom columns class was written.

Our UI makes calls to an web service which returns xml based messages, the message coming back, has all data for that call, so the actual records as well as any look up lists that may be needed to display meaningful data. Each call to get data has a corresponding init call, this call basically gets all fields that will be returned by the call. So we wanted to be able to say our columns come from the “response” and the data comes from the “response.message”. So what was born from this is below:

public class Columns
{
   private static Dictionary<DependencyObject, Dictionary<string, DataGridColumn>> columns = new Dictionary<DependencyObject, Dictionary<string, DataGridColumn>>();
   private static List<string> filter = new List<string>();

   public static readonly DependencyProperty BindingProperty = DependencyProperty.RegisterAttached("Binding", typeof(object), typeof(Columns), new PropertyMetadata(BindingChanged));
   public static readonly DependencyProperty FilterProperty = DependencyProperty.RegisterAttached("VisibleColumns", typeof(List<string>), typeof(Columns), new PropertyMetadata(VisibleColummnsChanged));

   public static object GetBinding(DependencyObject obj)
   {
      return (object)obj.GetValue(BindingProperty);
   }

   public static void SetBinding(DependencyObject obj, object value)
   {
      obj.SetValue(BindingProperty, value);
   }

   public static List<string> GetVisibleColumns(DependencyObject obj)
   {
      return (List<string>)obj.GetValue(FilterProperty);
   }

   public static void SetVisibleColumns(DependencyObject obj, List<string> value)
   {
      obj.SetValue(FilterProperty, value);
   }

   private static void BindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
   {
      var grid = sender as DataGrid;
      var response = e.NewValue as IEnvelope;
      grid.AutoGeneratingColumn += (s, ea) =>
      {
         if (!response.Definitions.ContainsKey(ea.PropertyName))
         {
            ea.Cancel = true;
            return;
         }
         if (response.Definitions[ea.PropertyName].FieldType is SingleSelect)
         {
            ea.Column = new DataGridSingleSelectColumn(ea.PropertyName, response);

            if (!columns.ContainsKey(sender))
               columns.Add(sender, new Dictionary<string, DataGridColumn>());
            if (columns[sender].ContainsKey(ea.PropertyName))
            {
               grid.Columns.Remove(columns[sender][ea.PropertyName]);
               columns[sender][ea.PropertyName] = ea.Column; ;
            }
            else
            {
               columns[sender].Add(ea.PropertyName, ea.Column);
            }
         }
         ea.Column.Header = response.Definitions[ea.PropertyName].Description;
         ea.Column.SortMemberPath = ea.PropertyName;

         if (filter.Count > 0)
            ea.Column.Visibility = filter.Contains(ea.PropertyName) ? Visibility.Visible : Visibility.Collapsed;
      };

      grid.ColumnWidth = DataGridLength.Auto;
   }

   private static void VisibleColummnsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
   {
      filter.Clear();
      filter.AddRange(e.NewValue as List<string>);
   }
}

 

There are some custom columns in there such as the DataGridSingleSelectColumn, FieldType, and IEnvelope that are specific to this solution, but the idea here is a column collection is created and if the data coming back indicates that the value is a look up value type, then the code gets the description for that id from the correct list.

To use this in XAML is pretty simple:

xmlns:cols="clr-namespace:Your.Namespace;assembly=Your.Assembly"
xmlns:swcd="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

....

<swcd:DataGrid asp:Columns.VisibleColumns="{Binding ListBinding}" asp:Columns.Binding="{Binding Response, Mode=TwoWay}" Margin="-1" ItemsSource="{Binding Response.Message, Mode=TwoWay}" IsReadOnly="True" SelectionMode="Single">

....

 

I know that this solution is pretty specific to how we retrieve data from our service but the principles are the same, for anyone else who wishes to create custom columns for the DataGrid.

« Newer Posts