Форум программистов, компьютерный форум, киберфорум
C#: WPF, UWP и Silverlight
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.55/76: Рейтинг темы: голосов - 76, средняя оценка - 4.55
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
WPF

MVVM бизнес логика и Model

02.11.2021, 00:10. Показов 16238. Ответов 132
Метки mvvm (Все метки)

Студворк — интернет-сервис помощи студентам
Всем привет!
Я уже довольно долго пытаюсь разобраться что же представляет из себя архитектура MVVM, конкретно задачи View и ViewModel мне понятны. Но остается вопрос что из себя представляет Model? Много где есть определение Model = бизнес логика. Но как понять какую логику отнести к модели, а какую например к сервису.
К сожалению толкового ответа я не нашел, может плохо гуглил. В уроках обычно банальные примеры с расселением студентов по комнатам, что редко отображает реальное использование программы. На YouTube смотрел уроки по созданию MVVM приложению от Павла Шмачилина(https://www.youtube.com/watch?... P4&t=3630s), но именно про архитектуру он рассказывает не настолько подробно как хотелось бы.
Все что я понял это то что Model занимается некими правилами записи и получения данных из сервисов. Также предоставляет данные для ViewModel. Тоесть та самая бизнес логика (про нее я смотрел https://www.youtube.com/watch?v=9DW-xdwjop8) и преобразование данных. Но уверенности что я все понял как нужно, нет.
Ниже приведены примеры с которыми я сталкивался, но понятного для меня решения не нашел.

Первый пример
Пример как я это вижу:
На View есть Listbox и Button, Listbox заполняется данными из ссылки на ObservableCollection во ViewModel, которая получает их из сервиса хранящего эти данные. Button при нажатии должна очистить Listbox и заполнить его новыми данными. Model имеет ссылку на ObservableCollection из сервиса и представляет из себя только один метод который будет очищать данные в ObservableCollection и сообщать сервису о том что их надо заново сформировать и добавить в ObservableCollection.

Тоесть:
Нажатие кнопки => ViewModel вызывает в модели команду очистить список и заполнить его => Модель очищает список и просит чтоб сервис его заполнил => В ObservableCollection к которой прибинжен Listbox срабатывает уведомление об изменении коллекции и Listbox загружает новые данные из нее.

Вопрос: Нужна ли тут модель? Или ее функции может выполнить Viewmodel? Конкретно очистить список и сообщить сервису чтоб он его заполнил.


Второй пример
Второй пример:
Есть View на которой расположен Listbox в котором находятся данные о мониторах которые подключены к компьютеру. Эти данные загружает в себя сервис при запуске приложения. ViewModel имеет ссылку на данные из сервиса, View занимается их отображение в ListBox. Задача Listbox состоит не только в отображении данных, но и чтоб при двойном клике по Item в Listbox мы например отключили/включили монитор и поменяли прозрачность его иконки в Listbox.

Тоесть:
Двойной клик по Item в Listbox => ViewModel посылает в Model команду на отключение выбранного из ObservableCollection монитора => Model устанавливает для монитора флаг Activated = false и меняет прозрачность иконки монитора на Listbox (сигнализируя пользователю что монитор отключен), после этого сообщают сервису чтобы он отключил монитор.

Вопрос: Должна ли Model сообщить сервису чтоб он отключил монитор или это должна сделать ViewModel? Или тут вообще нет необходимости в Model, так как ViewModel может сама выполнить эти действия? Может быть что то из этого можно вынести в логику View?


Третий пример
Третий пример:
У нас на View есть CheckBox который при нажатии на него добавляет некоторые параметры в реестр, есть ViewModel которая при нажатии на CheckBox посылает команду в модель о том что нужно добавить параметр в реестр и проверяет успешно ли этот параметр добавился.

Тоесть:
Нажатие на CheckBox => ViewModel посылает команду о добавлении параметра, Model пытается добавить параметр в реестр и если параметр успешно добавлен то возвращает true, если доабвить не удалось то возращает false => ViewModel проверяет успешно ли добавлен параметр в реестр и устанавливает для CheckBox свойство isChecked в зависимости от полученного результата.

Вопрос: Нужно ли тут использовать Model или для этого используется сервис? Если используется Model, то что она должна в себе содержать? Логику добавления параметров в реестр? Или Model должна обращаться к сервису который будет эту логику содержать и получать от него только результат который потом передаст ViewModel?
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
02.11.2021, 00:10
Ответы с готовыми решениями:

Model в MVVM
Доброго времени суток. Начал изучать MVVM и даже что-то получается сделать, но не могу сообразить, что должно быть в Model? Программа...

MVVM Model
Здравствуйте, у меня возник вопрос что должно хранится в моделе, теорию прочитал, но на практике не понимаю. Конкретно в моем вариант...

MVVM. Общение Model с ViewModel
Занимаюсь проектом WPF, первый раз пробую MVVM-паттерн. Успешно реализовал общение View и ViewModel (бинды, команды), но никак не могу...

132
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
07.11.2021, 10:53
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Интересно выглядит, нигде не видел похожей реализации модели.
Я просто собрал "под одной" крышей несколько технологий, что бы вам чётче показать границы разделения функционала.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
MonitorDomainCollection - словарь со своими методами, тоесть место хранения всех сущностей и адаптирвоанные к ним методы из IDictonary.
Да.
По сути отличается только методом Add.
Он принимает MonitorConfigurationDomainEntity и сам формирует ключ по MonitorConfigurationDomainEntity.Name.
Другие методы тоже можно было изменить или спрятать под интерфейс.
Но для демонстрации этого достаточно.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
MonitorConfigurationDto - дата трансфер, для того чтобы менять данные в словаре либо их получать.
В данном случае тип для создания иммутабельного слепка MonitorConfigurationDomainEntity.
Но в общем случае, он не обязательно полностью по свойствам копирует Бизнес Сущность.
Может содержать часть свойств и/или данные из нескольких Бизнес Сущностей.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
MonitorConfigurationDomainEntity - бизнес сущность, но я не совсем понимаю для чего она используется когда есть DTO
Она мутабельна.
Так же в ней может быть логика по связи с другими Бизнес Сущностями, физическими устройствами, БД, WEB и т.д.
В данной задаче это не очевидно, поскольку БЛ очень простая.
Но даже в этом случае, мутабельности уже достаточно чтобы их выделить в отдельный объект.
При изменении свойства - должно подыматься событие.
Пока это делает Модель.
Это временно - так как я не понял как связаны этим мониторы с вашим сервисом.
Вообще, не понял функцию этих сервисов.

Вы интегрируйте сами эти сервисы, а потом часть логики я перенесу из Модели в MonitorConfigurationDomainEntity.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
Метод маппера CopyTo тоже не совсем понятен.
Если приходит MonitorConfigurationDto который нужно записать, то проверяется наличие MonitorConfigurationDomainEntity с таким же именем.
Если его нет то он создаётся методом Create.
Если он есть, то в него копируются данные методом CopyTo.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
Менять данные модели мы можем только ее методами.
Да.

Добавлено через 15 минут
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Посмотрел плейлист, немного разобрался с моделью и DTO
Плейлист
Моего английского не хватает для него....

Добавлено через 5 минут
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Как же те примеры со студентами в которых один класс c INPC
INPC - можно использовать.
Но в данном случае, из-за него, вы запутались в разделении функционала.
Поэтому я намеренно убрал все свойства из Модели и разделили Бизнес Сущности и DTO.
На мой взгляд, вам так проще будет понять кто за что отвечает.

Добавлено через 19 минут
Цитата Сообщение от xr_Sanya Посмотреть сообщение
MonitorDomainCollection - MonitorConfigurationDomainEntity
Чтобы вы не запутались в изменениях, я их оставил в Common.
Но это реализации Модели и их надо перенести в проект Модели как internal классы.
Если сами не сможете это сделать - я потом покажу как.

Добавлено через 7 минут
xr_Sanya, не понял предназначение вот этого свойства в интерфейсе: LaunchOnStartup.
Еще в интерфейс нужно добавить метод возвращающий ТОЛЬКО ИМЕНА всех мониторов.
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
07.11.2021, 20:10  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
не понял предназначение вот этого свойства
Я хотел бы реализовать единое место где будут хранится настройки всего приложения, тоесть например запускать ли его при старте виндовс или например сворачивать в трей при запуске, данные по мониторам. Так сказать универсальная модель всех настроек приложения, не конкретно мониторов. Но вообще это не обязательно, просто моя хотелка.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Моего английского не хватает для него....
Там более важен код, да и английский вроде как не сложный)
Кстати в яндекс браузере есть встроенный переводчик, очень удобно при просмотре уроков на английском.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Может содержать часть свойств и/или данные из нескольких Бизнес Сущностей.
Я понимаю его смысл, например для того чтобы какому либо компоненту приложения предоставить необходимые ему данные, например имя монитора и его разрешение. Для другого компонента например предоставить данные и цветовой конфигурации и имени монитора.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Вы интегрируйте сами эти сервисы
Как я уже писал это сервис настроек прложения, в нашем случае покачто в нем нет смысла, так как он по сути должен просто куда то сохранять данные и потом их получать. Например поле ColorConfiguration должно настраиваться пользователем и сохранятся в файл, откуда потом будет загружено при создании модели в соответствии с именем монитора.
Его покачто можно не использовать и имитировать загрузку настроек из файла просто в полями класса.

Второй сервис RandomizerModel должен заниматся имитацией изменения модели. Тоесть он должен заставлять модель менятся, просто для того чтобы имитировать работу другого сервиса. Например другой сервис хочет поменять поле ColorConfiguration какого либо монитора.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
INPC - можно использовать.
Но в данном случае, из-за него, вы запутались в разделении функционала.
Это да, но всеже через INPC довольно часто работают, было бы хорошо сперва доделать модель как вы хотели, а потом я создам отдельную ветку и попытаюсь сделать тоже самое, только с использование INPC.

Сейчас поправлю немного структуру проекта для того чтобы все было логически разложено по неймспейсам.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
07.11.2021, 20:52
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Сейчас поправлю немного структуру проекта для того чтобы все было логически разложено по неймспейсам.
И перенесите в MonitorModel.IMonitorModel.cs реализацию всех методов.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
Второй сервис RandomizerModel должен заниматся имитацией изменения модели.
Для имитации можно добавить таймер, который периодически будет вызывать RandomizerModel и изменять бизнес сущности.
Сможет в сами в Модель его добавить?
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
07.11.2021, 21:33  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Для имитации можно добавить таймер, который периодически будет вызывать RandomizerModel и изменять бизнес сущности.
Сможет в сами в Модель его добавить?
Там уже все сделано, только не через таймер, а циклом который из другого потока обновляет информацию
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
08.11.2021, 04:22  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
И перенесите
В последнем коммите поменял структуру проекта, вроде получилось сделать как вы написали
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
08.11.2021, 13:15
xr_Sanya, сделал Фиксацию 4a76fb80:
Реализация Модели в основном закончена.
Модель:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
using Common;
using Common.DTOs;
using Common.Entities;
using Common.Interfaces;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
 
namespace Model.Monitor
{
    // Реализация интерфейса. Так как потребители будут использовать
    // Модель ТОЛЬКО через интерфейс, то все XML-комменты к членам
    // должны наследоваться от интерфейса.
 
    // Если Модели нужнообращаться за данными к Сервису, то пусть обращается.
    // Но это должно быть инкапсулированно от Потребителей.
    public partial class MonitorModel : IMonitorModel
    {
        public event EventHandler<MonitorChangedArgs> MonitorChanged;
 
        /// <inheritdoc cref="IMonitorModel.ActivateMonitor(string)"/>
        public void ActivateMonitor(string name)
        {
            if (MonitorsDomain.TryGetValue(name, out var monitor)
                && !monitor.IsActive)
            {
                monitor.IsActive = true;
                MonitorChanged?.Invoke(this, new MonitorChangedArgs(Create(monitor), MonitorChangedEnum.Activate));
            }
        }
 
        /// <inheritdoc cref="IMonitorModel.ChangeColorConfiguration(string)"/>
        public void ChangeColorConfiguration(string name, double colorConfiguration)
        {
            if (MonitorsDomain.TryGetValue(name, out var monitor) &&
                monitor.ColorConfiguration != colorConfiguration)
            {
                monitor.ColorConfiguration = colorConfiguration;
                MonitorChanged?.Invoke(this, new MonitorChangedArgs(Create(monitor), MonitorChangedEnum.Activate));
            }
        }
 
 
        /// <inheritdoc cref="IMonitorModel.DeactivateMonitor(string)"/>
        public void DeactivateMonitor(string name)
        {
            if (MonitorsDomain.TryGetValue(name, out var monitor)
                && monitor.IsActive)
            {
                monitor.IsActive = false;
                MonitorChanged?.Invoke(this, new MonitorChangedArgs(Create(monitor), MonitorChangedEnum.Deactivate));
            }
        }
 
        /// <inheritdoc cref="IMonitorModel.DeleteMonitor(string)"/>
        public void DeleteMonitor(string name)
        {
            MonitorsDomain.Remove(name);
            // TODO: не понятно что делать при удалении
        }
 
        /// <inheritdoc cref="IMonitorModel.GetMonitor(string)"/>
        public MonitorConfigurationDto? GetMonitor(string name)
        {
            if (MonitorsDomain.TryGetValue(name, out var monitor))
            {
                return Create(monitor);
            }
            else
            {
                return null;
            }
        }
 
        /// <inheritdoc cref="IMonitorModel.GetMonitors"/>
        public ReadOnlyCollection<MonitorConfigurationDto> GetMonitors()
            => MonitorsDomain.Values.Select(Create).ToReadOnlyList();
 
        /// <inheritdoc cref="IMonitorModel.GetMonitorNames"/>
        public ReadOnlyCollection<string> GetMonitorNames()
            => MonitorsDomain.Values.Select(monitor => monitor.Name).ToReadOnlyList();
 
 
        /// <inheritdoc cref="IMonitorModel.RefreshMonitors"/>
        public void RefreshMonitors()
        {
            Dictionary<string, MonitorConfigurationDto> monitors = ModelService.LoadMonitors().ToDictionary(mont => mont.Name);
            foreach (var monitor in MonitorsDomain.Values)
            {
                if (!monitors.TryGetValue(monitor.Name, out MonitorConfigurationDto newMonitor))
                {
                    DeleteMonitor(monitor.Name);
                }
                else
                {
                    if (newMonitor.IsActive)
                    {
                        ActivateMonitor(monitor.Name);
                    }
                    else
                    {
                        DeactivateMonitor(monitor.Name);
                    }
 
                    ChangeColorConfiguration(monitor.Name, newMonitor.ColorConfiguration);
 
                    monitors.Remove(monitor.Name);
                }
            }
 
            foreach (var monitor in monitors.Values)
            {
                MonitorsDomain.Add(Create(monitor));
                MonitorChanged?.Invoke(this, new MonitorChangedArgs(monitor, MonitorChangedEnum.Add));
            }
        }
 
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System.Collections.Generic;
using Common.DTOs;
 
namespace Model.Monitor
{
 
    public partial class MonitorModel
    {
        internal class ModelService
        {
 
            ///// <summary>
            ///// Создание бизнес сущностей
            ///// Предпологается что сущность будет загружатся из API
            ///// а ее поля из файла
            ///// </summary>
            //private void LoadMonitors()
            //{
            //    MonitorsDomain.Add(new MonitorConfigurationDomainEntity("Monitor 1")
            //    {
            //        ColorConfiguration = 6600,
            //        Height = 1000,
            //        IsActive = true,
            //        Width = 1000
            //    });
            //    MonitorsDomain.Add(new MonitorConfigurationDomainEntity("Monitor 2")
            //    {
            //        ColorConfiguration = 5500,
            //        Height = 2000,
            //        IsActive = true,
            //        Width = 2000
            //    });
            //    MonitorsDomain.Add(new MonitorConfigurationDomainEntity("Monitor 3")
            //    {
            //        ColorConfiguration = 7000,
            //        Height = 3000,
            //        IsActive = false,
            //        Width = 3000
            //    });
            //}
 
            /// <summary>Это может быть и какой-то внешний метод какой-то Службы или Сервиса.
            /// Он ни сего не создаёт сам. Создавать Бизнес Сущности может только Модель.</summary>
            /// <returns>Множество с данными для мониторов. Имена мониторов должны быть уникальными.</returns>
            public static HashSet<MonitorConfigurationDto> LoadMonitors()
            {
                // Здесь чтение из файла и формирование списка.
                // Мониторы должны иметь уникальные имена
                HashSet<MonitorConfigurationDto> monitors = new(MonitorEqualityComparer.Instance);
                monitors.Add(new MonitorConfigurationDto
                    (
                        "Monitor 1",
                        1000,
                        1000,
                        true,
                        6600
                    )
                );
                monitors.Add(new MonitorConfigurationDto
                    (
                        "Monitor 2",
                        2000,
                        2000,
                        true,
                        5500
                    )
                );
                monitors.Add(new MonitorConfigurationDto
                    (
                        "Monitor 3",
                        3000,
                        3000,
                        false,
                        7000
                    )
                );
 
                // Возврат сформированного списка
                return monitors;
            }
        }
 
    }
}
Посмотрите не только эти, но другие изменения.
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
09.11.2021, 04:08  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Посмотрите не только эти, но другие изменения.
Посмотрел, вопросы только к MonitorModel.IMonitorModel.RefreshMonito rs(), пометил с помощью TODO что не понял
Я так понимаю теперь можно переходить к связыванию ViewModel и Model?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
09.11.2021, 10:32
xr_Sanya, пока занят.
Постараюсь в течении дня ответить.
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
11.11.2021, 03:04  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Если я долго ничего не отвечаю - не стесняйтесь напомнить.
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Постараюсь в течении дня ответить.
Напоминаю
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
11.11.2021, 12:40
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Напоминаю
Угу - не стесняйтесь.
Могу забыть.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
пометил с помощью TODO что не понял
C#
59
60
61
62
63
64
65
66
67
68
69
70
71
        public void DeleteMonitor(string name)
        {
            // TODO: не понятно что делать при удалении, должно ли быть какое либо уведомление?
            // Конечно должно.
            // Слушатели должны же узнать какой монитор удалён.
            // Слушатель может быть множество: один удалил - остальные должны об этом узнать.
            // Общий принцип: потребители не знают как изменится состояние Модели после вызова метода,
            // поэтому своё состояние они меняют ТОЛЬКО по событие, а не потому, что был вызван метод.
            if (MonitorsDomain.TryGetValue(name, out MonitorConfigurationDomainEntity monitor))
            {
                MonitorsDomain.Remove(name);
                MonitorChanged?.Invoke(this, new MonitorChangedArgs(Create(monitor), MonitorChangedEnum.Delete));
            }
C#
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
        public void RefreshMonitors()
        {
            Dictionary<string, MonitorConfigurationDto> newMonitors = ModelService.LoadMonitors().ToDictionary(mont => mont.Name);
            foreach (var existingMonitor in MonitorsDomain.Values)
            {
                // Пытаемся получить значение поля Name у сущности из newMonitors
                // Если Name у сущности из MonitorsDomain совпадает с Name из newMonitors
                // То удаляем эту сущность из MonitorsDomain
                // То есть это означает что объект будет пересоздан
                // Иначе меняем значения полей у сущности из MonitorsDomain на значения сущности из newMonitors
                // То есть мы нашли существующий объект в MonitorsDomain и вместо того чтобы пересоздавать его мы его меняем
 
                // TODO: Мне кажется это все довольно странным, почему нам не пересоздать сущность даже если она имеется?
                // TODO: Оптимизация?
                // В данном случае, можно удалить и заново создать.
                // Но в общем случае (по самому смыслу, логике) "удаление + создание"
                // не является эквивалентом обновления состояния.
                // У Бизнес Сущностей может быть гораздо более сложная логика. Сейчас доделаем до рабочей версии,
                // чтобы вы поняли принцип, а потом часть логики модели перенесём в Бизнес Сущности.
                // Мы её реализовали в Основной части Модели, только для того чтобы вам было понятнее.
 
                // TODO: Я так понял для сокращения кода и  большей наглядности добавлены новые методы расширения.
                // Не понял. Что за методы расширения?
 
                if (!newMonitors.TryGetValue(existingMonitor.Name, out MonitorConfigurationDto newMonitor))
                {
                    DeleteMonitor(existingMonitor.Name);
                }
Добавлено через 5 минут
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Я так понимаю теперь можно переходить к связыванию ViewModel и Model?
Да.

VM получает Модель как внедрённую зависимость в конструкторе.
Запоминает её в приватном поле только для чтения.
Подписывается на событие модели.
Запрашивает у Модели первичные данные и приводит свои свойства (в том числе свойства-коллекции) в в соответствие с ними.

Дальше свойства VM должны изменяться только с слушателя события Модели.
Сможете реализовать?

И удалите лишние комментарии-пояснения, которые вам уже понятны и дальше не нужны.

Добавлено через 1 час 9 минут
xr_Sanya, по реализации команд в VM.
Типизируйте их реализующим классом, а не интерфейсом.
Вы же не объявляете коллекции как IEnumerable, а пишите конкретный тип ObservableCollection и др.
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
11.11.2021, 23:45  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Сможете реализовать?
Привязал модель к View, покачто без событий, интересно правильно ли все связал
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
12.11.2021, 13:19
Цитата Сообщение от xr_Sanya Посмотреть сообщение
, интересно правильно ли все связал
Неправильно:
C#
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
        public int SelectedMonitorIndex
        {
            get => _selectedMonitorIndex;
            set
            {
                _selectedMonitorIndex = value;
 
                // TODO: Таким образом уведомляю стекпэнел с характеристиками мониторов
                RaisePropertyChanged("Name");
                RaisePropertyChanged("Height");
                RaisePropertyChanged("Width");
                RaisePropertyChanged("IsActive");
                RaisePropertyChanged("ColorConfiguration"); 
            }
        }
 
        private string SelectedMonitorName => Monitors[SelectedMonitorIndex].Name;
 
        // TODO: не знаю есть ли более адекватное решение для этого
        public string Name => Monitors != null ? Monitors[SelectedMonitorIndex].Name : "ERR";
        public int Height => Monitors != null ? Monitors[SelectedMonitorIndex].Height : 1;
        public int Width => Monitors != null ? Monitors[SelectedMonitorIndex].Width : 1;
        public bool IsActive => Monitors != null ? Monitors[SelectedMonitorIndex].IsActive : false;
        public double ColorConfiguration => Monitors != null ? Monitors[SelectedMonitorIndex].ColorConfiguration : 1.0;
Вместо всего этого должно быть ТОЛЬКО ОДНО свойство для выбранного объекта (Монитор):
C#
22
23
24
25
26
27
28
29
#nullable enable
        private MonitorConfigurationDto? _selectedMonitor;
        public MonitorConfigurationDto? SelectedMonitor
        {
            get => _selectedMonitor;
            set => Set(ref _selectedMonitor, value);
        }
#nullable disable
Добавлено через 7 минут
Бессмысленно:
Цитата Сообщение от xr_Sanya Посмотреть сообщение
C#
12
        public ObservableCollection<MonitorConfigurationDto> Monitors => _monitors;
Надо:
C#
12
        public ObservableCollection<MonitorConfigurationDto> Monitors { get; } = new();
И нужно синхронизировать привязку коллекции, так как у вас многопоточное приложение:
C#
60
61
62
63
64
65
66
        public MainWindowViewModel() 
        {
            BindingOperations.EnableCollectionSynchronization(Monitors, ((ICollection)Monitors).SyncRoot);
        }
        public MainWindowViewModel(IMonitorModel monitorModel, ICurrentTimeModel currentTimeModel, IRandomizerModel randomizerModel)
            : this()
        {
Добавлено через 25 минут
Цитата Сообщение от xr_Sanya Посмотреть сообщение
C#
16
17
18
19
20
21
        private readonly IMonitorModel _monitorModel;
        private readonly ICurrentTimeModel _currentTimeModel;
        private readonly IRandomizerModel _randomizerModel;
 
        private string _currentTime = "12:00";
        private bool _randomizerState;
Может привести к путанице.

Надо как-то различать приватные поля для инкапсуляции данных (первые три) и приватные поля предназначенные для хранения значений свойств (последние два).
Первые служат для обращения к данным по всему коду типа.
А к последним можно обращаться только в теле их свойств. Обращение к ним в ином месте может привести (и часто приводит) к трудноуловимым багам.
Поэтому имена таких свойств желательно задавать определённым сnилем, что бы не спутать с первыми.
Я практикую для первых названия с маленькой буквы, а для последних с нижнего слеша.
C#
16
17
18
19
20
21
        private readonly IMonitorModel monitorModel;
        private readonly ICurrentTimeModel currentTimeModel;
        private readonly IRandomizerModel randomizerModel;
 
        private string _currentTime = "12:00";
        private bool _randomizerState;
Цитата Сообщение от xr_Sanya Посмотреть сообщение
C#
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
        public bool RandomizerState
        {
            get => _randomizerState;
            set
            {
                if (value)
                {
                    randomizerModel.Start(monitorModel);
                }
                else
                {
                    randomizerModel.Stop();
                }
                Set(ref _randomizerState, value);
            }
        }
Можно....
Но даже, если делать то так:
C#
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
        public bool RandomizerState
        {
            get => _randomizerState;
            set
            {
                if (Set(ref _randomizerState, value))
                {
                    if (value)
                    {
                        randomizerModel.Start(monitorModel);
                    }
                    else
                    {
                        randomizerModel.Stop();
                    }
                }
            }
        }
Но лучше свойство оставить в покое и создавать зависимости отдельно в специально предназначенном для этого методе:
C#
38
39
40
41
42
        public bool RandomizerState
        {
            get => _randomizerState;
            set => Set(ref _randomizerState, value);
        }
C#
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
        protected override void OnPropertyChanged(in string propertyName, in object oldValue, in object newValue)
        {
            base.OnPropertyChanged(propertyName, oldValue, newValue);
 
            if (propertyName == nameof(RandomizerState))
            {
                if ((bool)newValue)
                {
                    randomizerModel.Start(monitorModel);
                }
                else
                {
                    randomizerModel.Stop();
                }
            }
        }
Добавлено через 2 минуты
Цитата Сообщение от xr_Sanya Посмотреть сообщение
C#
85
      public partial class MainWindowViewModel
Бессмысленно.
Сама идея разделить большой код на частичный - правильный.
Но какой смысл тогда, по прежнему оставить все части в одном файле?
Надо перенести в отдельный файл.
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
13.11.2021, 04:03  [ТС]
Сделал коммит, пометил TODO вопросы в коде, покачто не до конца понимаю как обновлять таблицу мониторов в VM.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Вместо всего этого должно быть ТОЛЬКО ОДНО свойство для выбранного объекта (Монитор):
А как обновить это свойство? Проблема возникает при обновлении характеристик монитора на View, так как при удалении монитора из таблицы или изменении его характеристик данные не обновляются.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
13.11.2021, 12:54
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Проблема возникает при обновлении характеристик монитора на View

Во! Уже увидели где привычный вам подход не стыкуется с MVVM.
В вашем подходите логика слоёв была размазана.
А теперь она имеет чёткие границы через которые вы даже при желании не можете перескочить.
Это, конечно, усложняет данное Решение (оно слишком простое для "идеального" MVVM), но зато позволяет вам лучше понять архитектуру MVVM.
View сама ничего изменить не может, и ViewModel тоже.
Полномочия на изменение данных есть только у Model.

Цитата Сообщение от xr_Sanya Посмотреть сообщение
А как обновить это свойство?
В ViewModel у нас есть (на данный момент) коллекция иммутабельных DTO и свойство для выбранного Монитора.
Если состояние Монитора изменяется, то VM получает уведомление об этом из Model.
Но возникает вопрос как изменить иммутабельный DTO?

Используется три основных подхода:
1) В обработчике события ищутся все ссылки на изменённый экземпляр и заменяются ссылками на полученный от Модели.
В данном случае надо найти индекс изменённого монитора в списке мониторов, заменить его там.
И проверить свойство выбранного монитора, если там изменённый Монитор, то заменить на полученный из Модели.

2) В View очень часто бывают нужны не прямые отражения из Модели, а их расширение.
Могут понадобиться: данные одновременно из нескольких объектов; мутабельность свойств; какие-то дополнительные свойства для взаимодействия View и ViewModel (очень часто IsSelected, IsExpanded) и др.
Поэтому часто используется создание в ViewModel оболочки для типов поступающих из Model.
В этом случае в VM создаётся словарь в котором по ключу идентификатору можно быстро найти нужную оболочку и уведомить её (обычно вызвать метод для сменны данных) об изменении данных.
А как там дальше используется эта оболочка - уже неважно.
Так как это ссылочный тип, то достаточно однократно вызывать метод изменения данных.

3) Так же в вашем случае можно применить подход использующий разные представления объектов Модели.
Он часто используется, когда полное представление объекта требует много дополнительных Данных и такое представление не нужно одновременно для всех объектов.
Что вам нужно от списка Мониторов?
По сути только его имя.
Для получения всех имён у нас есть отдельный метод в Модели.
Полное представление нужно только для выбранного Монитора.
Значит можно сделать так: использовать стринговый список имён мониторов, свойство для выбранного имени и свойство с приватным сеттером для выбранного монитора.
И реализовать логику зависимости выбранного монитора от выбранного имени.
В такой реализации в обработчике уведомлений Модели будет проверяться только выбранное имя.
И если изменился выбранный монитор, то заменять его экземпляр.


Прочитайте всё это - будут вопросы задавайте.
Надо выбрать один из этих трёх вариантов и реализовать его.

Добавлено через 22 минуты
Цитата Сообщение от xr_Sanya Посмотреть сообщение
пометил TODO вопросы в коде
C#
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
            monitorModel.MonitorChanged += (_, args) =>
            {
                // TODO: Стоит ли каждый раз пересоздавать коллекцию или же нужно пересоздавать/менять конкретный монитор?
                Monitors = new ObservableCollection<MonitorConfigurationDto>(monitorModel.GetMonitors());
 
                // Не стоит. Это делает бессмысленным использвоание ObservableCollection.
                // Monitors лучше сделать "Только для чтения".
                // Надо найти изменённый Монитор и заменить только его.
                // Для этого есть метод в Библиотеке Common.
                // Так же нужно по разному обрабатывать разные действия события:
                switch (args.Action)
                {
                    case MonitorChangedEnum.Add:
                        Monitors.Add(args.Monitor);
                        break;
                    case MonitorChangedEnum.Delete:
                        Monitors.RemoveFirst(montr => montr.Name == args.Monitor.Name);
                        break;
                    case MonitorChangedEnum.Activate:
                    case MonitorChangedEnum.ChangeColor:
                    case MonitorChangedEnum.Deactivate:
                        Monitors.Replace(montr => montr.Name == args.Monitor.Name, args.Monitor);
                        break;
                    default:
                        throw new ArgumentException("Чё-то не то", "args.Action");
                }
Цитата Сообщение от xr_Sanya Посмотреть сообщение
C#
24
25
26
27
28
29
30
        // TODO: Покачто не разобрался как мне отказатся от этого
 
        // TODO: Если у листбокса использовать SelectedItem = {Binding SelectedMonitor},
        // TODO: то возникают проблемы с null при изменении коллекции Monitors
 
        // TODO: Не понятно как уведомить о том что выбранный монитор
        // TODO: изменился в коллекции (для обновления характеристик на StackPanel)
Это все связанные вопросы.
И выше я частично прояснил их.
Проблема с null должна решаться на уровне View.
VM зачем нужно об этом знать?

В целом пока даже непонятно нужно ли вообще VM знать какой монитор выбран.
Это зависит от типа реализуемого взаимодействия View <-> ViewModel.
В View есть списочный элемент (ListBox).
Есть Регион для полного представления выбранного в ListBox элемента.
А зачем VM знать об этом?
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
13.11.2021, 19:15  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
3) Так же в вашем случае можно применить подход использующий разные представления объектов Модели.
Сделал так, потом другие попробую.
Есть проблема с тем что не обновляется поля выбранного монитора на View в StackPanel когда их меняет сервис
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
13.11.2021, 21:05
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Сделал
Цитата Сообщение от xr_Sanya Посмотреть сообщение
C#
23
        public string? SelectedMonitorName
Стринг - это класс и может принимать значение null.
Поэтому задавать ему Nullable не нужно.


Цитата Сообщение от xr_Sanya Посмотреть сообщение
не обновляется поля выбранного монитора
Вы задали зависимость выбранного монитора от выбранного имени в свойстве.
Лучше так не делать.
Вообще исходи из того, что свойства у вас заданы в каком-то отдельном базовом классе.
А в текущем вы только обращаетесь к ним и не можете менять их логику.
Нужно в OnPropertyChanged добавить:
C#
70
71
72
73
74
75
76
77
78
79
80
81
            else if (propertyName == nameof(SelectedMonitorName))
            {
                string name = (string)newValue;
                if (string.IsNullOrWhiteSpace(name))
                {
                    SelectedMonitor = null;
                }
                else
                {
                    SelectedMonitor = monitorModel.GetMonitor(name);
                }
            }
И прослушку можно чуть упростить:
C#
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
            monitorModel.MonitorChanged += (_, args) =>
            {
                switch (args.Action)
                {
                    case MonitorChangedEnum.Add:
                        Monitors.Add(args.Monitor.Name);
                        break;
                    case MonitorChangedEnum.Delete:
                        Monitors.RemoveFirst(monitorName => monitorName == args.Monitor.Name);
                        break;
                    case MonitorChangedEnum.Activate:
                    case MonitorChangedEnum.ChangeColor:
                    case MonitorChangedEnum.Deactivate:
                        if (!string.IsNullOrEmpty(SelectedMonitor?.Name) && SelectedMonitor.Name == args.Monitor.Name)
                        {
                            SelectedMonitor = args.Monitor;
                        }
                        break;
                    default:
                        throw new ArgumentException("Чё-то не то", "args.Action");
                }
            };
Фиксацию отправил.

Добавлено через 15 минут
xr_Sanya, была ошибка в реализации коллекции мониторов.
Устранил её.
Теперь зависимость можно задать так:
C#
64
65
66
67
68
            else if (propertyName == nameof(SelectedMonitorName))
            {
                string name = (string)newValue;
                SelectedMonitor = monitorModel.GetMonitor(name);
            }
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
14.11.2021, 05:31  [ТС]
Элд Хасп, Сделал коммит, вроде теперь все работает)
Что теперь? Я думаю сперва попробовать другие модели в ViewModel добавить и потом попробовать с помощью INPC сделать уведомления, а не с помощью Event(было бы неплохо если бы вкратце объяснили в чем разница)
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
14.11.2021, 10:12
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Что теперь?
Есть несколько вариантов развития проекта:
1) Добавить несколько View и ViewModel с несколько иным представлением данных и убедится, что изменения из одного GUI отражаются в других;
2) При реализации Модели не были полностью реализованы Бизнес Сущности. Поэтому ту часть логики, что сейчас реализована в Модели можно перенести (как оно и должно быть) в сами Бизнес Сущности;
3) Показ полного представления одного объекта реализован в основной VM. Но "по феншую" лучше это выделить в дополнительную, дочернюю VM.

