2

I have a context menu on a textbox that I'm trying to bind the isChecked property to one of the properties in that textbox's datacontext with a value converter.

The problem I'm having is very similar, I beleive, to this post... WPF MenuItem.Command binding to ElementName results to System.Windows.Data Error: 4 : Cannot find source for binding with reference

In there Aran Mulholland suggests 3 different solutions. The one I've been trying to get working, and have yet to see an actual working example of, is #2. I think this is the most MVVM friendly approach, and to that end the most elegant... then again, I'm pretty new to this.

Here is my xaml

<DataTemplate x:Key="SFTemplateWithContextMenu">
        <TextBlock x:Name="Field" Text="{Binding Path=FieldName}" >
         <TextBlock.ContextMenu>
              <!--<ContextMenu PlacementTarget="{Binding ElementName=Field}" > -->
                    <ContextMenu DataContext="{Binding RelativeSource={RelativeSource Mode=Self}, Path=PlacementTarget.DataContext}">
                    <MenuItem  Header="Rename..." />
                    <MenuItem Header="Field Type">
                        <MenuItem.Resources>
                            <Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
                        </MenuItem.Resources>
                    <!--<MenuItem  Header="String" IsCheckable="True" IsChecked="{Binding Path=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  Header="String" IsCheckable="True" IsChecked="{Binding Path=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>

I'm using the DataTemplate to populate the following ListBox...

<ListBox DnD:DragDropHelper.IsDragSource="True"   Name="sourceFieldsLB" Height="238" HorizontalAlignment="Left" Margin="20,286,0,0" VerticalAlignment="Top" Width="150" ItemTemplate="{StaticResource SFTemplateWithContextMenu}"  ItemsSource="{Binding Selection.SourceFields, Mode=TwoWay}" AllowDrop="True" >

I downloaded Snoop to look inside and see just what is going on. I've tried a couple of different methods with varying degrees of failure.

The commented out piece is the previous way I was trying to accomplish my goal. The problem there is that i was getting the error... "cannot find source for binding with reference 'elementname=Field'" But the TextBlock shows using Snoop that its name IS Field.

In the current way I'm doing it I can see that the textblock has a local namescope and its name is Field - which is what I would expect and want. The ContextMenu value shows that it has a ContextMenu with 2 items... which is correct. So I click on ContextMenu to see how things look and low and behold the ContextMenu has no DataContext.

Any help and direction on this would be greatful. I'm not sure exactly what I'm missing here. I've looked around and whenever someone seems to get close to having this work they mention they found some "workaround" or other way to do it and never get it working. This HAS to have the ability to work... I'm just too new to this to see the missing piece.

I know it can be done to do a true MVVM way... right?

Community
  • 1
  • 1
Bryce Martin
  • 129
  • 3
  • 17

1 Answers1

4

Bryce, the main problem is that ContextMenus are not part of the standard visual tree and the only real connection they have is via the PlacementTarget property. So, it's normally best to hook that up as soon as possible. So...

Given a ViewModel

public class ViewModel
{
    public string Field { get; set; }
    public string FieldType { get; set; }
}

And a MainWindow

<Window x:Class="ContextMenuSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBlock Text="{Binding Field}">
            <TextBlock.ContextMenu>
                <ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}">
                    <MenuItem Header="{Binding FieldType}" />
                </ContextMenu>
            </TextBlock.ContextMenu>
        </TextBlock>
    </Grid>
</Window>

And an app.xaml.cs

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;

namespace ContextMenuSample
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            var shell = new MainWindow();
            shell.DataContext = new ViewModel { Field = "FirstName", FieldType = "String" };
            shell.Show();
        }
    }
}

You can see that the DataContext for the ContextMenu gets hooked up correctly by the line

<ContextMenu DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget.DataContext}">

and you should get a nicely behaved ContextMenu that talks to the underlying VieWModel.

Barracoder
  • 3,696
  • 2
  • 28
  • 31