2

I have created a very simple example to show my problem. Maybe I just think in a wrong way.

I want to select an Item of my TreeView - and I would like to see it in the View (Blue background).

To realize the TwoWayBinding I use this Behavior: Data binding to SelectedItem in a WPF Treeview

public class BindableSelectedItemBehavior : Behavior<TreeView>
{
    #region SelectedItem Property

    public object SelectedItem
    {
        get { return (object)GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));

    private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var item = e.NewValue as TreeViewItem;
        if (item != null)
        {
            item.SetValue(TreeViewItem.IsSelectedProperty, true);
        }
    }

    #endregion

    protected override void OnAttached()
    {
        base.OnAttached();

        this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        if (this.AssociatedObject != null)
        {
            this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
        }
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        this.SelectedItem = e.NewValue;
    }
}

But if I click on an Item it does not go into the 'if' of the OnSelectedItemChanged because e.newValue as TreeViewItem is null

My XAML is very simple:

<StackPanel>
    <TreeView xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
      ItemsSource="{Binding Items}">
        <i:Interaction.Behaviors>
            <local:BindableSelectedItemBehavior
                SelectedItem="{Binding Item}" />
        </i:Interaction.Behaviors>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate>
                <TextBlock Text="{Binding Text}"/>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
    <TextBox Text="{Binding Item.Text}"/>
</StackPanel>

Thank you guys!

Community
  • 1
  • 1
MisterPresident
  • 563
  • 7
  • 20

3 Answers3

4

Just for the sake of the convenience, here's the final solution combined of the OP and ghrod's answer:

namespace MyPoject.Behaviors
{
  using System.Windows;
  using System.Windows.Controls;
  using System.Windows.Interactivity;

  public class BindableSelectedItemBehavior : Behavior<TreeView>
  {
    #region SelectedItem Property

    public object SelectedItem
    {
      get { return (object)GetValue(SelectedItemProperty); }
      set { SetValue(SelectedItemProperty, value); }
    }
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register(
          nameof(SelectedItem),
          typeof(object),
          typeof(BindableSelectedItemBehavior),
          new FrameworkPropertyMetadata(null, 
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
            OnSelectedItemChanged));

    static void OnSelectedItemChanged(DependencyObject sender, 
      DependencyPropertyChangedEventArgs e)
    {
      var behavior = (BindableSelectedItemBehavior)sender;
      var generator = behavior.AssociatedObject.ItemContainerGenerator;
      if (generator.ContainerFromItem(e.NewValue) is TreeViewItem item)
        item.SetValue(TreeViewItem.IsSelectedProperty, true);
    }
    #endregion

    protected override void OnAttached()
    {
      base.OnAttached();

      AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
    }

    protected override void OnDetaching()
    {
      base.OnDetaching();

      if (this.AssociatedObject != null)
        AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
    }

    void OnTreeViewSelectedItemChanged(object sender, 
        RoutedPropertyChangedEventArgs<object> e) =>
      SelectedItem = e.NewValue;
  }
}
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
  • This solution requires C# 7.0. Changing the code (not tested) in OnSelectedItemChanged to: var item = generator.ContainerFromItem(e.NewValue) as TreeViewItem; item?.SetValue(TreeViewItem.IsSelectedProperty, true); should fix the problem... – Dean Kuga Jun 27 '18 at 17:26
  • Does this actually work for items that aren't at the root level? Best as I can tell, each subnode has its _own_ `ItemContainerGenerator`, so you need to recurse. But on top of that, the ItemContainerGenerator doesn't immediately populate its connected, so you need to attach to its `StatusChanged` event. Recursively. Surely that can't be the solution? – Sören Kuklau Jun 25 '20 at 12:41
3

SelectedItem property of TreeView does not return TreeViewItem in your case. It returns currently selected item from your bound Items collection. To get TreeViewItem from SelectedItem, you need to use ItemContainerGenerator here:

private static void OnSelectedItemChanged(DependencyObject sender, 
  DependencyPropertyChangedEventArgs e)
{
    var behavior = (BindableSelectedItemBehavior)sender;
    var generator = behavior.AssociatedObject.ItemContainerGenerator;
    var item = generator.ContainerFromItem(e.NewValue) as TreeViewItem;
    if (item != null)
    {
        item.SetValue(TreeViewItem.IsSelectedProperty, true);
    }
}
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
ghord
  • 13,260
  • 6
  • 44
  • 69
  • Thank you for answering, the problem is, that "generator.ContainerFromItem(e.NewValue)" returns null because my Tab is not active. Do you know how to solve this? – MisterPresident Aug 14 '15 at 06:35
  • @MisterPresident in my case, I'm trying to achieve this using a `TreeView`, and getting the container returns null too. – Shimmy Weitzhandler Nov 22 '17 at 13:14
0

Your OnSelectedItemChanged will only pass an object of type TreeViewItem if your actual model objects are of type TreeViewItem, which isn't going to be the case 99% of the time. You will instead have to retrieve TreeViewItem from a model object, but it will not be available if the node is currently collapsed, which makes selecting collapsed nodes very non-trivial.

I've made an effort to explain this very thoroughly in my blog, including code samples here

Tyrrrz
  • 2,492
  • 1
  • 19
  • 29