How to set CommandTarget for MenuItem inside a ContextMenu? - wpf

(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>

Related

Registering a Region within an ItemTemplate

I'm writing a WPF application with Prism and I'm using MVVM,
Now, I have a view with Items Control, and I want each item to have a certain look, with an option to add a unique context menu per item.
so it look's like this so far:
<ItemsControl Grid.Column="1" ItemsSource="{Binding DeviceHolders}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Image Name="image" Source="{Binding ImageIndex, Converter={StaticResource ImageIndexToLargeImageBitmapSource}}" Margin="5" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image.ContextMenu>
<ContextMenu <--THIS AS A UNIQUE REGION WHICH THE REGION NAME WILL BE string.format("{0}-{1}", "DeviceHolderRegion", DeviceHolder.ID)-->/>
</Image.ContextMenu>
</Image>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
As I wrote in the code, I want the context menu to register as a region with a unique name (for each device holder object I want to add a different context menu based on his type).
is there a way to do so?
Thanks.
I didn't know you could use a ContextMenu as a region - you learn something new every day!
Anyway, try the following:-
<ContextMenu>
<regions:RegionManager.RegionName>
<MultiBinding StringFormat="{}{0}-{1}">
<Binding Path="DeviceHolderRegion" />
<Binding Path="DeviceHolder.ID" />
</MultiBinding>
</regions:RegionManager.RegionName>
</ContextMenu>
I haven't tried running it, but VS XAML editor doesn't complain about the syntax, which is a promising start.

Caliburn Micro with Treeview Context Menu

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)" />

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.

UI object, on samelevel of XAML Tree, as CommandParameter

I have an XAML tree as follows:
<Window>
<Grid>
<DockPanel>
<DataGrid>
<DataGrid.Resources>
<CheckBox Command="{Binding Command}" CommandParameter="??" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<DataChart>
</DataChart>
</stackPanel>
</DockPanel>
</Grid>
</Window>
I want to have DataChart object as CommandParameter on ViewModel from a Command on DataGrid.
My Findings:
I'm getting DockPanel object as CommandParameter, then I have to apply method FindName("") to get the DataChart. And do further modifications.
But I want the DataChart object directly, to avoid TypeCasting or searching down the Tree.
You can keep datachart as a named resource in your DockPanel resources and use static resource binding to command parameter. Then use ContentControl to host it.
like this...
<DockPanel>
<DockPanel.Resources>
<DataChart x:Key="MyDataChart">
</DataChart>
</DockPanel.Resources>
<DataGrid>
<DataGrid.Resources>
<CheckBox
Command="{Binding Command}"
CommandParameter="{StaticResource MyDataChart}" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<ContentControl Content="{StaticResource MyDataChart}"/>
</stackPanel>
</DockPanel>
Hoping that you wont use same MyDataChart to host to another area (as that would result in "visual tree parent disconnect" error).
Although I must ask you this... why is there a lonely CheckBox in your DataGrid resources?
Also your's and mine solution breaks MVVM because we are supplying a UI control (Chart control) to a View Model.

UserControl's DataContext

I'm creating a UserControl I want to use something like this:
<controls:ColorWithText Color="Red" Text="Red color" />
So far, I've implemented similar controls like this:
<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
<StackPanel Orientation="Horizontal" >
<Border Width="15" Height="15" Background="{Binding Color, ElementName=ThisControl}" />
<TextBlock Text="{Binding Text, ElementName=ThisControl}" />
</StackPanel>
</UserControl>
where Color and Text are dependency properties of the control defined in code. This works, but specifying ElementName every time seems unnecessary.
Another option that works is using
<UserControl x:Class=… DataContext="{Binding ElementName=ThisControl}" Name="ThisControl">
and not specifying ElementNames, but that doesn't seem like a clean solution to me either.
I have two questions:
Why doesn't <UserControl DataContext="{RelativeSource Self}"> work?
What is the best way to do something like this?
For first one, try :
<UserControl DataContext="{Binding RelativeSource={RelativeSource Self}}">
And for second question, I think using ElementName or AncestorBinding is best way to bind to UserControl's properties.
Why can't you use <UserControl DataContext="{RelativeSource Self}">?
This is how you would use the control
<Grid DataContext="{StaticResource ViewModel}">
<!-- Here we'd expect this control to be bound to -->
<!-- ColorToUse on our ViewModel resource -->
<controls:ColorWithText Color="{Binding ColorToUse}" />
</Grid>
Now because we've hardcoded our data-context in the control it will instead attempt to lookup ColorToUse property on the ColorWithText object not your ViewModel, which will obviously fail.
This is why you can't set the DataContext on the user control. Thanks to Brandur for making me understand that.
What is the best way to do something like this?
Instead you should set the DataContext in the first child UI element in your control.
In your case you want
<StackPanel
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
Orientation="Horizontal" >
Now you have a DataContext which refers to your control so you can access any properties of that control using relative bindings.
I know this has been answered but none of the explanations give an Understanding of DataContext and how it works. This link does a great job for that.
EVERYTHING YOU WANTED TO KNOW ABOUT DATABINDING IN WPF, SILVERLIGHT AND WP7 (PART TWO)
In answer to your question #1
Why doesn't <UserControl DataContext="{RelativeSource Self}"> work?
This is a summary of the above link.
DataContext should not be set to Self at UserControl Element level. This is because it breaks the Inheritance of the DataContext. If you do set it to self and you place this control on a Window or another control, it will not inherit the Windows DataContext.
DataContext is inherited to all lower Elements of the XAML and to all the XAML of UserControls unless it is overwritten somewhere. By setting the UserControl DataContext to itself, this overwrites the DataContext and breaks Inheritance. Instead, nest it one Element deep in the XAML, in your case, the StackPanel. Put the DataContext binding here and bind it to the UserControl. This preserves the Inheritance.
See also this link below for a detailed explanation of this.
A SIMPLE PATTERN FOR CREATING RE-USEABLE USERCONTROLS IN WPF / SILVERLIGHT
In answer to your question #2
What is the best way to do something like this?
See code example below.
<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
<StackPanel Orientation="Horizontal" DataContext="{Binding ElementName=ThisControl}">
<Border Width="15" Height="15" Background="{Binding Color" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</UserControl>
Note that once you do this, you will not need the ElementName on each binding.
You should be using
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=Color}
for Databinding Related doubts always refer this sheet.
http://www.nbdtech.com/Blog/archive/2009/02/02/wpf-xaml-data-binding-cheat-sheet.aspx
You can set the datacontext to self at the constructor itself.
public ColorWithText()
{
InitializeComponent();
DataContext = this;
}
Now you can simply say
<UserControl x:Class="Namespace.ColorWithText" Name="ThisControl">
<StackPanel Orientation="Horizontal" >
<Border Width="15" Height="15" Background="{Binding Color}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</UserControl>
For the desperate souls, who are trying to make pdross's answer work and can't:
It's missing an essential detail - Path=DataContext.
The lower code segment starts working when you add it there with this being the result:
<StackPanel DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext}">

Resources