{"id":2418,"date":"2014-10-16T21:19:45","date_gmt":"2014-10-16T21:19:45","guid":{"rendered":"http:\/\/putridparrot.com\/blog\/?p=2418"},"modified":"2014-10-16T21:19:45","modified_gmt":"2014-10-16T21:19:45","slug":"dynamic-columns-within-the-xamdatagrid","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/dynamic-columns-within-the-xamdatagrid\/","title":{"rendered":"Dynamic columns within the XamDataGrid"},"content":{"rendered":"<p><em>Before I start this post, let me just say I\u2019m 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\u2019s the version they\u2019re using.<\/em><\/p>\n<p>In the past few posts I&#8217;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&#8217;m currently working on we also have the requirement to display an unknown number of columns.<\/p>\n<p>If we take the previous examples where we had the following view model <\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class EmployeeViewModel\r\n{\r\n   public EmployeeViewModel()\r\n   {\r\n      Manages = new ObservableCollection&lt;EmployeeViewModel&gt;();\r\n   }\r\n\r\n   public string Name { get; set; }\r\n   public ObservableCollection&lt;EmployeeViewModel&gt; Manages { get; private set; }\r\n}\r\n<\/pre>\n<p>Let&#8217;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.<\/p>\n<p>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.<\/p>\n<p>Another alternative might be to use a DataTable and obviously populate this as necessary.<\/p>\n<p>But I&#8217;m going to implement this functionality all inside a our XamDataGrid derived class. As we&#8217;re already handling similar changes in code for the hierarchical data, I don&#8217;t feel too dirty writing more code.<\/p>\n<p><strong>Let&#8217;s see the code<\/strong><\/p>\n<p>The view model will looks as follows<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class CourseViewModel\r\n{\r\n   public string Name { get; set; }\r\n   public double Score { get; set; }\r\n}\r\n\r\npublic class EmployeeViewModel\r\n{\r\n   public EmployeeViewModel()\r\n   {\r\n      Manages = new ObservableCollection&lt;EmployeeViewModel&gt;();\r\n      Courses = new ObservableCollection&lt;CourseViewModel&gt;();\r\n   }\r\n\r\n   public string Name { get; set; }\r\n   public ObservableCollection&lt;EmployeeViewModel&gt; Manages { get; private set; }\r\n   public ObservableCollection&lt;CourseViewModel&gt; Courses { get; private set; }\r\n\r\n   public double? this&#x5B;string course]\r\n   {\r\n      get\r\n      {\r\n         var result = Courses.FirstOrDefault(c =&gt; c.Name == course);\r\n         return result == null ? (double?)null : result.Score;\r\n      }\r\n   }\r\n}\r\n<\/pre>\n<p>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.<\/p>\n<p>In the code behind, we&#8217;ll declare the following (at the class level)<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate readonly IList&lt;Field&gt; dynamicParentFields = new List&lt;Field&gt;();\r\nprivate readonly IList&lt;Field&gt; dynamicChildFields = new List&lt;Field&gt;();\r\n\r\nprivate readonly List&lt;string&gt; courses = new List&lt;string&gt;();\r\n\r\nprivate readonly IValueConverter courseValueConverter;\r\n<\/pre>\n<p>and within the constructor of the code behind class we&#8217;ll have the following (after the DataContext has been assigned)<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\ncourseValueConverter = new CourseValueConverter();\r\n\r\nTraverseCourses((IList&lt;EmployeeViewModel&gt;) DataContext);\r\ncourses.Sort();\r\n<\/pre>\n<p>and here&#8217;s the rest of the methods in the code behind<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate void TraverseCourses(IEnumerable&lt;EmployeeViewModel&gt; employees)\r\n{\r\n   if (employees == null)\r\n      return;\r\n\r\n   foreach (var employee in employees)\r\n   {\r\n      TraverseCourses(employee.Manages);\r\n      foreach (var course in employee.Courses)\r\n      {\r\n         if (courses.FirstOrDefault(c =&gt; c == course.Name) == null)\r\n         {\r\n            courses.Add(course.Name);\r\n         }\r\n      }\r\n   }\t\t\t\r\n}\r\n\r\nprivate void AddDynamicFields(FieldLayout layout, IList&lt;Field&gt; fields)\r\n{\r\n   foreach (var field in fields)\r\n   {\r\n      layout.Fields.Remove(field);\r\n   }\r\n   fields.Clear();\r\n\r\n   var rootPropertyPath = new PropertyPath(&quot;&quot;);\r\n\r\n   foreach (var course in courses)\r\n   {\r\n      var field = CreateDynamicField(rootPropertyPath, course);\r\n      layout.Fields.Add(field);\r\n      fields.Add(field);\r\n   }\r\n}\r\n\r\nprivate UnboundField CreateDynamicField(PropertyPath rootPropertyPath, string course)\r\n{\r\n   return new UnboundField\r\n   {\r\n      Name = course,\r\n      Label = course,\r\n      DataType = typeof(object),\r\n      Binding = new Binding\r\n      {\r\n         Path = rootPropertyPath,\r\n         Mode = BindingMode.OneWay,\r\n         Converter = courseValueConverter,\r\n         ConverterParameter = course\r\n      },\r\n      Settings =\r\n      {\r\n         AllowEdit = false,\r\n         LabelTextAlignment = TextAlignment.Center,\r\n         EditorType = typeof(XamTextEditor)\r\n      },\r\n   };\r\n}\r\n<\/pre>\n<p>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<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate void grid_AssigningFieldLayoutToItem(object sender, \r\n            AssigningFieldLayoutToItemEventArgs e)\r\n{\r\n   if (e.Item != null)\r\n   {\r\n      AddDynamicFields(grid.FieldLayouts&#x5B;&quot;parent&quot;], dynamicParentFields);\r\n      AddDynamicFields(grid.FieldLayouts&#x5B;&quot;child&quot;], dynamicChildFields);\r\n\r\n      e.FieldLayout = \r\n          e.ParentExpandableFieldRecord == null ? \r\n          grid.FieldLayouts&#x5B;&quot;parent&quot;] : \r\n          grid.FieldLayouts&#x5B;&quot;child&quot;];\r\n   }\r\n}\r\n<\/pre>\n<p>The final piece of the jigsaw is the CourseValueConverter which looks like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class CourseValueConverter : IValueConverter\r\n{\r\n   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)\r\n   {\r\n      if (value is EmployeeViewModel &amp;&amp; parameter is string)\r\n      {\r\n         var result = value as EmployeeViewModel;\r\n         var course = (string)parameter;\r\n         return result&#x5B;course];\r\n      }\r\n      return null;\r\n   }\r\n\r\n   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)\r\n   {\r\n      throw new NotImplementedException();\r\n   }\r\n}\r\n<\/pre>\n<p><strong>How does this work<\/strong><\/p>\n<p>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&#8217;ll also need to handle data changes on the DataContext to ensure we refresh this list as needed.<\/p>\n<p>During the AssigningFieldLayoutToItem event we populate the field layouts (in this sample we&#8217;ve restricted the code to the two known field layouts, ofcourse you may need to handle this more generically).<\/p>\n<p>The AddDynamicField method clears any dynamic fields that we&#8217;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).<\/p>\n<p>The converter does the real work. It&#8217;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.<\/p>\n<p>Finally the instance of the EmployeeViewModel then checks it&#8217;s courses collection for the supplied course and if it&#8217;s got it, returns a value, in this case the score.<\/p>\n<p>If our data looked as follows<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic static class EmployeeViewModelFactory\r\n{\r\n   public static ObservableCollection&lt;EmployeeViewModel&gt; Create()\r\n   {\r\n      var employees = new ObservableCollection&lt;EmployeeViewModel&gt;();\r\n\r\n      var bob = new EmployeeViewModel { Name = &quot;Bob&quot; };\r\n      var bill = new EmployeeViewModel { Name = &quot;Bill&quot; };\r\n      var fred = new EmployeeViewModel { Name = &quot;Fred&quot; };\r\n      var alfred = new EmployeeViewModel { Name = &quot;Alfred&quot; };\r\n      var jim = new EmployeeViewModel { Name = &quot;Jim&quot; };\r\n      var jeff = new EmployeeViewModel { Name = &quot;Jeff&quot; };\r\n      var craig = new EmployeeViewModel { Name = &quot;Craig&quot; };\r\n\r\n      bob.Courses.Add(new CourseViewModel { Name = &quot;C2&quot;, Score = 100 });\r\n      bob.Courses.Add(new CourseViewModel { Name = &quot;C4&quot;, Score = 85 });\r\n\r\n      craig.Courses.Add(new CourseViewModel { Name = &quot;C1&quot;, Score = 98 });\r\n      craig.Courses.Add(new CourseViewModel { Name = &quot;C4&quot;, Score = 65 });\r\n\r\n      jeff.Courses.Add(new CourseViewModel { Name = &quot;C3&quot;, Score = 70 });\r\n\r\n      bob.Manages.Add(bill);\r\n      bob.Manages.Add(fred);\r\n\r\n      alfred.Manages.Add(jim);\r\n\r\n      jim.Manages.Add(jeff);\r\n\r\n      employees.Add(bob);\r\n      employees.Add(alfred);\r\n      employees.Add(craig);\r\n\r\n     return employees;\r\n   }\r\n}\r\n<\/pre>\n<p>our view will now look like this<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/putridparrot.com\/blog\/wp-content\/uploads\/2014\/10\/XamDataGrid5.png\" alt=\"Dynamically added columns\" \/><\/p>\n<p><strong>Addendum<\/strong><\/p>\n<p>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&#8217;ve heard of people creating behaviors for the XamDataGrid to handle the creation of the dynamic columns. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Before I start this post, let me just say I\u2019m 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\u2019s the version they\u2019re using. In the past few posts I&#8217;ve been looking at [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[84,85],"tags":[],"class_list":["post-2418","post","type-post","status-publish","format-standard","hentry","category-infragistics","category-xamdatagrid"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/2418","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=2418"}],"version-history":[{"count":9,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/2418\/revisions"}],"predecessor-version":[{"id":2458,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/2418\/revisions\/2458"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=2418"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=2418"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=2418"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}