WPF Binding a MenuItem in a CompositeCollection not working - wpf

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

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>

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>

WPF: relativesource data binding on a custom dependencyproperty

I'm trying to create a custom multi value combobox. So basically a combobox with some checkboxes as items. The idea is, to keep the whole control fully bindable, so that I can be reused any time.
Here's the XAML
<ComboBox x:Class="WpfExtensions.Controls.MultiSelectComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfExtensions.Controls"
mc:Ignorable="d" d:DesignHeight="23" d:DesignWidth="150">
<ComboBox.Resources>
<local:CheckBoxConverter x:Key="CheckBoxConverter" />
</ComboBox.Resources>
<ComboBox.ItemTemplateSelector>
<local:MultiSelectBoxTemplateSelector>
<local:MultiSelectBoxTemplateSelector.SelectedItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MultiSelectComboBox}}, Path=SelectedItems, Converter={StaticResource CheckBoxConverter}}" />
</DataTemplate>
</local:MultiSelectBoxTemplateSelector.SelectedItemsTemplate>
<local:MultiSelectBoxTemplateSelector.MultiSelectItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" HorizontalAlignment="Stretch"
Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Indeterminate="CheckBox_Checked" Click="CheckBox_Checked" />
</DataTemplate>
</local:MultiSelectBoxTemplateSelector.MultiSelectItemTemplate>
</local:MultiSelectBoxTemplateSelector>
</ComboBox.ItemTemplateSelector>
</ComboBox>
And the code behind for the custom property "SelectedItems"
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectComboBox));
[Bindable(true)]
public IList SelectedItems
{
get
{
return (IList)GetValue(SelectedItemsProperty);
}
private set
{
SetValue(SelectedItemsProperty, value);
}
}
Now when I test the project, the RelativeSource is resolved correctly towards the control itself, however the Binding on the path "SelectedItems" fails with the debugger stating, that there is no such Path on the RelativeSource object.
Did I mess up the binding or did I make a complete logical error?
You are setting a RelativeSource as the Source, instead set the RelativeSource propperty like so:
<TextBlock Text="{Binding Path=SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type local:MultiSelectComboBox}}, Converter={StaticResource CheckBoxConverter}}" />

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

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

Resources