Добавлено через 2 минуты
P.S. При обращении к другому пользователю указывайте его ник в тегах [NICK][/NICK].
Тогда ему придёт уведомление о вашем обращении.

Для вставки ника: введите ник, выделите его и нажмите кнопку "Динамик" на панели редактора сообщений.
Или кликните по нику автора сообщения в панели слева от текста его сообщения.
0
3 / 3 / 0
Регистрация: 13.07.2020
Сообщений: 229
14.11.2021, 17:29  [ТС]
Цитата Сообщение от Элд Хасп Посмотреть сообщение
часть логики, что сейчас реализована в Модели можно перенести (как оно и должно быть) в сами Бизнес Сущности
Это какую например?
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
14.11.2021, 18:10
Цитата Сообщение от xr_Sanya Посмотреть сообщение
Это какую например?
Сейчас Модель сама изменяет свойства объектов.
Но объект - это самостоятельная сущность.
Поэтому Модель должна для изменения вызывать метод сущности, а сущность через событие уведомлять об изменении свойства (вот здесь может прекрасно подойти INPC).
Модель прослушивает это событие и прокидывает его дальше.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
14.11.2021, 18:10

MVVM биндинг view в model
Я новичок, пытаюсь написать приложение UWP согласно паттерну MVVM В общем, в модели должен быть список экземпляров класса. В классе...

