Bind Items to MenuItem -> use Command - c#

I have a MenuItem, which has a collection of items in it. It looks like the File -> Open Menuitem.
So:
File
Open
Open from DataBase
File 1
File 2
File 3
XAML Code:
<Menu>
<MenuItem Header="File">
<MenuItem Header="Open">
<MenuItem Header="From Database" ItemsSource="{Binding OCFragebogen}"/>
</MenuItem>
</MenuItem>
</Menu>
I want to call a Command, when a specific item has been clicked. Example: User clicks on File 1, a command should be called where the "File 1" is the Command Parameter.
ViewModel contains the Items, which I want to display in the MenuItem "collection"
private ObservableCollection<string> _OCFragebogen;
public ObservableCollection<string> OCFragebogen
{
get
{
if (_OCFragebogen == null)
_OCFragebogen = new ObservableCollection<string>();
return _OCFragebogen;
}
set
{
_OCFragebogen = value;
RaisePropertyChanged(() => OCFragebogen);
}
}
To make it clear: When the user clicks on an item (from the ItemsSource) in the MenuItem, a Command should be called where I want to do something with the clicked Item.
Edit: Where do I have to use the command to call a method (RelayCommand) in my ViewModel? I want it to be used when an Item from the ItemsSource has been clicked + I want to pass the clicked item to the method.

This should work for you
<MenuItem Header="From Database"
ItemsSource="{Binding YourItemSource}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}, Path=DataContext.YourCommandName}"></Setter>
<Setter Property="CommandParameter" Value="{Binding}"></Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Try to change its ItemContainerStyle and then bind the command from ItemsSource item,
<MenuItem Header="From Database" ItemsSource="{Binding OCFragebogen}"
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding YourCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
I haven't tried if it works just a guess
edited answer
<MenuItem Header="From Database" ItemsSource="{Binding OCFragebogen}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}, Path=DataContext.YourCommand }" />
<Setter Property="CommandParameter" Value="{Binding RelativeSource={RelativeSource Self}, Path=Header}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

Related

WPF DataGrid Remove Column

I have created a DataGrid in WPF from a DataTable with AutoGenerateColumns = true. In the VM I have a command property for adding and deleting columns which manipulates the underlying DataTable.
When I call AddColumn from the main XAML window via Command="{Binding AddColumn}" it works as expected but when I call the RemoveColumn from a resource file with a context menu it calls the command property (im able to step through the code) but does not update the Grid.
<MenuItem Header="Delete" Command="{Binding Source={StaticResource AppViewModel}, Path=DeleteColumn}" CommandParameter="{Binding}" />
I now updated both commands to only set the DataTable to null, ie Dt = null and for the AddColumn this works as expected and removed the Grid and columns but for the RemoveColumn it does nothing... I also see no errors in the output window in regard to binding and when stepping through the code the property is called. I also tried to set the column to invisible which also did not work.
UPDATE
I call the DeleteColumn from the following (simplified) code in a resources file.
<Style x:Key="ColumnHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Style.Triggers>
<DataTrigger Binding="{Binding Content, ConverterParameter=*, Converter={StaticResource AfterDashConverter}, RelativeSource={RelativeSource self}}" Value="Green">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete" Command="{Binding Source={StaticResource AppViewModel}, Path=DeleteColumn}" />
</ContextMenu>
</Grid.ContextMenu>
<DataGridColumnHeader x:Name="PART_FillerColumnHeader" IsHitTestVisible="False"/>
<ItemsPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
The commands are very simple, the AddColumn works as its directly in my main view and the DeleteColumn does not as its in the above resource file. I have checked the commands fire properly.
AddColumn = new RelayCommand(_ =>
{
Dt = null;
}, true);
DeleteColumn = new RelayCommand(column =>
{
Dt = null;
}, true);
the main issue was my resource was creating a new viewmodel so it was not calling / impacting the VM instance that my view was dependent on. To fix this I needed to set the DataContext of my ContextMenu parent control (which was a grid) as follows:
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MyType}}}
If you just call the following it will create a new instance of your ViewModel and use that:
<MenuItem Header="Delete" Command="{Binding Source={StaticResource AppViewModel}, Path=DeleteColumn}" />

