for WPF developers
Home Profile Tips 全記事一覧

DataGrid コントロールの行ヘッダに行番号を表示する

(2017/05/17 14:22:47 created.)

DataGrid コントロールによって表形式でデータを表示したとき、その行ヘッダに行番号を入れたい場合は多々あります。このような機能は使い回しが効くように、添付ビヘイビアで作成しておくと便利です。

MainViewModel クラスと MainView を次のように定義します。

MainViewModel.cs
  1. namespace Tips_DataGrid1.ViewModels
  2. {
  3.     using System.Collections.ObjectModel;
  4.     using System.Linq;
  5.     using Tips_DataGrid1.Models;
  6.  
  7.     public class MainViewModel : NotificationObject
  8.     {
  9.         public MainViewModel()
  10.         {
  11.             this.People = new ObservableCollection<Person>(Enumerable.Range(0, 10).Select(x => new Person()
  12.             {
  13.                 Name = "田中 " + x + "太郎",
  14.                 Age = x,
  15.                 Gender = x % 3 != 0 ? Gender.Male : Gender.Female,
  16.                 IsAuthenticated = x % 4 != 0,
  17.             }));
  18.         }
  19.  
  20.         private ObservableCollection<Person> _people;
  21.         /// <summary>
  22.         /// 人物データコレクションを取得します。
  23.         /// </summary>
  24.         public ObservableCollection<Person> People
  25.         {
  26.             get { return this._people; }
  27.             private set { SetProperty(ref this._people, value); }
  28.         }
  29.     }
  30. }
MainView.xaml
  1. <Window x:Class="Tips_DataGrid1.Views.MainView"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         Title="MainView"
  5.         Height="300" Width="300"
  6.         WindowStartupLocation="CenterScreen">
  7.     <DataGrid ItemsSource="{Binding People}" />
  8. </Window>

DataGrid コントロールによって People プロパティの持つ人物データコレクションが表形式で表示されることになります。ここまでのコードによる実行結果は下図のようになります。


ここで、次のような添付ビヘイビアを定義します。添付ビヘイビアに関しては 添付ビヘイビアを作成する を参照してください。

