19

I'm trying to put a TreeView inside a ComboBox in WPF so that when the combo box is dropped, instead of a flat list the user gets a hierarchical list and whatever node they select becomes the selected value of the ComboBox.

I've searched quite a bit for how to accomplish this but the best I could find was only peices of potential soltuions that, because I'm ridiculously new to WPF, I couldn't make work.

I have enough knowledge of WPF and databinding that I can get my data into the treeview and I can even get the treeview inside of the combo box, however what I've been able to accomplish doesn't behave properly at all. I've attached a screenshot to show what I mean. In the screenshot the combo box is "open", so the treeview on the bottom is where I can select a node and the treeview "on top" is being drawn on top of the combobox where I want the text/value of the selected node in the tree to be displayed.

Basically what I don't know how to do is how do I get the treeview's currrently selected node to return its value back up to the combobox which then uses it as its selected value?

Here is the xaml code I'm currently using:

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

Screenshot: TreeView

Regent
  • 5,502
  • 3
  • 33
  • 59
  • I have no idea why I can't get the screenshot to post (stackoverflow just keeps giving me an error), so here's url: http://www.fixedvancouver.com/pics/TreeInComboBox1.JPG –  Apr 06 '09 at 20:12
  • You could spend a lot of time doing this. Rolling your own custom controls is hard work and very fiddly. I would seriously look at a different way of doing displaying your data. Users do not expect a Treeview in a ComboBox and non standard ways of displaying data can be confusing as it is not familiar. – Aran Mulholland Jan 25 '11 at 00:11
  • I have created the ComboBox which displays the TreeView instead of the list. It is in Silverlight, but I think it isn't difficult to rewrite it in WPF: http://vortexwolf.wordpress.com/2011/04/29/silverlight-combobox-with-treeview-inside/. – vortexwolf Apr 29 '11 at 14:19
  • 1
    @AranMulholland try telling users that, if that's what they've asked for. When my client asks me for something, it would be my dream to say "nah, you don't want that". I'd be fired so fast it would make your head spin. – Matt May 14 '12 at 15:08
  • @Matt are you saying that you are not allowed to try convincing the client that there are better ways to visualise data? My comment here comes from a fair bit of hard work recreating a custom combo box. Combo boxes look really simple but they actually have a lot of interaction, that being said if there is no other way and the client does not mind paying..... – Aran Mulholland May 16 '12 at 23:39

7 Answers7

16

For those who still need this control, I've implemented a WPF version of my Silverlight control. It works only with view models and requires these view models to implement a special interface, but apart of this it's not difficult to use.

In WPF it looks like this:

WPF Combobox with TreeView

You can download source code and sample application from here: WpfComboboxTreeview.zip

vortexwolf
  • 13,967
  • 2
  • 54
  • 72
  • @Khiem-KimHoXuan The links should work as 1st link is to my blog and 2nd link to my dropbox. I just opened them and they work well. – vortexwolf Sep 11 '15 at 22:40
  • Exactly what I need! Thank you for saving me hours of work! :) – Alex Pshul Aug 31 '16 at 17:44
  • Thanks for this, just to mention however that this line: DefaultStyleKeyProperty.OverrideMetadata(typeof(ComboBoxTreeView), new FrameworkPropertyMetadata(typeof(ComboBoxTreeView))); Should be in a static constructor - it only works in the default constructor as it is if there is one instance of that control – Chanakya Jan 30 '17 at 19:10
  • I updated links in wordpress posts, but I can't edit every stackoverflow post. The most reliable way is to open wordpress post and download from there. – vortexwolf Jun 30 '17 at 22:00
9

I had the same issue.

The easiest way to implement the behavior of a treeview in a combobox is to create a TextBox and stylize it to look like a combobox. Add an image next to it. The trick is to put the treeview in a popup control. Then, when the user clicks the textbox or the dropdown image you chose, the popup is displayed directly under the textbox.

Then, when the treeview item is selected, close the popup and place the text of the selected now in the textbox.

Here's an unstylized example:

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>

And here is the Code behind:

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;
      }
   }
}
Josh
  • 2,955
  • 1
  • 19
  • 28
1

This question is actually closely related to that one

So you would probably find this implementation helpful. This is a combobox with checkboxes inside, but you can get the idea on how to decouple the text in the box from the popup content with your tree.

It also demonstrates the idea that the IsSelected property should be on your model entities and then it is bound back to the checkbox Text property through the model. In other words, what you show in the combobox collapsed might be completely unrelated to the content... Well, maybe not completely, but in my app when a user selects several checkboxes in that combo I can show comma-separated in the top textbox, or I can show "Several options selected", or whatever.

HTH =)

Community
  • 1
  • 1
Massimiliano
  • 16,770
  • 10
  • 69
  • 112
1

It's an old topic but it can be useful to somebody.

Trying to do something similar with a combobox, I tried to use popup instead and it's working. To turn it into a nice feature it needs a lot of tweaking.

<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
    Works very well - but how do I get the popup to Close when an item is selected? – Sam Mar 25 '14 at 10:02
1

You might be able to use an event handler on the tree view to set the SelectedItem on the comboBox.

In order to do this you would need to set the Tag porperty of the tree view like so:

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

Now in the DoubleClick event you can get at the 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)
        {

        }
    }

You will also need to override the way the comboBox Item is selecte, otherwise the whole TreeView will be selected as soon as you click on it.

Kelly
  • 3,292
  • 1
  • 24
  • 24
  • "You will also need to override the way the comboBox Item is selecte, otherwise the whole TreeView will be selected as soon as you click on it." Can you elaborate on this please? –  Apr 07 '09 at 20:28
0

I think you can foreach treeViewItems then add into combo 1by1.

and in each treeviewitem expand event, append its children into combobox.

however, set expandable item's height to looks like in one row, such as 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);
}
// ======================================================
Oh my dog
  • 33
  • 5
0

There are two problems that you are having. The first is that when you select your one-and-only ComboBoxItem (the whole TreeView), that is what is returned into the ContentPresenter of the ComboBox's base ToggleButton. Simply making your ComboBox IsEditable will stop the whole TreeView from being put into the Content of the ComboBox but it still isn't selecting the item you chose in the TreeView. You'll have to use the SelectedItemChanged event in the TreeView to capture the selected item and then convert that into your 'SelectedItem'. Once you've selected the item and passed it to the ComboBox, set IsDropDownOpen to false.

Adam
  • 1