Caliburn Micro with Treeview Context Menu - wpf

I have my hierarchical treeview binding wonderfully to my ViewModel using Caliburn Micro. (The ViewModel has an Items property that returns an ObservableCollection - the treeview is named to this Items property - nothing wrong with the binding).
However the issue comes up with the context menu. The menu fires a method on an instance of the object that the treenode represents. What I rather want to achieve, is to have the menu fire a method on my root ViewModel, passing to it as a parameter the instance of the object represented by the clicked treenode.
Here is my XAML:
<HierarchicalDataTemplate DataType="{x:Type m:TaskGrouping}"
ItemsSource="{Binding Children}">
<Label Content="{Binding Name}"
FontWeight="Bold">
<Label.ContextMenu>
<ContextMenu>
<MenuItem Header="Add New SubFolder"
cal:Message.Attach="AddNewSubfolder" />
<MenuItem Header="Remove this folder"
cal:Message.Attach="RemoveFolder" />
</ContextMenu>
</Label.ContextMenu>
</Label>
</HierarchicalDataTemplate>
What changes do I need to make to my XAML in order to achieve what I want?

ContextMenus are located in a separate visual tree from everything else - it can be a pain to get the bindings right (I often have 10-15 minutes of fighting the bindings on them to get them right!)
You've got your Message.Attach attached property set, all you need to do is ensure that the action target is pointing to the VM rather than the data item. You can use Action.TargetWithoutContext to specify the target for actions (CM will otherwise use DataContext)
You will also need to get a binding path which points to the other visual tree - try using RelativeSource bindings - the ContextMenu also has a property called PlacementTarget which should point to the element that the ContextMenu is attached to
So possibly:
cal:Action.TargetWithoutContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=Label}}"
or
cal:Action.TargetWithoutContext="{Binding PlacementTarget.DataContext}"
You might have to experiment as I often get this almost right first time!
EDIT by OP(Shawn):
This is what worked for me eventually:
<Label Content="{Binding Name}"
Tag="{Binding DataContext, ElementName=LayoutRoot}">
<Label.ContextMenu>
<ContextMenu
cal:Action.TargetWithoutContext="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Run Task Now" cal:Message.Attach="SomeRootViewModelMethod($dataContext)" />

Related

How to handle context menu commands on items in TreeView

I am implementing an explorer-type view: on the left is a TreeView and on the right is a details view / editor view of the item selected on the left.
The TreeView contains different types of 'leaf' objects. I have defined HierarchialDataTemplates to define how the objects are presented and which context menu should be presented. The MenuItems have Commands which are bound to the view-model.
What I am struggling with is where to implement the Command handlers. The straight-forward place would be in the 'leaf' view-models but they don't know anything about the 'explorer' view-model and how to open the details/editor view.
The simplest solution would be to bind the leaf context menu to the explorer view-model as this is where the action is best executed.
How is this normally handled? Are there any 'well-known' patterns?
Just found the answer to this question - Bind to parent DataContext within DataTemplate - which put me on the right track.
The UserControl get a name and the command binding references this - Source={x:Reference uc}}.
<UserControl x:Class="View.AdminWorkstationView" Name="uc">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:RequiredDeviceViewModel}">
<DockPanel>
<DockPanel.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding DataContext.EditDeviceCommand, Source={x:Reference uc}}"
CommandParameter="{Binding}"
Header="Edit device" />
</ContextMenu>
</DockPanel.ContextMenu>
</DockPanel>
</DataTemplate>
</UserControl.Resources>

Access my Window's DataContext inside a DataTemplate

I'm working on a WPF application and I'm using the MVVM pattern. I use MVVMLight to help me handle some Events. I need to forward the "Click" event so that I can pass the arguments as well so that I can know for sure which item that sent the event. If I use the "Command" I cant know for sure that it was the selected item that sent the event - as the item doesnt need to be selected to right click on it.
This is my code for displaying a list of "order lines". There are two types of order lines, and for one of the data types; "AccessoryOrderLine" - I want to add a context menu.
My problem is that I cannot access my Window's DataContext. I've named the root node in the Window "root", and I'm trying to access the root's DataContext, but this failes with the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with
reference 'ElementName=root'.
BindingExpression:Path=DataContext.PackAccessory; DataItem=null;
target element is 'EventToCommand' (HashCode=5903270); target property
is 'Command' (type 'ICommand')
<ListBox HorizontalContentAlignment="Stretch" Margin="10,0,10,10" DockPanel.Dock="Bottom" Grid.Row="1" ItemsSource="{Binding OrderLines, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type m:UnitOrderLine}">
<v:OrderLine />
</DataTemplate>
<DataTemplate DataType="{x:Type m:AccessoryOrderLine}">
<v:OrderLine>
<v:OrderLine.ContextMenu>
<ContextMenu>
<MenuItem Header="Pack 1" IsCheckable="False">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding ElementName=root, Path=DataContext.PackAccessory }" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
</v:OrderLine.ContextMenu>
</v:OrderLine>
</DataTemplate>
</ListBox.Resources>
</ListBox>
I've also tried to use "TemplatedParent" and then I get access to my "OrderLine" DataContext, but I cant get one step further back to my "MainWindowModel".
http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
Found a solution to my problem :)
Found a solution. Updated my original post with the link to my solution.
Its not issue with the DataTemplate. Binding with ElemetName works in all cases except in the case of ContextMenu since it does not lies in the same visual tree as of your window. However, there is one hack where you can use the PlacementTarget property of your context menu.
For details refer to this link - http://social.msdn.microsoft.com/Forums/nl/wpf/thread/526ab350-8788-4bc6-a98a-1e4dee6ad33a
It contains exactly what you are trying to achieve here.
Seems like here are answers for your question:
ElementName Binding from MenuItem in
ContextMenu
WPF MenuItem.Command binding to
ElementName..

