Несколько представлений совместно используют одни и те же данные с двухсторонним связыванием данных между несколькими потоками
Приложение UWP (архитектура mvvm) У меня есть MainView, у которого есть коллекция в его ViewModel, используемая для привязки к GridView на MainView, и каждый элемент имеет TextBox с двухсторонним связыванием данных со свойством Description класса Note.
Xaml TextBox каждого элемента сетки.
<TextBlock Text="{x:Bind Description,Mode=TwoWay}"
Свойство коллекции, используемое для привязки к ItemSource gridview.
public ObservableCollection<Note> Notes { get; }
и это класс Примечание
public class Note : Observable
{
private string _description;
public string Description
{
get => _description;
set => Set(ref _description, value, nameof(Description));
}
}
класс Observable предназначен для двухсторонней привязки данных.
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Теперь все до этой точки работает отлично, когда я изменяю текст в текстовом поле, он также меняет значение Description.
Второй вид
Теперь у меня есть функция, где каждый GridViewItem имеет кнопку, которая открывает примечание в новом окне. и в этом новом окне есть только 1 TextBox, поэтому теперь вторичное представление и GridViewItem, открывшее это представление, используют один и тот же объект Note.
Этот TextBox во вторичном представлении также имеет двухстороннее связывание данных с описанием примечания.
Эта проблема
Я хочу, чтобы независимо от того, редактируется ли текстовое поле в виде сетки или текстовое поле во вторичном представлении, значение описания должно оставаться синхронизированным между этими двумя текстовыми полями, поэтому я попытался связать их 2-мя способами с одним и тем же объектом Note, следовательно, одинаково Описание объекта связано с ними обоими.
Здесь я ожидал ошибки, которая представляла собой ошибку многопоточности Marshalling, поэтому всякий раз, когда я пытаюсь изменить значение любого текстового поля, он пытался обновить пользовательский интерфейс в другом представлении (которое является другим потоком), что, конечно, недопустимо.
Я знаю о CoreDisptcher
Я уже знаю о функции диспетчера UWP для безопасного межпоточного взаимодействия, у меня уже есть все настройки, и если я использую его обычным способом, я могу легко использовать его для обновления интерфейса между потоками, и он полностью работает. Но моя проблема заключается в следующей строке:
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\
Исключение происходит, когда он пытается вызвать PropertyChanged. Я пытался обернуть следующую строку в моем Диспетчере:
OnPropertyChanged(propertyName);
но интерфейс INotify не позволяет мне иметь метод Set<>, который возвращает задачу, вместо этого ему нужно вернуть только объект, это точка, в которой я застрял, и я не знаю, как использовать Dispatcher в этом сценарии, пожалуйста дайте мне знать, если есть какой-то лучший способ сделать это, кажется, этот способ может быть не таким эффективным. Благодарю.
2 ответа
Поэтому мне, наконец, пришлось использовать совершенно другой подход, централизующий события TextChanged текстового поля MainView и тот, который находится во вторичном представлении.
По сути, я передал текстовое поле на главной странице через дополнительную страницу (вторичное представление), а затем подписался на его событие TextChanged. Я также подписался на событие TextChanged текстового поля во вторичном представлении, а затем с помощью обратных диспетчеров мне удалось без проблем синхронизировать текст между двумя окнами.
Примечание. Обязательно отмените подписку на события, когда дополнительное окно закрывается, чтобы предотвратить утечки памяти.
private async void PipBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = PipBox.Text;
await CoreApplication.MainView.Dispatcher.AwaitableRunAsync(() =>
{
if (parentBox.Text != text)
parentBox.Text = text;
});
}
private async void ParentBox_TextChanged(object sender, TextChangedEventArgs e)
{
string text = parentBox.Text;
// the awaitablerunasync extension method comes from "Windows Community Toolkit".
await _viewLifetimeControl.Dispatcher.AwaitableRunAsync(() =>
{
if (ViewModel.MyNote.Description != text)
ViewModel.MyNote.Description = text;
});
}
Обратите внимание, что у меня все еще есть двухстороннее связывание данных в обоих текстовых полях, и оно не вызывает никаких исключений, потому что я использую 2 разных экземпляра Note для обоих представлений.
<TextBox Text="{x:Bind ViewModel.MyNote.Description, Mode=TwoWay}"
x:Name="PipBox"/>
но так как у меня есть двухстороннее связывание данных в обоих текстовых полях, я легко могу синхронизировать оба экземпляра Note в отдельных потоках.
Я сохраню репозиторий github на тот случай, если он может кому-то помочь: https://github.com/touseefbsb/MultiWindowBindingSync
PS: Отдельное спасибо Martin Zikmund, который мне очень помог в разработке этого решения.
Лучшее решение в этом случае будет иметь отдельный набор INotifyPropertyChanged
экземпляры для каждого окна и использование какого-либо решения для обмена сообщениями, таких как EventHub
в MvvmLight, который публикует сообщение об изменении базовой модели, и все заинтересованные стороны должны обновить свои экземпляры.
Другим вариантом будет создание класса базовой модели, который поддерживает словарь INotifyPropertyChanged
экземпляры для каждого потока пользовательского интерфейса (так что это будет Dictionary<Dispatcher, YourModelClass>
, Теперь родитель будет подписываться на PropertyChanged
событие каждого дочернего экземпляра и после его выполнения будет передавать событие другим дочерним экземплярам, используя соответствующие Dispatcher
,
Также есть очень интересный полезный класс ViewSpecificBindableClass
Мариана Долинского на его GitHub, который потенциально может быть решением, которое позволит вам иметь "один" класс в нескольких представлениях, осведомленный о нескольких диспетчерах. Я еще не пробовал, но кажется многообещающим.