带有复选框的WPF TreeView

时间:2010-10-28 11:33:05

标签: wpf data-binding treeview checkbox

经过大量搜索,我没有找到解决以下问题的方法。 我需要一个带有“checkboxed”树视图项和CheckedItems属性的树视图控件,以方便数据绑定(例如,文件夹结构的树视图,当用户检查文件夹时,已检查文件夹的大小显示在文本框中)。

顺便说一句,我已阅读文章«Working with Checkboxes in the WPF TreeView», Josh Smith,但“IsChecked”方法在我的案例中并不合适,因为我需要将CheckedItems绑定为集合。

我将不胜感激任何帮助!

alt text

已附加图片链接。我希望列表框是绑定到CheckedItems CheckTreeView属性的数据。有没有人知道如何实现可能绑定到CheckTreeView集合的通用CheckedItems

1 个答案:

答案 0 :(得分:4)

<强>更新
最后,用缺少的功能更新了CheckBoxTreeView。 CheckBoxTreeViewLibrary源可以是downloaded here

  • 添加CheckedItems属性
  • CheckedItems是ObservableCollection<T>,其中T是ItemsSource的内部类型
  • CheckedItems支持双向绑定到源
  • 如果尚未生成CheckBoxTreeViewItem(未扩展为),则在生成之前,它的源不会出现在CheckedItems集合中

Control可以像常规TreeView一样使用。要为IsChecked属性添加双向绑定,必须合并CheckBoxTreeViewItemStyle.xaml ResourceDictionary。 e.g。

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="/CheckBoxTreeViewLibrary;component/Themes/CheckBoxTreeViewItemStyle.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

然后可以像这样使用ItemContainerStyle

<cbt:CheckBoxTreeView ...>
    <cbt:CheckBoxTreeView.ItemContainerStyle>
        <Style TargetType="{x:Type cbt:CheckBoxTreeViewItem}"
               BasedOn="{StaticResource {x:Type cbt:CheckBoxTreeViewItem}}">
            <Setter Property="IsChecked" Value="{Binding IsChecked}"/>
            <!-- additional Setters, Triggers etc. -->
        </Style>
    </cbt:CheckBoxTreeView.ItemContainerStyle>
</cbt:CheckBoxTreeView>

<强> CheckBoxTreeView.cs

namespace CheckBoxTreeViewLibrary
{
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CheckBoxTreeViewItem))]
    public class CheckBoxTreeView : TreeView
    {
        public static DependencyProperty CheckedItemsProperty =
            DependencyProperty.Register("CheckedItems",
                                        typeof(IList),
                                        typeof(CheckBoxTreeView));

        private RoutedEventHandler Checked_EventHandler;
        private RoutedEventHandler Unchecked_EventHandler;

        public CheckBoxTreeView()
            : base()
        {
            Checked_EventHandler = new RoutedEventHandler(checkBoxTreeViewItem_Checked);
            Unchecked_EventHandler = new RoutedEventHandler(checkBoxTreeViewItem_Unchecked);

            DependencyPropertyDescriptor dpd =
                DependencyPropertyDescriptor.FromProperty(CheckBoxTreeView.ItemsSourceProperty, typeof(CheckBoxTreeView));
            if (dpd != null)
            {
                dpd.AddValueChanged(this, ItemsSourceChanged);
            }
        }
        void ItemsSourceChanged(object sender, EventArgs e)
        {
            Type type = ItemsSource.GetType();
            if (ItemsSource is IList)
            {
                Type listType = typeof(ObservableCollection<>).MakeGenericType(type.GetGenericArguments()[0]);
                CheckedItems = (IList)Activator.CreateInstance(listType);
            }
        }

        internal void OnNewContainer(CheckBoxTreeViewItem newContainer)
        {
            newContainer.Checked -= Checked_EventHandler;
            newContainer.Unchecked -= Unchecked_EventHandler;
            newContainer.Checked += Checked_EventHandler;
            newContainer.Unchecked += Unchecked_EventHandler;
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            CheckBoxTreeViewItem checkBoxTreeViewItem = new CheckBoxTreeViewItem();
            OnNewContainer(checkBoxTreeViewItem);
            return checkBoxTreeViewItem;
        }

        void checkBoxTreeViewItem_Checked(object sender, RoutedEventArgs e)
        {
            CheckBoxTreeViewItem checkBoxTreeViewItem = sender as CheckBoxTreeViewItem;

            Action action = () =>
            {
                var checkedItem = checkBoxTreeViewItem.Header;
                CheckedItems.Add(checkedItem);
            };
            this.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
        }

        void checkBoxTreeViewItem_Unchecked(object sender, RoutedEventArgs e)
        {
            CheckBoxTreeViewItem checkBoxTreeViewItem = sender as CheckBoxTreeViewItem;
            Action action = () =>
            {
                var uncheckedItem = checkBoxTreeViewItem.Header;
                CheckedItems.Remove(uncheckedItem);
            };
            this.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
        }

        public IList CheckedItems
        {
            get { return (IList)base.GetValue(CheckedItemsProperty); }
            set { base.SetValue(CheckedItemsProperty, value); }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

<强> CheckBoxTreeViewItem.cs

namespace CheckBoxTreeViewLibrary
{
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(CheckBoxTreeViewItem))]
    public class CheckBoxTreeViewItem : TreeViewItem
    {
        public static readonly RoutedEvent CheckedEvent = EventManager.RegisterRoutedEvent("Checked",
            RoutingStrategy.Direct,
            typeof(RoutedEventHandler),
            typeof(CheckBoxTreeViewItem));

        public static readonly RoutedEvent UncheckedEvent = EventManager.RegisterRoutedEvent("Unchecked",
            RoutingStrategy.Direct,
            typeof(RoutedEventHandler),
            typeof(CheckBoxTreeViewItem));

        public static readonly DependencyProperty IsCheckedProperty =
            DependencyProperty.Register("IsChecked",
                                        typeof(bool),
                                        typeof(CheckBoxTreeViewItem),
                                        new FrameworkPropertyMetadata(false,
                                                                      FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                                                      CheckedPropertyChanged));

        private static void CheckedPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            CheckBoxTreeViewItem checkBoxTreeViewItem = (CheckBoxTreeViewItem)source;
            if (checkBoxTreeViewItem.IsChecked == true)
            {
                checkBoxTreeViewItem.OnChecked(new RoutedEventArgs(CheckedEvent, checkBoxTreeViewItem));
            }
            else
            {
                checkBoxTreeViewItem.OnUnchecked(new RoutedEventArgs(UncheckedEvent, checkBoxTreeViewItem));
            }
        }

        public CheckBoxTreeViewItem()
            : base()
        {
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            PropertyInfo parentTreeViewPi = typeof(TreeViewItem).GetProperty("ParentTreeView", BindingFlags.Instance | BindingFlags.NonPublic);
            CheckBoxTreeView parentCheckBoxTreeView = parentTreeViewPi.GetValue(this, null) as CheckBoxTreeView;
            CheckBoxTreeViewItem checkBoxTreeViewItem = new CheckBoxTreeViewItem();
            parentCheckBoxTreeView.OnNewContainer(checkBoxTreeViewItem);
            return checkBoxTreeViewItem;
        }

        [Category("Behavior")]
        public event RoutedEventHandler Checked
        {
            add
            {
                AddHandler(CheckedEvent, value);
            }
            remove
            {
                RemoveHandler(CheckedEvent, value);
            }
        }
        [Category("Behavior")]
        public event RoutedEventHandler Unchecked
        {
            add
            {
                AddHandler(UncheckedEvent, value);
            }
            remove
            {
                RemoveHandler(UncheckedEvent, value);
            }
        }

        public bool IsChecked
        {
            get { return (bool)base.GetValue(IsCheckedProperty); }
            set { base.SetValue(IsCheckedProperty, value); }
        }

        protected virtual void OnChecked(RoutedEventArgs e)
        {
            base.RaiseEvent(e);
        }
        protected virtual void OnUnchecked(RoutedEventArgs e)
        {
            base.RaiseEvent(e);
        }
    }
}

