0

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?

Bryce Martin
  • 129
  • 3
  • 17

1 Answers1

1

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...

  1. Set 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

Community
  • 1
  • 1
WPF-it
  • 19,625
  • 8
  • 55
  • 71
  • Since the TextBox is being generated via the DataTemplate from binding to the observable collection they will all have the same x:Name. Does the x:Name have to be unique? Won't it get confused as to which one to bind to if they all have the same name? – Bryce Martin May 16 '12 at 15:34
  • Names are uniquely **scoped** in WPF, so I guess the `ElementName` approach will work. See this link on namescoping... http://msdn.microsoft.com/en-us/library/ms746659.aspx – WPF-it May 17 '12 at 05:06
  • I have updated my post to reflect the current issues I'm having with your suggested solution... could you please take a look and let me know if it makes sense to you? – Bryce Martin May 22 '12 at 14:49
  • I guess then the proxy element approach will work. See the link I have provided. http://stackoverflow.com/questions/7711275/bind-datagrid-column-visibility-mvvm/7711611#7711611 – WPF-it May 22 '12 at 17:49