Несколько ViewModels в MainWindowViewModel

У меня наверняка возникнет глупый и много раз разъясненный вопрос, но простого гугления ответа на свой вопрос я не нашел. (Я только новичок в C#, так что не судите строго, ООП мне все еще немного сложно понять). У меня есть приложение WPF MVVM, внутри которого я хотел бы реализовать несколько ViewModel, для которых есть один View. Я хочу иметь возможность изменить SelectedViewModel на SelectedViewModel_One и SelectedViewModel_Two, где внутри будут реализованы одни и те же методы. (как в примере).

 public  class ViewModel : INotifyPropertyChanged, IDisposable
    {
        private bool _Disposed;
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null){}
        protected virtual bool Set<T>(ref T field, T value, [CallerMemberName] string PropertyName = null){}
        public void Dispose(){}
        protected virtual void Dispose(bool Disposing){}

    }


public partial class SelectedViewModel_One : ViewModel
    {
        public void Move(){}
    }

public partial class SelectedViewModel_Two : ViewModel
    {
        public void Move(){}
    }


     internal class MainWindowViewModel : ViewModel
     {
        private ViewModelAbstractBaseOrSmthLikeThat selectedViewModel = null;
        public ViewModelAbstractBaseOrSmthLikeThat SelectedViewModel
        {
             get { return selectedViewModel; }
             set { Set(ref selectedViewModel, value); }
        }
     }

     --------------
     --XAML
     --------------
<Window x:Class = "SampleApplication.Views.MainWindow"
        xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable = "d"
        xmlns:vm = "clr-namespace:SampleApplication.ViewModels"
        ResizeMode = "NoResize" WindowStartupLocation = "CenterScreen">

    <Window.DataContext>
        <vm:MainWindowViewModel></vm:MainWindowViewModel>
    </Window.DataContext>

    <Grid DataContext = "{Binding SelectedViewModel}">
          <!--Components and binding to SelectedViewModel -->
    </Grid>

Но как правильно организовать архитектуру приложения? Что мне нужно добавить или изменить в коде MainWindowViewModel вместо "ViewModelAbstractBaseOrSmthLikeThat"

Мне нужно работать с интерфейсами и реализовать базовый класс? Или дайте ссылку на простейший пример, как это сделать, чтобы, изменив в MainWindowViewModel - SelectedViewModel, я мог вызывать методы Move() из SelectedViewModel_One и SelectedViewModel_Two.


У меня есть несколько устройств, которые управляются через команды RS232. Все устройства имеют одинаковые функции, но команды разные. Вот почему для программы существует единый вид

SelectedViewModel_One и SelectedViewModel_Two могут наследоваться от базового класса или оба могут реализовывать интерфейс, оба подхода будут работать.
Étienne Laneville 09.06.2023 22:15

Вы должны определить Move() как член общего интерфейса или как абстрактный член общего абстрактного базового класса. Теперь, если вашей целью является предоставление одного конкретного класса модели представления, вы определяете свойство SelectedViewModel интерфейса типа или абстрактного базового класса.

BionicCode. 09.06.2023 22:17

@ÉtienneLaneville Не могли бы вы привести очень краткий пример в ответах?

Евгений Федоров 09.06.2023 22:58

В качестве альтернативы вы можете использовать композицию, а не наследование.

Andy 10.06.2023 11:24

@ Энди Не могли бы вы также привести очень краткий пример в ответах?

Евгений Федоров 11.06.2023 13:12
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вот пример того, как действовать с базовым классом:

public abstract class RS232DeviceViewModel : ViewModel
{
    public abstract void Move();
}

public class FirstDeviceViewModel : RS232DeviceViewModel
{
    public override void Move()
    {
        throw new NotImplementedException();
    }
}

public class SecondDeviceViewModel : RS232DeviceViewModel
{
    public override void Move()
    {
        throw new NotImplementedException();
    }
}

В вашем MainWindowViewModel вы должны объявить SelectedViewModel как RS232DeviceViewModel:

public class MainWindowViewModel
{
    private RS232DeviceViewModel selectedViewModel

    public RS232DeviceViewModel SelectedViewModel
    {
        get
        {
            return selectedViewModel;
        }
        set
        {
            if (selectedViewModel != value)
            {
                selectedViewModel = value;
                OnPropertyChanged();
            }
        }
    }
}

Поля и свойства автоматически инициализируются значением типа по умолчанию. Вам не нужно делать это явно.

BionicCode. 10.06.2023 00:14

@Бионический Код. да, я просто сопоставил исходное объявление в коде OP.

Étienne Laneville 10.06.2023 00:46

Подход, основанный на композиции, будет иметь функциональность в классе, который будет содержать обе модели представления. Поскольку это модели представления, класс может сам быть моделью представления, но он может быть просто создан как частный член моделей представления, а его функциональность представлена ​​​​как метод, функция или тому подобное. Последнее часто называют услугой. У вас может быть класс MoveCalculationService или класс SinalInterpretationService... например.

 public class FooViewModel : ObservableObject
 {
     public Bar SomeObject {get;set;}
     public Signal SomeSignal(param ....)
     {
     }
 }

Это можно использовать в обеих моделях просмотра

 public class SelectedViewModel_One  
 {
     public FirstDeviceViewModel (FooViewModel someMovingThing )
     {
        SomeMovingThing = someMovingThing;
     }

     // Maybe bind to SomeMovingThing.SomeProperty.
     public FooViewModel SomeMovingThing {get;set;}

     public void Move()
     {
         // Maybe reference instance of FooViewModel ;
     }
  }

Вы можете привязаться к SomeMovingThing.SomePropertyInThat. Вы можете ссылаться на него в методах в FirstDeviceViewModel. Вы можете передать уникальные параметры или функции из SelectedViewModel_One и SelectedViewModel_Two.

Композиция полезна для изоляции функциональности в повторно используемой и дружественной к внедрению зависимостей манере.

Композиция — довольно распространенный подход.

В командах, использующих SOLID, обычно ожидается, что модели представления не содержат определенной логики. Любые команды или функции обычно берутся из DI-контейнера, а это означает отдельные классы.

Возможно, вы захотите использовать одни и те же данные в нескольких моделях представления. Для такого рода вещей вы можете внедрить свою модель службы/представления, используя внедрение зависимостей, и использовать область этого для управления, когда вы получаете новый экземпляр или существующий.

Даже если вы просто пишете модульные тесты, будет проще, если вся ваша логика разбита на небольшие фрагменты и легко изолирована.

В некоторых приложениях вам может понадобиться сочетание обоих. Очень часто базовая модель представления имеет такие свойства, как, например, IsBusy и IsDirty.

Другие вопросы по теме