Reputation:
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:
Upvotes: 19
Views: 36572
Reputation: 1
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.
Upvotes: 0
Reputation: 14037
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:
You can download source code and sample application from here: WpfComboboxTreeview.zip
Upvotes: 16
Reputation: 62
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>
Upvotes: 1
Reputation: 33
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);
}
// ======================================================
Upvotes: 0
Reputation: 2975
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;
}
}
}
Upvotes: 10
Reputation: 16980
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 =)
Upvotes: 1
Reputation: 3342
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.
Upvotes: 1