<强> CheckBoxTreeViewItemStyle.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:cti="clr-namespace:CheckBoxTreeViewLibrary">
    <Style x:Key="TreeViewItemFocusVisual">
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate>
                    <Rectangle/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
    <Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
        <Setter Property="Focusable" Value="False"/>
        <Setter Property="Width" Value="16"/>
        <Setter Property="Height" Value="16"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Border Background="Transparent" Height="16" Padding="5,5,5,5" Width="16">
                        <Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="Transparent" Stroke="#FF989898">
                            <Path.RenderTransform>
                                <RotateTransform Angle="135" CenterY="3" CenterX="3"/>
                            </Path.RenderTransform>
                        </Path>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Stroke" TargetName="ExpandPath" Value="#FF1BBBFA"/>
                            <Setter Property="Fill" TargetName="ExpandPath" Value="Transparent"/>
                        </Trigger>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter Property="RenderTransform" TargetName="ExpandPath">
                                <Setter.Value>
                                    <RotateTransform Angle="180" CenterY="3" CenterX="3"/>
                                </Setter.Value>
                            </Setter>
                            <Setter Property="Fill" TargetName="ExpandPath" Value="#FF595959"/>
                            <Setter Property="Stroke" TargetName="ExpandPath" Value="#FF262626"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="{x:Type cti:CheckBoxTreeViewItem}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
        <Setter Property="Padding" Value="1,0,0,0"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type cti:CheckBoxTreeViewItem}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition MinWidth="15" Width="Auto"/>
                            <!--<ColumnDefinition Width="Auto"/>-->
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" MinHeight="15"/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
                        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                            <StackPanel Orientation="Horizontal">
                                <CheckBox Margin="0,2,4,0" x:Name="PART_CheckedCheckBox" IsChecked="{Binding IsChecked, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" />
                                <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                            </StackPanel>
                        </Border>
                        <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsExpanded" Value="false">
                            <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
                        </Trigger>
                        <Trigger Property="HasItems" Value="false">
                            <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
                        </Trigger>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                        </Trigger>
                        <MultiTrigger>
                            <MultiTrigger.Conditions>
                                <Condition Property="IsSelected" Value="true"/>
                                <Condition Property="IsSelectionActive" Value="false"/>
                            </MultiTrigger.Conditions>
                            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
                        </MultiTrigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
                <Setter Property="ItemsPanel">
                    <Setter.Value>
                        <ItemsPanelTemplate>
                            <VirtualizingStackPanel/>
                        </ItemsPanelTemplate>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>