DataGridの多段表示ですが、【WPF】ここまで出来る!DataGridで多段表示!(DataGridTemplateColumn編)の記事ではXAMLのレイアウトのみで実現しました。
1つのセルに複数の項目を表示する場合、単純なものであればXAMLだけでも良いのですが、複雑なロジックが伴う場合や、もっと凝ったレイアウトにしたい場合、XAMLだけではコードが複雑になってしまいます。
そこで、今回はセルにユーザーコントロールを張り付けて多段表示を実現してみました。
ユーザーコントロールにするメリットは、そのセルに関するC#のコードが分離できるため、修正や機能追加がやりやすくなるという点です。
今回の記事が、みなさんの開発のヒントにでもなれば幸いです。
UserControlを使ったDataGridの多段表示画面サンプル
今回は次の様なデザインになります。
商品データのセルにユーザーコントロールが張り付けられています。
ユーザーコントロールにすることで、例えば入力時に入力チェックを行ったり、ドロップダウンリストで入力候補を選ばせるといった処理が、メインのコードから切り離して開発できるようになります。
デメリットとしては、DataGridで選択した行の文字色が変わらない事です。
通常、DataGridは選択行の文字色が白になりますが、ユーザーコントロール側は独立しているので変化しません。
DataGridのデフォルトの選択色は比較的濃い青色なので、白文字でないと見難くなります。
フォーカスされたことをユーザーコントロール側に通知して、ユーザーコントロール側で文字色を白にするなどの方法が考えられますが、それも面倒なので、ここではDataGrid側の文字色を黒、選択色を薄い黄色に設定することで見難さを回避しています。
DataGridのレイアウト構造
前回の記事では、DataGridTemplateColumnに複数のコントロールを張り付けて多段表示を実現していましたが、今回はここにユーザーコントロールを張り付けます。
XAMLのコード
多段表示のセルをユーザーコントロールに替えたことで、XAMLのコードはかなりシンプルになりました。
9行目から始まる <Windows.Resource>のタグで、DataGridRowの背景色と文字色、選択セルの枠線の色を定義しています。
また、セルに使ったユーザーコントロールは通常のセルと同様、Binding に対応しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
<Window x:Class="DataGridLayoutSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:DataGridLayoutSample" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <Style TargetType="{x:Type DataGridRow}"> <Style.Resources> <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="LightYellow" /> <SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="LightYellow" /> <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" /> <SolidColorBrush x:Key="{x:Static SystemColors.ControlTextBrushKey}" Color="Black" /> </Style.Resources> </Style> </Window.Resources> <Grid> <DataGrid x:Name="uxDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding}" Margin="10,36,10,10"> <DataGrid.Columns> <DataGridTextColumn Header="日付" Width="100" Binding="{Binding Path=日付}"> <DataGridTextColumn.ElementStyle> <Style TargetType="TextBlock"> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="HorizontalAlignment" Value="Center"/> </Style> </DataGridTextColumn.ElementStyle> </DataGridTextColumn> <DataGridTemplateColumn Header="商品データ"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Grid> <local:CellUserControl Maker="{Binding メーカー}" ProductName="{Binding 商品名}" ProductCode="{Binding 商品コード}" Padding="0"></local:CellUserControl> </Grid> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> <Button x:Name="uxShowData" Content="表示" HorizontalAlignment="Right" Height="21" Margin="0,8,17,0" VerticalAlignment="Top" Width="101" Click="uxShowData_Click"/> </Grid> </Window> |
C#のコード
C#側のコードは前回の記事とまったく同じです。
つまり、ユーザーコントロールを作成したあと、XAML側でユーザーコントロールにBindingするよう記述を変えるだけで済んでしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Data; namespace DataGridLayoutSample { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void uxShowData_Click(object sender, RoutedEventArgs e) { uxDataGrid.DataContext = CreateDataTable(); } private DataTable CreateDataTable() { var dt = new DataTable(); dt.Columns.Add("日付"); dt.Columns.Add("メーカー"); dt.Columns.Add("商品コード"); dt.Columns.Add("商品名"); dt.Columns.Add("単価"); dt.Columns.Add("数量"); dt.Rows.Add(dt.NewRow().ItemArray = new string[] { "2020/10/11", "キャノン", "4147C001", "EOS Ra", "150000", "3" }); dt.Rows.Add(dt.NewRow().ItemArray = new string[] { "2020/10/12", "キャノン", "4147C012", "EOS R5", "160000", "6" }); dt.Rows.Add(dt.NewRow().ItemArray = new string[] { "2020/11/15", "ニコン", "4960759127693", "COOLPIX S9700 ", "20000", "1" }); dt.Rows.Add(dt.NewRow().ItemArray = new string[] { "2020/11/23", "ニコン", "4960759127716", "COOLPIX S6000", "20000", "4" }); dt.Rows.Add(dt.NewRow().ItemArray = new string[] { "2020/11/09", "SONY", "S26347C001", "Cyber-shotHX400V", "123000", "8" }); dt.Rows.Add(dt.NewRow().ItemArray = new string[] { "2020/12/01", "SONY", "S30447C004", "Cyber-shotWX700", "45000", "12" }); dt.Rows.Add(dt.NewRow().ItemArray = new string[] { "2020/12/11", "富士フィルム", "FJ55505", "FUJIFILM X-S10", "120000", "3" }); return dt; } } } |
ユーザーコントロールのXAML
ユーザーコントロールは次の様なレイアウトになっています。
単純にテキストボックス、コンボボックス、在庫区分の3つを張り付けただけであるため、XAMLもシンプルになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<UserControl x:Class="DataGridLayoutSample.CellUserControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:DataGridLayoutSample" mc:Ignorable="d" d:DesignHeight="56" Width="524" Height="57"> <Grid> <CheckBox Content="在庫区分" HorizontalAlignment="Left" Margin="425,6,0,0" VerticalAlignment="Top" Width="95" RenderTransformOrigin="0.979,1.2"/> <Label Content="商品名" HorizontalAlignment="Left" Margin="10,25,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.348,0.115"/> <TextBox x:Name="uxProductName" IsReadOnly="True" HorizontalAlignment="Left" Margin="58,29,0,0" TextWrapping="Wrap" Width="146" Height="19" VerticalAlignment="Top"/> <Label Content="メーカー" HorizontalAlignment="Left" Margin="10,2,0,0" VerticalAlignment="Top"/> <TextBox IsReadOnly="True" x:Name="uxMaker" HorizontalAlignment="Left" Height="19" Margin="58,6,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="146"/> <Label Content="商品コード" HorizontalAlignment="Left" Margin="209,2,0,0" VerticalAlignment="Top"/> <TextBox IsReadOnly="True" x:Name="uxProductCode" HorizontalAlignment="Left" Height="19" Margin="273,6,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="130"/> <Label Content="支店名" HorizontalAlignment="Left" Margin="222,25,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.348,0.115"/> <ComboBox Text="" HorizontalAlignment="Left" Margin="273,29,0,0" Width="130" Height="22" VerticalAlignment="Top"> <ComboBoxItem>北海道支店</ComboBoxItem> <ComboBoxItem>東京支店</ComboBoxItem> <ComboBoxItem>大阪支店</ComboBoxItem> <ComboBoxItem>愛知支店</ComboBoxItem> <ComboBoxItem>京都支店</ComboBoxItem> <ComboBoxItem>兵庫支店</ComboBoxItem> <ComboBoxItem>四国支店</ComboBoxItem> <ComboBoxItem>九州支店</ComboBoxItem> <ComboBoxItem>沖縄支店</ComboBoxItem> </ComboBox> </Grid> </UserControl> |
ユーザーコントロールのC#コード
ユーザーコントロールのC#コードは次の通りです。
特に中身は無いのですが、バインド(Binding) に対応するためのソースコードが大半を占めてしまいました。
普通のプロパティ(public string Maker {get;set;})のように記述するだけでは、Binding出来ません。
Binding に対応するためのプロパティには特別な書き方があり、これを「依存関係プロパティ」と呼んでいます。
今回はメーカー名、商品名、商品コードの3つが Binding できるようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace DataGridLayoutSample { /// <summary> /// CellUserControl.xaml の相互作用ロジック /// </summary> public partial class CellUserControl : UserControl { //=================================================================================== //メーカーの依存関係プロパティの定義 //=================================================================================== public static readonly DependencyProperty MakerProperty = DependencyProperty.Register("Maker", typeof(string), typeof(CellUserControl), new FrameworkPropertyMetadata("Maker", new PropertyChangedCallback(OnMakerChanged))); public string Maker { get { return (string)GetValue(MakerProperty); } set { SetValue(MakerProperty, value); } } private static void OnMakerChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var control = (CellUserControl)obj; control.uxMaker.Text = (obj != null) ? control.Maker : control.uxMaker.Text; } //=================================================================================== //商品コードの依存関係プロパティの定義 //=================================================================================== public static readonly DependencyProperty ProductCodeProperty = DependencyProperty.Register("ProductCode", typeof(string), typeof(CellUserControl), new FrameworkPropertyMetadata("ProductCode", new PropertyChangedCallback(OnProductCodeChanged))); public string ProductCode { get { return (string)GetValue(ProductCodeProperty); } set { SetValue(ProductCodeProperty, value); } } private static void OnProductCodeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var control = (CellUserControl)obj; control.uxProductCode.Text = (obj != null) ? control.ProductCode : control.uxProductCode.Text; } //=================================================================================== //商品名の依存関係プロパティの定義 //=================================================================================== public static readonly DependencyProperty ProductNameProperty = DependencyProperty.Register("ProductName", typeof(string), typeof(CellUserControl), new FrameworkPropertyMetadata("ProductName", new PropertyChangedCallback(OnProductNameChanged))); public string ProductName { get { return (string)GetValue(ProductNameProperty); } set { SetValue(ProductNameProperty, value); } } private static void OnProductNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var control = (CellUserControl)obj; control.uxProductName.Text = (obj != null) ? control.ProductName : control.uxProductName.Text; } /// <summary> /// コンストラクタ /// </summary> public CellUserControl() { InitializeComponent(); } } } |
まとめ
以上でユーザーコントロールを使ったDataGridの多段表示の解説は終了です。
DataGridTemplateColumn> タグに複数のコントロールを記述できますが、今回はそこにユーザーコントロールを記述することで多段表示を実現しました。
ユーザーコントロールにすることで、複雑なUI部分をメイン処理から独立させられるため、機能追加や改修などが行いやすくなるというメリットがあります。
また、通常のセルと同様、ユーザーコントロールを Binding に対応させることが出来ますが、その場合は通常のプロパティではなく、「依存関係プロパティ」という特別な記述が必要となります。
「依存関係プロパティ」については、こちらの記事で詳しく触れていますので、興味のある方がご覧ください。
コメント