ViewMakerで生成するWPF/Silverlightコントロール(15)DataGrid編

DataGridは業務には欠かせないコントロールで必須のものです。ただ、入力検証のカスタマイズなど細かい点を調整しようとするとなかなか難度の高い問題にぶつかります。そういう意味では、機能についてある程度割り切りをして(DataGridの仕様に合わせて)、利用することがポイントだと考えています。あと、WPF版はパフォーマンスに注意する必要があるので動作速度を確認しながら導入するようにします。場合によってはWPFやSilvetlightは柔軟なレイアウトが可能なので他のコントロールで代用することも選択肢のひとつです。

DataGridコントロール

DataGridについては標準では用意していないいくつかの設定項目を用意しています。できるだけ直観的な設定で動作するようにしたつもりです。たとえばDataGridTextColumnのTextAlignmentを用意しました。これは一覧表示時はもちろん編集時のTextBoxにも有効になっています。実装的にはWPF/SilverlightともカスタムコントロールではなくStyleを使うだけで対応しています。以下DataGridと列の指定可能な項目になります。DataGridの列はViewMakerでは3種類を用意しています。

    1. SelectedItem(現在選択しているアイテム(バインドするパス))
    2. IsReadOnly(読み取り専用)
    3. DoubleClickCommand(ダブりクリックした時に実行するコマンド)
    4. SelectionChangedCommand(選択データ変更時に実行するコマンド)
DataGridCheckBoxColumn
    1. IsThreeState(3値形式のチェックボックス
DataGridTextColumn
    1. StringFormat出力フォーマット)
    2. IsReadOnly(読み取り専用)
    3. IsEmptyAsNull(空文字をnull扱いするか)
    4. TextAlignment(テキスト配置位置)
DataGridComboBoxColumn
    1. ItemsSource(一覧用のデータソース)
    2. SelectedValuePath(ItemsSourceで選択されたデータの値を示すプロパティのパス)
    3. DisplayMemberPath(ItemsSourceの各アイテムの表示に利用するプロパティのパス)
共通
    1. Width(コントロール幅)

Silverlightでは標準でDataGridComboBoxColumnがないため、かわりにDataGridTemplateColumnを使って作成しています。WPF版についても、ItemsSourceもバインドできるようにStyleを利用してインスタンス化(Visual化?)したComboBoxのItemsSourceにバインドしています。DataGridComboBoxColumn自体がインスタンス化されないためDataGridComboBoxColumnのItemsSourceにバインド指定してもバインド処理が行われないようですが、ViewMakerはうまくやってくれるようにしています。

WPFサンプルイメージとXAMLコード

<DataGrid Name="DataGrid1" AutoGenerateColumns="false" ItemsSource="{Binding Path=DataGrid1,Mode=OneWay}" SelectedItem=
	"{Binding Path=SelectedData, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
	vg:DataGridDoubleClickBehavior.Command="{Binding Path=DoubleClickCommand, Mode=OneWay}" VerticalAlignment="Stretch" 
	HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" 
	xmlns:vg="clr-namespace:ViewMaker.Core.Wpf;assembly=ViewMaker.Core">
    <DataGrid.Columns>
        <DataGridTextColumn IsReadOnly="True" Binding="{Binding Path=Id, StringFormat=[\{0\}], Mode=TwoWay, ValidatesOnExceptions=True, 
	ValidatesOnDataErrors=True}" x:Name="Id51" Header="Id" />
        <DataGridTextColumn Binding="{Binding Path=Group, TargetNullValue='', Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
		x:Name="Group1" Header="Group" Width="100">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Setter Property="TextAlignment" Value="Right" />
                </Style>
            </DataGridTextColumn.ElementStyle>
            <DataGridTextColumn.EditingElementStyle>
                <Style TargetType="TextBox">
                    <Setter Property="TextAlignment" Value="Right" />
                </Style>
            </DataGridTextColumn.EditingElementStyle>
        </DataGridTextColumn>
        <DataGridComboBoxColumn SelectedItemBinding="{Binding Path=Name, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
		x:Name="Name51" Header="Name">
            <DataGridComboBoxColumn.ElementStyle>
                <Style TargetType="ComboBox">
                    <Setter Property="ItemsSource" Value="{Binding Path=NameList}" />
                </Style>
            </DataGridComboBoxColumn.ElementStyle>
            <DataGridComboBoxColumn.EditingElementStyle>
                <Style TargetType="ComboBox">
                    <Setter Property="ItemsSource" Value="{Binding Path=NameList}" />
                </Style>
            </DataGridComboBoxColumn.EditingElementStyle>
        </DataGridComboBoxColumn>
        <DataGridComboBoxColumn SelectedValuePath="Value" SelectedValueBinding="{Binding Path=Date, Mode=TwoWay, ValidatesOnExceptions=True, 
		ValidatesOnDataErrors=True}" DisplayMemberPath="Key" x:Name="Date51" Header="Date">
            <DataGridComboBoxColumn.ElementStyle>
                <Style TargetType="ComboBox">
                    <Setter Property="ItemsSource" Value="{Binding Path=DateList}" />
                </Style>
            </DataGridComboBoxColumn.ElementStyle>
            <DataGridComboBoxColumn.EditingElementStyle>
                <Style TargetType="ComboBox">
                    <Setter Property="ItemsSource" Value="{Binding Path=DateList}" />
                </Style>
            </DataGridComboBoxColumn.EditingElementStyle>
        </DataGridComboBoxColumn>
        <DataGridCheckBoxColumn Binding="{Binding Path=OnOff, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
		x:Name="OnOff51" Header="OnOff" Width="100" />
        <DataGridCheckBoxColumn Binding="{Binding Path=ThreeState, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
		IsThreeState="True" x:Name="ThreeState1" Header="ThreeState" />
    </DataGrid.Columns>
