(XAML#19)「DataGrid とクリップボード」
今回も17日目のビューモデルを使って WPF でデータを表示します。
[XAML]
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
ItemsSource="{Binding Persons}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="BirthDate" Binding="{Binding BirthDate, StringFormat=yyyy/MM/dd}" />
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Show Data" Click="Button_Click" />
</StackPanel>
</Grid>
DataGrid で選択中の行は [Ctrl]+[C] を押すと、クリップボードにコピーできます(選択する単位を行にするかセルにするかはSelectionModeプロパティで設定できます)。このとき、上記の BirthDate 項目のように StringFormat で書式を指定していても、クリップボードに転送されるテキストには反映されません(WPFのバグかもしれませんが、バインディングされているデータを StringFormat を無視して、ToString で文字列化しているだけのように見えます)。このため、クリップボードにも書式化した内容を転送したい場合は、StringFormat を使わずにバインディングする必要があります。
たとえば、値コンバーターを使って次のように記述できます。
[コード]
public class DateFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is DateTime)
return ((DateTime)value).ToString("yyyy/MM/dd");
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public static DateFormatConverter Converter = new DateFormatConverter();
}
[XAML]
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
Title="MainWindow" Height="325" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
ItemsSource="{Binding Persons}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="BirthDate"
Binding="{Binding BirthDate, Converter={x:Static local:DateFormatConverter.Converter}}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Show Data" Click="Button_Click" />
</StackPanel>
</Grid>
</Window>
DataGrid のカラムには、コントロールが決まっている DataGridTextColumn、DataGridCheckBoxColumn、DataGridComboBoxColumn、DataGridHyperlinkColumn に加えて、自由にコントロールを配置できる DataGridTemplateColumn があります(いずれも DataGridColumn というクラスから継承しています)。DataGridTemplateColumn は、DataTemplate を使って表示/編集するためのコントロールを自由に配置できます。
以下は、上記の BirthDate 項目に DatePicker を使う例です。
[XAML]
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" />
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="BirthDate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding BirthDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridComboBoxColumn Header="Gender" SelectedValueBinding="{Binding Gender}"
ItemsSource="{Binding Source={StaticResource GenderEnum}}"/>
</DataGrid.Columns>
DatePicker の SelectedDate のバインディングでは、Mode=TwoWay(双方向バインディング)と UpdateSourceTrigger=PropertyChanged を指定しています。これらの指定がないと、バインドは一方向(OneWay)になり値を変更できなくなり、UpdateSourceTrigger のデフォルト値が LostFocus になっているためドロップダウンを変更しただけではデータが更新されないことになります。
上記のように DataGridTemplateColumn.CellTemplate に DatePicker を含む DataTemplate を割り当てると、単純にデータを表示するときにも DatePicker が使われます。通常は、シンプルなテキストだけを表示し、クリックして編集するときだけ DatePicker を使いたい場合は、CellTemplate とは別に CellEditingTemplate を指定します。
<DataGridTemplateColumn Header="BirthDate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding BirthDate, StringFormat=yyyy/MM/dd}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding BirthDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
DateGridTemplateColumn を使った項目は、クリップボードに何も転送されないということに注意してください。自由にコントロールを配置できる代わりに、テンプレート自身は配置されているコントロールにバインディングされているデータを処理しないためです。このようなときにもクリップボードに何かを転送したい場合は、ClipboardContentBinding を使います。これは DataGridColumn に用意されているプロパティで、すべての種類のカラム定義で使うことができます。
たとえば、次のように定義します。
<DataGridTemplateColumn Header="BirthDate"
ClipboardContentBinding="{Binding BirthDate,
Converter={x:Static local:DateFormatConverter.Converter}}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding BirthDate, StringFormat=yyyy/MM/dd}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding BirthDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
※現時点では、ClipboardContentBinding でも StringFormat による書式指定が使えないことに注意してください。
なお、ここではバインディングされたデータを単純に文字列としてクリップボードに転送する方法について紹介しましたが、クリップボードに転送する内容をプログラムコードで処理したい場合は、CopyingCellClipboardContent イベントを使います。