Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handled TreeView.SelectedItemChanged event still bubbles

I have a TreeView bound to a hierarchy consisting of several different classes via HierarchicalDataTemplates. When an item in the tree is selected, the SelectedItemChanged event of course happily bubbles upwards through the parent items, as it should. What it should not do, but still does, is happily keeping on bubbling after I set e.Handled to true.

The event will still fire on the parent item, and the RoutedPropertyChangedEventArgs will look exactly like it was the parent item that was selected; even the OriginalSource property will point to the parent item, not the one that was originally selected. e.Handled will of course be false.

Pretty much the same question was asked here, but I'm not using EventAggregator or CAL, and the workaround found here doesn't help much because I am not specifically after a mouse event.

Is there any way to precisely get the item that was actually selected or to forcefully stop the bubbling madness (without resorting to a very violent and unethical hack using global variables that I can think of)?

Thanks for any insights.

like image 522
TeaDrivenDev Avatar asked May 24 '11 22:05

TeaDrivenDev


2 Answers

After reading Rick's answer, I talked to a co-worker who it turned out had had the same problem before. I tried various things in my application (finding: the TreeViewItem.Selected event behaves exactly the same wrong way) and a test project and found that in the test app the events were firing exactly as one would expect it. So there had to be a significant difference in the surroundings (the XAML and ViewModel classes were nearly identical) that led to this difference in behavior - and the culprit looks to be COM, precisely hosting the WPF controls in a COM application.

My colleague's application is a Word extension using VSTO, while mine is a VSPackage for Visual Studio 2010 - and both Word and VS2010 are still native code for the most part. My test application, on the other hand, is of course a small plain WPF project. I added a WinForms form with an ElementHost to it that in turn hosted a UserControl with a TreeView, but that still worked as it should, so to me it really looks like the host COM application is in some way interfering with the events raised on the TreeView and TreeViewItems.

Luckily, my colleague found/googled a solution that will do for the time being - after you're done with the actual reaction to the event, at the end of the event handler method, again set the TreeViewItem as selected and focus it:

item.Focus();
item.IsSelected = true;

We don't understand why, but this will stop the items from being wrongfully re-selected (this is obviously not really a bubbling event in the WPF sense).

Thanks again to Rick for his nudge towards keeping TreeView and TreeViewItem events separate in my mind.

like image 68
TeaDrivenDev Avatar answered Nov 18 '22 11:11

TeaDrivenDev


Your question mystifies me because the SelectedItemChanged event is a TreeView event, not a TreeViewItem event. "Hey man, my event was nowhere near your event!"

When the selected item changes, the TreeView raises the SelectedItemChanged event on itself, the TreeView, and if unhandled it begins bubbling up towards the root element of the page.

Writing a small test program helps when you want proof.

Here's a small TreeView contained in a Grid:

<Grid TreeView.SelectedItemChanged="Grid_SelectedItemChanged">
    <TreeView SelectedItemChanged="TreeView_SelectedItemChanged">
        <TreeViewItem TreeView.SelectedItemChanged="TreeViewItem_SelectedItemChanged" Header="Item1">
            <TreeViewItem TreeView.SelectedItemChanged="TreeViewItem_SelectedItemChanged" Header="Item2">
                <TreeViewItem TreeView.SelectedItemChanged="TreeViewItem_SelectedItemChanged" Header="Item3"/>
            </TreeViewItem>
        </TreeViewItem>
    </TreeView>
</Grid>

and here's the code-behind for the test:

private void Grid_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    SelectedItemChanged(sender, e, "Grid");
}

private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    SelectedItemChanged(sender, e, "TreeView");
    e.Handled = true;
}

private void TreeViewItem_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    SelectedItemChanged(sender, e, "TreeViewItem");
}

private void SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e, string handler)
{
    Debug.WriteLine("");
    Debug.WriteLine(string.Format("SelectedItemChanged: handler = {0}", handler));
    Debug.WriteLine(string.Format("e.NewValue.Header = {0}", (e.NewValue as TreeViewItem).Header));
    Debug.WriteLine(string.Format("sender = {0}", sender));
    Debug.WriteLine(string.Format("e.Source = {0}", e.Source));
    Debug.WriteLine(string.Format("e.OriginalSource = {0}", e.OriginalSource));
}

and running it and clicking the first item produces this debug output:

SelectedItemChanged: handler = TreeView
e.NewValue.Header = Item1
sender = System.Windows.Controls.TreeView Items.Count:1
e.Source = System.Windows.Controls.TreeView Items.Count:1
e.OriginalSource = System.Windows.Controls.TreeView Items.Count:1

which shows that the event is raised on the TreeView itself and setting e.Handled to true prevents the Grid from receiving the event. Comment that line out and it does bubble up to the Grid.

But in no case does the TreeViewItem ever have the SelectedItemChanged handler called.

Try using small test programs when things are not behaving as you think they should. It's much easier to do experiments and get to the heart of the problem!

like image 37
Rick Sladkey Avatar answered Nov 18 '22 11:11

Rick Sladkey