C#のWPFでTreeViewをカスタマイズしてボタンを追加

ツリー構造を展開したり折りたたんだりできるTreeViewコントロールで、各アイテムの右側にボタンを追加したような物を作る。

 

その他

TreeViewコントロールにチェックボックスを付けたり

CheckBoxコントロールのデザインを変えたりもしている。

↓こんな感じ。

右側にボタンを追加しているが、右寄せで配置する為には、TreeView.ItemTemplateのカスタマイズだけでは実現できそうになかったので、TreeViewItemのTempleteを使う。

XAML


<TreeView x:Class="WpfApplication1.CheckTreeView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApplication1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <TreeView.Resources>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TreeViewItem">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <ToggleButton Name="Expander"
                                          Grid.Column="0" Grid.Row="0"
                                          IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsExpanded,Mode=TwoWay}">
                                <ToggleButton.Template>
                                    <ControlTemplate TargetType="ToggleButton">
                                        <Grid Width="16" Height="16" Background="Transparent">
                                            <Path x:Name="ExpandOn" Margin="3" Stroke="#FF989898" Data="M5,0L5,10L10,5z"/>
                                            <Path x:Name="ExpandOff" Margin="3" Fill="#FF989898" Stroke="#FF989898" Data="M3,10L10,10L10,3z"/>
                                        </Grid>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="IsChecked" Value="False">
                                                <Setter TargetName="ExpandOff" Property="Visibility" Value="Collapsed" />
                                            </Trigger>
                                            <Trigger Property="IsChecked" Value="True">
                                                <Setter TargetName="ExpandOn" Property="Visibility" Value="Collapsed" />
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </ToggleButton.Template>
                            </ToggleButton>
                            <Border Name="BackBorder" Grid.Column="1" Grid.Row="0" Padding="5,0,0,0" BorderThickness="0,0,0,1" BorderBrush="LightGray" Background="{TemplateBinding Panel.Background}">
                                <ContentPresenter Content="{TemplateBinding HeaderedContentControl.Header}" ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}" ContentStringFormat="{TemplateBinding HeaderedItemsControl.HeaderStringFormat}" ContentSource="Header" Name="PART_Header" HorizontalAlignment="Stretch"/>
                            </Border>
                            <ItemsPresenter Name="ItemsHost" Grid.Column="1" Grid.Row="1"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="HasItems" Value="False">
                                <Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
                            </Trigger>
                            <Trigger Property="IsExpanded" Value="False">
                                <Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
                            </Trigger>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter TargetName="BackBorder" Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="CustomStyle" TargetType="{x:Type CheckBox}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type CheckBox}">
                        <BulletDecorator>
                            <BulletDecorator.Bullet>
                                <Grid>
                                    <Grid x:Name="EnableTrue" Width="17" Height="14">
                                        <Rectangle x:Name="CheckNull" Width="12" Height="12" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="LightGray" Stroke="Gray" StrokeThickness="1"/>
                                        <Rectangle x:Name="CheckRect" Width="12" Height="12" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="White" Stroke="LightGray" StrokeThickness="1"/>
                                        <Path x:Name="CheckMark" IsHitTestVisible="False" SnapsToDevicePixels="False" StrokeThickness="2" Data="M 3 5 L 5 8 L 13 0" Stroke="SteelBlue"/>
                                    </Grid>
                                    <Grid x:Name="EnableFalse" Width="17">
                                        <Rectangle Width="12" Height="12" HorizontalAlignment="Left" VerticalAlignment="Center" Fill="WhiteSmoke" Stroke="Gray" StrokeThickness="1" StrokeDashArray="1,3"/>
                                    </Grid>
                                </Grid>
                            </BulletDecorator.Bullet>
                            <BulletDecorator.Child>
                                <ContentPresenter/>
                            </BulletDecorator.Child>
                        </BulletDecorator>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="True">
                                <Setter TargetName="EnableTrue" Property="Visibility" Value="Visible" />
                                <Setter TargetName="EnableFalse" Property="Visibility" Value="Collapsed" />
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter TargetName="EnableTrue" Property="Visibility" Value="Collapsed" />
                                <Setter TargetName="EnableFalse" Property="Visibility" Value="Visible" />
                                <Setter Property="Foreground" Value="Gray"/>
                            </Trigger>
                            <Trigger Property="IsChecked" Value="{x:Null}">
                                <Setter TargetName="CheckNull" Property="Visibility" Value="Visible" />
                                <Setter TargetName="CheckRect" Property="Visibility" Value="Collapsed" />
                                <Setter TargetName="CheckMark" Property="Visibility" Value="Visible" />
                            </Trigger>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="CheckNull" Property="Visibility" Value="Collapsed" />
                                <Setter TargetName="CheckRect" Property="Visibility" Value="Visible" />
                                <Setter TargetName="CheckMark" Property="Visibility" Value="Visible" />
                            </Trigger>
                            <Trigger Property="IsChecked" Value="False">
                                <Setter TargetName="CheckNull" Property="Visibility" Value="Collapsed" />
                                <Setter TargetName="CheckRect" Property="Visibility" Value="Visible" />
                                <Setter TargetName="CheckMark" Property="Visibility" Value="Hidden" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </TreeView.Resources>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="{x:Type local:CheckTreeSource}" ItemsSource="{Binding Children}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <CheckBox Grid.Column="0" Margin="2" VerticalAlignment="Center" IsChecked="{Binding IsChecked}" Style="{StaticResource CustomStyle}" Click="CheckBox_Click"/>
                <TextBlock Grid.Column="1" Text="{Binding Text}" VerticalAlignment="Center"/>
                <Button Grid.Column="2" Content="..." Padding="5,0,5,0" Margin="2" FontSize="8"/>
            </Grid>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

