How to Bind ICommand to a runtime built MenuItem - wpf

I have a collection of MenuItemViewModel, that is bound to a MenuItem's ItemsSource.
I would like to bind an ICommand to the MenuItem objects created through this binding.
The problem is that the actual MenuItem controls are only added at runtime, since I'm binding to their view models.
This is the GUI I have right now.
MenuItemViewModel.vb, containing the command to bind (all these members have corresponding public properties):
Private _text As String
Private _menuItems As IList(Of MenuItemViewModel)
Private _commandMenuItem As RelayCommand
This is my NotifTilesViewModel:
Private _comPortMenuItems As ObservableCollection(Of MenuItemViewModel)
Public ReadOnly Property ComPortItems As ObservableCollection(Of MenuItemViewModel)
Get
Return _comPortMenuItems
End Get
End Property
So, this ViewModel is the one I have bound to the notification tile UserControl you saw on the attached picture.
This is a relevant piece of my UserControl's XAML:
<Button.ContextMenu>
<ContextMenu x:Name="notifTileContextMenu">
<MenuItem Name="***" Command="{Binding Path=***}" Header="{Resx ResxName=***, Key=***}"/>
<MenuItem Name="***" Header="{Resx ResxName=***, Key=***}"/>
<Separator/>
<MenuItem Name="mItemOpenPort" Header="{Resx ResxName=***, Key=***}" ItemsSource="{Binding Path=ComPortItems}"/>
</ContextMenu>
</Button.ContextMenu>
As you can see, I bind the ItemsSource property to the ObservableCollection of MenuItemsViewModel.
But how can I also bind each of the correspondig MenuItem controls to the public property that I have for _commandMenuItem?

I don't have a Visual Studio available, but I think you can make do by using a style in the MenuItem with a bound ItemsSource.
Something like this (probably not 100% correct, but it should give you the idea):
<Button.ContextMenu>
<ContextMenu x:Name="notifTileContextMenu">
<MenuItem Name="***" Command="{Binding Path=***}" Header="{Resx ResxName=***, Key=***}"/>
<MenuItem Name="***" Header="{Resx ResxName=***, Key=***}"/>
<Separator/>
<MenuItem Name="mItemOpenPort" Header="{Resx ResxName=***, Key=***}" ItemsSource="{Binding Path=ComPortItems}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding FileOpenCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</ContextMenu>
</Button.ContextMenu>

Related

Bind to ViewModel from ContextMenu in ListBox Item

I have a ViewModel with a command 'OpenCommand', a flag 'IsConextMenuVisible' and an observable list 'Links'.
public ObservableList<string> Links { get; set; }
public bool IsContextMenuVisible { get; set; }
public ICommand OpenCommand { get; set; }
in XAML i want the following to work.
<ListBox ItemsSource="{Binding Links}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.ContextMenu>
<ContextMenu Visibility="{Binding IsContextMenuVisible, Converter={StaticResource BoolToVisibiltyHiddenConverter}}">
<MenuItem Header="Open" Command="{Binding OpenCommand}" CommandParameter="{Binding}"/>
</ContextMenu>
</Textblock.ContextMenu>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've already tried some binding expressions for the inner bindings on the ContextMenu, but nothing seems to work. Something like:
Visibility="{Binding Path=DataContext.IsContextMenuVisible,
Converter={StaticResource BoolToVisibilityCollapsedConverter},
RelativeSource={RelativeSource AncestorType=ListBox}}"
This is "problematic" as the kids say because the context menu isn't in the visual tree, so no flavor of RelativeSource is going to work.
You can often bind to properties of PlacementTarget, but in this case you need a visual ancestor of the PlacementTarget, and RelativeSource won't do an ancestor of something else.
In WPF, when there's a gap in the visual tree, the last ditch option is always a BindingProxy. Here's what that class looks like (including the URL of the StackOverflow question I stole it from -- that class has been copied and pasted around many, many questions and answers on this site):
// https://stackoverflow.com/questions/24452264/bindingproxy-binding-to-the-indexed-property
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
And you would use it like this. First create the BindingProxy as a resource, in a location where it can "see" the desired element:
<Window.Resources>
<local:BoolToVisibiltyHiddenConverter x:Key="BoolToVisibiltyHiddenConverter" />
<!-- {Binding} with no path will be the window's datacontext, the main viewmodel. -->
<local:BindingProxy Data="{Binding}" x:Key="MainViewModelBindingProxy" />
</Window.Resources>
And then use it for the Source of the binding. The desired DataContext will be the Data property of the proxy object, so provide paths relative to Data:
<TextBlock.ContextMenu>
<ContextMenu
Visibility="{Binding Data.IsContextMenuVisible,
Converter={StaticResource BoolToVisibiltyHiddenConverter},
Source={StaticResource MainViewModelBindingProxy}}"
>
<MenuItem
Header="Open"
Command="{Binding OpenCommand}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</TextBlock.ContextMenu>
Now you've got another problem: The menu is still popping up. It just doesn't happen to be visible. If the user right clicks, it'll pop up invisibly, and suddenly appear when IsContextMenuVisible changes to true. That's not what you want.
You could omit the converter and just bind directly to ContextMenu.IsEnabled: It'll still pop up, but it'll be grayed out. This is consistent with common Windows UI practice.
You could also have a style trigger so that the TextBlock only has a ContextMenu when you want it to have one. Because that trigger is on the TextBlock, it's in the visual tree we can use a conventional RelativeSource for the binding.
<TextBlock Text="{Binding}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger
Binding="{Binding Data.IsContextMenuVisible,
RelativeSource={RelativeSource AncestorType=ListBox}}"
Value="True">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu >
<MenuItem
Header="Open"
Command="{Binding OpenCommand}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

