Привязка ContentControl к глубокому пути в WPF

Приложение, которое я сейчас пишу, использует MVVM с шаблоном ViewModel-first. У меня есть XAML, подобный следующему:

<ContentControl Content="{Binding FooViewModel.BarViewModel.View, Mode=OneWay}"/>

Каждая виртуальная машина представляет собой DependencyObject. Каждое свойство является DependencyProperty. В зависимости от состояния приложения значение свойства BarViewModel элемента FooViewModel может измениться, что приведет к изменению значения свойства View. К сожалению, когда это происходит, новый вид не отображается, а остается старый.

Это очень расстраивает. Я думал, что если какая-либо часть выражения пути изменится, привязка будет обновлена, но, похоже, это не так. Когда я использовал более поверхностные выражения пути, такие как FooViewModel.View, и изменил значение свойства FooViewModel, это обновило ContentControl, к которому оно привязано, но не в этом случае.

Если ваше решение состоит в том, что я отказываюсь от ViewModel-first, это не вариант, хотя я ценю ваш совет. Я должен заставить это работать как есть.

ПОЯСНЕНИЕ

Это вопрос о привязке данных, а не о MVVM или о том, как его реализовать. Вы можете безопасно игнорировать аспекты MVVM, если это поможет вам подумать о проблеме или если у вас есть другое представление о том, как следует реализовать MVVM. Это большой существующий проект, в котором нельзя изменить шаблон проектирования MVVM. (Слишком поздно для этого.)

Итак, с учетом сказанного, правильный вопрос, на который нужно ответить, заключается в следующем:

Учитывая выражение пути привязки, в котором каждый элемент является DependencyProperty, а конечное свойство является представлением, привязанным к ContentControl, почему изменение свойства в середине пути не приводит к обновлению привязки?


person Gregory Higley    schedule 31.01.2012    source источник
comment
вы должны опубликовать свои FooViewModel, BarViewModel и View. Я думаю, вы кое-что путаете.   -  person blindmeis    schedule 31.01.2012
comment
Можно ли использовать контейнеры для управления глубокими путями? Другими словами, привяжите свой контент в ContentControl к представлению, затем окружите его сеткой с контроллером домена, привязанным к BarViewModel, и окружите его сеткой с контроллером домена, привязанным к FooViewModel. Я не пробовал эту привязку к свойствам зависимостей (поэтому это комментарий, а не ответ), но это может сработать.   -  person HiredMind    schedule 25.04.2012
comment
У меня такая же проблема, когда я пытаюсь установить xaml на путь внутреннего класса на моем mvvm. как и в моем mvvm, нужно RaisePropertyChange(MvvmClass.InnerClass.PropertyName), это не сработает.   -  person ramnz    schedule 27.12.2012


Ответы (2)


Хотя я ожидаю, что это сработает, с вашим подходом есть несколько проблем.

Во-первых, ваши модели представления не должны использовать DependencyObject или DependencyProperty, это привязывает их к WPF. Вместо этого они должны реализовать INotifyPropertyChanged. Это позволяет повторно использовать ваши модели представлений в других технологиях представления, таких как Silverlight.

Во-вторых, ваши модели представления не должны иметь ссылок на ваши представления, поэтому вам не нужно требовать свойства View в ваших моделях представлений.

Я бы серьезно подумал об использовании MVVM-фреймворка для композиции представлений — например, Caliburn.Micro делает разработку модели представления первой. чрезвычайно прост и уже предоставляет базовый класс модели представления, который реализует INotifyPropertyChanged, и механизм для построения композиций представлений с соглашениями.

т.е. у вас может быть модель представления проводника со свойством ActiveItem, и вы просто помещаете ContentControl в свое представление с тем же именем, что и свойство:

<ContentControl x:Name="ActiveItem" />

Вы можете использовать метод ActivateItem() для изменения текущего активного элемента.

Caliburn.Micro также имеет множество других функций, таких как возможность разместить элемент управления Button с x:Name="Save" в вашем представлении, а ваш метод Save в вашей модели представления будет автоматически вызываться при нажатии кнопки.

person devdigital    schedule 31.01.2012
comment
Спасибо, но у меня нет проблем с MVVM. Это большой существующий проект, в котором все остальные аспекты работают нормально. Это WPF, и мы не планируем использовать Silverlight. Свойства являются свойствами зависимости, поскольку наш базовый класс использует ViewModel‹T› CSLA.NET, который является объектом зависимости. Поэтому мы не можем принять ни одно из ваших предложений и должны работать с ним как есть. - person Gregory Higley; 31.01.2012
comment
Да, я не знаю, почему Рокки выбрал такой подход, мы взяли его базу модели представления как пример работы с CSLA и написали свою, которая использует только INotifyPropertyChanged. Сказав это, база модели представления Рокки также реализует INotifyPropertyChanged, хотя и не особенно элегантным образом, поскольку он реализует только вспомогательный метод OnPropertyChanged на основе строки. Итак, вы можете попробовать реализовать свойства BarViewModel и View как свойства INotifyPropertyChanged вместо свойств DependencyProperty и посмотреть, работает ли это. - person devdigital; 31.01.2012
comment
Я попробую это. Кстати, вы заявляете, что наши модели представлений не должны иметь ссылок на наши представления, но это не то, как работает VM-first с внедрением зависимостей. Сначала в виртуальной машине ваше представление внедряется в конструктор как интерфейс, а затем отображается как свойство. Это прекрасно работает и полностью проверяемо, так как нет прямой ссылки на само представление. У меня нет планов менять его, потому что я нахожу его чрезвычайно гибким подходом. - person Gregory Higley; 31.01.2012
comment
Оказывается, INotifyPropertyChanged было решением. Очаровательный. Я не знал, что вы можете смешивать и сочетать INotifyPropertyChanged со свойствами зависимостей в одном и том же объекте. - person Gregory Higley; 31.01.2012
comment
Я думаю, что Рид очень хорошо отвечает на вопрос здесь - stackoverflow.com/a/3670669/248164. Вы должны спросить, почему ваша модель представления требует ссылки на представление. Предположительно, вы предоставляете определенные типы элементов управления в своем интерфейсе представления, что опять же, вероятно, связывает ваши модели представления с WPF и смешивает проблемы представления с кодом модели представления. - person devdigital; 31.01.2012
comment
Я прочитал пост Рида, и он не относится ко мне. У меня нет никакой логики представления в моих моделях представлений, которые чисты и проверяемы. На мои представления ссылаются исключительно через абстрактные интерфейсы, которые обычно пусты. Они используются исключительно для ViewModel-first DI и привязки. - person Gregory Higley; 31.01.2012
comment
давайте продолжим это обсуждение в чате - person Gregory Higley; 31.01.2012
comment
я проголосовал за вас, потому что ответ с -1 выглядит не очень красиво :) - person blindmeis; 31.01.2012

Каждая виртуальная машина является DependencyObject. Каждое свойство является DependencyProperty.

Зачем? модель представления должна быть простым классом с INotifyPropertyChanged, а свойства должны быть простыми свойствами.

и если вы хотите, чтобы ваша другая модель представления отображалась по-другому, вы должны использовать DataTemplate.

 <Window>
  <Window.Resources>
    <DataTemplate DataType="{x:Type local:MyViewModelA}>
     <MyViewA/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type local:MyViewModelB}>
     <MyViewB/>
    </DataTemplate>
   </Windows.Resources>
   <Grid>
     <ContentControl Content="{Binding MyActualVM}"/>
   </Grid>
  </Window>

РЕДАКТИРОВАТЬ: кстати, вы всегда привязываетесь к последнему свойству: FooViewModel.BarViewModel.View --> поэтому INotifyPropertyChanged (если он поднят) просто работает для .View

EDIT2: другой подход может заключаться в том, чтобы получить BindingExpression вашего элемента управления содержимым и вызвать его.

System.Windows.Data.BindingExpression expr = //get it from your contentcontrol
expr.UpdateTarget();

EDIT3: и простой способ mvvm - просто используйте INotifyPropertyChanged

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.MyFooVM = new FooVM();
        this.MyFooVM.MyBarVM = new BarVM(){View = "erster"};

        this.DataContext = this;

    }

    public FooVM MyFooVM { get; set; }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.MyFooVM.MyBarVM = new BarVM(){View = "zweiter"};
    }
}

public class INPC : INotifyPropertyChanged
{
    #region Implementation of INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropChanged(string property)
    {
        var handler = PropertyChanged;

        if(handler != null)
            handler(this, new PropertyChangedEventArgs(property));
    }

    #endregion
}

public class FooVM:INPC
{
    private BarVM _myBarVm;
    public BarVM MyBarVM
    {
        get { return _myBarVm; }
        set { _myBarVm = value;OnPropChanged("MyBarVM"); }
    }
}

public class BarVM : INPC
{
    private string _view;
    public string View
    {
        get { return _view; }
        set { _view = value;OnPropChanged("View"); }
    }
}
person blindmeis    schedule 31.01.2012
comment
Как я сказал DevDigital, это большой существующий проект, в котором все остальные аспекты работают нормально. Свойства являются свойствами зависимости, поскольку наш базовый класс использует ViewModel‹T› CSLA.NET, который является объектом зависимости. Поэтому мы не можем принять ни одно из ваших предложений и должны работать с ним как есть. Я понимаю, как работает привязка данных и что вам нужно привязать. Это не моя проблема. Пожалуйста, перечитайте мой вопрос. - person Gregory Higley; 31.01.2012
comment
Что касается mvvm и модели представления в частности, мне не нравится, что модель представления наследуется от пространства имен system.windows. модульное тестирование намного чище без этого пространства имен. но это только моя мысль. Я имею в виду, что кто-то (возможно, Джош Смит) написал, что если у вас есть System.Windows в вашем использовании - это больше не чистая модель просмотра. - person blindmeis; 31.01.2012