個人的に、ListViewのGridViewにほしいなと思う機能として
- セルに罫線を引きたい
- 横スクロール時に常に表示される列を指定したい
- 出来るなら、Excelみたいな編集ができると嬉しい
あたりを常々思っています。
どれも、WPFToolkitのDataGridを使えば不満は解決されるのですが、なんというか正式リリース前のものということで、遊ぶのには最適だけど実際に使うにはどうかなぁ・・・と思ってたりしています。
簡単に触った範囲内では、バグらしきものも見当たらないので多分大丈夫だと思ってはいます。
今回は、このListViewのGridViewにほしい機能の1つである「セルに罫線を引きたい」といった部分をゴリっと力技?でやってみようと思います。
下準備
とりあえず、下準備として普通にListViewのGridViewにデータを表示するものを作ってみようと思います。そこに罫線表示を付け加えていくという流れです。
WpfListViewGridLineという名前でプロジェクトを新規作成します。
プロジェクトを作成したら、表示するデータを格納するクラスとして、いつものPersonクラスを作成します。
namespace WpfListViewGridLine
{
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
}
Personクラスを作成したら、Window1クラスのコンストラクタでダミーのデータ2000件くらいをDataContextに入れておきます。
using System.Linq;
using System.Windows;
namespace WpfListViewGridLine
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
// ダミーデータ作成
DataContext = Enumerable.Range(1, 2000).Select(i =>
new Person
{
ID = i,
Name = "田中 太郎 " + i,
Age = i % 100
});
}
}
}
ダミーデータを作成したら、それを表示するListViewをWindow1.xamlのほうに書いていきます。
ここでは、単純にGridViewColumnを使って表示するだけです。
<Window x:Class="WpfListViewGridLine.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<ListView ItemsSource="{Binding}" >
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="名前" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="年齢" DisplayMemberBinding="{Binding Age, StringFormat={}{0}際}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ここまでで実行すると、以下のようにListViewにデータが表示されます。
罫線表示を作りこんでいく
下準備が出来たので、罫線を表示する部分を作りこんでいきます。
罫線は、ListViewの一行を表すクラスであるListViewItemに対するスタイルで色々やることで実現します。
そのため、WindowのResourcesにStyleを1つ定義して、そいつをListViewのItemContainerStyleに設定します。
<Window x:Class="WpfListViewGridLine.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style x:Key="lineGridStyle" TargetType="{x:Type ListViewItem}">
<!-- TODO: ここに罫線に関するスタイルを定義する -->
</Style>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding}"
ItemContainerStyle="{StaticResource lineGridStyle}">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="名前" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="年齢" DisplayMemberBinding="{Binding Age, StringFormat={}{0}際}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
※)ここから先に示すXAMLは、Window.Resourcesの下のlineGridStyleのみになります。
罫線を引くためのメカニズムですが、ListViewItemクラスには、そういったプロパティがないので力技を使うことになります。
具体的に言うと、ListViewItemのTemplateを差し替えて罫線を表示するものにしてしまう!ということになります。
とりあえず、GridViewRowPresenterを置いて、もとの見た目を復元します。
<Style x:Key="lineGridStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<!-- まず、元の見た目を再現 -->
<GridViewRowPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
実行して、見た目が以前と変わらないことを確認します。
この状態だと、選択行の色が変わらないので、色がつくようにします。
ListViewのGridViewを使ったときみたいな綺麗なグラデーションがかった色にしたかったのですが、やりかたがわからなかったのでListBoxと同じ色になるようにしました。
ここらへんの上手いやり方がわかる型はコメントくださいm(_ _)m
選択時に色買えは、背景色と前景色を指定するためのGridとContentControlをTemplateに新たに置いて、TriggersプロパティにIsSelectedがTrueの時にシステムカラーからそれっぽい色を設定するようにしました。
<Style x:Key="lineGridStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<!-- 背景色担当 -->
<Grid Name="background">
<!-- 前景色担当 -->
<ContentControl Name="foreground">
<GridViewRowPresenter />
</ContentControl>
</Grid>
<ControlTemplate.Triggers>
<!-- 選択されている場合 -->
<Trigger Property="IsSelected" Value="True">
<!-- 背景色をハイライト時の拝啓色に -->
<Setter TargetName="background"
Property="Background"
Value="{x:Static SystemColors.HighlightBrush}" />
<!-- 前景色をハイライト時のテキストの色に -->
<Setter TargetName="foreground"
Property="Foreground"
Value="{x:Static SystemColors.HighlightTextBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
これで選択時にそれっぽい色になります。
ここまで長かったですが、ついに罫線表示の部分を作りこんでいきます。
罫線は、GridViewRowPresenterの上に、罫線のレイヤを重ねることで表示させます。表示するために必要な矩形は、GridViewRowPresenterのColumnsプロパティとバインドしたItemsControlにBorderを表示させることで実現しています。
<Style x:Key="lineGridStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid Name="background">
<ContentControl Name="foreground">
<!-- バインドするために名前をつけた -->
<GridViewRowPresenter Name="rowPresenter"/>
</ContentControl>
<!-- GridViewRowPresenterのColumnsとバインド! -->
<ItemsControl ItemsSource="{Binding ElementName=rowPresenter, Path=Columns}">
<!-- 表示は水平方向に -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ボーダーを表示する -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- 右側と下側に罫線を引く、ボーダーの幅は列の表示幅にバインドする -->
<Border Margin="1,0,0,0"
BorderBrush="Gray"
BorderThickness="0,0,1,1"
Width="{Binding ActualWidth}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="background"
Property="Background"
Value="{x:Static SystemColors.HighlightBrush}" />
<Setter TargetName="foreground"
Property="Foreground"
Value="{x:Static SystemColors.HighlightTextBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ここまでで実行すると、見た目上できてそうに見えるところまで完成です。
実は、この状態だと、セル内の余白部分をクリックしても行選択が出来ないという悲しいことがおきます。
なので、行いっぱいに表示するようにして、この問題を解決して完成です。
<Style x:Key="lineGridStyle" TargetType="{x:Type ListViewItem}">
<!-- セル内の余白部分をクックしても行選択になるようにする -->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Grid Name="background">
<ContentControl Name="foreground">
<GridViewRowPresenter Name="rowPresenter"/>
</ContentControl>
<ItemsControl ItemsSource="{Binding ElementName=rowPresenter, Path=Columns}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="1,0,0,0"
BorderBrush="Gray"
BorderThickness="0,0,1,1"
Width="{Binding ActualWidth}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="background"
Property="Background"
Value="{x:Static SystemColors.HighlightBrush}" />
<Setter TargetName="foreground"
Property="Foreground"
Value="{x:Static SystemColors.HighlightTextBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
実行して動きを確認すると…
いい感じ。