ContextMenu click of DataGrid in view model

I have use the following code snippet to get the ContextMenu click of DataGrid in view model
Code Snippet[XAML]
<DataGrid AutoGenerateColumns="True" ItemsSource="{Binding GDCSource}" x:Name="dataGrid">
<DataGrid.ContextMenu>
<ContextMenu >
<ContextMenu.Items>
<MenuItem Header="Export To Excel" Command="{Binding ExportCommand}" CommandParameter="{Binding ElementName=dataGrid}"/>
</ContextMenu.Items>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
Code Snippet[C#]
private BaseCommand exportcommand;
public BaseCommand ExportCommand
{
get
{
if (exportcommand == null)
exportcommand = new BaseCommand(ExportCommandExcecuted);
return exportcommand;
}
}
public void ExportCommandExcecuted(object param)
{
var grid = param as GridDataControl;
}
But I cannot get the Command parameter as DataGrid. Can you please look into this and provide suggestion to achieve this dataGrid in ViewModel. Thanks in advance
Am not sure if I should even tell you how to do this since your breaking MVVM if you are essentially doing this and I cannot figure out a reason for you wanting this functionality.
If you're not using MVVM, then you could try having your MenuItem such as
<MenuItem Command="{Binding ExportCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget}"
Header="Export To Excel" />
If you're using MVVM,
Just don't reference UI elements in the VM. You got the ItemSource collection in the VM. Raise a plain simple ICommand and all the data you need is available without you getting the DataGrid to the VM.

WPF Binding a MenuItem in a CompositeCollection not working

I'm having problems binding a command to a menuitem in a compositecollection. The MenuItem is part of ContextMenu which is defined in the UserControl.Resources.
The problem is that the binding of the New label is not working. When I place the MenuItem outside of the composite collection it will work. Any ideas?
<UserControl.Resources>
<ContextMenu x:Key="DataGridRowContextMenu">
<MenuItem Header=" Set label"/>
<MenuItem.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource labelsSelectSource}}" />
<MenuItem Header=" New label..."
Command="{Binding DataContext.NewLabel,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type UserControl}}}"/>
</CompositeCollection>
</MenuItem.ItemsSource>
</MenuItem>
<UserControl.Resources/>
This is happening because of the fact that ContextMenu is not in the same visual tree as its containing parent, resulting in data binding issues. Since the ContextMenu is not in the same visual tree, ElementName, RelativeSouce (FindAncestor), etc bindings will not work.
You can get around this through
In the code behind for the UserControl:
NameScope.SetNameScope(DataGridRowContextMenu, NameScope.GetNameScope(this));
using the PlacementTarget property like this -
<ContextMenu
x:Key="DataGridRowContextMenu">
DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
.
.
.
<MenuItem
Header=" New label..."
Command="{Binding DataContext.NewLabel}"/>
Or use other solutions from -
ElementName Binding from MenuItem in ContextMenu
WPF: DataContext for ContextMenu
I keep struggling around with this crazy ContextMenu and its MenuItems for a long time. But I found a solution to work with it creating a custom behaviour "BindingProxyBehaviour" with a dependancyproperty named "Data". This property holds an object for example your DataContext(maybe your ViewModel if you use MVVM pattern).
public class BindingProxyDataBehavior : Freezable
{
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxyDataBehavior), new UIPropertyMetadata(null));
protected override Freezable CreateInstanceCore()
{
return new BindingProxyDataBehavior();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
}
Just add the BindingProxy as a resource in you xaml file like this.
<UserControl.Resources>
<ResourceDictionary>
<behaviors:BindingProxyDataBehavior x:Key="BindingProxyViewModel" Data="{Binding}"/>
<behaviors:BindingProxyDataBehavior x:Key="BindingProxyViewModelDynamicDataList" Data="{Binding DynamicDataListObject}"/>
</ResourceDictionary>
</UserControl.Resources>
In my case I am using a CompositeCollection to mix up static and dynamic MenuItems. Therefore the second resource with key "BindingProxyViewModelDynamicDataList".
VoilĂ  now you can easily access your data no matter where your ContextMenu is.
My ContexMenu position in xaml tree is UserControl->Grid->DataGrid->DataGridTemplateColumn->CellTemplate->DataTemplate->TextBox.Template->Grid->TextBlock->controls:IconButton(simple customButton control derived from button) and here we are inside the IconButton:
<controls:IconButton.ContextMenu>
<ContextMenu x:Name="AnyContextMenuName">
<ContextMenu.Resources>
<HierarchicalDataTemplate DataType="{x:Type DynamicDataListItemType}">
<TextBlock Text="{Binding DynamicDataListItemProperty}"/>
</HierarchicalDataTemplate>
</ContextMenu.Resources>
<ContextMenu.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource BindingProxyViewModelDynamicDataList}, Path=Data}"/>
<Separator/>
<MenuItem Header="Your static header" Command="{Binding Source={StaticResource BindingProxyViewModel}, Path=Data.ViewModelCommandForYourStaticMenuItem}"/>
</CompositeCollection>
</ContextMenu.ItemsSource>
<ContextMenu.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Foreground" Value="{DynamicResource DefaultForegroundBrush}"/>
<Setter Property="MenuItem.Command" Value="{Binding Source={StaticResource BindingProxyViewModel}, Path=Data.ViewModelCommandForDynamicMenuItems}"/>
<Setter Property="MenuItem.CommandParameter" Value="{Binding}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</controls:IconButton.ContextMenu>
I hope that I could help sombody with this short post.
Use CommandTarget property or make a staticRessource of your datacontext like
<MenuItem Header=" New label..."
Command="{Binding Path=NewLabel,Source={StaticResource viewModel}}"/>

