Click here to Skip to main content
15,880,543 members
Articles / Desktop Programming / WPF
Article

Row Header and Cell Retrieval in the WPF Toolkit DataGrid Using the MVVM Pattern

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
13 Apr 2011CPOL2 min read 36.4K   1.6K   14  
This article shows the implementation of a row header in a grid based on data grouping, and retrieving cell information from a cell-based grid.

Image 1

1. Introduction

This article shows the implementation of a row header in a grid based on data grouping, and retrieving cell information from a cell-based grid.

The items in the above grid have two groups. The first group has values “Row Header 1” and “Row Header 2”. The second group has values “Group 11”, “Group 12”, “Group 21”, and “Group 22”. The grid shows two row headers based on the groups. The grid is cell-based. It will pop up a form related to the information in the cell when clicking a cell. This requires accessing cell information when there is a mouse click on a cell. This article shows the implementation of these two requirements using the WPF Toolkit DataGrid (http://wpf.codeplex.com/releases/view/40535) using the MVVM pattern (http://wpf.codeplex.com/wikipage?title=WPF%20Model-View-ViewModel%20Toolkit).

2. Row Header in Grid

The DataGrid in the WPF Toolkit provides data grouping in a grid. GroupStyle creates a group style for a row header. The template for a GroupItem describes the row header as a TextBlock, and rotates it accordingly.

XML
<GroupStyle>
  <GroupStyle.ContainerStyle>
   <Style TargetType="{x:Type GroupItem}">
     <Setter Property="Margin" Value="0,0,0,0"/>
     <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type GroupItem}">
         <HeaderedContentControl BorderBrush="#FFA4B97F" BorderThickness="0,0,0,1">
           <HeaderedContentControl.Header>
            <TextBlock FontWeight="Bold" FontSize="12" 
                       Text="{Binding Path=Name}" Margin="5,0,0,0">
              <TextBlock.RenderTransform>
                <RotateTransform Angle="270" /> 
              </TextBlock.RenderTransform>
            </TextBlock>
           </HeaderedContentControl.Header>
           <HeaderedContentControl.Content>
            <ItemsPresenter/>
           </HeaderedContentControl.Content>
         </HeaderedContentControl>
        </ControlTemplate>
      </Setter.Value>
     </Setter>
     <Style.Triggers>
          <DataTrigger Binding="{Binding Name}" Value="Group 11">
            <Setter Property="Template"  Value="{StaticResource defaultGroup}" />
          </DataTrigger>
          <DataTrigger Binding="{Binding Name}" Value="Group 12">
            <Setter Property="Template"  Value="{StaticResource defaultGroup}" />
          </DataTrigger>
          <DataTrigger Binding="{Binding Name}" Value="Group 21">
            <Setter Property="Template"  Value="{StaticResource defaultGroup}" />
          </DataTrigger>
          <DataTrigger Binding="{Binding Name}" Value="Group 22">
            <Setter Property="Template"  Value="{StaticResource defaultGroup}" />
          </DataTrigger>
     </Style.Triggers>
   </Style>
  </GroupStyle.ContainerStyle>
</GroupStyle>

The triggers switch the header style to “defaultGroup” based on the row header name.

Multiple layers of the row header use multiple data grouping described in the ItemsSource in the DataGrid, which is a CollectionViewSource, and has GroupDescriptions to define groups.

XML
<WpfToolkit:DataGrid x:Name="listView" 
            ItemsSource="{Binding Source={StaticResource src}}"  />

<CollectionViewSource x:Key='src' Source="{Binding Items}">
  <CollectionViewSource.GroupDescriptions>
     <PropertyGroupDescription PropertyName="Category1"/>
     <PropertyGroupDescription PropertyName="Category2"/>
  </CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

3. Cell Retrieval in the Grid

