5

Навели меня на мысль, что весь мой код является "говнокодом", потому что мои WPF приложения не соответствуют паттерну MVVM. Я начал про это читать, но в интернетах информация изложена слишком сложно для моего понимания. Поэтому я задам несколько вопросов, которые откроют мне глаза (я надеюсь)

  • Допустим, я создаю UserControl. При этом UserControl.xaml является View, а UserControl.xaml.cs - ViewModel? Или ViewModel - это еще один дополнительный класс, например UserControlViewModel? Я пока склоняюсь ко второму варианту, но не уверен.

  • Если в прошлом вопросе правильным является второй вариант, то где мне создавать DependenceProperties? В UserControl.xaml.cs или в UserControlViewModel? Я пока склоняюсь к первому варианту.

  • Если UserControl содержит команды, которые надо добавить к главному меню окна, в котором его расположили, то как это лучше осуществить, не отклоняясь от MVVM? Причем сделать это надо с иерархией. Первое, что приходит на ум - создать в ресурсах UserControl новое Menu. но при слиянии иерархию надо сохранить. То есть если в главном окне уже есть меню "Файл", и оно используется в контроле, то элементы контрола должны добавиться к элементам этого меню в окне без создания еще одного меню "Файл".

UPD:

И еще, что такое Model?

UPD2:

К примеру, мне надо вывести в ListBox коллекцию экземпляров класса MyClass

public class MyClass
{
    public string Prop1 { get; set; }
    public string Prop2 { get; set; }
}

в виде Prop1(Prop2)

Как мне поступить, чтобы соответствовать MVVM?

Раньше я делал так

<ListBox ItemsSource="{Binding MyClassCollection}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock .......... // короче тут биндинги к Prop1 и Prop2
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Получается, что это неверный подход? Или что?

UPD3:

Еще вопрос. Вью и вью-модель связываются, например, через DataСontext вьюшки (других способов я не знаю). В этом случае вью-модель не имеет параметров у конструктора. А как тогда связать вью-модель и модель?

UPD4:

У меня еще вопрос. Вот есть у меня модель, вью-модель и вью. Вью-модель подписана на изменение свойств модели (хоть через PropertyChanged, хоть через отдельные события). Тогда получится следующее. Во вьюшке чего то меняется (пользователь изменил текст в текстовом поле). Через биндинг это передается в сеттер вью-модели, в которой написано следующее _model.Property = value. Но я ведь подписан на изменение свойств модели. Получается, что модель после изменения Property сообщит вью-модели, что ее свойство изменилось, вью-модель вызовет PropertyChanged и во вьюшке применится это значение в текстовом поле. По сути ничего страшного не произойдет, кроме того, что программа выполнит ЛИШНИЕ ДЕЙСТВИЯ

iRumba
  • 5,946
  • Уточню. 1) ViewModel присваивается DataContext-у. 3) Во вью-модели я думаю можно определить много реализаций ICommand, далее кнопки и пункты меню привязывать к этим командам. Но тут (п.3) возможны разные механизмы. Если будет время, то покажу один вариант, как меня научили собирать меню. – Andrey K. Oct 30 '15 at 08:26
  • @AndreyKomissarov, буду очень признателен ) – iRumba Oct 30 '15 at 08:27
  • UPD2 - все верно. – Vlad Oct 30 '15 at 09:25
  • Можно еще через DataTemplateSelector, например. – Vlad Oct 30 '15 at 09:39
  • @Vlad, но как же ДОП2 может быть верным? Ведь там есть модель (MyClass) и View. а как же ViewModel? – iRumba Oct 30 '15 at 09:49
  • Не обязательно лепить ViewModel для каждой Model. Если модели достаточно для представления, то не нужно ничего придумывать. Если же становятся нужны какие-то доп. свойства или команды, то тогда уже нужно создавать модель представления. – Vlad Oct 30 '15 at 09:54
  • upd3 - я думаю, что ViewModel - обертка над Model. То есть, вью-модель содержит модель среди своих свойств (мемберов) – Andrey K. Oct 30 '15 at 11:23
  • @iRumba, написал как собирать меню. Не много не по теме ответил, но все равно там более по теме, чем в этом вопросе. – Andrey K. Oct 30 '15 at 12:16
  • @iRumba: Возможно, вам поможет это: http://ru.stackoverflow.com/a/379331/10105 – VladD Oct 30 '15 at 13:23

1 Answers1

4
  1. Модель представления - это еще один дополнительный класс. Представление не обязательно должно быть UserControl'ом. Это может быть, например, словарь ресурсов с шаблоном данных для модели представления. Тогда ни о каком code-behind речи быть не может.
  2. Свойства зависимости нужно создавать в code-behind контрола (т.е. UserControl.xaml.cs). Это часть представления и модели представления о них знать не нужно.
  3. UserControl не должен содержать команды. Он должен привязываться к командам модели представления. Если нужна иерархия, то делайте иерархию команд в модели представления плагина (предполагаю, что вы делаете что-то плагинообразное) и сливайте их с командами модели представления главного окна. Еще я бы посоветовал избавиться от иерархии команд и подумать над тем, как их можно представить плоским списком.

UPD

Я согласен с Discord, потому и посоветовал делать плоский список. Это может быть набор команд в модели представления: OpenFileCommand, SaveFileCommand и т.д. А в представлении это уже будет иерархией: меню файл содержит пункты "открыть" и "сохранить".

