15

Right now you have to double click or click the + icon. Is there any way to make it so if a user clicks anywhere on the node it expands?

AKoran
  • 1,846
  • 6
  • 22
  • 36

7 Answers7

19

I had this same problem and found a good solution thanks to another StackOverflow post.

In the control.xaml's TreeView element, you can hook directly into the TreeViewItem's Selected event:

<TreeView ItemsSource="{StaticResource Array}" TreeViewItem.Selected="TreeViewItem_Selected"/>

Then in your control.xaml.cs code behind, you can grab that selected TreeViewItem from the RoutedEventArgs and set it to IsExpanded:

private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
    TreeViewItem tvi = e.OriginalSource as TreeViewItem;

    if (tvi == null || e.Handled) return;

    tvi.IsExpanded = !tvi.IsExpanded;
    e.Handled = true;
}

Nice and clean. Hopefully that helps someone!

Community
  • 1
  • 1
BJennings
  • 644
  • 5
  • 9
  • 1
    +1 for the XAML code. Solved the problem perfectly. (So does the accepted answer (which I also upvoted)). – Avada Kedavra Feb 16 '12 at 09:18
  • 2
    In my opinion, this is the best method! There's just one flaw: When you try to collapse a non-selected item by clicking on the collapse icon, it gets selected and the event handler prevents collapsing it. You have to encapsulate the event handler in `if (!e.Handled) {...}` and set `e.Handled = true;` right after toggling the `IsExpanded` property. – kroimon Sep 13 '12 at 21:41
  • 1
    Another issue with this is that when you click an already-selected treeview, it doesn't collapse or expand. – Josh Noe Apr 21 '16 at 14:10
6

Maybe is not the most elegant solution but this works:

    static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
    {
        while (source != null && source.GetType() != typeof(T))
            source = VisualTreeHelper.GetParent(source);

        return source;
    }

then in the TreeViewItem.Selected Handler:

        private void Treeview_Selected(object sender, RoutedEventArgs e)
        {
            var treeViewItem = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
            if (treeViewItem != null) treeViewItem.IsExpanded = true;
        }

the VisualUpwardSearch magic is taken from here: Select TreeView Node on right click before displaying ContextMenu

Regards

Community
  • 1
  • 1
Marcote
  • 2,977
  • 1
  • 25
  • 24
  • 1
    Bumping a very old answer. I am trying this solution but I have a label with AccessText and I get the System.Windows.Documents.Run element as OriginalSource. This is not a visual or visual3d element and hence GetParent throws an exception. Any idea how to go about it? – Piyush Parashar Oct 09 '15 at 12:25
5

I had the same problem and I did it with the Style functionnality so that you don't need to handle the event.

I defined a style for the TreeViewItem

<Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
    <!--<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>-->
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">
                        <CheckBox Style="{StaticResource TreeViewItemCB}" IsChecked="{Binding Path=IsExpanded,Mode=OneWayToSource,RelativeSource={RelativeSource TemplatedParent}}"  ClickMode="Press">
                            <Grid Background="{StaticResource TreeViewItemBackground}" Margin="0">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions> 
                            <Border Name="Bd">
                                    <ContentPresenter x:Name="PART_Header" ContentSource="Header"/>
                            </Border>
                            <ItemsPresenter x:Name="ItemsHost" Grid.Row="1"/>
                            </Grid>
                        </CheckBox>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The important part is to defined the checkBox in the ControlTemplate and the binding with it. When the CheckBox is checked, the item will be expanded with just one click.

<CheckBox Style="{StaticResource TreeViewItemCB}" IsChecked="{Binding Path=IsExpanded,Mode=OneWayToSource,RelativeSource={RelativeSource TemplatedParent}}"  ClickMode="Press">

this is the style for the checkBox so that it stretches and doesn't display the box with the stroke.

<Style x:Key="TreeViewItemCB" TargetType="CheckBox" BasedOn="{StaticResource baseStyle}">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="KeyboardNavigation.TabNavigation" Value="None" />
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="VerticalAlignment" Value="Stretch"/>
    <Setter Property="HorizontalAlignment" Value="Stretch"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="CheckBox">
                <ContentPresenter VerticalAlignment="Stretch" HorizontalAlignment="Stretch" RecognizesAccessKey="True"/>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
Daniel
  • 9,312
  • 3
  • 48
  • 48
4

@BJennings provided a great answer. However, if you want to expand or collapse an already selected item, it doesn't work. To improve that, you can simply add: tvi.IsSelected = false; (if you do not care whether the item is in a selected state.)

So, the whole codes look like this:

private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
    TreeViewItem tvi = e.OriginalSource as TreeViewItem;

    if (tvi == null || e.Handled) return;

    tvi.IsExpanded = !tvi.IsExpanded;
    tvi.IsSelected = false;
    e.Handled = true;
}
folkboat
  • 167
  • 1
  • 7
1

Another approch would be to use Attached propperties.

public class VirtualOneClickExpandButtonBehavior : DependencyObject
{
    public static bool GetEnabled(DependencyObject obj)
    {
        return (bool)obj.GetValue(EnabledProperty);
    }

    public static void SetEnabled(DependencyObject obj, bool value)
    {
        obj.SetValue(EnabledProperty, value);
    }


    public static readonly DependencyProperty EnabledProperty =
        DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(VirtualOneClickExpandButtonBehavior), 
            new UIPropertyMetadata(false, EnabledPropertyChangedCallback
                ));

    private static void EnabledPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        var treeView = dependencyObject as TreeView;

        if (treeView == null) return;

        treeView.MouseUp += TreeView_MouseUp;
    }

    private static void TreeView_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        var treeViewItem = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
        if (treeViewItem != null) treeViewItem.IsExpanded = !treeViewItem.IsExpanded;
    }

    static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
    {
        while (source != null && source.GetType() != typeof(T))
            source = VisualTreeHelper.GetParent(source);

        return source;
    }
}

And then you can use it like this.

<TreeView controls:VirtualOneClickExpandButtonBehavior.Enabled="true" ItemsSource="{Binding HierarchicalModel}"/>

This is a good approch if you use the MVVM pattern because you don't need the codebehind.

And thaks to Markust for his VisualUpwardSearch(DependencyObject source)

Xiol
  • 170
  • 10
0

The accepted solution has odd behaviour when you're navigating with the keyboard, and doesn't collapse the item when it's already selected. Alternatively, just derive a new class from TreeViewItem and override the MouseLeftButtonDown method. You also need to set your TreeView.ItemsSource to a collection of your new TreeViewItem class.

protected override void OnMouseLeftButtonDown(System.Windows.Input.MouseButtonEventArgs e)
{
    if (!e.Handled && base.IsEnabled)
    {
        this.IsExpanded = !this.IsExpanded;
        e.Handled = true;
    }
    base.OnMouseLeftButtonDown(e);
}
ACyclic
  • 5,904
  • 6
  • 26
  • 28
-1
<TreeView.ItemContainerStyle>
  <Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="Cursor" Value="Hand" />
    <EventSetter Event="MouseUp" Handler="TreeViewItem_Click"/>
  </Style>
</TreeView.ItemContainerStyle>


private void TreeViewItem_Click(object sender, MouseButtonEventArgs e)
        {
            ((TreeViewItem) sender).IsExpanded = !((TreeViewItem) sender).IsExpanded;
            Thread.Sleep(700);
        }

Here is the answer, Enjoy it

Answer by: Ali Rahimy

user3578181
  • 1,896
  • 2
  • 17
  • 12