How do I add a command to items in dynamically generated ContextMenu - c#

I have a context menu that is being populated from an ObservableCollection. I want the user to be able to click on any of those items, then a method is called passing the clicked item's text as a parameter.
I've started by following the answer to this question. However, I'm getting an error in my console output and my method is not being called.
System.Windows.Data Error: 40 : BindingExpression path error: 'FunctionToCall' property not found on 'object' ''MenuItem' (Name='myMenu')'. BindingExpression:Path=FunctionToCall; DataItem='MenuItem' (Name='myMenu'); target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')
here is my xaml
<MenuItem Name="myMenu" Header="display text" ItemsSource="{Binding}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding FunctionToCall, RelativeSource={RelativeSource AncestorType=MenuItem}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
And my view model code
RelayCommand _command;
public ICommand FunctionToCall
{
get
{
if (_command == null)
{
_command = new RelayCommand(p => this.InnerMethod(p));
}
return _command ;
}
}
public void InnerMethod(object parameter)
{
....
The other answer suggests playing around with adding one or two DataContexts to the Binding, I've tried this and I still get the same error although it says DataContext property cannot be found instead of FunctionToCall.
I found the definition of RelayCommand here.

The real problem is with your binding. Use the DataContext property of MenuItem to actually get to the ViewModel instance
<MenuItem Name="myMenu" Header="display text" ItemsSource="{Binding}" >
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding DataContext.FunctionToCall, RelativeSource={RelativeSource AncestorType=MenuItem}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
MenuItem will get ViewModel as DataContext. So actually we want..
MenuItem.DataContext.FunctionToCall
Hopefully you don't need the different menu items to bind to different commands else you have to change your design a little.
As Per Your Comments:
You'll need a List<MenuItem> MenuItems to bind with ContextMenu ItemSource property as
public class MenuItem
{
public string Header { get; set; }
public ICommand Command { get; set; }
}
XAML:
<ContextMenu ItemsSource="{Binding MenuItems}" >
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" >
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
And add as many contextmenu item you want in your ViewModel AS YOU WANT.

This is how to do it.
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
xaml
<MenuItem Header="{Binding Item1}" Command="{Binding FunctionToCall}" CommandParameter="{Binding Header, RelativeSource={RelativeSource Self}}"/>
ViewModel
public class ViewModel
{
ICommand _cmd = new CustomCommand();
public ICommand FunctionToCall
{
get { return _cmd; }
set { _cmd = value; }
}
public string Item1 { get; set; }
public ViewModel() { Item1 = "1Header"; }
}
Command
public class CustomCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MessageBox.Show(parameter.ToString());
}
}
So, in your case assuming you want to pass Header of MenuItem as parameter to your command, do following changes :
<Setter Property="Command" Value="{Binding FunctionToCall}"/>
<Setter Property="CommandParameter" Value="{Binding Header, RelativeSource={RelativeSource Self}}"/>

Related

Create a binding from a viewmodel collection class to a property in the datacontext

I have a viewmodel with multiple counters which are used in serval methods.
In the view model there is also a collection of class MenuItem which holds information the create the dynamic menuitems in the ribbon. On some of those menuitems i want to display the counter through a badge.
But to do this i need the bind the badge to the counter property.
In my menuitem class i have the path for the binding, but how can i thell my menuitem template to bind to the path it has in it's own binding.
Examples are simplified
public class ViewmodelSample
{
public int counter1 { get; set; }
public ICollection<MenuItem> MenuItems { get; set; } = new ObservableCollection<MenuItem>();
public void Sample()
{
MenuItems.Add(new MenuItem()
{
Name = "Test button",
CounterPath = "counter1"
});
}
public class MenuItem
{
public string Name { get; set; }
public string CounterPath { get; set; }
}
}
<ItemsControl ItemsSource="{Binding Path=MenuItems}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Path={Binding CounterPAth}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You cannot bind the Path property in the Binding markup extension, it is not a DependencyProperty.
You could identify the target counter with e.g. a Counter property of type int. Apply a Style to your TextBlock with triggers that provide the bindings to the corresponding counter properties on the ViewmodelSample. You need a RelativeSource binding as the counters are in the parent DataContext.
<Style x:Name="CounterTextBlockStyle" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Counter}" Value="1">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.counter1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Counter}" Value="2">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.counter2}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Counter}" Value="3">
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}, Path=DataContext.counter3}"/>
</DataTrigger>
</Style.Triggers>
</Style>
For now i have created a helper class.
Maybe is not perfect but this works for me (for now).
Helper class
public class DynamicBindingHelper
{
public string PropertyNaam { get; set; }
public static string GetPath(DependencyObject obj)
{
return (string)obj.GetValue(PathProperty);
}
public static void SetPath(DependencyObject obj, string value)
{
obj.SetValue(PathProperty, value);
}
public static readonly DependencyProperty PathProperty =
DependencyProperty.RegisterAttached("Path", typeof(string), typeof(DynamicBindingHelper), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnPathChanged)));
private static void OnPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || (e.NewValue is string val && val.IsNullOrWhiteSpace()))
return;
if (d == null)
return;
var binding = new Binding($"DataContext.{e.NewValue}")
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(UserControl), 1),
Mode=BindingMode.OneWay
};
switch (d)
{
case TextBlock _:
BindingOperations.ClearBinding(d, TextBlock.TextProperty);
BindingOperations.SetBinding(d, TextBlock.TextProperty, binding);
break;
case Badge _:
BindingOperations.ClearBinding(d, Badge.ContentProperty);
BindingOperations.SetBinding(d, Badge.ContentProperty, binding);
break;
}
}
}
WPF usage
<dx:Badge helper:DynamicBindingHelper.Path="{Binding TellerNaam}" Padding="2,2,2,3" FontSize="10" Margin="-3,5,3,-5" />

