(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>