Binding to two different DataContexts in a ContextMenu

I'm trying to bind to a property of a container from inside a DataTemplate. A simplified version of my markup looks like:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type myCustomItem}">
<!--Visual stuff-->
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Item"
Command="{Binding myCustomItemsICommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CustomContainerType}}, Path=ContainerProperty}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<CustomContainerType/>
</Grid>
My approach is based on this post but it doesn't seem to be working. The issue seems to arise from the placement of the ContextMenu within the visual tree. Basically I am trying to bind the Command to the DataContext of the DataTemplate but bind the CommandParameter to a DataContext outside the DataTemplate.
ContextMenus are not in the same visual tree as the rest of the controls, there are a few questions regarding how to do bindings accross that boundary but this might be somewhat difficult without specifying names.
ElementName fails as well because of the lacking tree connection, but you could use x:Reference in the Binding.Source instead.

WPF Treeview contextmenu IsChecked binding MVVM

I've got a TreeView to which I associate a ContextMenu. That contextmenu has an item whose IsChecked property I want to bind to my ViewModel. Since I am using a tree each treeitem is bound to a subproperty of my ViewModel.
In the VS2010 output window I am seeing this databinding error:
BindingExpression path error: 'IsAutoStart' property not found on 'object' ''HostMgmtViewModel' (HashCode=12565727)'. BindingExpression:Path=IsAutoStart; DataItem='HostMgmtViewModel'
This clearly shows it is trying to bind to my ViewModel and not to the treeitem's associated data. How do I bind to the correct object? Remember my contextmenu is associated with the whole TreeView not to the specific treeitem.
---------- Edit
As xandy pointed out below the resolution to my problem was to bind the IsChecked like this:
{Binding Path=PlacementTarget.SelectedItem.IsDisabledStart, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}
<TreeView Name="tview" Grid.Row="0" Tag="{Binding RelativeSource={RelativeSource Self}, Path=SelectedItem}">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Name="miC" Header="{Binding Path=Tag.Key}" DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"></MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
</TreeView>
This is the working code snippet I have. Courtesy of [this].1 All you need is to change the binding path in the tag. I am currently binding the Treeview to a dictionary, so it is the Key property of it. It should not have any problem in binding to any object collections. One interesting finding is context menu is not in part of element tree and this cause the problem. I could bind the text box with no problem:
<TextBlock Grid.Row="1" DataContext="{Binding ElementName=tview, Path=SelectedItem}">
<TextBlock.Text>
<Binding Path="Key" />
</TextBlock.Text>
</TextBlock>
But it is not functioning if for menuitem if I put the same thing.

How to set CommandTarget for MenuItem inside a ContextMenu?

