2010-10-28 98 views
5

經過大量的搜索之後,我還沒有找到解決以下問題的方法。 我需要一個帶有「checkboxed」treeview項目和CheckedItems屬性的treeview控件,以方便數據綁定(例如,當用戶檢查文件夾時,文件夾結構的樹視圖,文本框中顯示選中文件夾的大小)。帶有複選框的WPF TreeView

順便說一句,我已閱讀文章,但在我的情況下「IsChecked」方法並不合適,因爲我需要將CheckedItems作爲集合綁定。

我將不勝感激任何幫助!

alt text

圖像鏈接已被附接。我希望列表框能夠綁定到CheckedItems屬性CheckTreeView。有誰知道如何實現通用CheckTreeView可能綁定到CheckedItems集合?

回答

4

更新
最後得到周圍與缺少的功能更新CheckBoxTreeView。所述CheckBoxTreeViewLibrary源可以是downloaded here

  • 添加CheckedItems屬性
  • CheckedItems是ObservableCollection<T>其中T是INTERAL類型的ItemsSource的
  • CheckedItems支持雙向綁定到源
  • 如果CheckBoxTreeViewItem不是招」 (尚未擴展到),則源代碼將不會在CheckedItems集合中生成,直至生成爲止

控件可以像普通的TreeView一樣使用。要爲IsChecked屬性添加雙向綁定,必須合併CheckBoxTreeViewItemStyle.xaml ResourceDictionary。例如

<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> 
+0

尼斯代碼!謝謝!但在將ItemsSource綁定到Customers(列表)和CheckedItems綁定到SelectedCustomers(ObservableCollection ())之後,會引發空引用異常。如果更改SelectedCustomers屬性類型ObservableCollection (),則不會引發異常。如何解決這個問題呢? – 2010-11-10 09:55:59

+0

順便說一下,如何爲CheckedItems屬性提供雙向數據綁定? – 2010-11-11 05:41:13

+0

@Serge:更新了我的答案,超過一個月後,但更晚,然後永遠不會:)我不知道你是否仍然需要一個CheckBoxTreeView,但我更新的答案應該包括最後一個失蹤的功能 – 2010-12-20 16:55:34