0
votes

I have an strange issue with a WPF TreeView in C#. When running my App i want to expand and collapse all nodes from the selected TreeView node. When i select a root node, the IsSelected property is not updated. When i manually expand the root node and click on the child node, the IsSelected property is updated. The problem is that when i use the context menu to expand and collapse the child nodes on the root node, the root node is not expanded. When i expand the root node after using the context menu i can see that all child nodes have been expanded correctly.

I have searched a lot in the internet, but couldn't find a similar issue. Furthermore i read a lot of tutorials and howtos on C# WPF TreeViews. So far my implementation seems to me to be correct, thats why i don't understand why the root property IsSelected is not set when clicking on the node.

Can anyone help me? I have implemented the TreeView, ViewModel and Model as follows. Thanks in advance

BR

Michael


<TreeView
Grid.Row="1"
Grid.Column="0"
Margin="5"
ContextMenuOpening="FrameworkElement_OnContextMenuOpening"
ItemsSource="{Binding HierarchialTestObjects}"
SelectedItemChanged="TreeView_OnSelectedItemChanged">
<TreeView.ItemTemplate>
    <HierarchicalDataTemplate DataType="{x:Type vm:HierarchialTestObjectViewModel}" ItemsSource="{Binding Childs}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <StackPanel Orientation="Horizontal">
            <Label Content="{Binding Path=Name}" />
        </StackPanel>
    </HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ContextMenu>
    <ContextMenu>
        <MenuItem Command="{Binding ExpandAllTreeItemsCommand}" Header="Expand All" />
        <MenuItem Command="{Binding CollapseAllTreeItemsCommand}" Header="Collapse All" />
    </ContextMenu>
</TreeView.ContextMenu>

namespace mynamespace.subspace.report.data
{
// ... usings removed

public class HierarchialTestObject
{
    public string Name { get; set; }

    public List<HierarchialTestObject> Childs { get; set; } = new List<HierarchialTestObject>();

    public List<string> Path { get; set; } = new List<string>();

    public List<string> ParentPath => Path?.AsEnumerable()?.Reverse()?.Skip(1)?.Reverse()?.ToList();

    public IHierarchialItem Item { get; set; }

    public HierarchialTestObject(IHierarchialItem item)
    {
        Item = item;
        Name = item.ShortDescription;
    }

    public void SetChilds(IEnumerable<IHierarchialItem> childs)
    {
        childs.ToList().ForEach(c => Childs?.Add(new HierarchialTestObject(c)));
    }
}
}

ViewModel

namespace mynamespace.subspace.report.viewmodel
{
// ... usings removed

public class HierarchialTestObjectViewModel : ViewModelBase
{
    public delegate void NotifyTreeItemSelected(HierarchialTestObjectViewModel item);

    public event NotifyTreeItemSelected OnTreeItemSelected;

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (_isSelected != value)
            {
                SetProperty(ref _isSelected, value, nameof(IsSelected));
                if (value)
                {
                    // this is a hack-ish workaround because i cannot use PRISM or other similar libs
                    OnTreeItemSelected?.Invoke(this);
                }
            }
        }
    }

    private bool _isExpanded;
    public bool IsExpanded
    {
        get { return _isExpanded; }
        set
        {
            if (_isExpanded != value)
            {
                SetProperty(ref _isExpanded, value, nameof(IsExpanded));
                RaisePropertyChanged(nameof(IconType));
            }
        }
    }



    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                SetProperty(ref _name, value, nameof(Name));
            }
        }
    }



    private IHierarchialItem _item;
    public IHierarchialItem Item
    {
        get { return _item; }
        set
        {
            if (_item != value)
            {
                SetProperty(ref _item, value, nameof(Item));
            }
        }
    }


    private List<string> _path;
    public List<string> Path
    {
        get { return _path; }
        set
        {
            if (_path != value)
            {
                SetProperty(ref _path, value, nameof(Path));
            }
        }
    }

    private ObservableCollection<HierarchialTestObjectViewModel> _childs;
    public ObservableCollection<HierarchialTestObjectViewModel> Childs
    {
        get { return _childs; }
        set
        {
            if (_childs != value)
            {
                SetProperty(ref _childs, value, nameof(Childs));
            }
        }
    }

    public HierarchialTestObjectViewModel(HierarchialTestObject obj)
    {
        var childs = obj.Childs.Select(c => new HierarchialTestObjectViewModel(c)).ToList();
        Path = obj.Path ?? new List<string>();
        Childs = new ObservableCollection<HierarchialTestObjectViewModel>(childs);
        Item = obj.Item;
        Name = obj.Name;
    }

    public void ExpandRecursively()
    {
        IsExpanded = true;
        Childs?.ToList().ForEach(c => c.ExpandRecursively());
    }

    public void CollapseRecursively()
    {
        IsExpanded = false;
        Childs?.ToList().ForEach(c => c.CollapseRecursively());
    }

}
}
1
What is a DataContext for the TreeView? How do you initialize a HierarchialTestObjects property?Rekshino

1 Answers

2
votes

The problem is that you never set the ItemContainerStyle of the root items. Only for the child items. Therefore the IsExpanded and IsSelected properties of the root data models are not connected to their TreeViewItem data container.

Just set the TreeView.ItemContainerStyle to solve the issue:

<TreeView.ItemContainerStyle>
  <Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
  </Style>  
</TreeView.ItemContainerStyle>

Remarks

TreeViewItem.IsSelected is configured with BindingMode.TwoWay by default.
TreeViewItem.IsSelected and TreeViewItem.IsExpanded have their Binding.UpdateSourceTrigger set to UpdateSourceTrigger.PropertyChanged by default, which is the default for all Dependency Property except they are explicitly set to a different trigger, which is the case for only a few properties (e.g. TextBox.Text is set to UpdateSourceTrigger.LostFocus by default).
This way you can reduce code and improve readability.