Question

I have a TreeView control with 2-level hierarchy. If an item of the second level is selected and a user clicks on another item, I need to ask him whether he is sure to move to another item. If his answer is 'no', I need to prevent another TreeViewItem from selection.

I try this way:

<TreeView.Resources>
    <Style TargetType="{x:Type TreeViewItem}">
        <EventSetter Event="PreviewKeyDown" Handler="TreeViewItem_OnKeyDown" />
    </Style>
</TreeView.Resources>


private void TreeViewItem_OnMouseDown(object sender, MouseButtonEventArgs e)
{
    var selectedTreeViewItem = sender as TreeViewItem;
    if (selectedTreeViewItem != null)
    {
        var myData = selectedTreeViewItem.Header as MyData;
        if (myData != null && selectedNode != null)
        {
            if (!selectedNode.DoYouAgreeToMoveToAnotherItem())
            {
                e.Handled = true;
            }
            else
            {
                myTreeView.Focus();
                myData.IsNodeSelected = true;
            }
        }
    }
}

In a way that works. However, the problem is that I get OnMouseDown event twice: for the first-level item and for the second level item. For example, for this tree: Russia - Moscow - Piter USA - New-York - Boston If I click Boston, I get first event for the USA and then for Boston. So, I can't distinguish the cases:

  1. when user clicks on USA
  2. when user click on Boston and I just get the first part of tunneling event

In TreeViewItem_OnMouseDown I need to know the TreeViewItem which user clicked on.

Could you advice me, how I can determine the TreeViewItem which user clicked on in TreeViewItem_OnMouseDown? Again, if I just check a sender. It maybe USA, but actually user clicked on Boston. So I need to realize that it was Boston.

Was it helpful?

Solution

Well this is just a rough example but you could potentially handle it something like this from code behind.

XAML

<TreeView ...>
    <TreeView.Resources>
        <Style TargetType="{x:Type TreeViewItem}">
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="treeViewItem_PreviewMouseLeftButtonDown" />
        </Style>
    </TreeView.Resources>
    ...
</TreeView>

Some Helper Methods

public static T GetFirstAncestorOfType<T>(DependencyObject source) where T : class
{
    while (source != null && !(source is T))
        source = VisualTreeHelper.GetParent(source);
    return source as T;
}

public static MessageBoxResult DoYouAgreeToMoveToAnotherItem()
{
    return MessageBox.Show("Select a different item?", "Select?", MessageBoxButton.YesNo);
}

Code Behind

private TreeViewItem _selectedItem = null;

void treeViewItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    TreeViewItem tvi = GetFirstAncestorOfType<TreeViewItem>(e.OriginalSource as DependencyObject);

    if (_selectedItem != null &&
        tvi != null &&
        tvi != _selectedItem &&
        MessageBoxResult.Yes != DoYouAgreeToMoveToAnotherItem())
    {
        e.Handled = true;
    }
    else
    {
        // Update _selectedItem for comparison the next time this method fires.
        _selectedItem = tvi;
        if(_selectedItem != null)
            _selectedItem.IsSelected = true;
    }
}

You could also likely accomplish this with pure MVVM. I don't have time to post an example using TreeView but the solution might involve using Dispatcher something like the solutions offered here...

WPF: Cancel a user selection in a databound ListBox?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top