0
votes

I have a TreeView with multiple HierarchicalDataTemplate & DataTemplate items and I'm using Caliburn Micro for mvvm. The ItemsSource for the treeview is pointing to a collection in the viewmodel called 'TreeData' and I tried adding a specific ContextMenu for each HierarchicalDataTemplate & DataTemplate.

In the ContextMenu I use the caliburn functionality "cal:Message.Attach" to call a function in the

I made a smaller example of the treeview to illustrate the problem.

In the ViewModel (the collection object):

public class MyViewModel
{
    // TreeData object
    public ObservableCollection<TestRoot> TreeData = new ObservableCollection<TestRoot>()
    {
        new TestRoot()
        {
            Name = "Root item"
        }
    };

    // the function on the viewmodel that should be called
    public void DoSomething(object item)
    {
        MessageBox.Show("MyViewModel - DoSomething called");
    }
}

The collection object:

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

    // caliburn calls this instead of the one on the viewmodel
    public void DoSomething(object item)
    {
        MessageBox.Show("TestRoot - DoSomething called");
    }
}

MyView.xaml treeview with only one (Hierarchical)DataTemplate:

<TreeView Margin="5" ItemsSource="{Binding TreeData}">
    <TreeView.Resources>
        <DataTemplate DataType="{x:Type vm:TestRoot}" >

            <StackPanel Orientation="Horizontal">
                <StackPanel.ContextMenu>
                    <ContextMenu>
                        <!-- caliburn (?) chooses the method on the collection object, not the viewmodel -->
                        <MenuItem Header="test dosomething" cal:Message.Attach="DoSomething($dataContext)"></MenuItem>
                    </ContextMenu>
                </StackPanel.ContextMenu>

                <TextBlock Text="{Binding Name}"/>
            </StackPanel>

        </DataTemplate>
    </TreeView.Resources>
</TreeView>

In another piece of code, I placed the ContextMenu in the TreeView.ContextMenu. There it worked as it 'should', pointing to the method on the viewmodel.

Looking around for a solution, I see things like "inheritance context". I think it might have something to do with it, but I'm not sure. How can I tell caliburn it must look in the viewmodel for my method, instead of the item in the TreeView I clicked on?

Or is there another possibility? For example: defining the different ContextMenus in the Resources and pointing them to the DataTemplates? But, wont that cause the exact same problem?

Please note that I'd like to keep the code-behind as minimal as possible. Thanks

update

For the completeness, here's the real development code. This should be right, no?

<TreeView ItemsSource="{Binding OrderTreeViewData.OrderTreeViewCategories}"
          cal:Message.Attach="[Event SelectedItemChanged] = [Action OnSelectedItemChanged($this)]">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <!-- We have to select the item which is right-clicked on -->
            <EventSetter Event="TreeViewItem.PreviewMouseRightButtonDown"
                            Handler="TreeViewItem_PreviewMouseRightButtonDown"/>
            <!-- set expanded -->
            <Setter Property="TreeViewItem.IsExpanded" Value="True"/>
        </Style>
    </TreeView.ItemContainerStyle>

    <TreeView.Resources>                   
        <!--  dredge  nodes -->
        <HierarchicalDataTemplate DataType="{x:Type programs:DredgeRoot}" 
                                    ItemsSource="{Binding Dredgezones}">
            <StackPanel>
                <StackPanel.ContextMenu>
                    <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
                        <MenuItem Header="Add dredge zone" cal:Message.Attach="TreeViewAddDredgeZone($datacontext)"></MenuItem>
                    </ContextMenu>
                </StackPanel.ContextMenu>

                <TextBlock Text="{Binding Name}"/>
            </StackPanel>
        </HierarchicalDataTemplate>

        <!-- omitted other templates -->

    </TreeView.Resources>
</TreeView>
2

2 Answers

0
votes

Unfortunately there is still one tricky part to deal with. Due to the specific Popup behavior it doesn't inherit DataContext. To access proper context you have to get the PlacementTarget:

  <StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}">
      <StackPanel.ContextMenu>
         <ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
               <MenuItem Header="test dosomething" cal:Message.Attach="DoSomething"/>
         </ContextMenu>
      </StackPanel.ContextMenu>
  </StackPanel> 
0
votes

I think you have the nearly same problem as me, maybe see this topic : Bind contextMenu to a different viewmodel from treeview

You can try to use a command : Try to change you code to :

<ContextMenu x:Key="MyContextMenu">
                    <MenuItem Header="Add dredge zone" Command="{Binding PlacementTarget.Tag.DataContext.TreeViewAddDredgeZoneCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" 
                          CommandParameter="{Binding}"></MenuItem>
                </ContextMenu>

Then add to your hierarchicalDataTemplate a Tag and ContextMenu

<HierarchicalDataTemplate DataType="{x:Type programs:DredgeRoot}" 
                                ItemsSource="{Binding Dredgezones}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}} ContextMenu="{StaticResource MyContextMenu}">

And in your viewmodel you can add your command with something like this :

public ICommand TreeViewAddDredgeZoneCommand
    {
        //your code here
    }