将TreeView的布局更改为多个ListBox

时间:2010-03-03 09:09:09

标签: wpf templates listview treeview

我正在尝试从此处更改数据绑定树视图的布局:

alt text http://www.freeimagehosting.net/uploads/776c1333f5.png

对此:

alt text http://www.freeimagehosting.net/uploads/99106b4cfd.png

当然,选择必须正常:

alt text http://www.freeimagehosting.net/uploads/8b628758d7.png

您对如何做到这一点有任何想法吗?我一直在尝试更改模板,但我找不到有这种行为的方法。也许一个组件已经存在......

感谢您的帮助!

3 个答案:

答案 0 :(得分:4)

这很难。它似乎需要一个HierarchicalDataTemplate,但因为你想要的行为需要多个ItemsControls,它不会按预期工作。我认为没有办法在XAML中创建TreeView模板来执行此操作。您最好的选择是创建某种自定义项控件。您可能需要在代码中而不是在XAML中执行项绑定,因为如果没有HierarchicalDataTemplate,则XAML无法理解嵌套关系。

话虽如此,如果您保证只有2级嵌套(如您的示例所示),您可以使用以下标记轻松完成此操作:

<Window.Resources>
    <DataTemplate x:Key="ItemTemplate">
        <TextBlock Text="{Binding Name}"/>
    </DataTemplate>
</Window.Resources>

<StackPanel Orientation="Horizontal">
    <ListBox Name="Level1" Width="150" Height="150"
             ItemsSource="{Binding Collection}" 
             ItemTemplate="{StaticResource ItemTemplate}"/>
    <ListBox Name="Level2" Width="150" Height="150"
             ItemsSource="{Binding ElementName=Level1, Path=SelectedValue.Children}"
             ItemTemplate="{StaticResource ItemTemplate}"/>
    <ListBox Name="Level3" Width="150" Height="150"
             ItemsSource="{Binding ElementName=Level2, Path=SelectedValue.Children}"
             ItemTemplate="{StaticResource ItemTemplate}"/>
</StackPanel>

其中Collection是您的根项集合,并且每个项目都有一个名为Children的属性,其中包含子集合。

但我认为你要求的是一个可以支持任意数量嵌套级别的项目控件,而不仅仅是2.所以在这种情况下,我会在代码隐藏中执行此操作。绑定将是相同的 - 也就是说,在每个级别,ListBox应绑定到父级别的项目。但是你显然需要迭代并为每个嵌套级别创建一个ListBox

答案 1 :(得分:0)

我终于找到了出路,但就像你说Charlie一样,它涉及创建ListBox:

  • 我创建了一个新的CustomControl,它继承了Control(我不能同时使用Selector或TreeView,因为我无法从派生类中管理SelectedItem属性)
  • 在此CustomControl的模板中是ItemsControl。此ItemsControl将其ItemTemplate属性设置为包含ListBox的DataTemplate。
  • CustomControl具有int类型的Depth属性。此属性指示应生成的ListBox的数量。
  • CustomControl自动将ListBox一起数据绑定:每个ListBox的ItemsSource属性都数据绑定到可视树中前一个ListBox的SelectedItem的children属性。
  • CustomControl具有SelectedItem属性和SelectionChanged事件(如Selector派生类)。
  • 我将一个IsReallySelected附加属性添加到生成的ListBoxItem中。这使得能够使用ListBoxItem的IsSelected将控件后面的ViewModel类的IsSelected属性进行数据绑定。我必须创建一个附加属性,因为当选择了ListBoxItem并且父ListBox将IsSelectionActive设置为true时,它的值为true。

我在博客上写了这个解决方案(包含源代码)on my blog

答案 2 :(得分:0)

太糟糕了,在你完成所有工作之前,我没有注意到这个问题。很容易重新设计TreeView以这种方式出现:唯一需要的代码是一个非常简单的附加属性,“VisibleWhenCurrentOf”。