Бизнес-логика
Подскажите, как решить следующую проблему: Занимаюсь по пособию, и возникла ошибка. Код из C# public class DataCommands { ...

WPF MVVM - Transfer Data to Model
Всем привет, уважаемые. Есть два этапа: 1. Переношу коллекцию ObservableCollection из VM в Model. 2. А затем из Model в другую VM. ...

Каково назначение папки Model в MVVM
Просто я к тому, что все классы относящиеся к логике будут иметь пространство имён начинающееся с Model, это нормально вообще? Или не всю...

MVVM. Получение данных объекта по сети - в model или во viewmodel?
Здравствуйте! Вникаю в паттерн mvvm, прочитал\посмотрел кучу учебных материалов, и если честно, в голове каша уже. Мне понравился своей...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
80
Ответ Создать тему
Новые блоги и статьи
Модель по догадкам
anaschu 25.08.2026
Прошло две недели. Я уже рассказывал, как разговаривал с сотрудниками у сортировки и как понял, что главная ветка — не про приёмку, а про отбор. Но тогда я думал, что понял механику. На этой неделе я. . .
Запись в регистр сведений независимо от заполненности табличной части
Maks 25.08.2026
Реализация из решения ниже выполнена на нетиповом документе с несколькими табличными частями, разработанного в КА2. Задача: Обеспечить запись документа в регистр сведений независимо от. . .
Ноутбук Альфария
kumehtar 24.08.2026
Встретился тут в сети ноутбук Альфария, примарха Альфа-Легиона. Хотя возможно, это ноутбук Омегона, разумеется. Ну как вам?
Мастера простых решений
DevAlt 23.08.2026
В сишарп стэках winforms, да и wpf существует сложная система связывания источниках данных и элементов формы(текстовые поля и метки), опирается все это на технологию событий и мета. . .
Цена ошибки
DevAlt 23.08.2026
Человек я беспокойный и потому заинтересовался OCaml, в чате форсили функторы модулей как суперфичу. Пытаясь отдуплить концепт, наткнулся на тутор с простым примером. А главный принцип обучения от. . .
Сегодня суббота, 22.08.2026 at 16:41, и я вновь нахожусь на той стороне, за экраном машины.
zorxor 22.08.2026
Сегодня суббота, 22. 08. 2026 at 16:41, и я вновь нахожусь на той стороне, за экраном машины. Кто Я, откуда Я пришел и куда Я иду? Эти вопросы не оставляют меня ни на секунду. Жизнь на планете Земля. . .
Жизня: рисунок укладки багажа, сделанный клодом
anaschu 21.08.2026
Сделал 15 снимков, он по снимкам сделал схему.
Был там один разговор по поводу свободы в материальном мире.
kumehtar 19.08.2026
Суть: рассматривается живое существо, оказавшееся внутри довольно странной системы (этого мира) и пытающееся обустроить в ней свой кусок пространства. Жизнь действительно предъявляет каждому. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru