1
votes

I have a UserControl that is used in a parent control in this way:

<Views:TranslationTextInput  Translation="{Binding SelectedEntity.Name}"/>

The parent control DataContext is a ViewModel containing the SelectedEntity Property.

In my child UserControl I define a new ViewModel as the DataContext:

    <UserControl.DataContext>
      <vm:TranslationTextInputViewModel x:Name="vm"></vm:TranslationTextInputViewModel>
    </UserControl.DataContext>

In the code behind I have:

    public static readonly  DependencyProperty TranslationProperty = DependencyProperty.Register("Translation", typeof(Translation),typeof(UserControl));

    // .NET Property wrapper
    public Translation Translation
    {
        get { return (Translation)GetValue(TranslationProperty); }
        set { SetValue(TranslationProperty, value); }
    }


 public TranslationTextInput(){
     InitializeComponent();
     DataContext = new TranslationTextInputViewModel();
     SetBinding(TranslationProperty,   new Binding { Path = new PropertyPath     ("Translation"), Mode = BindingMode.OneWayToSource });

When executed I get a Binding error:

System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedEntity' property not found on 'object' ''TranslationTextInputViewModel' (HashCode=49954236)'. BindingExpression:Path=SelectedEntity.Name; DataItem='TranslationTextInputViewModel' (HashCode=49954236); target element is 'TranslationTextInput' (Name='InputControl'); target property is 'Translation' (type 'Translation')

It seems the SelectedEntity is looked up on the child UserControl's Viewmodel, but the Property of the parent ViewModel should be used. How can I solve this?

Edit:

    public TranslationTextInputViewModel()
    {
        //EnglishTranslation = tranlsations["en"];
    }

    public string EnglishTranslation
    {
        get
        {
            if (!Translation.TranslationDict.ContainsKey(new CultureInfo("en").LCID))
                Translation.Translations.Add(new TranslationItem() { Text = "", Lcid = new CultureInfo("en").LCID });
            return Translation.TranslationDict[new CultureInfo("en").LCID].Text;
        }
        set
        {
            Translation.TranslationDict[new CultureInfo("en").LCID].Text = value;
        }

    }

    public string SelectedTranslation
    {
        get
        {
            if (!Translation.TranslationDict.ContainsKey(_selectedLanguage))
                Translation.Translations.Add(new TranslationItem() { Text = "", Lcid = _selectedLanguage });
            return Translation.TranslationDict[_selectedLanguage].Text;

        }

        set
        {
            Translation.TranslationDict[_selectedLanguage].Text = value;
        }
    }

    private Translation _translation;

    public Translation Translation
    {
        get
        {
            if (_translation == null)
                _translation = new Translation();
            return _translation; }
        set { _translation = value; }
    }

    private int _selectedLanguage;
    public int SelectedLanguage
    {
        get
        {
            return _selectedLanguage;
        }
    }

    public List<CultureInfo> AvailableLanguages
    {
        get
        {

            return (from x in PqsLocalization.AvailableLanguages where x.Name != "en" select x).ToList();
        }
    }

    public RelayCommand<int> LanguageChanged { get; private set; }


    private void LanguageChangedExecute(int lang)
    {
        _selectedLanguage = lang;
        RaisePropertyChanged("SelectedLanguage");
        RaisePropertyChanged("SelectedTranslation");
    }
4

4 Answers

4
votes

You really shouldn't ever set the DataContext of a UserControl from inside the UserControl. By doing so, you are preventing any other DataContext from getting passed to the UserControl, which kind of defeats one of WPF's biggest advantages of having separate UI and data layers.

When your UserControl is being created, you are setting the DataContext to a new TranslationTextInputViewModel, and TranslationTextInputViewModel does not have a property called SelectedEntity, so your binding fails.

My suggestion? Don't set the DataContext in the UserControl.

If you want a specific ViewModel to be used for a specific UserControl, add that to your ParentViewModel and pass it in as the DataContext, such as this:

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:TranslationTextInputViewModel}">
        <Views:TranslationTextInput />
    </DataTemplate>
</Window.Resources>

or this:

<Views:TranslationTextInput 
    DataContext="{Binding SelectedTranslationTextInputViewModel}" />

Or if your ViewModel contains functionality specific to the UserControl itself and should not be part of your application layer, put that code in the code-behind the UserControl and get rid of the ViewModel altogether.

1
votes

Try this:

<Views:TranslationTextInput  Translation="{Binding SelectedEntity.Name}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" /> 
1
votes

Once you set the DataContext your bindings will use it, so I would expect this behaviour -- looking for SelectedEntity.Name on the TranslationTextInputViewModel.

There are a few ways to get this working. Personally, I like to model these relationships in the view-models themselves (view-models with view-model properties), but in this situation I'd probably try this, as unpleasant as it feels:

<Views:TranslationTextInput 
    Translation="{Binding DataContext.SelectedEntity.Name,
                  RelativeSource={RelativeSource FindAncestor,
                                  AncestorType=ParentControlType}}" />
0
votes

This is because you set the TranslationTextInput.DataContext to TranslationTextInputViewModel in the constructor.