Несколько представлений совместно используют одни и те же данные с двухсторонним связыванием данных между несколькими потоками

Приложение 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, который потенциально может быть решением, которое позволит вам иметь "один" класс в нескольких представлениях, осведомленный о нескольких диспетчерах. Я еще не пробовал, но кажется многообещающим.

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