这样做的方法是:

  1. 样式TreeViewItem要在ListBox之外的ControlTemplate中添加ItemsPresenter

  2. 使用“VisibleWhenCurrentOf”控制TreeViewItem模板的可见性,以便给定项目仅在ItemsPresenter 内可见,如果它是ListBox中的当前项目。

  3. 重新定位细节

    以下是相关模板的XAML:

    <ControlTemplate TargetType="TreeView">
      <DockPanel>
    
        <ListBox
          ItemsSource="{TemplateBinding ItemsSource}"
          IsSyncrhonizedWithCurrentItem="true"
          Style="{DynamicResource BoxesTreeViewBoxStyle}"
          ItemTemplate="{Binding HeaderTemplate}"
          ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
    
        <ItemsPresenter />
      </DockPanel>
    </ControlTemplate>
    
    <ControlTemplate TargetType="TreeViewItem">
      <DockPanel
        local:VisibilityHelper.VisibleWhenCurrentOf="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor,HeaderedItemsControl,2}">
    
        <ListBox
          ItemsSource="{TemplateBinding ItemsSource}"
          IsSyncrhonizedWithCurrentItem="true"
          Style="{DynamicResource BoxesTreeViewBoxStyle}"
          ItemTemplate="{Binding HeaderTemplate}"
          ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
    
        <ItemsPresenter />
      </DockPanel>
    </ControlTemplate>
    

    除了条件可见性之外,这两个模板是相同的。这样做的方式是树项前面的“+”变为ListBox,除了ListBox中选择的项目之外的所有项目都被隐藏。

    您的BoxesTreeViewBoxStyle应在ListBox附近设置一个边距,以便它们正确放置。您可以通过在样式中添加ListBox属性值来进一步简化此操作,但我发现在ControlTemplate中设置它们更方便,因此我可以重新设置ListBox而不必记住这些设置。

    附加财产

    以下是VisibleWhenCurrentOf附加属性的代码:

    public class VisibilityHelper : DependencyObject
    {
    
      // VisibleWhenCurrentOf
      public static object GetVisibleWhenCurrentOf(DependencyObject obj) { return (object)obj.GetValue(VisibleWhenCurrentOfProperty); }
      public static void SetVisibleWhenCurrentOf(DependencyObject obj, object value) { obj.SetValue(VisibleWhenCurrentOfProperty, value); }
      public static readonly DependencyProperty VisibleWhenCurrentOfProperty = DependencyProperty.RegisterAttached("VisibleWhenCurrentOf", typeof(object), typeof(VisibilityHelper), new UIPropertyMetadata
      {
        PropertyChangedCallback = (sender, e) =>
        {
          var element = sender as FrameworkElement;
          if(e.OldValue!=null)
          {
            var oldView = e.OldValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
            oldView.CurrentChanged -= UpdateVisibilityBasedOnCurrentOf;
            if(e.NewValue==null) element.DataContextChanged -= UpdateVisibilityBasedOnCurrentOf;
          }
          if(e.NewValue!=null)
          {
            var newView = e.NewValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
            newView.CurrentChanged += UpdateVisibilityBasedOnCurrentOf;
            if(e.OldValue==null) element.DataContextChanged += UpdateVisibilityBasedOnCurrentOf;
          }
          UpdateVisibilityBasedOnCurrentOf(sender);
        }
      });
    
      static void UpdateVisibilityBasedOnCurrentOf(object sender, DependencyPropertyChangedEventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
      static void UpdateVisibilityBasedOnCurrentOf(object sender, EventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
      static void UpdateVisibilityBasedOnCurrentOf(object sender)
      {
        var element = sender as FrameworkElement;
    
        var source = GetVisibleWhenCurrentOf(element);
        var view = source==null ? null : source as ICollectionView ?? CollectionViewSource.GetDefaultView(source);
    
        var visible = view==null || view.CurrentItem == element.DataContext;
    
        element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
      }
    
    }
    

    此处没有任何复杂内容:任何时候DataContext或视图的Current更改,都会重新计算可见性。 PropertyChangedCallback只是设置事件处理程序来检测这些条件,UpdateVisibiltyBasedOnCurrentOf处理程序重新计算可见性。

    此解决方案的优势

    由于此解决方案是真正的TreeView:

    1. 您可以免费获得所有选择处理功能。
    2. 适用于任意数量的树级别。
    3. 您可以使用HierarchicalDataTemplate的所有功能,包括HeaderTemplateHeaderTemplateSelector
    4. 您可以在每个级别使用不同的ItemsSource绑定,而不是每个需要“儿童”级别的集合
    5. 代码少于自定义控件