WPF Pass MenuItem selected as MethodParameter to ObjectDataProvider - wpf

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

Related

How to Bind ICommand to a runtime built MenuItem

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>

Context Menu Binding to Parent Window's Datacontext

I have a TreeListControl that binds to a collection in my VM. I also want to define the context menu inside the treelistcontrol having its header text bind to another string in my VM. how can I set the data context in this case? I tried to
<Window.DataContext>
<model:ViewModel></model:ViewModel>
</Window.DataContext>
<Grid>
<Button Grid.Row="1" Command="{Binding CellCheckedCommand}"></Button>
<TextBlock Text="{Binding HeaderText}" Grid.Row="2">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}" Header="{Binding HeaderText}"></MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
but it doesn't work.
Here is the ViewModel
public DelegateCommand CellCheckedCommand { get; set; }
private String _HeaderText;
public String HeaderText
{
get
{
return _HeaderText;
}
set
{
_HeaderText = value;
NotifyPropertyChanged("HeaderText");
}
}
public void NotifyPropertyChanged(String name)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
private void CellCheckedMethod()
{
HeaderText = "Changed";
}
Provide a name for your window and explicitly bind to it such as
<window x:Name="ReportsPage"/>
...
<MenuItem DataContext="{Binding ElementName=ReportsPage}"/>
UPDATE
Since the context menu is actually in its own window, binding is a bit trickier. Hence the best bet is to walk up the RelativeSource to the context's parent and pull the header text from there:
<Window.DataContext>
<local:MainVM HeaderText="Jabberwocky" />
</Window.DataContext>
...
<TextBlock Text="{Binding HeaderText}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=Parent.DataContext.HeaderText,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
</TextBlock.ContextMenu>
Which for this context produces this
This binds to a Window:
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
If the command AddItemCommand and property AddItemText are defined on the Window ViewModel, bind to Window DataContext:
DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext}"

ContextMenu on TreeView-Element in MVVM-style

I have the "simple" task to have a ContextMenu on a TreeView(Element) that is done in MVVM-way.
When searching the web I found some solutions that I could bring to work with buttons etc. but not with the TreeView. I think the problem is with setting the ItemsSource-Property of TreeView that gives every single item an own DataContext.
Here's my little Test-App where you can see the principle working for button but not for the TreeView-Elements:
MainWindow.xaml:
<Window x:Class="ContextMenu.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid >
<StackPanel>
<TextBlock Text="{Binding MyText}" />
<Button Tag="{Binding DataContext,RelativeSource={RelativeSource Mode=Self}}" Content="Click me">
<Button.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu}}" />
</ContextMenu>
</Button.ContextMenu>
</Button>
<TreeView ItemsSource="{Binding MyList}" Tag="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag.MyText,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}" />
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Grid>
</Window>
Codebehind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowVM();
}
}
MainWindowVM.cs:
public class MainWindowVM
{
public string MyText { get; set; }
public ObservableCollection<TreeElement> MyList { get; set; }
public MainWindowVM()
{
MyText = "This is my Text!";
MyList = new ObservableCollection<TreeElement>();
MyList.Add(new TreeElement("String 1"));
MyList.Add(new TreeElement("String 2"));
}
}
public class TreeElement
{
public string Name { get; set; }
public TreeElement(string Name)
{
this.Name = Name;
}
}
Thanks for your help!!
Joerg
You are close.
What you are doing:
you set the Tag of TreeView to its own DataContext. This is
unnecessary.
you try to get Tag.MyText from your ContextMenu.PlacementTarget - which is TextBlock. The TextBlock has no Tag set.
What you should do:
set the Tag of the TextBlock to DataContext of the Window
(Window is TextBlock ancestor and you should look it up via
RelativeSource Mode=FindAncestor).
the second part is OK - you have TextBlock.Tag set in the first step.

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

ContextMenu on ListBox Item with DataTemplate

I have a ListBox with different classes of items. DataTemplates are used to present those objects in the appropriate way. I want to have different context menus in the DataTemplates of these classes.
Everything works fine using the mouse, but using the keyboard I can't bring up the context menu.
This is probably because the keyboard-focus is not on the contents of the DataTemplate, but on the ListBoxItem.
How can I get the ListBoxItem to refer to the Content's ContextMenu?
Sample code:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WpfApplication8"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type my:Orange}">
<TextBlock>
Orange
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Peel"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type my:Apple}">
<TextBlock>
Apple
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Uncore"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Fruits}"/>
</Grid>
</Window>
using System.Windows;
using System.Collections.ObjectModel;
namespace WpfApplication8
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Fruits = new ObservableCollection<Fruit>();
Fruits.Add(new Apple());
Fruits.Add(new Apple());
Fruits.Add(new Orange());
this.DataContext = this;
}
public ObservableCollection<Fruit> Fruits { get; set; }
}
public class Fruit
{
}
public class Apple : Fruit
{
}
public class Orange : Fruit
{
}
}
I too had this problem. Reading Bea Stollnitz' blog gave me an idea.
I started with a data template like this in my resources:
<ContextMenu x:Key="MyMenu">
<MenuItem Header="A" />
<MenuItem Header="B" />
<MenuItem Header="C" />
</ContextMenu>
<DataTemplate x:Key="MyTemplateKey" DataType="{x:Type local:myType}">
<TextBlock ContextMenu="{StaticResource MyMenu}" >
<Run Text="{Binding Path=MyBindingPath}" FontSize="20" FontWeight="Bold" />
</TextBlock>
</DataTemplate>
As described above, this causes the keyboard menu key not to invoke the context menu, although right clicking does work. The problem is the context menu needs to be on the ListBoxItem, not the template inside.
Hey presto!
<Style x:Key="ContextLBI" TargetType="{x:Type ListBoxItem}">
<Setter Property="ContextMenu" Value="{StaticResource MyMenu}">
</Setter>
</Style>
Now, just remove the ContextMenu from the data template, and set your style on your list box like this:
<ListBox ItemTemplate="{StaticResource MyTemplateKey}"
ItemContainerStyle="{StaticResource ContextLBI}"
... >
</ListBox>
This guy have similar problem as you: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5737a331-2014-4e39-b87c-215ae6a7cdd4.
Instead of fighting with focus, add a context menu to the listbox. Add a ContextMenuOpening event handler to your listbox. In that handler, depending on data context of currently selected item, add whatever menuitems you need programmatically.
I found a solution. In the code-behind I will give each ListBoxItem the context menu I find from its visual children.
It gives me the possibility of adding the context menus to the DataTemplates for the various class, thus giving me the polymorphism I like. I also prefer to declare the menus in XAML. And it works with keyboard navigation, as well as mouse use.
The code could probably have been put in an attached property or something for elegance.
I add a loaded event handler and this code:
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
foreach (var item in list.Items)
{
ListBoxItem lbItem = list.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
lbItem.ContextMenu = FindContextMenu(lbItem);
}
}
private ContextMenu FindContextMenu(DependencyObject depObj)
{
ContextMenu cm = depObj.GetValue(ContextMenuProperty) as ContextMenu;
if (cm != null)
return cm;
int children = VisualTreeHelper.GetChildrenCount(depObj);
for (int i = 0; i < children; i++)
{
cm = FindContextMenu(VisualTreeHelper.GetChild(depObj, i));
if(cm != null)
return cm;
}
return null;
}

Resources