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

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

(XAML#20)「DataGrid と ScrollViewer」

»

ScrollViewer は、他のコントロールを配置するためのコンテナとして使うコントロールで、その領域に表示しきれないコントロールをスクロールバーなどを使って表示する機能を持っています。DataGrid 自身も、表示するデータが多ければスクロールバーを使って全体を表示できる機能を持っているため、直接 ScrollViewer に DataGrid を配置する必要はありません。しかし、DataGrid を一部の領域だけで使う場合など、他のコントロールと組み合わせる場合は、間接的に ScrollViewer に配置することもあるでしょう。

これまで同様、DataGrid を使ってデータを表示しますが、これを ScrollViewer 上の Grid に配置し、ウィンドウを小さくした上で、下部にカレンダーを配置します。これで、ScrollViewer 上のコントロールはスクロールバーを使わなければ全体を表示できなくなります。

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="240" Width="320">
    <ScrollViewer>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </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>
            <Calendar Grid.Row="2"/>
        </Grid>
    </ScrollViewer>
</Window>

少し操作してみるとわかるとおり、DataGrid 以外の部分ではマウスホイールによって ScrollViewer の内容が上下に移動するのに対し、DataGrid 上では、ホイール操作が使えません。これは DataGrid 自身にも ScrollViewer の機能が組み込まれており、これがホイール操作を奪っているためです。TreeView、ListView、ListBox のように、自分でスクロール機能を持つコントロールでも、同じような動作になります。

これを避けるために、DataGrid に対するホイール操作を、DataGrid が配置されている ScrollViewer に伝達するようにプログラムします。以下は、DataGrid の縦スクロールバーを抑止し、処理前のホイールイベントに対するイベントハンドラーを記述しています。VisualTreeHelper は、WPF のコントロール階層を扱うための便利なクラス(静的クラス)で、GetParent はツリーの親を調べるためのメソッドです。

[XAML]
<DataGrid Grid.Row="0"
    ItemsSource="{Binding Persons}"
    AutoGenerateColumns="False"
    ScrollViewer.VerticalScrollBarVisibility="Disabled"
    PreviewMouseWheel="DataGrid_PreviewMouseWheel">

[コード]
private void DataGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    // 念のためにイベントの発生元が DataGrid かどうか確認する
    if (sender is DataGrid)
    {
        var element = sender as UIElement; // 発生元の親を探索して ScrollViewer を探す
        while (element != null) {
            if (element is ScrollViewer)
            {
                // ScrollViewer が見つかったら、直接スクロールさせる
                var viewer = (ScrollViewer)element;
                viewer.ScrollToVerticalOffset(viewer.VerticalOffset - e.Delta / 3);
                return;
            }
            element = VisualTreeHelper.GetParent(element) as UIElement;
        }
    }
}

もちろん、以下のようにスタイルとして定義しておけば、DataGrid ごとにプロパティやイベントを設定する必要はなくなります。

<Window.Resources>
    <Style TargetType="{x:Type DataGrid}">
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" />
        <EventSetter Event="PreviewMouseWheel" Handler="DataGrid_PreviewMouseWheel" />
    </Style>
</Window.Resources>

Xaml20

Comment(0)