Потребителски контрол, използващ грешен контекст на данни

Имам 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
Хей, благодаря за твоето обяснение. Моделът на изглед щеше да съдържа само функционалност на контролата (Избран език ...). Бихте ли ми предложили просто да създам proopterys в код отзад, да внедря интерфейса INotifyPropertyChanged и да обвържа полетата си с код отзад, без да задавам DataContext? - person TheJoeIaut; 10.10.2012
comment
Ако други части на приложението могат да повлияят на тази стойност, бих направил тези DependencyProperties, така че всеки, който използва контролата, да може да зададе стойността. Ако те се използват само вътрешно в рамките на Control, бих поставил под съмнение необходимостта от тях изобщо, защото звучи така, сякаш те трябва или да са статични, или да се основават на някакъв UI обект в UserControl и можете да се свържете директно с UI елемент, съдържащ свойството. - 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. Това, което не разбирам, е как трябва да изглежда без 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