Its too bad I didn't notice this question before you went to all that work. It is easy to restyle a TreeView to appear this way: The only code required is a single very simple attached property, "VisibleWhenCurrentOf".
The way to do it is to:
Style TreeViewItem
to include a ListBox
in its ControlTemplate
outside the ItemsPresenter
.
Control the visibility of the TreeViewItem
template using "VisibleWhenCurrentOf", so that a given item is only visible inside the ItemsPresenter if it is the current item within the ListBox.
Restyling details
Here is the XAML for the relevant templates:
<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>
These two templates are identical except for the conditional visibilty. The way this works is that the "+" in front of the tree item becomes a ListBox
, and all items except the one selected in the ListBox
are hidden.
Your BoxesTreeViewBoxStyle
should set a margin around the ListBox
so they will space correctly. You can actually simplify this further by putting the ListBox
property values in the style, but I find it more convenient to set them in the ControlTemplate
so I can restyle the ListBox
without having to remember these settings.
Attached property
Here is the code for the VisibleWhenCurrentOf
attached property:
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;
}
}
There is nothing complex here: Any time DataContext
or the view's Current
changes, visibilty is recomputed. The PropertyChangedCallback
simply sets event handlers to detect these conditions and the UpdateVisibiltyBasedOnCurrentOf handler recomputes visibility.
Advantages of this solution
Since this solution is a real TreeView:
- You get all the selection handling functionality for free.
- It works with any number of tree levels.
- You can use all the features of
HierarchicalDataTemplate
, including HeaderTemplate
and HeaderTemplateSelector
- You can use different
ItemsSource
bindings at each level rather than every collection requiring a "Children" proerty
- It is a lot less code than a custom control