DataGridBehavior.cs
  1. namespace Tips_DataGrid1.Views.Behaviors
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.Windows;
  6.     using System.Windows.Controls;
  7.     using System.Windows.Controls.Primitives;
  8.     using System.Windows.Media;
  9.  
  10.     /// <summary>
  11.     /// <c>System.Windows.Controls.DataGrid</c> コントロールに関する添付ビヘイビアを表します。
  12.     /// </summary>
  13.     public class DataGridBehavior
  14.     {
  15.         #region DisplayRowNumber 添付プロパティ
  16.         /// <summary>
  17.         /// DisplayRowNumber 添付プロパティの定義
  18.         /// </summary>
  19.         public static DependencyProperty DisplayRowNumberProperty = DependencyProperty.RegisterAttached("DisplayRowNumber", typeof(int?), typeof(DataGridBehavior), new FrameworkPropertyMetadata(null, OnDisplayRowNumberChanged));
  20.  
  21.         /// <summary>
  22.         /// DisplayRowNumber 添付プロパティを取得します。
  23.         /// </summary>
  24.         /// <param name="target">対象とする DependencyObject を指定します。</param>
  25.         /// <returns>取得した値を返します。</returns>
  26.         public static int? GetDisplayRowNumber(DependencyObject target)
  27.         {
  28.             return (int?)target.GetValue(DisplayRowNumberProperty);
  29.         }
  30.  
  31.         /// <summary>
  32.         /// DisplayRowNumber 添付プロパティを設定します。
  33.         /// </summary>
  34.         /// <param name="target">対象とする DependencyObject を指定します。</param>
  35.         /// <param name="value">設定値を指定します。</param>
  36.         public static void SetDisplayRowNumber(DependencyObject target, int? value)
  37.         {
  38.             target.SetValue(DisplayRowNumberProperty, value);
  39.         }
  40.  
  41.         /// <summary>
  42.         /// DisplayRowNumber 添付プロパティ変更イベントハンドラ
  43.         /// </summary>
  44.         /// <param name="sender">イベント発行元</param>
  45.         /// <param name="e">イベント引数</param>
  46.         private static void OnDisplayRowNumberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  47.         {
  48.             var dataGrid = sender as DataGrid;
  49.             if (dataGrid == null)
  50.                 return;
  51.  
  52.             if (e.NewValue != null)
  53.             {
  54.                 var displayNumber = GetDisplayRowNumber(dataGrid);
  55.  
  56.                 EventHandler<DataGridRowEventArgs> loadedRowHandler = null;
  57.                 loadedRowHandler = (object _, DataGridRowEventArgs ea) =>
  58.                 {
  59.                     if (displayNumber == null)
  60.                     {
  61.                         dataGrid.LoadingRow -= loadedRowHandler;
  62.                         return;
  63.                     }
  64.                     ea.Row.Header = ea.Row.GetIndex() + displayNumber;
  65.                 };
  66.                 dataGrid.LoadingRow += loadedRowHandler;
  67.  
  68.                 ItemsChangedEventHandler itemsChangedHandler = null;
  69.                 itemsChangedHandler = (object _, ItemsChangedEventArgs ea) =>
  70.                 {
  71.                     if (displayNumber == null)
  72.                     {
  73.                         dataGrid.ItemContainerGenerator.ItemsChanged -= itemsChangedHandler;
  74.                         return;
  75.                     }
  76.                     // 子要素の DataGridRow クラスに対してのみヘッダ情報を書き換える
  77.                     GetVisualChildCollection<DataGridRow>(dataGrid).
  78.                         ForEach(d => d.Header = d.GetIndex() + displayNumber);
  79.                 };
  80.                 dataGrid.ItemContainerGenerator.ItemsChanged += itemsChangedHandler;
  81.             }
  82.         }
  83.         #endregion DisplayRowNumber 添付プロパティ
  84.  
  85.         /// <summary>
  86.         /// 指定された型の子要素をリストとして取得します。
  87.         /// </summary>
  88.         /// <typeparam name="T">リストアップする型を指定します。</typeparam>
  89.         /// <param name="parent">子要素を持つ親を指定します。</param>
  90.         /// <returns>指定された型の子要素のみを集めたリストを返します。</returns>
  91.         private static List<T> GetVisualChildCollection<T>(object parent)
  92.             where T : Visual
  93.         {
  94.             var visualCollection = new List<T>();
  95.             GetVisualChildCollection(parent as DependencyObject, visualCollection);
  96.             return visualCollection;
  97.         }
  98.  
  99.         /// <summary>
  100.         /// 指定された型の子要素を与えられたリストに追加します。
  101.         /// </summary>
  102.         /// <typeparam name="T">リストアップする型を指定します。</typeparam>
  103.         /// <param name="parent">子要素を持つ親を指定します。</param>
  104.         /// <param name="visualCollection">リストアップするためのリストを指定します。</param>
  105.         private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection)
  106.             where T : Visual
  107.         {
  108.             int count = VisualTreeHelper.GetChildrenCount(parent);
  109.             for (int i = 0; i < count; i++)
  110.             {
  111.                 DependencyObject child = VisualTreeHelper.GetChild(parent, i);
  112.                 if (child is T)
  113.                 {
  114.                     visualCollection.Add(child as T);
  115.                 }
  116.                 if (child != null)
  117.                 {
  118.                     GetVisualChildCollection(child, visualCollection);
  119.                 }
  120.             }
  121.         }
  122.     }
  123. }

非常に長いですが、DisplayRowNumber という名前の添付プロパティを持っており、このプロパティに指定された数値を始めとして行ヘッダをナンバリングするようにしています。

DataGrid コントロールは各行を DataGridRow コントロールをコンテナとして表現しています。したがって、上記の添付ビヘイビアの中では、DataGrid が持つ DataGridRow コントロールを抽出し、それぞれの DataGridRow.Header プロパティにアクセスして行ヘッダを変更しています。

どの DataGridRow コントロールが何番目であるかは DataGridRow クラスが持つ GetIndex() メソッドで取得しています。GetIndex() メソッドは DataGrid コントロールの Items プロパティを参照して何番目かを取得しているため、列ヘッダをクリックすることなどによるソーティングがおこなわれたとしても、一行目は必ず 0 となります。

上記のコードでは、DisplayRowNumber 添付プロパティを基準として行番号を振っているため、任意の数値から開始させることができます。

この添付ビヘイビアの使用例は次のようになります。

MainView.xaml
  1. <Window x:Class="Tips_DataGrid1.Views.MainView"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:b="clr-namespace:Tips_DataGrid1.Views.Behaviors"
  5.         Title="MainView"
  6.         Height="300" Width="300"
  7.         WindowStartupLocation="CenterScreen">
  8.     <DataGrid ItemsSource="{Binding People}" b:DataGridBehavior.DisplayRowNumber="1" />
  9. </Window>

添付ビヘイビアを使用するために名前空間 "b" の定義を追加しています。後は通常の添付プロパティを設定する要領で DataGrid コントロールに DisplayRowNumber 添付プロパティを記述します。

このコードの実行結果は次のようになります。DisplayRowNumber 添付プロパティに 1 を指定したため、行番号は 1 から始まっています。また、あくまでも行番号であるため、列をソートしたとしても、この番号は変わらず 1 から順に表示されるようになります。