A cell-based grid is set using the DataGrid as: SelectionUnit="Cell". Cell information is retrieved in the code-behind by handling the event SelectedCellsChanged: ((Microsoft.Windows.Controls.DataGrid)sender).CurrentCell. But WPF MVVM puts only view-related code in the code-behind. The business logic should be in the ViewModel. One way to move the event handler to the ViewModel is using Attached Dependency Properties, which bind a Command in the ViewModel to Dependency Properties in the View.

The DataGridItemsHelper class registers an ICommand as a command dependency property for the MouseUp event of the DataGrid, and passes the selected DataGridRow and DataGridCell to the Command handler.

C#
public class DataGridItemsHelper
{
      public static readonly DependencyProperty CommandProperty = 
        DependencyProperty.RegisterAttached("Command", typeof(ICommand), 
        typeof(DataGridItemsHelper), 
        new UIPropertyMetadata(null, new PropertyChangedCallback(OnCommandChanged)));

      public static ICommand GetCommand(DependencyObject obj)
      {
             return (ICommand)obj.GetValue(CommandProperty);
      }

      public static void SetCommand(DependencyObject obj, ICommand value)
      {
             obj.SetValue(CommandProperty, value);
      }

      private static void OnCommandChanged(DependencyObject sender, 
                          DependencyPropertyChangedEventArgs e)
      {
             if (e.OldValue != null)
                   ((ItemsControl)sender).MouseUp -= 
                       new MouseButtonEventHandler(OnMouseUpClick);

             if (e.NewValue != null)
                   ((ItemsControl)sender).MouseUp += 
                       new MouseButtonEventHandler(OnMouseUpClick);
      }

      private static void OnMouseUpClick(object sender, MouseButtonEventArgs e)
      {
             DependencyObject source = (DependencyObject)e.OriginalSource;

             var row = TryFindParent<DataGridRow>(source);
             var cell = TryFindParent<DataGridCell>(source);
             if (cell == null || row == null ) return;
             var command = GetCommand((DependencyObject)sender);
             if (command != null)
             {
                   if (command.CanExecute(new DataGridItem(row, cell)))
                         command.Execute(new DataGridItem(row, cell));
             }
      }

      public static T TryFindParent<T>(DependencyObject child)
        where T : DependencyObject
      {
             DependencyObject parentObject = GetParentObject(child);
             if (parentObject == null) return null;
             T parent = parentObject as T;
             if (parent != null)
             {
                   return parent;
             }
             else
             {
                   return TryFindParent<T>(parentObject);
             }
      }

      public static DependencyObject GetParentObject(DependencyObject child)
      {
             if (child == null) return null;
             ContentElement contentElement = child as ContentElement;

             if (contentElement != null)
             {
                   DependencyObject parent = ContentOperations.GetParent(contentElement);
                   if (parent != null) return parent;

                   FrameworkContentElement fce = contentElement as FrameworkContentElement;
                   return fce != null ? fce.Parent : null;
             }
             return VisualTreeHelper.GetParent(child);
      }
}

The dependency property “Command” in the DataGrid binds a command property “CellClickCommand” in the ViewModel to the OnMouseUp event in the DataGrid cell.  

C#
utils:DataGridItemsHelper.Command = "{Binding CellClickCommand}" 

CellClickCommand” is a delegate command in ViewModel, which passes the DataGridRow and DataGridCell for the selected cell to the command handler.

C#
public DelegateCommand<DataGridItem> CellClickCommand { get; private set; }

CellClickCommand = new DelegateCommand<DataGridItem>(
                       CellClickSelection, CanCellClickSelection);

private void CellClickSelection(DataGridItem item)
{
      MessageBox.Show("Cell's column is " + 
                      item.Gridcell.Column.Header.ToString() +
                      " it's ID is " + 
                      ((RowHeaderGrid.Models.Item)(item.Gridrow.Item)).ID);
}

4. Conclusion

This implementation gives a solution for row header based data grouping, and cell retrieval in the WPF Toolkit DataGrid, using the MVVM pattern.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
Yunyou Yao is a Senior IT Specialist in Houston, USA.

Comments and Discussions

 
-- There are no messages in this forum --