Solving the display of alternate layouts for hierarchical view model in 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.

From previous posts you’ll notice we had to create an altered view of our preferred view model, i.e. we should just have an EmployeeViewModel which has a property, named Manages, which is itself a collection of EmployeeViewModel objects, instead we ended up creating a ManagerViewModel and EmployeeViewModel (please refer to the previous posts for more info). So I want to use the following view model now (and fix this problem with field layouts)

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

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

and just for completeness, here’s a factory to show our sample data

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.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;
   }
}

So here’s the XAML we’d like to use

<igDP:XamDataGrid DataSource="{Binding}" GroupByAreaLocation="None" x:Name="grid"
      FieldPositionChanged="Grid_OnFieldPositionChanged">
   <igDP:XamDataGrid.Resources>
      <Style TargetType="{x:Type igDP:LabelPresenter}">
         <EventSetter Event="SizeChanged" Handler="EventSetter_OnHandler"/>
      </Style>
  </igDP:XamDataGrid.Resources>

  <igDP:XamDataGrid.FieldLayoutSettings>
     <igDP:FieldLayoutSettings ExpansionIndicatorDisplayMode="CheckOnDisplay"
           AutoGenerateFields="False"/>
     </igDP:XamDataGrid.FieldLayoutSettings>
     <igDP:XamDataGrid.FieldLayouts>
                
        <igDP:FieldLayout>
           <igDP:FieldLayout.Fields>
              <igDP:Field Name="Name" />
              <igDP:Field Name="Manages" Visibility="Hidden" />
           </igDP:FieldLayout.Fields>
        </igDP:FieldLayout>

        <igDP:FieldLayout>
           <igDP:FieldLayout.Settings>
              <igDP:FieldLayoutSettings LabelLocation="Hidden" />
           </igDP:FieldLayout.Settings>
           <igDP:FieldLayout.Fields>
              <igDP:Field Name="Name" />
              <igDP:Field Name="Manages" Visibility="Hidden" />
           </igDP:FieldLayout.Fields>
       </igDP:FieldLayout>
                
    </igDP:XamDataGrid.FieldLayouts>
</igDP:XamDataGrid>

and finally here’s the code behind

private void EventSetter_OnHandler(object sender, SizeChangedEventArgs e)
{
   var lp = sender as LabelPresenter;
   if (lp != null)
   {
      if (grid.FieldLayouts[0].Fields.Contains(lp.Field))
      {
         var f = grid.FieldLayouts[1].Fields[lp.Field.Index];
         f.Width = new FieldLength(lp.Field.CellWidthResolved);
      }

      if (grid.FieldLayouts[1].Fields.Contains(lp.Field))
      {
         var f = grid.FieldLayouts[0].Fields[lp.Field.Index];
         f.Width = new FieldLength(lp.Field.CellWidthResolved);
      }
   }
}

private void Grid_OnFieldPositionChanged(object sender, FieldPositionChangedEventArgs e)
{
   if (grid.FieldLayouts[0].Fields.Contains(e.Field))
   {
      foreach (var field in grid.FieldLayouts[0].Fields)
      {
         if (field.Index < grid.FieldLayouts[1].Fields.Count)
         {
            var field2 = grid.FieldLayouts[1].Fields[field.Index];
            field2.ActualPosition = new FieldPosition(field.ActualPosition.Column,
                        field.ActualPosition.Row,
                        field.ActualPosition.ColumnSpan,
                        field.ActualPosition.RowSpan);
         }
      }
   }
}

Note: I’m using the two methods I originally defined which expect two field layout objects only, feel free to substitute them for the more generic ones I showed in a previous post

Now the problem we had previously with this self-referencing view model is that XamDataGrid matches to the first field layout everytime, we’re going to programmatically change this. As you can see from the XAML, the second field layout hides the LabelLocation, so what we want is that when rendering the child nodes, we use the second field layout and obviously when rendering the parent, we want the first field layout. So let’s assign keys to both field layouts.

I’ve given the key “parent” to the first field layout and “child” to the second, i.e.

<igDP:FieldLayout Key="parent">
   <!--- removed for brevity -->
</igDP:FieldLayout>

<igDP:FieldLayout Key="child">
   <!--- removed for brevity -->
</igDP:FieldLayout> 

Now in the code behind (assuming the XamDataGrid has the x:Name “grid”) we can simply add one line to the constructor of the Window hosting the XamDataGrid

grid.AssigningFieldLayoutToItem += grid_AssigningFieldLayoutToItem;

and then create the following method

private void grid_AssigningFieldLayoutToItem(object sender, AssigningFieldLayoutToItemEventArgs e)
{
   if (e.Item != null)
   {
      e.FieldLayout = 
           e.ParentExpandableFieldRecord == null ? 
           grid.FieldLayouts["parent"] : 
           grid.FieldLayouts["child"];			
   }
}

So now you’ll notice that child nodes no longer display the header but the parent nodes still do.

If you’ve recreated this code you’ll notice we’re back to the problem we fixed in a previous post, regarding the child columns not in alignment with the parents.

We can fix this quite easily (although all the kudos for this goes to a colleague of mine who solved this a while ago). What we need to do is created a new DataRecordPresenterStyle for the child layout’s field layout settings, thus

<igDP:FieldLayoutSettings 
     LabelLocation="Hidden" 
     DataRecordPresenterStyle="{StaticResource childDataRecordStyle}"/>

and the corresponding childDataRecordStyle will be declared in the resources section as

<controls:ChildDataRecordConverter x:Key="childDataRecordConverter" />

<Style x:Key="childDataRecordStyle" TargetType="{x:Type igDP:DataRecordPresenter}">
   <Setter Property="RenderTransform">
      <Setter.Value>
         <TranslateTransform Y="0" 
               X="{Binding NestingDepth, Converter={StaticResource childDataRecordConverter}}" />
      </Setter.Value>
   </Setter>
</Style>

Finally we need the converter code

public class ChildDataRecordConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      return value == null ? Binding.DoNothing : -(((int) value/2)*17);
   }

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

The trick here is we’re going to transform the rendering of the data record using the nesting depth.

At this point we should now have a view model that looks more natural and columns all lining up, but we still have one problem to solve. The child records need to indent slightly to better show they’re child nodes of a parent node – currently it’s difficult to see (when expanded) whan are child and what are parent nodes. So we’ll look at solving this in the next post.