ツリー構造を展開したり折りたたんだりできるTreeViewコントロールにチェックボックスを付ける。
さらに、チェックを付けると、親階層や子階層のチェック状態も変化するようなやつ。
↓こんな感じ。
親階層は、子階層が「全てチェックされている」「全てチェックされていない」「まちまち」の3状態を必要とするが、CheckBoxのIsCheckedプロパティは、True、False、の他にnullを持てるのでそれを利用する。
まずはTreeViewを継承したCheckTreeViewコントロールを作る。
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}"/>
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:CheckTreeSource}" ItemsSource="{Binding Children}">
<CheckBox Margin="1" IsChecked="{Binding IsChecked}" Click="CheckBox_Click">
<TextBlock Text="{Binding Text}"/>
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
IsExpandedをバインド (10~12行目)
TreeViewItemのStyle定義を追加して、TreeViewItemのIsExpandedプロパティへバインドする。
HierarchicalDataTemplateを定義 (14から20行目)
TreeView.ItemTemplateをつかって、HierarchicalDataTemplateを定義する。
HierarchicalDataTemplateのDataTypeにデータの型をセット。
HierarchicalDataTemplateのItemsSourceには子要素となるChildrenプロパティをバインドする。
HierarchicalDataTemplate内にCheckBoxを配置してIsCheckedプロパティをバインド。
Clickイベントを使って、親階層、子階層の状態を制御する。
ソース(CheckTreeViewクラス)
public partial class CheckTreeView : TreeView
{
public CheckTreeView()
{
InitializeComponent();
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
var checkBox = (CheckBox)sender;
var source = (CheckTreeSource)checkBox.DataContext;
source.UpdateChildStatus();
source.UpdateParentStatus();
}
}
データは後述するCheckTreeSourceクラスがバインドされる事を想定している。
CheckBox_Clickメソッド (8~15行目)
引数senderにはクリックされたコントロールが格納されている。
そのコントロールのDataContextプロパティからバインドされるデータを取得。
ソース(CheckTreeSourceクラス)
public class CheckTreeSource : INotifyPropertyChanged
{
private bool _IsExpanded = true;
private bool? _IsChecked = false;
private string _Text = "";
private CheckTreeSource _Parent = null;
private ObservableCollection<CheckTreeSource> _Children = null;
public bool IsExpanded
{
get { return _IsExpanded; }
set { _IsExpanded = value; OnPropertyChanged("IsExpanded"); }
}
public bool? IsChecked
{
get { return _IsChecked; }
set { _IsChecked = value; OnPropertyChanged("IsChecked"); }
}
public string Text
{
get { return _Text; }
set { _Text = value; OnPropertyChanged("Text"); }
}
public CheckTreeSource Parent
{
get { return _Parent; }
set { _Parent = value; OnPropertyChanged("Parent"); }
}
public ObservableCollection<CheckTreeSource> Children
{
get { return _Children; }
set { _Children = value; OnPropertyChanged("Childen"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
if (null == this.PropertyChanged) return;
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public void Add(CheckTreeSource child)
{
if (null == Children) Children = new ObservableCollection<CheckTreeSource>();
child.Parent = this;
Children.Add(child);
}
public void UpdateParentStatus()
{
if (null != Parent)
{
int isCheckedNull = 0;
int isCheckedOn = 0;
int isCheckedOff = 0;
if (null != Parent.Children)
{
foreach (var item in Parent.Children)
{
if (null == item.IsChecked) isCheckedNull += 1;
if (true == item.IsChecked) isCheckedOn += 1;
if (false == item.IsChecked) isCheckedOff += 1;
}
}
if ((0 < isCheckedNull) || (0 < isCheckedOn) || (0 < isCheckedOff))
{
if (0 < isCheckedNull)
Parent.IsChecked = null;
else if ((0 < isCheckedOn) && (0 < isCheckedOff))
Parent.IsChecked = null;
else if (0 < isCheckedOn)
Parent.IsChecked = true;
else
Parent.IsChecked = false;
}
Parent.UpdateParentStatus();
}
}
public void UpdateChildStatus()
{
if (null != IsChecked)
{
if (null != Children)
{
foreach (var item in Children)
{
item.IsChecked = IsChecked;
item.UpdateChildStatus();
}
}
}
}
}
プロパティ変更が正しく通知されるようINotifyPropertyChangedインターフェースを実装。
IsExpandedプロパティ
展開されているか折りたたまれているかを格納。
IsCheckedプロパティ
チェック状態を格納。
Textプロパティ
表示する文字列。
Childrenプロパティ
子要素となるデータを格納する。
Parentプロパティ
親要素を格納する事で階層をさかのぼって参照出来るようにしている。
Parentプロパティを正しくセットする為、Addメソッドを作っている。(49~54行目)
UpdateParentStatusメソッド
子要素のチェック状態を集計してチェック状態を更新する。(60~82行目)
さらに、再帰的に親要素のUpdateParentStatusを呼び出す。(83行目)
UpdateChildStatusメソッド
子要素のチェック状態を更新する。(93~97行目)
さらに、再帰的に子要素のUpdateChildStatusを呼び出す。(96行目)
使用例
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="250">
<Grid Margin="20">
<local:CheckTreeView ItemsSource="{Binding TreeRoot}">
</local:CheckTreeView>
</Grid>
</Window>
public partial class MainWindow : Window
{
public ObservableCollection<CheckTreeSource> TreeRoot { get; set; }
public MainWindow()
{
InitializeComponent();
TreeRoot = new ObservableCollection<CheckTreeSource>();
var item1 = new CheckTreeSource() { Text = "Item1", IsExpanded = true, IsChecked = false };
var item11 = new CheckTreeSource() { Text = "Item1-1", IsExpanded = true, IsChecked = false };
var item12 = new CheckTreeSource() { Text = "Item1-2", IsExpanded = true, IsChecked = false };
var item2 = new CheckTreeSource() { Text = "Item2", IsExpanded = false, IsChecked = false };
var item21 = new CheckTreeSource() { Text = "Item2-1", IsExpanded = true, IsChecked = false };
TreeRoot.Add(item1);
TreeRoot.Add(item2);
item1.Add(item11);
item1.Add(item12);
item2.Add(item21);
DataContext = this;
}
}


コメント
コメントを投稿