(This question is related to another one, but different enough that I think it warrants placement here.)
Here's a (heavily snipped) Window:
<Window x:Class="Gmd.TimeTracker2.TimeTrackerMainForm"
xmlns:local="clr-namespace:Gmd.TimeTracker2"
xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
x:Name="This"
DataContext="{Binding ElementName=This}">
<Window.CommandBindings>
<CommandBinding Command="localcommands:TaskCommands.ViewTaskProperties"
Executed="HandleViewTaskProperties"
CanExecute="CanViewTaskPropertiesExecute" />
</Window.CommandBindings>
<DockPanel>
<!-- snip stuff -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- snip more stuff -->
<Button Content="_Create a new task" Grid.Row="1" x:Name="btnAddTask" Click="HandleNewTaskClick" />
</Grid>
</DockPanel>
</Window>
and here's a (heavily snipped) UserControl:
<UserControl x:Class="Gmd.TimeTracker2.TaskStopwatchControl"
xmlns:local="clr-namespace:Gmd.TimeTracker2"
xmlns:localcommands="clr-namespace:Gmd.TimeTracker2.Commands"
x:Name="This"
DataContext="{Binding ElementName=This}">
<UserControl.ContextMenu>
<ContextMenu>
<MenuItem x:Name="mnuProperties" Header="_Properties" Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="What goes here?" />
</ContextMenu>
</UserControl.ContextMenu>
<StackPanel>
<TextBlock MaxWidth="100" Text="{Binding Task.TaskName, Mode=TwoWay}" TextWrapping="WrapWithOverflow" TextAlignment="Center" />
<TextBlock Text="{Binding Path=ElapsedTime}" TextAlignment="Center" />
<Button Content="{Binding Path=IsRunning, Converter={StaticResource boolToString}, ConverterParameter='Stop Start'}" Click="HandleStartStopClicked" />
</StackPanel>
</UserControl>
Through various techniques, a UserControl can be dynamically added to the Window. Perhaps via the Button in the window. Perhaps, more problematically, from a persistent backing store when the application is started.
As can be seen from the xaml, I've decided that it makes sense for me to try to use Commands as a way to handle various operations that the user can perform with Tasks. I'm doing this with the eventual goal of factoring all command logic into a more formally-defined Controller layer, but I'm trying to refactor one step at a time.
The problem that I'm encountering is related to the interaction between the command in the UserControl's ContextMenu and the command's CanExecute, defined in the Window. When the application first starts and the saved Tasks are restored into TaskStopwatches on the Window, no actual UI elements are selected. If I then immediately r-click a UserControl in the Window in an attempt to execute the ViewTaskProperties command, the CanExecute handler never runs and the menu item remains disabled. If I then click some UI element (e.g., the button) just to give something focus, the CanExecute handlers are run with the CanExecuteRoutedEventArgs's Source property set to the UI element that has the focus.
In some respect, this behavior seems to be known-- I've learned that menus will route the event through the element that last had focus to avoid always sending the event from the menu item. What I think I would like, though, is for the source of the event to be the control itself, or the Task that the control is wrapping itself around (but Task isn't an Element, so I don't think it can be a source).
I thought that maybe I was missing the CommandTarget property on the MenuItem in the UserControl, and my first thought was that I wanted the command to come from the UserControl, so naturally I first tried:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding ElementName=This}" />
This failed as an invalid binding. I'm not sure why. Then I thought, "Hmmm, I'm looking up the tree, so maybe what I need is a RelativeSource" and I tried this:
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TaskStopwatchControl}}}" />
That also failed, but when I looked at my xaml again, I realized that the ContextMenu is in a property of the UserControl, it's not a child element. So I guessed (and at this point it was a guess):
<MenuItem x:Name="mnuProperties"
Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding RelativeSource={x:Static RelativeSource.Self}}" />
And that also failed.
One failed guess-and-check like this is enough to make me back off and realize that I'm missing some sort of fundamental concept here, though. So what do I do?
Is my understanding re: the role of CommandTarget correct in that this provides a mechanism to modify the source of a command?
How do I bind from a MenuItem in UserControl.ContextMenu to the owning UserControl? Or am I doing something wrong simply because I perceive a need to?
Is my desire to have the context of a command set by the element that was clicked to generate the context menu, as opposed to the element that had focus before the context menu, incorrect? Perhaps I need to write my own command instead of using the RoutedUICommand:
private static RoutedUICommand viewTaskPropertiesCommand = new RoutedUICommand("View a task's details.", "ViewTaskProperties", typeof(TaskCommands));
public static RoutedUICommand ViewTaskProperties
{
get { return viewTaskPropertiesCommand; }
}
Is there some deeper fundamental flaw in my design? This is my first significant WPF project, and I'm doing it on my own time as a learning experience, so I'm definitely not opposed to learning a superior solution architecture.
1: Yes, CommandTarget controls where the RoutedCommand starts routing from.
2: ContextMenu has a PlacementTarget property that will allow access to your UserControl:
<MenuItem x:Name="mnuProperties" Header="_Properties"
Command="{x:Static localcommands:TaskCommands.ViewTaskProperties}"
CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ContextMenu}}}"/>
To avoid repeating this in every MenuItem you could use a Style.
3 & 4: I would say your desire is reasonable. Since the Execute handler is on the Window it doesn't matter right now, but if you had different regions of the application, each with their own Execute handler for the same command, it would matter where the focus was.
Similar solution I found was using the Tag property of the parent to grab the datacontext:
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem
Header="{Binding Path=ToolbarDelete, Mode=Default, Source={StaticResource Resx}}"
Command="{Binding RemoveCommand}"
CommandParameter="{Binding DataContext.Id, RelativeSource={RelativeSource TemplatedParent}}"/>
</ContextMenu>
</Grid.ContextMenu>
<TextBlock Text="{Binding Name}" Padding="2" />
</Grid>

Resources