How to make TreeView node selected/expanded when ItemsSource = custom object collection?
Asked Answered
R

3

5

I have a TreeView intended to operate on two levels. For his I have two HierarchicalDataTemplate and two custom types. The ItemsSource is linked to a ObservableCollection and everything works fine. I just can't figure how to make a node selected or expanded from codebehind. Somewhere was mentioned a very good idea of binding IsExpanded and IsSelected properties to the corresponding properties in my custom types. The only problem is that the HierarchicalDataTemplate does not implement a TreeViewItem directly, so how can I access these properties in the following code?

<TreeView Name="treeViewNotes" AllowDrop="True" PreviewMouseLeftButtonDown="treeViewNotes_PreviewMouseLeftButtonDown" PreviewMouseMove="treeViewNotes_PreviewMouseMove" Drop="treeViewNotes_Drop" DragEnter="treeViewNotes_DragEnter">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type dataclasses:NoteFolder}" ItemsSource="{Binding Notes}">
            <StackPanel Orientation="Horizontal">
                <Image Height="16" Source="{Binding TreeViewIcon}" Tag="{Binding Self}"/>
                <TextBlock Text="{Binding Title}" Tag="{Binding Self}" Margin="3"/>
            </StackPanel>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type dataclasses:Note}">
            <StackPanel Orientation="Horizontal">
                <Image Height="16" Source="{Binding TreeViewIcon}" Tag="{Binding Self}"/>
                <TextBlock Text="{Binding Title}" Tag="{Binding Self}" Margin="3"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
</TreeView>

My goal is when creating and adding a new Note to some NoteFolder, to make this Note selected and the Folder expanded. The same would be needed to further improve the UI response on drag&drop.

Rahmann answered 26/3, 2015 at 19:28 Comment(0)
G
9

You could try changing the TreeView's ItemContainerStyle in the following way, so that its IsExpanded and IsSelected properties would bind to the DataContext's IsExpanded and IsSelected:

<TreeView x:Name="..." ItemsSource="{Binding RootNode}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <!-- Items in the ItemsSource need to have these properties for the binding to work -->
            <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            <!-- You can also optionally change some style values based on IsSelected and IsExpanded values -->
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsSelected}" Value="True">
                    <Setter Property="BorderThickness" Value="4 0 0 1"/>
                    <Setter Property="BorderBrush" Value="DeepSkyBlue"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding IsSelected}" Value="False">
                    <Setter Property="BorderThickness" Value="4 0 0 1 "/>
                    <Setter Property="BorderBrush" Value="Transparent"/>
                </DataTrigger>
            </Style.Triggers>
         </Style>
     </TreeView.ItemContainerStyle>
     <TreeView.Resources>
        <HierarchicalDataTemplate>
             ...
        </HierarchicalDataTemplate>
     </TreeView.Resources>
</TreeView>

Of course, then each item coming from the ItemSource and present in the hierarchy needs to have these properties.

Gruesome answered 26/3, 2015 at 21:24 Comment(1)
Your method works! I just want to point that the custom objects have to implement INotifyPropertyChanged.Rahmann
R
3

The only problem is that the HierarchicalDataTemplate does not implement a TreeViewItem directly, so how can I access these properties in the following code?

You can do that in TreeView.ItemContainerStyle:

<TreeView ItemTemplate="{StaticResource ResourceKey=treeViewDataTemplate}"
      ItemsSource="{Binding Data}"
      Name="trvTreeView">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
            <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" />
            <EventSetter Event="Expanded" Handler="TreeViewItem_Expanded" />
            <EventSetter Event="Collapsed" Handler="TreeViewItem_Collapsed" />
            <EventSetter Event="PreviewMouseRightButtonDown" Handler="TreeViewItem_PreviewMouseRightButtonDown" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>

In the code behind:

private void TreeViewItem_Expanded(object sender, RoutedEventArgs e)
{
    TreeViewItem treeViewItem = e.OriginalSource as TreeViewItem;
    if (treeViewItem != null)
    {
        BaseObjectExplorerNode baseObjectExplorerNode = treeViewItem.Header as BaseObjectExplorerNode;
        if (baseObjectExplorerNode != null)
        {
            baseObjectExplorerNode.IsExpanded = true;
        }
    }
}

private void TreeViewItem_Collapsed(object sender, RoutedEventArgs e)
{
    TreeViewItem treeViewItem = e.OriginalSource as TreeViewItem;
    if (treeViewItem != null)
    {
        BaseObjectExplorerNode baseObjectExplorerNode = treeViewItem.Header as BaseObjectExplorerNode;
        if (baseObjectExplorerNode != null)
        {
            baseObjectExplorerNode.IsExpanded = false;
        }
    }
}

And then for example:

root.IsExpanded = true;
Revels answered 26/3, 2015 at 21:30 Comment(0)
U
1

I don't see any problem. You can make a StyleSelector, two Style's and bind to properties.

XAML:

<TreeView Name="treeViewNotes" AllowDrop="True" PreviewMouseLeftButtonDown="treeViewNotes_PreviewMouseLeftButtonDown" PreviewMouseMove="treeViewNotes_PreviewMouseMove" Drop="treeViewNotes_Drop" DragEnter="treeViewNotes_DragEnter">
    <TreeView.Resources>
        <!-- StyleSelector for containers -->
        <notdataclasses:NoteStyleSelector x:Key="NoteStyleSelector" />

        <!-- Style for a NoteFolder's container -->
        <Style x:Key="NoteFolderStyle" TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
            <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" />
            <Setter Property="ItemContainerStyleSelector" Value="{StaticResource NoteFolderStyle}" />
        </Style>
        <!-- Style for a Note's container -->
        <Style x:Key="NoteStyle" TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
        </Style>

        <!-- ... -->
    </TreeView.Resources>
    <TreeView.ItemContainerStyleSelector>
        <StaticResource ResourceKey="NoteStyleSelector" />
    </TreeView.ItemContainerStyleSelector>
</TreeView>

NoteStyleSelector:

public sealed class NoteStyleSelector : StyleSelector
{
    public override Style SelectStyle(object item, DependencyObject container)
    {
        FrameworkElement fe = container as FrameworkElement;

        if (fe!= null)
        {
            if (item is Note)
            { return (Style)fe.FindResource("NoteStyle"); }

            if (item is NoteFolder)
            { return (Style)fe.FindResource("NoteFolderStyle"); }
        }

        return base.SelectStyle(item, container);
    }
}

In a drop handler:

currentFolder.Nodes.Add(pastedNode);

currentFolder.IsExpanded = true;
currentNode.IsSelected = true;

Data casses:

public class Note : INotifyPropertyChanged
{
    // Only the IsSelected property because a Note can not be expanded
    public bool IsSelected { get { /* ... */ } set { /* ... */ } }
}

public class NoteFolder : INotifyPropertyChanged
{
    public bool IsSelected { get { /* ... */ } set { /* ... */ } }
    public bool IsExpanded { get { /* ... */ } set { /* ... */ } }
}
Udella answered 26/3, 2015 at 21:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.