2

This problem relates mainly to context menus, but in my specific case it's about a TreeView control.

The TreeView item contains a StackPanel, and on that StackPanel is a ContextMenu property, which I have assigned to a StaticResource (which is a ContextMenu of course). Said ContextMenu leads to an ICommand and, thus, does its thing.

At present (and this is the default behaviour I believe), right clicking on an item in the TreeView does not select that item. This is common in Windows, but doesn't happen here. I would like it to happen (but I don't know how).

A little follow up information: I do have a selected item in the TreeView and this changes with a mouse left click. It's not a left click event, though, rather the event is 'SelectedItemChanged'. This leads to a method whereby I set the 'SelectedItem' in my data context (view model) to the SelectedItem. It has to be done this way because a TreeView's selected item is 'read only'.

That code is here, although I'm not sure how useful it is to the issue at hand:

private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        if (this.ScenesTreeView01 == null)
            return;

        if (this.ScenesTreeView01.DataContext == null)
            return;

        var DataContext = this.ScenesTreeView01.DataContext as ScenesViewModel;

        if (e.NewValue is SceneViewModel)
        {
            DataContext.SelectedScene = (SceneViewModel)e.NewValue;
        }

        if (e.NewValue is CharacterViewModel)
        {

            DataContext.SelectedCharacter = (CharacterViewModel)e.NewValue;
        }
    }

Since there doesn't seem to be a place where it says 'okay you left clicked and so here is the selected item', I don't know what to do to tell it to assign the selected item on a right click (as well as a left click).

How can I do this?

Edit: I am using MVVM so when we have a method like SelectedItemChanged with a parameter which is RoutedPropertyChangedEventArgs e, e.Source refers me back to my view model, not to a TreeViewItem.

3 Answers3

2

You could add an IsSelected property to your SceneViewModel and CharacterViewModel classes and bind the IsSelected of the TreeViewItem to these properties using a style. In the same style you could then hook up an event handler for the PreviewMouseRightButtonDown to set the source property:

<TreeView x:Name="treeView">
    <TreeView.Resources>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
            <EventSetter Event="PreviewMouseRightButtonDown" Handler="treeView_PreviewMouseRightButtonDown" />
            <Setter Property="ContextMenu">
                <Setter.Value>
                    <ContextMenu>
                        <MenuItem>1</MenuItem>
                    </ContextMenu>
                </Setter.Value>
            </Setter>
        </Style>
    </TreeView.Resources>
</TreeView>

private void treeView_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
    TreeViewItem tvi = sender as TreeViewItem;
    CharacterViewModel cvm = tvi.DataContext as CharacterViewModel;
    if (cvm != null)
    {
        cvm.IsSelected = true;
    }
    else
    {
        SceneViewModel svm = tvi.DataContext as SceneViewModel;
        if (svm != null)
            svm.IsSelected = true;
    }
}

Make sure that the CharacterViewModel and SceneViewModel classes implement the INotifyPropertyChanged interface and raise the PropertyChanged event in the setter of the new IsSelected property.

mm8
  • 163,881
  • 10
  • 57
  • 88
  • I thought I'd elaborate since I said this wasn't working for me in that other thread. The first part of your code I put into the existing TreeView.Resource tag. I also have, in the code behind, the method that you mention. It doesn't fire, but since the hook up is in XAML I don't know how to check what's going on to determine why it's not firing. Also I don't understand why you had a context menu in there. Is that needed since I already have some context menus and they work okay. – TheFaithfulLearner Mar 30 '17 at 17:41
  • Hey man, quick update since I've been on this for a long time now. It seems the reason it wasn't working for me was because I already had a style for that type in my XAML and didn't know that you can't just have another one. So I put in the parts of yours into the existing one I have and it worked (so thanks to you for that), except for some reason the method fires once in the case of the top level item ('scene') and twice if I click on a sub level item ('character'). So thanks for getting me this far – TheFaithfulLearner Mar 31 '17 at 18:24
  • Regarding the multiple calls issue: In this case. using `MouseRightButtonDown` might be better than `PreviewMouseRightButtonDown`, since former will travel down the tree, making the first incomming calls a false positive while the latter will make the first call on the correct `TreeViewItem`. Also set `e.Handled=true` in order to avoid the event traveling up the tree... – grek40 Apr 07 '17 at 15:40
0

A quick solution might be to simply register the TreeView's MouseRightButtonDown event, check if the click was on a TreeViewItem and select it:

TreeView.MouseRightButtonDown += Tv_MouseRightButtonDown;

void Tv_MouseRightButtonDown(object sender, MouseButtonEventArgs e) {
    var tvItem = e.Source as TreeViewItem;
    if (tvItem != null) {
        tvItem.IsSelected = true;
    }
}
wilford
  • 586
  • 1
  • 6
  • 17
  • Sorry. I added an edit to explain why this won't work for me. Since I'm using MVVM, e.Source refers me to some data object from my ViewModel, not a TreeViewItem. Should have included that in my question, so again my apologies for not being clear. – TheFaithfulLearner Mar 27 '17 at 12:24
  • Did you only verify this for the SelectedItemChanged event, or also for MouseRightButtonDown? I would have expected Source/OriginalSource to always point to the framework elements that actually raised the event. If you're not sure, a tool like Snoop or WPF Inspector can be useful to figure out where events originate and where they're handled. Anyway, if you use MVVM, it may be a cleaner approach to create a Style for TreeViewItem and bind its IsSelected property to something in your viewmodel. – wilford Mar 27 '17 at 12:47
  • I know this is a late response, but the problem was the same so I can't use this solution. Thanks for taking the time to answer though, – TheFaithfulLearner Mar 30 '17 at 11:28
0

You can use a behavior for it:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

public class SelectOnRMBBehavior : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty CurrentItemProperty = DependencyProperty.Register("CurrentItem", typeof(TreeViewItem), typeof(SelectOnRMBBehavior), new PropertyMetadata(null));

    public TreeViewItem CurrentItem
    {
        get
        {
            return (TreeViewItem)GetValue(CurrentItemProperty);
        }
        set
        {
            SetValue(CurrentItemProperty, value);
        }
    }

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

        AssociatedObject.PreviewMouseRightButtonDown += AssociatedObject_PreviewMouseRightButtonDown;
    }

    private void AssociatedObject_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
        if (CurrentItem!=null)
        {
            CurrentItem.IsSelected = true;
        }
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PreviewMouseRightButtonDown -= AssociatedObject_PreviewMouseRightButtonDown;

        base.OnDetaching();
    }
}

and then just put your behavior to the element which will be clicked(I suppose a container in DataTemplate):

<StackPanel>
    <i:Interaction.Behaviors>
        <b:SelectOnRMBBehavior CurrentItem="{Binding RelativeSource={RelativeSource AncestorType=TreeViewItem}}"/>
    </i:Interaction.Behaviors>
</StackPanel>
Rekshino
  • 6,954
  • 2
  • 19
  • 44