</DataGrid>

SilverlightサンプルイメージとXAMLコード

<sdk:DataGrid Name="DataGrid1" AutoGenerateColumns="false" ItemsSource="{Binding Path=DataGrid1,Mode=OneWay}" SelectedItem="
	{Binding Path=SelectedData, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" VerticalAlignment="Stretch" 
	HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
    <sdk:DataGrid.Columns>
        <sdk:DataGridTextColumn IsReadOnly="True" Binding="{Binding Path=Id, StringFormat=[\{0\}], Mode=TwoWay, ValidatesOnExceptions=True, 
	ValidatesOnDataErrors=True}" x:Name="Id51" Header="Id" />
        <sdk:DataGridTextColumn Binding="{Binding Path=Group, TargetNullValue='', Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
		x:Name="Group1" Header="Group" Width="100">
            <sdk:DataGridTextColumn.ElementStyle>
                <Style TargetType="TextBlock">
                    <Setter Property="TextAlignment" Value="Right" />
                </Style>
            </sdk:DataGridTextColumn.ElementStyle>
            <sdk:DataGridTextColumn.EditingElementStyle>
                <Style TargetType="TextBox">
                    <Setter Property="TextAlignment" Value="Right" />
                </Style>
            </sdk:DataGridTextColumn.EditingElementStyle>
        </sdk:DataGridTextColumn>
        <sdk:DataGridTemplateColumn x:Name="Name51" Header="Name">
            <sdk:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=NameList,Mode=OneWay}" IsTabStop="False" SelectedItem="{Binding Path=Name, Mode=TwoWay, 
			ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellTemplate>
            <sdk:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=NameList,Mode=OneWay}" SelectedItem="{Binding Path=Name, Mode=TwoWay, 
			ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellEditingTemplate>
        </sdk:DataGridTemplateColumn>
        <sdk:DataGridTemplateColumn x:Name="Date51" Header="Date">
            <sdk:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=DateList,Mode=OneWay}" IsTabStop="False" SelectedValue="{Binding Path=Date, Mode=TwoWay, 
			ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" SelectedValuePath="Value" DisplayMemberPath="Key" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellTemplate>
            <sdk:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <ComboBox ItemsSource="{Binding Path=DateList,Mode=OneWay}" SelectedValue="{Binding Path=Date, Mode=TwoWay, 
			ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" SelectedValuePath="Value" DisplayMemberPath="Key" />
                </DataTemplate>
            </sdk:DataGridTemplateColumn.CellEditingTemplate>
        </sdk:DataGridTemplateColumn>
        <sdk:DataGridCheckBoxColumn Binding="{Binding Path=OnOff, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
		x:Name="OnOff51" Header="OnOff" Width="100" />
        <sdk:DataGridCheckBoxColumn Binding="{Binding Path=ThreeState, Mode=TwoWay, ValidatesOnExceptions=True, ValidatesOnDataErrors=True}" 
		IsThreeState="True" x:Name="ThreeState1" Header="ThreeState" />
    </sdk:DataGrid.Columns>
    <i:Interaction.Behaviors>
        <vg:DataGridDoubleClickBehavior vg:Command="{Binding Path=DoubleClickCommand, Mode=OneWay}" 
	xmlns:vg="clr-namespace:ViewMaker.Core.Silverlight;assembly=SilverlightViewMaker.Core" />
    </i:Interaction.Behaviors>
</sdk:DataGrid>

ViewModelコード

[View(ViewControlType.Grid)]
[ViewProperty("MinHeight", 150)]
public class DataGridSample : ViewModel
{
    public class GridItem
    {
        [ViewProperty(DataGridTextColumnViewElement.Properties.IsReadOnly, true)]
        [ViewProperty(DataGridTextColumnViewElement.Properties.StringFormat, "[{0}]")]
        public int Id { get; set; }

        [ViewProperty(DataGridTextColumnViewElement.Properties.Width, 100)]
        [ViewProperty(DataGridTextColumnViewElement.Properties.TextAlignment, LayoutHorizontalAlignment.Right)]
        [ViewProperty(DataGridTextColumnViewElement.Properties.IsEmptyAsNull, true)]
        public string Group { get; set; }

        [View(ViewControlType.DataGridComboBoxColumn)]
        [ViewProperty(DataGridComboBoxColumnViewElement.Properties.ItemsSource, "NameList")]
        public string Name { get; set; }

        [View(ViewControlType.DataGridComboBoxColumn)]
        [ViewProperty(DataGridComboBoxColumnViewElement.Properties.ItemsSource, "DateList")]
        [ViewProperty(DataGridComboBoxColumnViewElement.Properties.DisplayMemberPath, "Key")]
        [ViewProperty(DataGridComboBoxColumnViewElement.Properties.SelectedValuePath, "Value")]
        public DateTime Date { get; set; }

        [ViewProperty(DataGridColumnViewElement.Properties.Width,100)]
        public bool OnOff { get; set; }

        [ViewProperty(DataGridCheckBoxColumnViewElement.Properties.IsThreeState, true)]
        public bool? ThreeState { get; set; }

        [Browsable(false)]
        public List<string> NameList { get { return new List<string>{ "One", "Two", "Three" }; } }

        [Browsable(false)]
        public Dictionary<string, DateTime> DateList
        {
            get
            {
                return new Dictionary<string, DateTime> { 
                    {"yestaday", DateTime.Today.AddDays(-1) },
                    {"today", DateTime.Today },
                    {"tomorrow", DateTime.Today.AddDays(1) }
                };
            }
        }

        public static List<GridItem> CreateData(int size)
        {
            var list = new List<GridItem>();
            for (int i = 0; i < size; i++)
            {
                list.Add(new GridItem
                {
                    Id = i,
                    Name = "One",
                    Date = DateTime.Today,
                    OnOff = (i % 3) == 0
                });
            }
            return list;
        }
    }

    public string DoubleClickItem
    {
        get { return _doubleClickItem; }
        set { _doubleClickItem = value; OnPropertyChanged("DoubleClickItem"); }
    }
    private string _doubleClickItem;

    [View(ViewControlType.DataGrid)]
    [ViewProperty(DataGridViewControl.Properties.SelectedItem, "SelectedData")]
    [ViewProperty(DataGridViewControl.Properties.DoubleClickCommand, "DoubleClickCommand")]
    public List<GridItem> DataGrid1 { get; set; }

    [Browsable(true)]
    public GridItem SelectedData
    {
        get { return _selectedData; }
        set { _selectedData = value; OnPropertyChanged("SelectedData"); }
    }
    private GridItem _selectedData;

    [Browsable(false)]
    public ICommand DoubleClickCommand { get { return CreateCommand(DoubleClick); } }
    private void DoubleClick(object arg)
    {
        var item = arg as GridItem;
        if (item != null)
        {
            DoubleClickItem = item.Id.ToString() + ":" + (item.Group ?? "(null)");
        }
    }

    public DataGridSample()
    {
        DataGrid1 = GridItem.CreateData(10);
    }
}