IsExpandedをバインド (11行目)

TreeViewItemのStyle定義を追加して、TreeViewItemのIsExpandedプロパティへバインドする。

詳細はこちらを参照

 

 

ControlTemplateを定義 (14~61行目)

TreeViewItem.Templateを使って、ControlTemplateを定義する。

ここで、デザインの全てを定義し直すイメージ。

ControlTemplateの中は2行2列のGridコントロールを配置。

 

1行目の1列目は、折り畳み・展開を表す三角形を表示するエリア。

1行目の2列目は、バインドされるデータを表示するエリア。

2行目の1列目は、使用しない。(インデントのようなスペースになる)

2行目の2列目は、データの子要素を配置する。 

 

 

折り畳み・展開を表す三角形 (24~43行目)

ToggleButtonを使って作成する。

 

ToggleButton.IsCheckedプロパティにTreeViewItem.IsExpandedプロパティをバインドさせて、

ToggleButtonの見た目とIsExpandedを連動させる。 (26行目)

 

ToggleButton.Templateで、折り畳んだ時の三角形と展開した時の三角形を作る。 (29~32行目)

 

ToggleButtonのTriggersを定義して、ToggleButton.IsCheckedプロパティの値によって、どちらの三角形を表示するかを切り替える。 (33~40行目)

 

 

バインドされるデータを表示する (44~46行目)

Borderを使って下線を引いている。 (44行目)

その中にContentPresenterを配置して、TreeViewItem.Headerプロパティの値をセットして、

TreeView.ItemTemplateの内容が表示されるようにする。

 

 

子要素を表示する (47行目)

Gridの2行目の2列目に、ItemsPresenterを配置する。

 

 

子要素を持たない場合は三角形を表示しない (50~52行目)

折り畳み・展開の三角形マークは、子要素を持たない場合必要無いので非表示にする。

TreeViewItemのTriggersを使い、HasItemsプロパティを見る事で、子要素の有無を確認出来る。 

 

 

折り畳み時に子要素を表示しない (53~55行目)

 IsExpandedプロパティがFalseの時は、子要素を非表示にする。

 

 

チェックボックスのデザインを変更  (65~115行目)

チェックボックスのデザインを変える為にスタイルを定義している。

詳細は「WPFのチェックボックスのデザインをカスタマイズ」を参照

 

 

 

 

その他のソースなどは

WPFのTreeViewへデータをバインドする」や

WPFでチェックボックス付きTreeViewを作る」を参照