Usercontrol с использованием неправильного Datacontext

У меня есть UserControl, который используется в родительском элементе управления следующим образом:

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

DataContext родительского элемента управления - это ViewModel, содержащая свойство SelectedEntity.

В моем дочернем UserControl я определяю новую ViewModel как DataContext:

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

В коде позади у меня есть:

    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 });

При выполнении я получаю ошибку привязки:

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')

Кажется, что SelectedEntity просматривается в Viewmodel дочернего UserControl, но следует использовать Property родительской ViewModel. Как я могу это решить?

Редактировать:

    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");
    }

person TheJoeIaut    schedule 10.10.2012    source источник


Ответы (4)


Вы действительно никогда не должны устанавливать DataContext из UserControl внутри UserControl. Поступая таким образом, вы предотвращаете передачу любых других DataContext в UserControl, что сводит на нет одно из самых больших преимуществ WPF - наличие отдельных уровней пользовательского интерфейса и данных.

Когда ваш UserControl создается, вы устанавливаете DataContext на новый TranslationTextInputViewModel, а TranslationTextInputViewModel не имеет свойства с именем SelectedEntity, поэтому ваша привязка не выполняется.

Мое предложение? Не устанавливайте DataContext в UserControl.

Если вы хотите, чтобы конкретная ViewModel использовалась для определенного UserControl, добавьте ее в свой ParentViewModel и передайте как DataContext, например:

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

или это:

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

Или, если ваш ViewModel содержит функции, специфичные для самого UserControl, и не должен быть частью вашего уровня приложения, поместите этот код в код программной части UserControl и полностью избавьтесь от ViewModel.

person Rachel    schedule 10.10.2012
comment
Привет, спасибо за объяснение. Viewmodel содержала бы только функциональные возможности элемента управления (Selected Language ...). Не могли бы вы предложить мне просто создать свойства в исходном коде, реализовать интерфейс INotifyPropertyChanged и привязать мои поля к исходному коду без установки DataContext? - person TheJoeIaut; 10.10.2012
comment
Если другие части приложения могут влиять на это значение, я бы сделал эти DependencyProperties так, чтобы любой, кто использует элемент управления, мог установить значение. Если они используются только внутри элемента управления, я бы поставил под сомнение необходимость их вообще, потому что похоже, что они должны быть либо статическими, либо основываться на каком-либо объекте пользовательского интерфейса внутри UserControl, и вы можете напрямую привязаться к пользовательскому интерфейсу элемент, содержащий свойство. - person Rachel; 10.10.2012
comment
Другие части Приложения не могут изменять эти свойства. Хотя я также не могу напрямую подключаться к компонентам пользовательского интерфейса. Элемент перевода (свойство зависимости) имеет словарь, в котором хранятся разные переводы. В моем пользовательском элементе управления есть текстовое поле и набор кнопок (по одной для каждого языка). Если кнопка нажата, она должна быть проверена, если она уже является частью словаря, и если нет, то добавляется. Затем отображается перевод для выбранного языка. Что вы предлагаете по этому поводу? Заранее спасибо? - person TheJoeIaut; 10.10.2012
comment
@TheJoeIaut Я не уверен в вашей конкретной ситуации, но лично, если бы свойство Translation, передаваемое в UserControl, содержало Словарь языков, я бы написал языковые кнопки в UserControl, используя ItemsControl, привязанный к этому списку. Если словарь языков должен быть определен в UserControl, я бы полностью удалил его из объекта Transation и поместил в код программной части UserControl's. - person Rachel; 10.10.2012
comment
(продолжение) Или, если языки действительно должны быть определены как в объекте Translation, так и в UserControl, я бы либо добавил функциональность в программный код UserControl, либо добавил команду AddLanguage к объекту Translation, к которому могут быть привязаны ваши кнопки пользовательского интерфейса. , и передайте ему язык, который нужно добавить, если необходимо, как CommandParameter. Например, у меня может быть объект Translation, содержащий OriginalText, TranslatedText (привязанный к вашему TextBox), ObservableCollection<Language>, ICommand AddLanguage(language) и ICommand TranslateText(language) - person Rachel; 10.10.2012
comment
Я разместил свою старую модель просмотра в стартовом посте. Флаги уже отображаются через ItemsControl и были привязаны к LanguageChanged в ViewModel. Я не понимаю, как это должно выглядеть без модели просмотра. Будет ли текстовое поле по-прежнему привязано к текущему переводу (свойство в коде позади?)? Должна ли команда быть определена таким же образом в коде позади? - person TheJoeIaut; 10.10.2012
comment
Из чего состоит ваше Translation свойство зависимостей? Если это всего лишь строка, которую можно перевести, я бы поместил все остальное в код программной части и полностью избавился от ViewModel. Свяжите ItemsControl с ObservableCollection<Language> в конструкторе, который определяет доступные языки, и заставьте Button перейти к команде, которая переводит свойство зависимости строки на выбранный язык, и установите эту строку непосредственно на TranslatedLanguageTextBox.Text - person Rachel; 10.10.2012

Попробуй это:

<Views:TranslationTextInput  Translation="{Binding SelectedEntity.Name}" DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}" /> 
person Danilo Vulović    schedule 10.10.2012

Как только вы установите DataContext, ваши привязки будут использовать его, поэтому я ожидал такого поведения - поиск SelectedEntity.Name на TranslationTextInputViewModel.

Есть несколько способов заставить это работать. Лично мне нравится моделировать эти отношения в самих моделях представления (модели представления со свойствами модели представления), но в этой ситуации я бы, вероятно, попробовал это, как бы неприятно это ни казалось:

<Views:TranslationTextInput 
    Translation="{Binding DataContext.SelectedEntity.Name,
                  RelativeSource={RelativeSource FindAncestor,
                                  AncestorType=ParentControlType}}" />
person Jay    schedule 10.10.2012
comment
Имеет ли модель представления (многоразового) пользовательского контроля, как я правильно сделал? Такое чувство, что так быть не должно ... - person TheJoeIaut; 10.10.2012

Это связано с тем, что вы установили для TranslationTextInput.DataContext значение TranslationTextInputViewModel в конструкторе.

person Miklós Balogh    schedule 10.10.2012