Если у вас ситуация, когда пол проекта написана на WinForms, а половина на WPF, то, возможно, ваше решение вполне подходит. Просто после переноса проекта нужно не забыть переделать эту часть.

Когда мы перетаскивали проект в WinForms на WPF, то сначала изменили главное окно приложения: оно научилось работать с плагинами и формировать меню из них. Все компоненты на WinForms мы обернули в WindowsFormsHost А потом уже переделывали плагины на WPF по одному.

Модель - это то, для чего вы делали модель представления (например, какой-нибудь объект из БД). Примерно так: модель - то, что нужно показать пользователю; представление - то, что видит пользователь; модель представления - связующее звено с командами и доп. свойствами, необходимыми для корректной работы представления.

UPD2

С ходу довольно сложно придумать нормально решение, а на практике с именно такой задачей я не сталкивался. Например, вы можете сделать пачку интерфейсов типа:

// команды, расширяющие пункт меню "Файл".
interface IFileCommandsProvider
{
    List<MyCommand> Commands { get; }
}

А потом: если вкладка реализует этот интерфейс, то добавить перечень команд в меню.

Либо подумать над вариантом с чем-то вроде RegionManager'а из Prism.

UPD4

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

public int Property
{
    get { return _model.Property; }
    set
    {
        if (_model.Property != value)
        {
            _model.Property = value;
            OnPropertyChanged();
        }
    }
}

Тогда всю работу со свойством Property внутри модели представления нужно будет осуществлять через свойство модели представления. Т.е. кода _model.Property = value больше нигде не должно встречаться. Тогда вам не нужно будет подписываться на Model.PropertyChanged("Property").

Vlad
  • 3,104
  • http://ru.stackoverflow.com/questions/462453/%D0%9A%D0%B0%D0%BA-%D0%B2-%D0%BC%D0%B5%D0%BD%D1%8E-%D0%B4%D0%BE%D0%B1%D0%B0%D0%B2%D0%B8%D1%82%D1%8C-%D1%81%D0%B5%D0%BF%D0%B0%D1%80%D0%B0%D1%82%D0%BE%D1%80-%D0%B5%D1%81%D0%BB%D0%B8-%D0%BE%D0%BD%D0%BE-%D0%B7%D0%B0%D0%BF%D0%BE%D0%BB%D0%BD%D1%8F%D0%B5%D1%82%D1%81%D1%8F-%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%D0%B8-%D0%BE%D1%82-icommand/462455?noredirect=1#comment507269_462455 посмотрите мой код и ответ пользователя Discord. Мне кажется у меня сделано именно так, как вы сейчас описали в п3, но я не уверен что правильно этот п3 понял ) – iRumba Oct 30 '15 at 08:32
  • А как без иерархии? Перекладываем крупный проект на WPF из WinForm. Интерфейс меняется, но если мы резко изменим главное меню, то пользователи засыплют нас гневными письмами, что ПО сломалось, пропали пункты меню и тд. Такое уже было ) – iRumba Oct 30 '15 at 08:35
  • Добавил еще пункт к вопросу – iRumba Oct 30 '15 at 08:45
  • Дополнил ответ. – Vlad Oct 30 '15 at 09:01
  • Но когда я строю меню в окне, откуда я знаю, какие команды сможет выполнять контент таба? Это окно умеет OpenFileCommand и я, как разработчик окна об этом знаю, но табы делают другие программисты. Им что, каждый раз просить меня добавить поддержку новой команды? Или я снова не правильно понял? – iRumba Oct 30 '15 at 09:01
  • дополнил вопрос ) – iRumba Oct 30 '15 at 09:16
  • К ДОП2. У меня в данный момент так и сделано, только у меня не лист команд, а дерево. То есть MyCommand реализует интерфейсы ICommand и ICollection. А контент табов, реализует интерфейс с методом MyCommand GetCommand(). Дополнил вопрос – iRumba Oct 30 '15 at 09:38
  • Ну мне предложенное мной же решение тоже не очень нравится. Но тут нужно глубже тему ковырять, чтобы что-то толкового вышло. Меня смущает дерево неограниченной вложенности. На практике не бывает таких пунктов меню. Поэтому предложил их задавать списками (возможно вложенными). – Vlad Oct 30 '15 at 09:49
  • Ну даже если вложенность ограничена. В любом случае, таб может формировать отчеты, изменять свои фильтры, менять свои настройки отображения. И все эти команды должны быть вложены в разные меню. Причем если окно тоже умеет создавать отчеты, то меню Отчеты будет состоять из собственных отчетов и отчетов открытого таба. А меню Фильтры будет добавлено. И все это будет находиться внутри обобщенного корневого узла. Как само меню состоит из Menu, в которое вложены MenuItem. К тому же в этом случае я обхожусь всего одним методом TheMyCommand.Merge(MyCommand) – iRumba Oct 30 '15 at 09:55
  • Модель можете создавать как угодно: из ресурсов, конструктора, настроек. Еще неплохо бы определиться с тем, что сначала создается в вашем ПО: представление или модель представления. Это тема для отдельного холивара. Я стараюсь придерживаться принципа ViewModel first. – Vlad Oct 30 '15 at 09:57
  • Добавил еще вопрос. Ответьте, если можете. – iRumba Nov 02 '15 at 04:50