C#のWPFでチェックボックス付きTreeViewを作る

ツリー構造を展開したり折りたたんだりできるTreeViewコントロールにチェックボックスを付ける。

さらに、チェックを付けると、親階層や子階層のチェック状態も変化するようなやつ。

 

↓こんな感じ。 

親階層は、子階層が「全てチェックされている」「全てチェックされていない」まちまち」の3状態を必要とするが、CheckBoxのIsCheckedプロパティは、True、False、の他にnullを持てるのでそれを利用する。

まずはTreeViewを継承したCheckTreeViewコントロールを作る。

(TreeViewへのバインドについてはこちらなんかを参照)

 

 

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;
        }
    }

 

 

コメントをお書きください

コメント: 3
  • #1

    Nowm678Cik (火曜日, 13 8月 2019 10:35)

    ソース(CheckTreeSourceクラス)の7行目 ObservableCollectionは、ObservableCollection<T>であり1型引数が必要であるため、このままではコンパイルできません。
    その他のObservableCollectionも同様です。

  • #2

    AraramiStudio (火曜日, 13 8月 2019 11:53)

    Nowm678Cikさん
    ご指摘いただきましてありがとうございます。
    ObservableCollection<CheckTreeSource>とすべきところがObservableCollectionのみになっていましたので本文を修正しました。

  • #3

    Nowm678Cik (水曜日, 14 8月 2019 08:36)

    早速修正ありがとうございます。
    また、開示いただいているソースは、とても参考になり、勉強になります。