Reputation: 129
I have 3 listboxes all bound two 3 separate observable collections of the same type. My ViewModel has the observable collections exposed via properties. This is for some drag and drop grouping, source list box can have items dragged onto two different lists. But I want to give the user the ability to right click on a listboxitem and set the item's properties. Things like Type, Name, etc. I am using a data template in the since I want all three boxes to be the same in functionality. This works well, and I can get the context menu to pop up when I click on individual items with no problem. My trouble is that I have one propery called FieldType. It is an enum that has 4 potential values. I can't, for the life of me, figure out how to bind the IsChecked property of the MenuItem to that property... functionally anyway. Here is what I have tried....
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu >
<ContextMenu.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverterc" />
</ContextMenu.Resources>
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="String" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverterc}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.Date}}"/>
<MenuItem Header="Barcode" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.BarCode}}" />
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
In the code above you can see on the String, Date, and Barcode MenuItems what I was trying to do (gotta love code that is a work in process). My issue is the exposed property that it should call. I don't know how, in my ViewModel property, to get to the item in the observable collection that corresponds to the item clicked. I have a value converter EnumToBoolean that will sit on the binding to get the checked or not. The problem is the property that is setting/getting that particular item in the observable collection.
Any thoughts? Need more code? Need me to clarify anything? How close am I? By the way, the ViewModel code is written in VB 2010.
Thanks Bryce
EDIT: I have tried the following using Angel's suggestion...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" >
<TextBlock.ContextMenu PlacementTarget="{Binding ElementName=Field}">
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this gives me an error that says...Cannot set properties on property elements. Not sure if this is a TextBlox vs TextBox issue? You used TextBox in your example... my guess is that your code would do the same. So I then tried the following...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu PlacementTarget="{Binding ElementName=Field}" >
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this causes binding errors... System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=Field'. BindingExpression:(no path); DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'PlacementTarget' (type 'UIElement')
So it appears that the binding is not working. Any thoughts?
Upvotes: 0
Views: 1112
Reputation: 19895
ContextMenu
is not part of visual tree. So it wont, by default, connect \ bind to the datacontext of the TextBlock on which it is applied...
So 2 ways to do this...
ContextMenu.PlacementTarget
and refer that as the Path
in individual MenuItem's Binding.e.g.
<TextBox x:Name="MyTextBlock">
<TextBox.ContextMenu PlacementTarget="{Binding ElementName=MyTextBlock}">
<MenuItem
Header="{Binding PlacementTarget.DataContext.MyHeader,
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"
</TextBox.ContextMenu>
</TextBox>
So in the example above... you want to connect menu item with the data context of the text box. So you define PlacementTarget
on the ContextMenu
. This placement target can only be set with 2 types of bindings... ElementName
or StaticResource
. And once the context menu is connected to the visual element via PlacementTarget
, use the Path
in the binding of the meuitem to resolve the data context property i.e. MyHeader
.
OR
Use proxy element approach...
Bind datagrid column visibility MVVM
Upvotes: 1