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
Related
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>
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}}" />
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}}"/>
I have a ToolBar containing Buttons, some of the Buttons have only an Image for content, others have only Text. I am trying to bind the width property of the Button Image to a custom Property on my derived ToolBar class. It works sometimes but fails other times with the following error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='NuiWpfCore.Controls.ToolBar', AncestorLevel='1''. BindingExpression:Path=IconSize; DataItem=null; target element is 'Image' (Name=''); target property is 'Width' (type 'Double')
Here is the xaml containing the element binding that is failing. The DataTemplate is returned from a DataTemplateSelector which is created inline:
<pres:ToolBar x:Class="NuiWpfCore.Controls.ToolBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:pres="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:core="clr-namespace:NuiWpfCore"
xmlns:ctrls="clr-namespace:NuiWpfCore.Controls"
xmlns:select="clr-namespace:NuiWpfCore.Selectors"
xmlns:converters="clr-namespace:NuiWpfCore.Converters"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase">
<ToolBar.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/NuiWpfCore;component/Controls/MenuBarTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:ListPairToStringConverter x:Key="ListPairToStringConverter" />
<converters:IconMetaDataToImageConverter x:Key="IconMetaDataToImageConverter" />
<converters:IconMetaDataToImageConverterParameter x:Key="IconToImageConverterParameter"
ConvertToImage="False" Width="16" Height="16" />
</ResourceDictionary>
</ToolBar.Resources>
<ToolBar.ItemTemplateSelector>
<select:ToolBarItemDataTemplateSelector>
<!-- other DataTemplates omitted for brevity -->
<select:ToolBarItemDataTemplateSelector.IconCommand>
<DataTemplate DataType="{x:Type core:PropertyElement}">
<Button IsEnabled="{Binding Path=CanEdit}" Command="{Binding}">
<Button.Content>
<Image
Width="{Binding Path=IconSize,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ctrls:ToolBar}} }"
Height="{Binding Path=Width,
RelativeSource={RelativeSource Self}}"
Source="{Binding Path=MetaData,
Converter={StaticResource IconMetaDataToImageConverter},
ConverterParameter={StaticResource IconToImageConverterParameter}}"/>
</Button.Content>
</Button>
</DataTemplate>
</select:ToolBarItemDataTemplateSelector.IconCommand>
<!-- other DataTemplates omitted for brevity -->
</select:ToolBarItemDataTemplateSelector>
</ToolBar.ItemTemplateSelector>
</pres:ToolBar>
Here is the ToolBar class with the Source Property for the binding.
public partial class ToolBar : System.Windows.Controls.ToolBar, Views.IView
{
public ToolBar() : base()
{
InitializeComponent();
IconSize = 32;
}
public int IconSize { get; set; }
}
This ToolBar class is sometimes used in a ToolBarTray and other times it is not, but the bind search fails in both cases in certain scenarios.
Does anybody have any ideas as to why this might be failing?
Have you considered making IconSize on your ToolBar an inherited property?
public static readonly DependencyProperty IconSizeProperty =
DependencyProperty.RegisterAttached( "IconSize", typeof(double), typeof(ToolBar ),
new FrameworkPropertyMetadata(32, FrameworkPropertyMetadataOptions.Inherits));
public static double GetIconSize(DependencyObject target)
{
return (double)target.GetValue(IconSizeProperty);
}
public static void SetIconSize(DependencyObject target, double value)
{
target.SetValue(IconSizeProperty, value);
}
Then you can just access the IconSize like
<Button.Content>
<Image
Width="{Binding RelativeSource={RelativeSource Self}, ctrls::ToolBar.IconSize}"
Height="{Binding Path=Width,RelativeSource={RelativeSource Self}}"
Source="{Binding Path=MetaData,
Converter={StaticResource IconMetaDataToImageConverter},
ConverterParameter={StaticResource IconToImageConverterParameter}}"/>
First you should set it on your toolbar, and every other element down the tree can access this property.
Sorry out of my head, not 100% guarenteed to be correct. But the overall idea of
Value Inheritance is a good way to solve this.
The DataTemplate looks like it is being defined inside a DataTemplateSelector declaration, which isn't part of the Visual Tree, and so won't be able to navigate up from there if the Binding were being evaluated in that spot. Where is the template actually being applied?
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;
}