WPF:ComboBox 内的 TreeView

19

我正在尝试在WPF中将一个TreeView放入ComboBox中,这样当下拉列表框展开时,用户会得到一个分层列表,并且他们选择的任何节点都将成为ComboBox的选定值。

我已经搜索了很多如何完成这个任务的方法,但我能找到的最好的方法只是一些可能解决方案的碎片,因为我对WPF还非常陌生,所以无法让它们起作用。

我有足够的WPF和数据绑定知识,可以将我的数据加载到树形视图中,甚至可以将树形视图放置在组合框中,但是我所能完成的并不能正常工作。我已经附上了一个截图来说明我的意思。在截图中,“open”状态的组合框底部的树形视图是我可以选择节点的地方,“on top”状态的树形视图则被绘制在组合框的顶部,而我希望显示所选节点的文本/值的地方应该是组合框内。

基本上,我不知道该怎么做才能使树形视图当前选择的节点将其值返回到组合框,并将其用作选定值。

以下是我目前使用的XAML代码:

        <ComboBox Grid.Row="0" Grid.Column="1"  VerticalAlignment="Top">
        <ComboBoxItem>
            <TreeView ItemsSource="{Binding Children}" x:Name="TheTree">
                <TreeView.Resources>
                    <HierarchicalDataTemplate DataType="{x:Type Core:LookupGroupItem}" ItemsSource="{Binding Children}">
                        <TextBlock Text="{Binding Path=Display}"/>                            
                    </HierarchicalDataTemplate>
                </TreeView.Resources>
            </TreeView>
        </ComboBoxItem>
    </ComboBox>

屏幕截图:TreeView


我不知道为什么无法发布屏幕截图(stackoverflow一直给我错误),所以这里是网址:http://www.fixedvancouver.com/pics/TreeInComboBox1.JPG - user77369
你可能需要花费很多时间来完成这个任务。自己编写定制控件是非常困难和琐碎的工作。我建议你认真考虑一种不同的方式来展示你的数据。用户并不希望在下拉框中看到树形视图,非标准的数据展示方式可能会让人感到困惑,因为它们不太熟悉。 - Aran Mulholland
我已经创建了一个ComboBox,它显示TreeView而不是列表。它是在Silverlight中编写的,但我认为重写成WPF并不难:http://vortexwolf.wordpress.com/2011/04/29/silverlight-combobox-with-treeview-inside/. - vortexwolf
1
@AranMulholland试着告诉用户,如果这是他们要求的。当我的客户向我提出要求时,我梦想着能够说“不,你不想要那个”。但如果我这样做,我会被解雇得飞快。 - Matt
@Matt,你是在说你不能试图说服客户有更好的数据可视化方式吗?我的评论来自于重新创建自定义组合框的艰辛工作。组合框看起来很简单,但实际上它们有很多交互作用。话虽如此,如果没有其他方法并且客户不介意支付的话…… - Aran Mulholland
7个回答

16
对于那些仍需要这个控件的人,我已经实现了一个 WPF 版本的Silverlight 控件。它只能与视图模型一起使用,并要求这些视图模型实现一个特殊的接口,但除此之外,它并不难使用。
在 WPF 中,它看起来像这样: WPF 带 TreeView 的下拉列表框 你可以从这里下载源代码和示例应用程序:WpfComboboxTreeview.zip

@Khiem-KimHoXuan 这些链接应该是有效的,第一个链接是我的博客,第二个链接是我的Dropbox。我刚刚打开它们,它们运行良好。 - vortexwolf
正是我所需要的!感谢您节省了我数小时的工作时间! :) - Alex Pshul
谢谢您,只是想提醒一下,这行代码:DefaultStyleKeyProperty.OverrideMetadata(typeof(ComboBoxTreeView), new FrameworkPropertyMetadata(typeof(ComboBoxTreeView))); 应该在静态构造函数中 - 它只能在默认构造函数中工作,如果有一个控件实例。 - Chanakya
我更新了WordPress帖子中的链接,但无法编辑每个stackoverflow 帖子。最可靠的方法是打开WordPress帖子并从那里下载。 - vortexwolf

9
我遇到了同样的问题。
实现将树形视图行为添加到组合框中最简单的方法是创建一个文本框并将其样式设置为看起来像组合框。在其旁边添加一张图片。关键是将树形视图放在弹出控件中。然后,当用户单击您选择的文本框或下拉图像时,弹出窗口会直接显示在文本框下方。
然后,当选择树形视图项目时,关闭弹出窗口并将所选文本放置在文本框中。
以下是一个未经过样式处理的示例:
XAML:
<Window x:Class="ComboBoxTreeView.MainWindow"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="MainWindow" Height="350" Width="525" MouseEnter="Window_MouseEnter">
   <Grid Margin="15">
      <Grid.RowDefinitions>
         <RowDefinition Height="30" />
         <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <TextBox Grid.Row="0" x:Name="header" Width="300" Height="30" PreviewMouseDown="header_PreviewMouseDown" HorizontalAlignment="Left" />
      <Popup Grid.Row="1" x:Name="PopupTest" AllowsTransparency="True" IsOpen="False">
         <TreeView x:Name="Tree1" Initialized="Tree1_Initialized" SelectedItemChanged="Tree1_SelectedItemChanged">
            <TreeViewItem Header="Test1" x:Name="Tree1Item1">
               <TreeViewItem Header="1test1" />
               <TreeViewItem Header="2test2" />
            </TreeViewItem>
            <TreeViewItem Header="Test2" />
         </TreeView>
      </Popup>
   </Grid>
