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

CommunityToolkit.Mvvm и потоки

22.05.2024, 14:36. Показов 2157. Ответов 27
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Модель представления с обработкой события потери соединения выдает ошибку "Данный тип CollectionView не поддерживает изменения в своем SourceCollection из потока, отличного от потока Dispatcher."
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
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
internal sealed partial class MainViewModel : ObservableValidator
{
   [ObservableProperty]
   [NotifyCanExecuteChangedFor(nameof(ConnectCommand)]
   private bool _isConnected = false;
 
   [ObservableProperty]
   private ObservableCollection<LogMessage>? _log = [];
 
   public MainViewModel()
   {
      //                                           System.NotSupportedException:                                           
      WeakReferenceMessenger.Default.Register<LogChangedMessage>(this, (r, m) => Log?.Insert(0, m.LogMessage));
   }
   
   private bool CanConnect() => !IsConnected;
   
   [RelayCommand(CanExecute = nameof(CanConnect))]
   private void Connect()
   {
      connectionService!.Events.Disconnect += OnDisconnected;
   }
 
   private void OnDisconnected()
   {
      WeakReferenceMessenger.Default.Send(new LogChangedMessage(new LogMessage("Потеря соединения !!!")));
      IsConnected = false;
   }
}
Попытался решить проблему:
C#
1
2
3
4
5
6
7
8
9
10
11
   public MainViewModel()
   {
      WeakReferenceMessenger.Default.Register<LogChangedMessage>(this, (r, m) 
         => App.Current.Dispatcher.Invoke(() => Log?.Insert(0, m.LogMessage)));
   }
   