How do I create a "Window" menu in XAML?

I am creating an MVVM Wpf client application. I want create menu in the main View for the application that his a menu item called "Window" on it. That menu item will dynamically update itself with a submenu of menuitems who are made up of the list of active windows running in the application. I created a ViewManager whom each View registers itself with to compile a list of active windows.
I am trying to do this in XAML but getting an error when I click on "Window"
<MenuItem Header="Window">
<ItemsControl ItemsSource="{Binding ViewMgr.Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd ,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</MenuItem>
How do I create a dynamically updated list of menuitems on my menu in XAML using a MVVM style of data bindings and commands?
You are adding a new ItemsControl as a single child of the menu item, instead of adding each view as one child of the menu item itself. You probably get the error because the styles TargetType doesn't match. MenuItem inherits from ItemsControl itself and exposes a property ItemsSource. Try the following:
<MenuItem ItemsSource="{Binding ViewMgr.Views}" DisplayMemberPath="Title">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.OpenWindowCmd, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

WPF: MenuItem icon in the wrong place when using data template

I have the following code:
<Menu>
<MenuItem ItemsSource="{Binding SomethingMenuItems}" Header="Something"/>
</Menu>
Where MenuItems is a collection of objects of type SomethingMenuItem.
I also have:
<DataTemplate DataType="{x:Type SomethingMenuItem}">
<MenuItem Header="{Binding OrderTypeName}">
<MenuItem.Icon>
<Image Source="{Binding IconName}"/>
</MenuItem.Icon>
</MenuItem>
</DataTemplate>
I'd expect to get (I get something like this when I hardcode the menu items):
What I get instead is:
What am I doing wrong?
You might want to use ItemContainerStyle instead of DataTemplate. You have to style the container of the data item than just providing a template for data item.
With your DataTemplate, you basically displaying another nested MenuItem as content for each MenuItem generated for your Menu Something, and your inner MenuItem has the image in the correct place. I am attaching VisualTree from Snoop here for your reference.
Below is the Style for the container of the data item (in this case a MenuItem):
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
<Style TargetType="MenuItem" >
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.Resources>
</MenuItem>
You can see no nested MenuItems when you apply the above style, have added image here
With the above style applied, this is how the Menu looks:
Refer to this MSDN page to know more about ItemContainerStyle.
So, sthotakura's answer set me on the right track, but the code he posted didn't quite work because the style got applied to the parent menuitem as well.
In case someone else has a similar problem and stumbles on this question, here's the code that works:
<MenuItem ItemsSource="{Binding SomethingMenuItems}"
Header="Something">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" >
<Style.Resources>
<Image Source="{Binding IconPath}" x:Key="IconImage" x:Shared="False"/>
</Style.Resources>
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{StaticResource IconImage}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>

WPF Submenu for context menu