</Window>

这里是代码背后的实现:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ComboBoxTreeView
{
   /// <summary>
   /// Interaction logic for MainWindow.xaml
   /// </summary>
   public partial class MainWindow : Window
   {
      public MainWindow()
      {
         InitializeComponent();
      }

      private void Window_MouseEnter(object sender, MouseEventArgs e)
      {

      }

      private void Tree1_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
      {
         var trv = sender as TreeView;
         var trvItem = trv.SelectedItem as TreeViewItem;
         if (trvItem.Items.Count != 0) return;
         header.Text = trvItem.Header.ToString();
         PopupTest.IsOpen = false;
      }

      private void Tree1_Initialized(object sender, EventArgs e)
      {
         var trv = sender as TreeView;
         var trvItem = new TreeViewItem() { Header="Initialized item"};
         var trvItemSel = trv.Items[1] as TreeViewItem;
         trvItemSel.Items.Add(trvItem);
      }

      private void header_PreviewMouseDown(object sender, MouseButtonEventArgs e)
      {
         PopupTest.Placement = System.Windows.Controls.Primitives.PlacementMode.RelativePoint;
         PopupTest.VerticalOffset = header.Height;
         PopupTest.StaysOpen = true;
         PopupTest.Height = Tree1.Height;
         PopupTest.Width = header.Width;
         PopupTest.IsOpen = true;
      }
   }
}

1

这是一个老话题,但对某些人可能很有用。

尝试使用组合框实现类似的功能,我试图使用弹出窗口来代替,并且它能够工作。 为了将其变成一个好的特性,需要进行大量调整。

<Expander Header="TestCS">
    <Popup IsOpen="{Binding IsExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Expander}}}">
        <TreeView ItemsSource="{Binding CSTree.CSChildren}">
            <TreeView.Resources>
                <HierarchicalDataTemplate ItemsSource="{Binding CSChildren}" DataType="{x:Type ViewModel:ObservableCS}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock FontSize="16" Text="{Binding CSName}"></TextBlock>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.Resources>
        </TreeView>
    </Popup>
</Expander>


1
非常好用 - 但是当选择一个项目时,如何关闭弹出窗口? - Sam

1

你可以在树形视图上使用事件处理程序来设置组合框的SelectedItem。

为了实现这一点,您需要设置树形视图的Tag属性,如下所示:

<TreeView Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}"  MouseDoubleClick="treeview_MouseDoubleClick" ItemsSource="{Binding Children}" x:Name="TheTree">

现在在DoubleClick事件中,您可以获取ComboBox:

    private void treeview_MouseDoubleClick(object sender, RoutedEventArgs e)
    {
        try
        {
            TreeView tv = sender as TreeView;
            if(tv == null)
                return;
            var cB = tv.Tag as ComboBox;
            cB.SelectedItem = tv.SelectedItem;
        }
        catch (Exception e)
        {

        }
    }

您还需要覆盖comboBox项目的选择方式,否则一旦单击它,整个TreeView将被选中。


您还需要覆盖(comboBox)下拉框选择项的方式,否则一旦单击它,整个TreeView将被选中。您能详细说明一下吗? - user77369

1

这个问题实际上与那个密切相关。

所以你可能会发现这个实现很有帮助。这是一个带有复选框的组合框,但是你可以从中了解如何将框中的文本与弹出内容分离。

它还演示了IsSelected属性应该在你的模型实体上,然后通过模型绑定回复选框Text属性的思想。换句话说,在组合框折叠时显示的内容可能与内容无关...好吧,也许不完全无关,但在我的应用程序中,当用户在该组合框中选择多个复选框时,我可以在顶部文本框中显示逗号分隔的选项,或者显示“已选择几个选项”,或其他任何内容。

希望对你有所帮助 =)


0

我认为你可以使用foreach循环遍历treeViewItems,然后逐个添加到组合框中。

在每个treeviewitem的展开事件中,将其子项附加到combobox中。

但是,请将可展开项的高度设置为看起来像一行,例如Height = 18d。

// == Append Item into combobox =================
TreeViewItem root = new TreeViewItem();
root.Header = "item 1";
TreeViewItem t1 = new TreeViewItem();
t1.Header = "Expanding...";
root.Items.Add(t1);
// ==============================================

// == root expandind event ==============================
root.Height = 18.00d;
TreeViewItem[] items = GetRootChildren(root.Tag);
foreach(TreeViewItem item in items)
{
    combox1.Items.Add(item);
}
// ======================================================

0

您遇到了两个问题。第一个问题是,当您选择唯一的ComboBoxItem(整个TreeView)时,它将返回到ComboBox基本ToggleButton的ContentPresenter中。简单地将ComboBox设置为IsEditable将阻止整个TreeView被放入ComboBox的Content中,但仍然无法选择您在TreeView中选择的项。您需要使用TreeView中的SelectedItemChanged事件来捕获所选项,然后将其转换为“SelectedItem”。选择完项目并将其传递给ComboBox后,将IsDropDownOpen设置为false。


有什么更好的实现方法吗? - Tomasz Chudzik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接