Binding a dynamic list to a column of ComboBoxes in a DataGrid only works until actually showing the list

I'm working on a WPF application according to the MVVM pattern and am facing a challenge that I abstracted in the code below.
The app contains a DataGrid with 2 ComboBox columns (each generated in a different manner). The aim is to have a ComboBox present only those items that have not yet been selected by the other ComboBoxes in the same column.
The comboboxes are Bound to an ObservableCollection of Professions. Each profession has a Boolean "Selectable", and a ComboBox should only show those entries with a value of "true".
The list contains:
Painter
Poet
Scientist
To simulate an interactive Command from XAML to the ViewModel, I placed a button that will set the Scientist to Selectable to "false".
App.xaml:
<Application x:Class="wpf_ComboBoxColumn.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
</Application>
MainWindow.xaml.cs:
using System.Windows;
namespace wpf_ComboBoxColumn
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
MainWindow.xaml:
<Window x:Class="wpf_ComboBoxColumn.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpf_ComboBoxColumn"
xmlns:viewModel="clr-namespace:wpf_ComboBoxColumn"
Title="Combobox Column Binding" Height="350" Width="460">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
<Setter Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Selectable}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Selectable}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.DataContext>
<viewModel:MainViewModel />
</Grid.DataContext>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridComboBoxColumn
Header="ComboBoxColumn"
SelectedValueBinding="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedValuePath="Description"
DisplayMemberPath="Description"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Professions}"/>
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Professions}"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
<DataGridTemplateColumn Header="TemplateColumn">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=DataContext.Professions, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Description"
SelectedValue="{Binding Profession, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="210,290,0,0" VerticalAlignment="Top" Width="75" Command="{Binding DebugCommand}"/>
</Grid>
</Window>
CustomCommand.cs (ICommand implementation):
using System;
using System.Windows.Input;
namespace wpf_ComboBoxColumn
{
public class CustomCommand: ICommand
{
private readonly Action<object> execute;
public CustomCommand(Action<object> execute)
{
this.execute = execute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
MainViewModel.cs:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace wpf_ComboBoxColumn
{
public class NotifyUIBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Profession
{
public string Description { get; set; }
public Boolean Selectable { get; set; }
}
public class Person
{
public string Name { get; set; }
public string Profession { get; set; }
}
public class MainViewModel : NotifyUIBase
{
public ObservableCollection<Person> People { get; set; }
public ObservableCollection<Profession> Professions { get; set; }
public ICommand DebugCommand { get; set; }
public MainViewModel()
{
DebugCommand = new CustomCommand(Debug);
People = new ObservableCollection<Person>
{
new Person{Name="Tom", Profession="" },
new Person{Name= "Dick", Profession="" },
new Person{Name= "Harry", Profession="" }
};
Professions = new ObservableCollection<Profession>
{
new Profession{ Description="Painter", Selectable=true},
new Profession{ Description="Poet", Selectable=true},
new Profession{ Description="Scientist", Selectable=true},
};
}
private void Debug(object obj)
{
Professions[2].Selectable = false;
}
}
}
Now consider the following scenario (I'm still trying to figure out how to include screen shots):
Open the app: This will show a grid with 3 columns:
First column shows the names "Tom", "Dick" and "Harry".
Second column contains a ComboBox for each person. It requires multiple clicks to open.
Third column also contains a ComboBox for each person. This one is recognizable as such.
Choose "Scientist" for Tom
Click the button (to fake that we executed code that changed Profession.Selectable)
Click on the Combobox for Dick
This will indeed show the remaining Professions (without Scientist), for the rightmost column of ComboBoxes. The leftmost column will still show all options, so this one fails right away.
Click on the Combobox for Tom again
This will, even for the rightmost column of ComboBoxes, show all options again (or rather: still)!
It turns out that the list, once shown, is not dynamically updated. Until we click on it, it is (makes me think of Quantum Mechanics, but that's another story)
The question is: Is there a way to force a refresh of the ItemsSource? Preferrably, of course, respecting MVVM, but at this point, I'll go for any working solution, using either ComboBox-type.
Thanks!
You need to raise the PropertyChanged event on the Selectable property. You're binding to it, and then you're changing it, so if you want the view to change based on this property, it needs to raise PropertyChanged.

How to define a Command for a ComboBox?

I'm wondering if is possible bound a Command to the ComboBox, I've actually implemented the Command logic on a Menu, in this way:
<Menu HorizontalAlignment="Left" VerticalAlignment="Stretch">
<MenuItem Header="Theme" Width="100"
ItemContainerStyle="{StaticResource ThemeColorMenuItemStyle}"
ItemsSource="{Binding Themes, Mode=OneTime}" />
</Menu>
where the ItemContainerStyle have this structure:
<Style x:Key="AccentColorMenuItemStyle"
BasedOn="{StaticResource MetroMenuItem}" TargetType="{x:Type MenuItem}">
<Setter Property="CommandParameter" Value="{Binding }" />
<Setter Property="Command" Value="{Binding DataContext.ApplyAccentCommand,
RelativeSource={RelativeSource AncestorType=Window}}" />
<Setter Property="Header" Value="{Binding Name, Mode=OneWay}" />
<Setter Property="Icon" Value="{StaticResource AccentMenuIcon}" />
</Style>
and this is the command:
public ICommand ApplyAccentCommand { get; } = new SimpleCommand(o => ApplyAccent((Swatch)o));
private static void ApplyAccent(Swatch swatch)
{
new PaletteHelper().ReplaceAccentColor(swatch);
}
this MenuItem bound a Theme collection provided by MaterialDesignInXaml as Swatch model, that have this class:
public class Swatch
{
public Swatch(string name, IEnumerable<Hue> primaryHues, IEnumerable<Hue> accentHues);
public string Name { get; }
public Hue ExemplarHue { get; }
public Hue AccentExemplarHue { get; }
public IEnumerable<Hue> PrimaryHues { get; }
public IEnumerable<Hue> AccentHues { get; }
public bool IsAccented { get; }
public override string ToString();
}
so, returning to the question: is possible have this logic on a ComboBox? 'cause the MenuItem doesn't have the SelectedItem property, and I need this.
You can use Blend Behaviors and bind an event to a command. You will need to refer System.Windows.Interactivity namespace, which you can get by installing the Expression.Blend.Sdk NuGet package.
Once installed, add the following XAML namespace to your page:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And then use it as follows:
<ComboBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding MyCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
The MvvmLight toolkit also offers more advanced version of InvokeCommandAction called EventToCommand that allows you to specify a EventArgsConverter as well to be able to get a specific value from the event's EventArgs instance.

WPF command not working for submenu items in MVVM application

I have a menu which is built from a collection at runtime. This is all working as shown.
But if the menu contains child items (Child1, Child2 etc) the ReactiveCommand MenuCommand is never called.
If I remove all children from the menu so that the menu only contains parent items then MenuCommand is called. I am fairly new to WPF. I have re-created the problem in a sample app (code below). There are no visible binding errors in VS.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
public class Service
{
public Service(string menuHeading, string menuSubHeading)
{
MenuHeading = menuHeading;
MenuSubHeading = menuSubHeading;
}
public string MenuHeading { get; set; }
public string MenuSubHeading { get; set; }
}
public static class MenuBuilder
{
public static ReactiveList<MenuItem> Build(ReactiveList<Service> services)
{
ReactiveList<MenuItem> menuItems = new ReactiveList<MenuItem>();
foreach (var service in services)
{
AddOrUpdate(menuItems, service);
}
return menuItems;
}
private static void AddOrUpdate(ReactiveList<MenuItem> menu, Service service)
{
if (menu.Any((_ => _.Header.ToString() == service.MenuHeading)))
{
var item = menu.FirstOrDefault(x => x.Header.ToString() == service.MenuHeading);
item.Items.Add(new MenuItem() { Header = service.MenuSubHeading });
//if above line removed MenuCommand works
}
else
{
menu.Add(new MenuItem() { Header = service.MenuHeading });
var item = menu.FirstOrDefault(x => x.Header.ToString() == service.MenuHeading);
item.Items.Add(new MenuItem() { Header = service.MenuSubHeading });
//if above line removed MenuCommand works
}
}
}
public class MainWindowViewModel : ReactiveObject
{
public MainWindowViewModel()
{
MenuCommand = ReactiveCommand.Create<Object>(selectedItem => OnMenuItemSelected(selectedItem));
MenuCommand.Execute().Subscribe();
}
public ReactiveCommand<Object, Unit> MenuCommand { get; }
private ReactiveList<MenuItem> servicesMenu;
private ReactiveList<Service> Services = new ReactiveList<Service>()
{
new Service("Parent1", "Child1"),
new Service("Parent2", "Child1"),
new Service("Parent2", "Child2"),
};
public ReactiveList<MenuItem> ServicesMenu
{
get
{
if (servicesMenu == null)
{
servicesMenu = MenuBuilder.Build(Services);
return servicesMenu;
}
else
{
return servicesMenu;
}
}
}
private void OnMenuItemSelected(Object selectedItem)
{
//This method is only called when the menu does not contain any child items
}
}
<Grid>
<StackPanel Orientation="Vertical">
<Button Name="Button" Content="Button" Padding="5" HorizontalAlignment="Left"
Tag="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}">
<Button.ContextMenu>
<ContextMenu x:Name="MainMenu" ItemsSource="{Binding ServicesMenu}"
DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Command"
Value="{Binding DataContext.MenuCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}}" />
<Setter Property="CommandParameter"
Value="{Binding RelativeSource={RelativeSource Self}}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</Grid>
Updated XAML after suggestions form Glenn
<Grid>
<StackPanel Orientation="Vertical">
<Button Name="Button" Content="Button" Padding="5" HorizontalAlignment="Left"
Tag="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}">
<Button.ContextMenu>
<ContextMenu x:Name="MainMenu" ItemsSource="{Binding ServicesMenu}"
DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Header}" />
<Setter Property="Command" Value="{Binding Command}" />
<!--<Setter Property="Command" Value="{Binding MenuCommand}" /> was also tried-->
<Setter Property="CommandParameter" Value="{Binding}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
</Grid>
I suspect this is because child items placement target wouldn't be the Button like you expect, it would be the parent MenuItem.
One way I've gotten around this in the past is using MVVM approach for these type of menu items.
Create a Menu Item VM (you call them Service above) for your items (similar to what you already doing). In the VM have a Command property and pass in your command as part of it's constructor. Then you can just do {Binding MenuCommand} from your Item Container Style.
Also don't create the MenuItem's directly in your ViewModel, instead just bind direct to the Services. I would also recommend creating your sub-services as a ObservableCollection directly inside your Service, then in your item container set the ItemsSource property to bind to the sub-children of your Services.

WPF: context menu in TreeViewItem binding to root

I have TreeView control and I need to bind property from root (window/usercontrol) DataContext in context menu in that treeview.
<TextBox Text="{Binding Header}"></TextBox>
<TreeView ItemsSource="{Binding Items}" Grid.Row="1">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{{ BINDING TO HEADER PROPERTY FROM WINDOW DATACONTEXT}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
public ObservableCollection<string> Items { get; set; }
public string Header { get { return _header; } set { _header = value; } }
I've tried multiple things: I've added x:Name="WindowRoot" to Window and {Binding Header, ElementName=WindowRoot} but it didn't work, I've tried multiple FindAncestor and RelativeSource but it didn't work.
Can someone help me?
Edit:
This is simplified case, in my normal application I use Unity + Prism, so ViewModel is AutoDiscovered (prism:ViewModelLocator.AutoWireViewModel="True") and it generally works. By "works" I mean: TreeView shows items from my collection, so it is connected, the problem is with context menu binding only.
In this simplified example I have ugly and simple code-behind, because I only want to test this ContextMenu binding:
public partial class MainWindow : Window
{
public ObservableCollection<string> Items { get; set; }
private string _header = "testtest";
public string Header { get { return _header ; } set { _header = value; } }
public MainWindow()
{
Items = new ObservableCollection<string>();
Items.Add("ItemTest");
InitializeComponent();
this.DataContext = this;
}
}
You could bind the Tag property of the TreeViewItem to the parent window or user control using a {RelativeSource} and then bind the Header property of the MenuItem to the Tag property of the PlacementTarget of the ContextMenu:
<TreeView x:Name="tv" ItemsSource="{Binding Items}" Grid.Row="1">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=Window}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{Binding PlacementTarget.Tag.DataContext.Header, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
The reason you cannot bind directly to any property of the window from the MenuItem is that a ContextMenu resides in a different element tree than the parent window or user control.

Categories

Resources