If the Context menu is "hardcoded" in xaml, then it is easy to add submenus. For example:
<ContextMenu>
<MenuItem Header="Comm1" Command="{Binding Blabla1}">
<MenuItem Header="SubComm1" Command="{Binding Blabla2}"></MenuItem>
</MenuItem>
<MenuItem Command="Comm2"></MenuItem>
<MenuItem Command="Comm3"></MenuItem>
</ContextMenu>
This means, that the ContextMenu has three elements (Comm1, Comm2 and Comm3) and Comm1 has submenu SubComm1.
I have made my ContextMenu a bit more flexible:
<ContextMenu ItemsSource="{Binding ContextMenuItemsSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding ContextMenuCommandHeader}"></Setter>
<Setter Property="Command" Value="{Binding ContextMenuCommand}"></Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Basically I can have any number of elements in ContextMenu, and any element can have any Command. How can I add submenu to ContextMenu element?
You can set MenuItem.ItemsSource to the nested collection. This will generate the submenu for any menuitem. For this the model backing your MenuItem should have submenuitems collection in it
<ContextMenu ItemsSource="{Binding ContextMenuItemsSource}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="ItemsSource" Value="{Binding ContextMenuSubItems}"></Setter>
<Setter Property="Header" Value="{Binding ContextMenuCommandHeader}"></Setter>
<Setter Property="Command" Value="{Binding ContextMenuCommand}"></Setter>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
Similarly you can set the MenuItem.ItemContainerStyle and MenuItem.ItemTemplate to style your submenuitems.
Specifying explicitly, without binding a collection you can add sub menus by nesting them.
A MenuItem can have other MenuItem elements within it as child/sub menus and can go up to several levels. The following code adds three children menu items to first menu item.
<MenuItem Header="_File">
<MenuItem Header="_Open" IsCheckable="true" />
<MenuItem Header="_Close" IsCheckable="true" />
<MenuItem Header="_Save" IsCheckable="true" />
</MenuItem>
Source: https://www.c-sharpcorner.com/uploadfile/mahesh/menus-in-wpf/

"No target found for method" thrown by Caliburn Message.Attach()

I have a list box for which I am styling ItemContainer to include a context menu. Here is the xaml for the same.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
...
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
I have coded the target method in ViewModel as given below.
public void DeleteGroup() { //ToDo
...
}
The ViewModel is set as the DataContext of the UserControl in which there is the ListBox.
The above code results in "no target found for method". I am not sure why this doesn't work. I have also tried the following variation
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
cal:Action.Target="{Binding ElementName=UCRelayDispositionView, Path=DataContext}">
where UCRelayDispositionView is the name of the UserControl.
Why does the above code do not work?
Edit: 1
Also tried the following
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
cal:Action.TargetWithoutContext="{Binding ElementName=UCRelayDispositionView, Path=DataContext}">
and this
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup"
cal:Action.TargetWithoutContext="{Binding ElementName=UCRelayDispositionView}">
EDIT: 2
I have tried to use the Tag in the following way on ItemContainer but it doesn't work either.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Tag" Value="{Binding Path=DataContext, ElementName=UCRelayDispositionView}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Remove Group"
cal:Message.Attach="DeleteGroup()"
cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}"/>
</ContextMenu>
</Setter.Value>
</Style>
</ListBox.ItemContainerStyle>
EDIT 3: Binding Errors
System.Windows.Data Error: 40 : BindingExpression path error: 'PlacementTarget' property not found on 'object' ''MenuItem' (Name='')'. BindingExpression:Path=PlacementTarget.Tag; DataItem='MenuItem' (Name=''); target element is 'MenuItem' (Name=''); target property is 'TargetWithoutContext' (type 'Object')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=UCRelayDispositionView'. BindingExpression:Path=DataContext; DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'Tag' (type 'Object')
Your problem lies in that you are trying to bind the target to an element which doesn't exist in the same visual tree e.g. you have a ContextMenu on which the item resides.
To correctly get an action target, you need to use the ContextMenus PlacementTarget property.
Check out the following answer on SO for the XAML
WPF Context Menus in Caliburn Micro
So the following XAML should work:
<MenuItem Header="Blah" cal:Message.Attach="SomeMethod()" cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">
This should look for the PlacementTarget on the ContextMenu and set the target for the action to the value of PlacementTarget.Tag (which should be the ListBoxItem).
If you set ListBoxItem.Tag (as you have already done) to be the DataContext of the parent container (the ListBox) you should be ok
so the tag binding should be:
<Setter Property="Tag" Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"/>
e.g. the whole thing should be:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Tag" Value="{Binding Path=DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}">
<MenuItem Header="Remove Group" cal:Message.Attach="DeleteGroup()" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
Adding to Charleh's answer, if you're going to be using the same data context as the control, then you can just bind the DataContext instead of a Tag. Makes it a bit cleaner too.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" >
<MenuItem Header="Remove Group"
cal:Message.Attach="DeleteGroup()" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>

Categories