   private void OnDisconnected()
   {
      WeakReferenceMessenger.Default.Send(new LogChangedMessage(new LogMessage("Потеря соединения !!!")));
      IsConnected = App.Current.Dispatcher.Invoke(() => false);
   }
И все равно получаю ту же ошибку, но уже в сгенерированном CommunityToolkit классе при вызове из сеттера свойства IsConnected метода ConnectCommand.NotifyCanExecuteChanged()
И там у меня этих вызовов NotifyCanExecuteChanged не один.

Наверняка, в CommunityToolkit есть решение. Но, какое?
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
22.05.2024, 14:36
Ответы с готовыми решениями:

Custom Source Generators + CommunityToolkit.Mvvm в ViewModel
1. Возможно ли совместное использование в одной ViewModel? Сомнения связаны с тем, что каждый из генераторов создаёт свой *.g.cs...

MVVM и потоки
Здравствуйте. Мужики, наверное избитая для вас тема. Я не понимаю как работать с потоками во ViewModel. Использую Thread и...

Рисую линию без MVVM все ОК. C MVVM произвольный старт
Добрый день всем. Рисую линию при помощи анимации. Все получилось -рисуется, стирается. Но у нас все задания с MVVM, чтобы ViewModel...

27
 Аватар для Andrey-MSK
3391 / 2277 / 388
Регистрация: 14.08.2018
Сообщений: 7,699
Записей в блоге: 4
07.06.2024, 11:49
Студворк — интернет-сервис помощи студентам
EmcSYAx0a, Для разметки используйте тэг [XML][/XML]. Невозможно читать...
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--<Button Grid.Row="1" Content="Disconnect" Command="{Binding DisconnectCommand}" />-->
<Button Grid.Row="1"
      Content="Disconnect"
      Command="{Binding DisconnectWscCommand}"
      CommandParameter="{Binding Tag, ElementName=ConnectionIndicator}"
      />
<Ellipse x:Name="ConnectionIndicator"
       Grid.Row="2" 
       Tag="{Binding IsConnected}"
       Fill="{Binding Path=IsConnected, Converter={StaticResource BoolToFillColorConverter}}"
       ToolTip="{Binding Path=IsConnected, Converter={StaticResource BoolToToolTipConverter}}"
       Height="12" Width="12"
       />
Добавлено через 11 минут
Цитата Сообщение от EmcSYAx0a Посмотреть сообщение
XML
1
CommandParameter="{Binding Tag, ElementName=ConnectionIndicator}"
Зачем такие сложности? Чем CanExecute() не устраивает?
C#
1
private bool DisconnectWscCanExecute() => !IsConnected;
0
0 / 0 / 0
Регистрация: 14.08.2019
Сообщений: 76
07.06.2024, 14:33  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
Зачем такие сложности? Чем CanExecute() не устраивает?
Как я понимаю, для DisconnectWscCommand требуется параметр, благодаря которому происходит синхронизация контекста
0
 Аватар для Andrey-MSK
3391 / 2277 / 388
Регистрация: 14.08.2018
Сообщений: 7,699
Записей в блоге: 4
07.06.2024, 14:46
EmcSYAx0a, За доступность команды отвечает метод CanExecute(), вот им и надо рулить отслеживая состояние нужных свойств VM. Зачем для этого контекст?
0
0 / 0 / 0
Регистрация: 14.08.2019
Сообщений: 76
07.06.2024, 15:17  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
За доступность команды отвечает метод CanExecute(), вот им и надо рулить отслеживая состояние нужных свойств VM. Зачем для этого контекст?
Ну, может и не надо. Но ваш ответ от 24.05.2024, 11:30 я, честно говоря, не понял, поэтому попробовал адаптировать реализацию Standard, которую предложил Элд Хасп.
И если заменить
C#
1
private bool CanDisconnectWsc(object? parameter) => IsConnected;
на
C#
1
private bool CanDisconnectWsc() => IsConnected;
, получим ошибку компилятора "CS1503 Аргумент 2: не удается преобразовать из "группа методов" в "System.Predicate<object?>".
C#
1
2
public IRelayCommand DisconnectWscCommand
   => _disconnectWscCommand ??= new RelayCommandSyncContext(DisconnectWsc, CanDisconnectWsc)
0
 Аватар для Andrey-MSK
3391 / 2277 / 388
Регистрация: 14.08.2018
Сообщений: 7,699
Записей в блоге: 4
07.06.2024, 15:27
Цитата Сообщение от EmcSYAx0a Посмотреть сообщение
C#
1
CanDisconnectWsc(object? parameter)
Тут достаточно
C#
1
CanDisconnectWsc(object? obj)
То бишь параметр тут не обязателен. Сигнатура зависит от реализации класса команды. В классическом RelayCommand с object, в Community без. С object удобнее, так как можно делать ветвление в команде в зависимости от типа параметра. Но в любом случае в CanExecute() параметр не обязателен, если проверяется доступность условием свойств VM. Пример для общего случая
C#
1
2
3
4
5
6
7
8
9
private RelayCommand _showMachineGroupCommand;
 
private void ShowMachineGroupExecuted(object obj)
    => ShowMachineGroup();
private bool ShowMachineGroupCanExecute(object obj)
    => _branch == Branches.MEKS || _branch == Branches.MEIS;
 
public RelayCommand ShowMachineGroupCommand
    => _showMachineGroupCommand ??= new RelayCommand(ShowMachineGroupExecuted, ShowMachineGroupCanExecute);
Доступность команды определяется выполнением условия
C#
1
_branch == Branches.MEKS || _branch == Branches.MEIS;
и никакие параметры не нужны.
Вот с параметром
C#
1
2
3
4
private void ShowWeldingProgressDataExecuted(object obj)
    => OpenWeldingProgressData();
private bool ShowWeldingProgressDataCanExecute(object arg)
    => arg != null; // доступность команды при существующем параметре
Добавлено через 1 минуту
EmcSYAx0a, Вариант с асинхронным вызовом команды
C#
1
2
3
4
private async void RemovePDExecuted(object obj)
    => await RemoveProgressDataAsync();
private bool RemovePDCanExecute(object obj)
    => SelectedProgressData != null;
0
0 / 0 / 0
Регистрация: 14.08.2019
Сообщений: 76
07.06.2024, 16:49  [ТС]
Цитата Сообщение от Andrey-MSK Посмотреть сообщение
и никакие параметры не нужны.
Простите, не воспринимайте как претензию, и я очень ценю ваши советы, но по-моему, вы меня уводите куда-то не туда. Как я понимаю, то, что параметр
XML
1
CommandParameter="{Binding Tag, ElementName=ConnectionIndicator}"
передается в CanExecute , это "побочный эффект" "неклассической" реализации RelayCommandSyncContext. Параметр (объект) все равно нужен для выполнения RelayCommandSyncContext. Я спрашивал, что обязательно ли передавать элемент, который привязан к свойству, от которого зависит CanExecute или можно сослаться на что-то всегда присутствующее в UI потоке.
0
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
07.06.2024, 17:35
Цитата Сообщение от EmcSYAx0a Посмотреть сообщение
Если в примере в MainView.xaml убрать комментарии с элементов с командами без синхронизации и закомментировать элементы с командами с синхронизацией контекста, ошибка вернется. Именно эту проблему вы и помогли решить.
Понял.

Цитата Сообщение от EmcSYAx0a Посмотреть сообщение
Обязателен ли элемент с Binding IsConnected, и что предать CommandParameter если такового элемента не создавать?
Вооще, не понял... А зачем CommandParameter="{Binding Tag .... вместо CommandParameter="{Binding IsConnected}"?

Да и передавать его в команду нет большого смысла. Это значение команда сама может получить из свойства VM.

Цитата Сообщение от EmcSYAx0a Посмотреть сообщение
Практический: Что сделать, чтобы окно не становилось обесцвеченным когда курсор мыши уходит за его пределы?
Изменить шаблоны элементов.

Добавлено через 16 минут
Цитата Сообщение от EmcSYAx0a Посмотреть сообщение
"CS1503 Аргумент 2: не удается преобразовать из "группа методов" в "System.Predicate<object?>".
Там базовая реализация ICommand. В ней в параметр всегда передаются не типизированный (object) аргумент.
Для подключения безпараметрического метода или метода с типизированным параметром нужно либо создавать лямбду, либо создать дополнительный конструктор и производный класс.

Для безпараметрических методов, добавьте такие конструкторы:
C#
37
38
39
40
41
42
43
44
45
46
47
48
49
        /// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
        public RelayCommandSyncContext(Action execute, Func<bool> canExecute)
            : this
            (
                execute is not null ? _ => execute() :  ?? throw new ArgumentNullException(nameof(execute)),
                canExecute is not null ? _ => canExecute() :  ?? throw new ArgumentNullException(nameof(canExecute)
            )
        {  }
 
        /// <inheritdoc cref="RelayCommand(ExecuteHandler, CanExecuteHandler)"/>
        public RelayCommandSyncContext(Action execute)
            :this(execute is not null ? _ => execute() :  ?? throw new ArgumentNullException(nameof(execute)), AlwaysTrue)
        { }
Добавлено через 3 минуты
Если параметр команды ни к чему не привязан, то в команду будет передаваться null. Для безпараметрических методов - это безразлично, поскольку любой параметр игнорируется.
Для методов с параметров уже, возможно, надо будет предусмотреть обработку null, или выкидывание исключение - зависит от логику, которую нужно реализовать.

Добавлено через 2 минуты
Так же в моей базовой реализации нет подписки на CommandManager, так как для этого потребуется создать зависимость от неуниверсальных сборок.
Можно внедрить подписку, но я предпочитаю внедрять фабрику команд в VM и уже в ней регистрировать платформозависимые реализации.
1
Модератор
Эксперт .NET
 Аватар для Элд Хасп
16165 / 11285 / 2891
Регистрация: 21.04.2018
Сообщений: 33,174
Записей в блоге: 2
07.06.2024, 22:23
Цитата Сообщение от Элд Хасп Посмотреть сообщение
но я предпочитаю внедрять фабрику команд в VM и уже в ней регистрировать платформозависимые реализации.
EmcSYAx0a, пример фабрики (накидал "на коленках"):
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
    public class RelayCommandFactory
    {
        private Func<Action<object?>, Predicate<object?>, IRelayCommand>? baseCreater;
        private Func<Action<object?>, IRelayCommand>? base1Creater;
        private Func<Action, Func<bool>, IRelayCommand>? emptyCreater;
        private Func<Action, IRelayCommand>? empty1Creater;
 
        private readonly Dictionary<Type, object> RelayCommandTFactories = new();
 
        private class RelayCommandTFactory<T>
        {
            public Func<Action<T?>, Predicate<T?>, IRelayCommand>? tCreater;
            public Func<Action<T?>, IRelayCommand>? t1Creater;
        }
 
        public void Clear()
        {
            baseCreater = null;
            base1Creater = null;
            emptyCreater = null;
            empty1Creater = null;
 
            RelayCommandTFactories.Clear();
        }
 
        public IRelayCommand GetCommand(Action<object?> execute, Predicate<object?> canExecute)
            => baseCreater?.Invoke(execute, canExecute) ?? throw new NullReferenceException("Базовый фабричный метод не инициализирован.");
        public IRelayCommand GetCommand(Action<object?> execute)
        {
            if (base1Creater is null)
            {
                if (baseCreater is null)
                    throw new NullReferenceException("Базовый фабричный метод не инициализирован.");
 
                base1Creater = exe => baseCreater(exe, RelayCommandSyncContext.AlwaysTrue);
            }
            return base1Creater(execute);
        }
 
        public IRelayCommand GetCommand(Action execute, Func<bool> canExecute)
        {
            execute = execute ?? throw new ArgumentNullException(nameof(execute));
            canExecute = canExecute ?? throw new ArgumentNullException(nameof(canExecute));
            if (emptyCreater is null)
            {
                if (baseCreater is null)
                    throw new NullReferenceException("Базовый фабричный метод не инициализирован.");
 
                emptyCreater = (act, func) => baseCreater(_ => act(), _ => func());
            }
            return emptyCreater(execute, canExecute);
        }
        public IRelayCommand GetCommand(Action execute)
        {
            execute = execute ?? throw new ArgumentNullException(nameof(execute));
            if (empty1Creater is null)
            {
                if (base1Creater is null)
                    throw new NullReferenceException("Базовый фабричный метод не инициализирован.");
 
                empty1Creater = act => base1Creater(_ => act());
            }
            return empty1Creater(execute);
        }
 
        public IRelayCommand GetCommand<T>(Action<T?> execute, Predicate<T?> canExecute)
        {
            Type type = typeof(T);
            if (!RelayCommandTFactories.TryGetValue(type, out object? value))
            {
                value = new RelayCommandTFactory<T>();
                RelayCommandTFactories.Add(type, value);
            }
            RelayCommandTFactory<T> tFactory = (RelayCommandTFactory<T>)value;
            if (tFactory.tCreater is null)
            {
                tFactory.tCreater = (exe, can) => GetCommand(Convert(exe), Convert(can));
            }
 
            return tFactory.tCreater(execute, canExecute);
        }
        public IRelayCommand GetCommand<T>(Action<T?> execute)
        {
            Type type = typeof(T);
            if (!RelayCommandTFactories.TryGetValue(type, out object? value))
            {
                value = new RelayCommandTFactory<T>();
                RelayCommandTFactories.Add(type, value);
            }
            RelayCommandTFactory<T> tFactory = (RelayCommandTFactory<T>)value;
            if (tFactory.t1Creater is null)
            {
                tFactory.t1Creater = (exe) => GetCommand(Convert(exe));
            }
 
            return tFactory.t1Creater(execute);
        }
 
        private static Action<object?> Convert<T>(Action<T?> execute)
        {
            execute = execute ?? throw new ArgumentNullException(nameof(execute));
            return arg => execute((T?)arg);
        }
 
        private static Predicate<object?> Convert<T>(Predicate<T?> canExecute)
        {
            canExecute = canExecute ?? throw new ArgumentNullException(nameof(canExecute));
            return arg => arg is T t && canExecute(t);
        }
 
        public void Register(Func<Action<object?>, Predicate<object?>, IRelayCommand>? creater)
        {
            baseCreater = creater ?? throw new ArgumentNullException(nameof(creater));
        }
        public void Register(Func<Action<object?>, IRelayCommand>? creater)
        {
            base1Creater = creater ?? throw new ArgumentNullException(nameof(creater));
        }
        public void Register(Func<Action, Func<bool>, IRelayCommand>? creater)
        {
            emptyCreater = creater ?? throw new ArgumentNullException(nameof(creater));
        }
        public void Register(Func<Action, IRelayCommand>? creater)
        {
            empty1Creater = creater ?? throw new ArgumentNullException(nameof(creater));
        }
        public void Register<T>(Func<Action<T?>, Predicate<T?>, IRelayCommand>? creater)
        {
            Type type = typeof(T);
            if (!RelayCommandTFactories.TryGetValue(type, out object? value))
            {
                value = new RelayCommandTFactory<T>();
                RelayCommandTFactories.Add(type, value);
            }
            RelayCommandTFactory<T> tFactory = (RelayCommandTFactory<T>)value;
            tFactory.tCreater = creater;
        }
        public void Register<T>(Func<Action<T?>, IRelayCommand>? creater)
        {
            Type type = typeof(T);
            if (!RelayCommandTFactories.TryGetValue(type, out object? value))
            {
                value = new RelayCommandTFactory<T>();
                RelayCommandTFactories.Add(type, value);
            }
            RelayCommandTFactory<T> tFactory = (RelayCommandTFactory<T>)value;
            tFactory.t1Creater = creater;
        }
 
        private static RelayCommandFactory? _default;
        public static RelayCommandFactory Default => _default ??= new();
    }
Регистрация фабрики в View или App:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public static class WpfRelayCommandRegisterer
    {
        private static readonly ConditionalWeakTable<IRelayCommand, EventHandler> hands = new();
        public static void Register(RelayCommandFactory factory)
        {
            factory.Clear();
            factory.Register
            (
                (act, func) =>
                {
                    var command = new RelayCommandSyncContext(act, func);
                    EventHandler requerySuggested = (_, _) => command.NotifyCanExecuteChanged();
                    hands.Add(command, requerySuggested);
                    CommandManager.RequerySuggested += requerySuggested;
                    return command;
                }
            );
        }
 
        public static void Register() => Register(RelayCommandFactory.Default);
 
    }
Добавлено через 1 минуту
Регистрация фабрики для UWP:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public static class UwpRelayCommandRegisterer
    {
        public static void Register(RelayCommandFactory factory)
        {
            factory.Clear();
            factory.Register
            (
                (act, func) =>
                {
                    var command = new RelayCommand(act, func);
                    return command;
                }
            );
        }
 
        public static void Register() => Register(RelayCommandFactory.Default);
 
    }
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
07.06.2024, 22:23

Приложение MVVM. Быстрая "Нестрогая MVVM" реализация
В этой теме будет размещена простая и быстрая реализация. Основная тема:...

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

Что такое потоки ввода, потоки вывода?
Здарова всем! Не так давно уже прогаю на С++ и все НИКАК не могу понять, что такое потоки ввода, потоки вывода..! вот допустим...

Потоки ввода/вывода, файловые потоки
Объясните, пожалуйста, механизм считывания данных. Перелопатил кучу статей в интернете и всё равно не понял как это работает. Понял...

Байтовые потоки и потоки символов
Объясните, пожалуйста подробно что имелось ввиду На самом низком уровне все операции ввода/вывода в С# оперируют байтами. Подобный...


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

Или воспользуйтесь поиском по форуму:
28
Ответ Создать тему
Новые блоги и статьи
Был там один разговор по поводу свободы в материальном мире.
kumehtar 19.08.2026
Суть: рассматривается живое существо, оказавшееся внутри довольно странной системы (этого мира) и пытающееся обустроить в ней свой кусок пространства. Жизнь действительно предъявляет каждому. . .
Когда логика программы не спасает от человеческих ошибок
Maks 18.08.2026
В последнее время всё чаще и чаще сталкиваюсь с таким явлением, как абсолютная невнимательность (или глупость) пользователей. Проявляется это чаще всего на работе в коллективе. Допустим, человек с. . .
Лето уходит
kumehtar 17.08.2026
Мысли в слух
kumehtar 17.08.2026
Забавно, насколько сейчас стала доступна информация. Например о магии, духовном развитии, медитациях, и других подобных направлениях, ранее зачастую тайных, передаваемых от учителя к ученику. Хотя. . .
Перемещение строк из ТЧ в другой документ с учетом текущего пробега
Maks 17.08.2026
Реализация из решения ниже выполнена на примере нетипового документа "Автозапчасти", с ТЧ "Шины". За основу взят алгоритм отсюда: https:/ / www. cyberforum. ru/ blogs/ 359708/ 10838. html Задача: . . .
Саморегулирующийся социальный контракт для сервера cross-section.
Hrethgir 14.08.2026
С кодом конечно таких глубоких размышлений пока не было, впрочем я уже привык к алгоритмизации. Суть предмета записи: снова в диалоге с нейросетью (я взял пока себе ник для учётки админа - Rector). . . .
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
Часы с будильником на основе класса QLCDNumber
Uhbif79 12.08.2026
Всем добрый день, выкладываю программу часов с будильником на основе класса QLCDNumber. Здесь я пробовал самостоятельно создавал классы, впервые столкнулся с видимостью переменной одного класса из. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru