オルタナティブ・ブログ > IT's my business >

IT業界のコメントマニアが始めるブログ。いつまで続くのか?

(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 イベントを使います。

Xaml19

Comment(0)