How to bind MenuItem.Header to Window/UserControl dependency property?

I`m wondering how can I bind MenuItem.Header to the parent Window/UserControl dependency property? Here is a simple example:
Window1.xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" x:Name="self">
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=MenuText, ElementName=self}" />
</ContextMenu>
</Grid.ContextMenu>
<TextBlock Text="{Binding Path=MenuText, ElementName=self}"/>
</Grid>
</Window>
Window1.xaml.cs:
public partial class Window1 : Window {
public static readonly DependencyProperty MenuTextProperty = DependencyProperty.Register(
"MenuText", typeof (string), typeof (Window1), new PropertyMetadata("Item 1"));
public Window1()
{
InitializeComponent();
}
public string MenuText {
get { return (string)this.GetValue(MenuTextProperty); }
set { this.SetValue(MenuTextProperty, value); }
}
}
In my case, textblock displays "Item 1", and context menu displays empty item. What I`m doing wrong? It seems for me, that I faced serious misunderstaning of WPF databinding principles.
You should see this in the Output window of Visual Studio:
System.Windows.Data Error: 4 : Cannot
find source for binding with reference
'ElementName=self'.
BindingExpression:Path=MenuText;
DataItem=null; target element is
'MenuItem' (Name=''); target property
is 'Header' (type 'Object')
That is because the ContextMenu is disconnected from the VisualTree, you need to do this Binding differently.
One way is via ContextMenu.PlacementTarget (which should be the Grid), you could use its DataContext to establish a binding, e.g.:
<MenuItem Header="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.DataContext.MenuText}"/>
or set up the DataContext in the ContextMenu itself:
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}">
<MenuItem Header="{Binding Path=MenuText}"/>
</ContextMenu>
If this is not an option (because the DataContext of the Grid cannot be the Window/UserControl) you can try to pass the reference to the Window/UserControl through the Tag of your Grid for example.
<Grid ...
Tag="{x:Reference self}">
<Grid.ContextMenu>
<!-- The DataContext is now bound to PlacementTarget.Tag -->
<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.Tag}">
<MenuItem Header="{Binding Path=MenuText}"/>
</ContextMenu>
...
As a side-note: Because of this behavior i tend to define a helper-style in App.xaml to make all ContextMenus "pseudo-inherit" the DataContext from their parent:
<!-- Context Menu Helper -->
<Style TargetType="{x:Type ContextMenu}">
<Setter Property="DataContext" Value="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"/>
</Style>
Alternative to H.B.'s solution is this attached behavior: ContextMenuServiceExtensions.DataContext Attached Property

