I have a UserControl with Button inside which opens a ContextMenu when left clicked. I'm trying to pass UserControl's parent Window as a parameter to ContextMenu item's command to close that window, but with no avail. I've tried everything with RelativeSource and PlacementTarget, but parameter is always null. I'm aware that ContextMenu is not part of parent window's VisualTree. I'm currently stuck with this approach, but it is not working.
<Grid x:Name="LayoutRoot">
<Button
HorizontalAlignment="Left"
Margin="0"
Style="{DynamicResource ButtonStyle1}"
VerticalAlignment="Top"
Width="120"
Height="25"
Content="Dashboard Menu"
TextElement.FontWeight="Bold"
Foreground="AliceBlue"
>
<!--Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={ x:Type Window}}}"-->
<Button.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}" >
<MenuItem Header="Open Log Viewer" Command="{StaticResource openLogViewer}" />
<Separator />
<MenuItem Header="Exit" Command="{StaticResource exit}" CommandParameter="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource AncestorType=Window}}"/>
</ContextMenu>
</Button.ContextMenu>
</Button>
</Grid>
Command is a Referenced command defined in UserControl.Resources:
<my:CommandReference x:Key="exit" Command="{Binding Exit}" />
and it's Execute part is triggered but parameter is always null. So, my question is, what is the right way to bind parent window as CommandParameter of MenuItem. Any help is appreciated, because this thing is bothering me for almost two days.
Right way here is to not pass parent Window to the VM as CommandParameter. If this is MVVM you should be using a Messenger(MVVM Light) / EventAggregator(Prism) approach to send a Message to the Window's code-behind when the command is triggered to Close it.
Referencing Window in the VM is just plain wrong.
Just for reference, what your trying to do "can be done"
something like:
<Grid x:Name="LayoutRoot">
<Button HorizontalAlignment="Left"
Margin="0"
Style="{DynamicResource ButtonStyle1}"
VerticalAlignment="Top"
Width="120"
Height="25"
Content="Dashboard Menu"
TextElement.FontWeight="Bold"
Foreground="AliceBlue"
Tag="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Open Log Viewer" Command="{StaticResource openLogViewer}" />
<Separator />
<MenuItem Command="{StaticResource exit}"
CommandParameter="{Binding PlacementTarget.Tag,
RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
Header="Exit" />
...
Update:
Download Link
When executing the "Exit" command from the ContextMenu you should see Sender Object: MvvmLight16.MainWindow in your Output Window. This output is sent from the VM.
Related
In the below piece of code, nested menu's get generated from an observable collection called CollectionOfAuthors. I've placed two commands, One at the 'ToplevelMenuItem' and another for submenu item (through textblock.InputBindings).
Though the command for submenuitem is working, I am unable to hit the command for the TopLevelMenuItem:
Need to understand what extra i need to do ?
<MenuItem x:Name="TopLevelMenuItem" Header="Authors" ItemsSource="{Binding CollectionOfAuthors}" Command="{Binding DataContext.RefreshAuthorsList, RelativeSource={RelativeSource AncestorType=Menu}}" >
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<TextBlock Text="{Binding AuthorName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding BookName}" >
<TextBlock.InputBindings>
<MouseBinding Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
Try with
<MenuItem x:Name="TopLevelMenuItem" Header="Authors" ItemsSource="{Binding CollectionOfAuthors}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SubmenuOpened">
<i:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
If you want to prevent from firing command from submenues you will need to pass {Binding .} to command, and if param is not type of your ViewModel, then don't fire your action.
Also, you need to add reference to System.Windows.Interactivity and
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" in your xaml header
I have TreeView with HierarchicalDataTemplate. On TreeView I have ContextMenu
<TreeView Name="_packageTreeView" ItemsSource="{Binding PackageExtendedList}" Behaviors:TreeViewInPlaceEditBehavior.IsEditable="True">
<TreeView.ContextMenu>
<ContextMenu StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">bla bla bla</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
As you can see, I bind Command to menu item. AddPackageCommand defined in ViewModell class as usually. Invoke command works fine, but I always have null in CommandParameter. I found some questions similar to my, but I don't understand solutions. For example:
CommandParameters in ContextMenu in WPF
Anyway it doesn't work for me :( What am I doing wrong?
Updated
That seems to be working, but it's all the same, I don't understand why CommandParameter doesn't work with TreeView.Name.
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
for examplle, such a sample works fine
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding PackageTreeItemChangeCommand}" CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"/>
</i:EventTrigger>
What's the hell...
And anyway, I have TreeView object in CommandParameter, not TreeViewItem. I can get SelectedItem from TreeView, but how can I send exactly TreeViewItem as CommandParameter?
to Sheridan
Question was WHY this doesn't work.
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}"
And this works
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
WHY sometimes I can use direct TreeView control name and sometimes I cannot.
As I understand, matter is different DataContext of TreeView control and ContextMenu because ContextMenu has its own VisualTree and it is not the part of TreeView ViaualTree.
Unfortunately, that approach doesn't work too, I have null again. I set TreeView.Tag, sure.
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={
RelativeSource Self}}" StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
This is the easiest way, but if I have SelectedItem property in ViewModel it has no sense bind it to CommandParameter, because I already have it in ViewModel.
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
You showed us that you already have an answer... why on earth did you post yet another question on this same subject instead of simply following the example in the answer? It doesn't work for you, because you didn't copy the answer properly.
In your example post answer, the Tag property is set to the TreeView control that the menu is applied on, but you haven't done this.
Your next problem is that you have ignored this Tag property again in the CommandParameter... somehow, you have changed this from the correct answer:
CommandParameter="{Binding PlacementTarget.Tag, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ContextMenu}}}
to this in your question:
CommandParameter="{Binding PlacementTarget, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ContextMenu}}}"
All you needed to do was copy and paste it. All the same, you might have even more luck doing this:
<TreeView Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}"
Name="_packageTreeView" ItemsSource="{Binding PackageExtendedList}"
Behaviors:TreeViewInPlaceEditBehavior.IsEditable="True">
<TreeView.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.Tag, RelativeSource={
RelativeSource Self}}" StaysOpen="true">
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding ElementName=_packageTreeView, Path=SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Childs}">bla bla bla</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Look at the TreeView.Tag property... this is set to its own DataContext, which means that whatever is set as the DataContext of the TreeView is now available in the Tag property object.
Next, look at the ContextMenu.DataContext property... this is set to the Tag property of the PlacementTarget, which is the control that the ContextMenu is applied to, or in this case, the Treeview.
If you haven't worked it out yet, this means that the DataContext of the ContextMenu is now set to the same object as the DataContext of the TreeView. If this is not what you want because your Command is on a different object, then just change the Binding path in the Tag property to point to wherever the object that had the Command is.
The last thing that you can do to make this simpler is to add a property to your view model/code behind that binds to the TreeView.SelectedItem property:
<TreeView SelectedItem="{Binding SelectedItem}"... />
Then you can simply refer to this property for your CommandParameter:
<MenuItem Header="Добавить пакет" Height="20" Command="{Binding AddPackageCommand}"
CommandParameter="{Binding SelectedItem}">
<MenuItem.Icon>
<Image Source="/Resources/ManualAdd.png" Width="15" Height="15"></Image>
</MenuItem.Icon>
</MenuItem>
This last part of course assumes that you have set your view model/code behind as the Tag property of the TreeView. If you still don't understand this, take a look at the Context Menus in WPF page on WPF Tutorial.NET.
UPDATE >>>
I simply don't understand why you posted this question. First you said you couldn't do something, but then provided us with a link to a valid solution in another post. After trying to help you, you then say that it did work, but you don't know why... but then you correctly answered your own question again:
As I understand, matter is different DataContext of TreeView control and ContextMenu because ContextMenu has its own VisualTree and it is not the part of TreeView ViaualTree.
As you said, the ContextMenu has its own visual tree. This means that it is not aware of controls, named or otherwise, in another visual tree. However, if the ContextMenu.DataContext is provided with an object such as the containing view, then it can be aware of controls in another visual tree (more specifically, the visual tree of the controls in the view).
This whole issue seems to be down to a lack of knowledge on your part about Binding in general and Binding.Path syntax more specifically. Please take a look at the following articles on MSDN for more help on this topic:
Binding.Path Property
Property Path Syntax
RelativeSource MarkupExtension
So many people try to run with WPF before they can walk.
I'm trying to bind a command to a menuitem in WPF. I'm using the same method that's been working for all my other command bindings, but I can't figure out why it doesn't work here.
I'm currently binding my commands like this:
Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.MyCommand}"
This is where it goes wrong (this is inside a UserControl)
<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}"
Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" CommandParameter="{Binding Name}"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.RemoveCommand}"/>
</ContextMenu>
</Button.ContextMenu>
...
The first command binding works like it should, but the second one refuses to do anything.
I've tried changing the ancestor level and naming my Control to access it through ElementName instead of RelativeSource, but still no change. It keeps saying "Cannot find source for binding with reference..."
What am I missing?
(Edit) Since you mentioned this is in an ItemsControl's template, things are different:
1) Get the BindingProxy class from this blog (and read the blog, as this is interesting information): How to bind to data when the DataContext is not inherited.
Basically the elements in the ItemsControl (or ContextMenu) are not part of the visual or logical tree, and therefore cannot find the DataContext of your UserControl. My apologies for not writing more on this here, but the author has done a good job explaining it step by step, so there's no way I could give a complete explanation in just a few lines.
2) Do something like this: (you may have to adapt it a bit to make it work in your control):
a. This will give you access to the UserControl DataContext using a StaticResource:
<UserControl.Resources>
<BindingProxy
x:Key="DataContextProxy"
Data="{Binding}" />
</UserControl.Resources>
b. This uses the DataContextProxy defined in (a):
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove" CommandParameter="{Binding Name}"
Command="{Binding Path=Data.RemoveCommand, Source={StaticResource DataContextProxy}}"/>
</ContextMenu>
This has worked for us in things like trees and datagrids.
ContextMenu is in different logical tree, that's why RelativeSource doesnt work. But context menu inherit DataContext from its "container", in this case it is Button. It is enough in common case but in your case you need two "data contexts", of ItemsControl item and of ItemsControl itself.
I think you have no other choice but combine your view models into one, implement custom class to be used as ItemsControl item data context and contain both "Name" and "Remove command" or your item's view model can define RemoveCommand "proxy", that would call parent command internally
EDIT:
I slightly changed Baboon's code, it must work this way:
<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}"
Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
CommandParameter="{Binding Name}"
Command="{Binding Path=PlacementTarget.Tag.DataContext.RemoveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
koshdim is spot on, it works like a charm!! Thanks Koshdim
I modified his code to fit in my context menu
<DataGrid
AutoGenerateColumns="False"
HeadersVisibility="Column"
Name="dgLosses"
SelectedItem="{Binding SelectedItem, Mode= TwoWay}"
AllowDrop="True"
ItemsSource="{Binding Losses}"
Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}">
<DataGrid.ContextMenu >
<ContextMenu >
<MenuItem Header="Move to Top " Command="{Binding PlacementTarget.Tag.MoveToTopCommand,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
<MenuItem Header="Move to Period 1" Command="{Binding PlacementTarget.Tag.MoveToPeriod1Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
<MenuItem Header="Move to Period 2" Command="{Binding PlacementTarget.Tag.MoveToPeriod2Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
<MenuItem Header="Move to Period 3" Command="{Binding PlacementTarget.Tag.MoveToPeriod3Command,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" ></MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
That's a tricky issue, sure marginally you will find a quick workaround, but here is a no-magic-solution:
<Button Height="40" Margin="0,2,0,0" CommandParameter="{Binding Name}"
Tag={Binding}
Command = "{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ConnectCommand}">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="Remove"
CommandParameter="{Binding Path=PlacementTarget.Tag.Name, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
Command="{Binding Path=PlacementTarget.Tag.RemoveCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"/>
</ContextMenu>
</Button.ContextMenu>
...
It boils down to using the Tag of the PlacementTarget (the Button here).
I created a WPF application and am following MVVM Pattern. I have a context menu in my xaml and I need to bind the command and Header text. Using the following code I can bind the Header of the context menu with the "MenuItemName" which is a property in BOList which is an observable collection. My issue is that command is not getting fired? I changes the Item source of the Context Menu to datacontext
(DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}")
Command is working fine but my header is getting blank. Is there a way to bind my header and command of a menu item? Here the command MenuClick is a Icommand property in the VM and MenuItemName is a property inside BOList which is an observable collection binded to my ListBox.
<Grid>
<StackPanel Orientation="Vertical">
<Button x:Name="btnClickMe" Command="{Binding ButtonCommand}" Content="Click Me" />
<ListBox ItemsSource="{Binding BOList}" x:Name="lstDemo" SelectedItem="{Binding BussinessObj,Mode=OneWayToSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="stkStyleRender" Orientation="Horizontal" Background="Cyan" Width="525" Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" >
<TextBlock x:Name="txtId" Text="{Binding FirstName}"></TextBlock>
<TextBlock x:Name="txtName" Text="{Binding LastName}"></TextBlock>
<StackPanel.ContextMenu>
<ContextMenu x:Name="cntMnuTest" ItemsSource ="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" >
<MenuItem Header="{Binding MenuItemName}" Command="{Binding MenuClick}" CommandParameter="Icon"></MenuItem>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Hi Identified the issue.
If we set Item Source of the context menu to BOList (observable collection ) command wont get fired because the ICommand definition is in Window data context (vm).
We need to handle the code like wise.
Since debugging is not possible for binding , I was beating around the bush :-)
This link helped me a lot WPF Tutorial - Debug Databinding Issues in WPF
In context menu use DataContext instead of using Items source
then bind your menu item
try this:
<MenuItem Header="{Binding Path=PlacementTarget.MenuItemName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}" Command="{Binding Path=PlacementTarget.MenuClick, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}" />
I want to bind a command in my ViewModel to a menuItem which is in DataTemplate. I can do that with using Tag. Is there any method which can do the same task but without using tag.
<Window.Resources>
<DataTemplate x:Key="StudentListBoxItemTemplate">
<StackPanel Tag="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}">
<TextBlock Text="{Binding Name}"/>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Trigger" Command="{Binding PlacementTarget.Tag.TriggerCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox
ItemsSource="{Binding StudentList}"
ItemTemplate="{StaticResource StudentListBoxItemTemplate}">
</ListBox>
</StackPanel>
My ViewModel
public class MainViewModel {
public ICommand TriggerCommand { ... }
public ObservableList<Student> StudentList { ... }
}
With your current design, you need to get from the ContextMenu through the StackPanel and back to the DataContext of the containing ListBox. What makes this awkward is that the DataContext of the StackPanel is already narrowed down to a particular student.
There are at least two ways to make this easier:
Provide a TriggerCommand property in Student so the command is right there where you need it
Provide a Parent property in the Student to escape the narrowed scope
You can try to add click event to the menuItem like following
<Menu Style="{StaticResource bellRingersFontStyle}" Height="23" Name="menu1" Width="Auto" DockPanel.Dock="Top" VerticalAlignment="Top">
<MenuItem Header="_File">
<MenuItem Header="_New Member" Name="newMember" Click="newMember_Click" >
<MenuItem.Icon>
<Image Source="Face.bmp" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Save Member Details" Name="saveMember" IsEnabled="False" Click="saveMember_Click">
<MenuItem.Icon>
<Image Source="Note.bmp" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="E_xit" Name="exit" Click="exit_Click" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About Middleshire Bell Ringers" Name="about" Click="about_Click" >
<MenuItem.Icon>
<Image Source="Ring.bmp" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
Try binding the command to Click. My VS is down so can't check at this moment.
One way would be to get the context menu collection presentation defined in your viewmodel, which will contain the header string and command action (maybe with predicate).
The viewmodel creates an observable collection of the contextmenu items and the view binds that to ContextMenu itemssource and sets the displaymember path to header string.