WPF Pass MenuItem selected as MethodParameter to ObjectDataProvider

I am trying to pass Selected MenuItem's Text/Header string as the MethodParameter to my ObjectDataProvider. I have seen examples like these on the internet but haven't been able to adapt it to the Menu Control specifically. I am new to WPF and need some help accomplish this. Any help would be greatly appreciated.
Below is the code snippet, XAML for the ObjectDataProvider
<Window.Resources>
<ObjectDataProvider x:Key="NMInfo" ObjectType="{x:Type local:NMInfoProvider}" MethodName="GetDcmsInfomation" IsAsynchronous="True">
<ObjectDataProvider.MethodParameters>
<x:Static Member="system:String.Empty" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
XAML for the Menu control
<Menu Name="nmMenu" Height="25" HorizontalAlignment="Stretch" VerticalAlignment="Top" FontSize="12" DockPanel.Dock="Top">
<Menu.BitmapEffect>
<DropShadowBitmapEffect/>
</Menu.BitmapEffect>
<MenuItem Header="File">
<MenuItem Header="SNYC12P10650" IsCheckable="True" ToolTip="Production" Click="MenuItem_Clicked">
<MenuItem.IsChecked>
<Binding Source="{StaticResource NMInfo}" Path="MethodParameters[0]" BindsDirectlyToSource="True" Mode="OneWayToSource"/>
</MenuItem.IsChecked>
</MenuItem>
<MenuItem Header="GPRI12D10217" IsCheckable="True" ToolTip="QA" Click="MenuItem_Clicked">
<MenuItem.IsChecked>
<Binding Source="{StaticResource NMInfo}" Path="MethodParameters[0]" BindsDirectlyToSource="True" Mode="OneWayToSource"/>
</MenuItem.IsChecked>
</MenuItem>
<MenuItem Header="GPRI12D10219" IsCheckable="True" ToolTip="Dev" Click="MenuItem_Clicked">
<MenuItem.IsChecked>
<Binding Source="{StaticResource NMInfo}" Path="MethodParameters[0]" BindsDirectlyToSource="True" Mode="OneWayToSource"/>
</MenuItem.IsChecked>
</MenuItem>
<Separator/>
<MenuItem Header="Close"/>
</MenuItem>
</Menu>
What you need to do is Bind the Header property, not IsChecked. I'm assuming you only want to do this when the item is checked though. While this would be feasible by using a Style for the MenuItem, I would advocate doing this sort of work in a ViewModel.
Instead of having an ObjectDataProvider, your VM would expose boolean properties for each of the checkable menu items. When any of these properties changed, it could call that method itself, and expose the object as a read-only property. Just set the DataContext of the whole control to an instance of your VM, and the bindings would work.
Something like so:
public class NMInfoViewModel : INotifyPropertyChanged
{
private bool isSNYC12P10650 = false;
public bool IsSNYC12P10650
{
get { return isSNYC12P10650; }
set
{
if (value == isSNYC12P10650) return;
isSNYC12P10650 = value;
OnPropertyChanged("IsSNYC12P10650");
if (value)
NMInfo = NMInfoProvider.GetDcmsInfomation("SNYC12P10650");
}
}
...
private NMInfo nMInfo;
public NMInfo NMInfo
{
get { return nMInfo; }
private set
{
if (value == nMInfo) return;
nMInfo = value;
OnPropertyChanged("NMInfo");
}
}
}
And your MenuItems would look like this:
<MenuItem Header="SNYC12P10650" IsCheckable="True"
ToolTip="Production" IsChecked="{Binding IsSNYC12P10650}" />

Resources