Несколько представлений совместно используют одни и те же данные с двухсторонней привязкой данных между несколькими потоками - PullRequest
0 голосов
/ 10 сентября 2018

приложение UWP (архитектура mvvm) У меня есть MainView , в котором есть коллекция в ViewModel , используемая для привязки к GridView на MainView и каждом элементе имеет TextBox с двухсторонней привязкой данных с Описание свойство класса Примечание .

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 .

Этот текстовый блок в дополнительном представлении также имеет 2-стороннюю привязку данных с Описание примечания.

Проблема

Я хочу, чтобы независимо от того, редактировалось ли текстовое поле в виде сетки или текстовое поле во вторичном представлении, значение описания должно оставаться синхронизированным между этими 2 текстовыми полями, поэтому я попытался связать их 2 способами с одним и тем же объектом Note следовательно, один и тот же объект Description связан с ними обоими.

Здесь я ожидал ошибку, которая была Marshalling threading error , поэтому всякий раз, когда я пытаюсь изменить значение любого текстового поля, он пытался обновить пользовательский интерфейс в другом представлении (которое является другим потоком), что, конечно, не допускается.

Я знаю о CoreDisptcher

Я уже знаю о функции диспетчера UWP для безопасного межпоточного взаимодействия, у меня уже есть все настройки, и если я использую его обычным способом, я могу легко использовать его для обновления интерфейса между потоками, и он полностью работает. Но моя проблема заключается в следующей строке:

protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\

Исключение возникает при попытке вызвать PropertyChanged Я попытался заключить следующую строку в моем Диспетчере:

OnPropertyChanged(propertyName);

но Интерфейс INotify не позволяет мне иметь метод Set <>, который возвращает задачу, вместо этого он должен возвращать только объект, это точка, в которой я застрял, и я не знаю, как используйте Dispatcher в этом сценарии, пожалуйста, дайте мне знать, если есть какой-то лучший способ сделать это, кажется, этот способ может быть не таким эффективным. Благодарю.

Ответы [ 2 ]

0 голосов
/ 18 сентября 2018

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

<TextBox Text="{x:Bind ViewModel.MyNote.Description, Mode=TwoWay}"
                 x:Name="PipBox"/>

но поскольку у меня есть двухсторонняя привязка данных в обоих текстовых полях, я могу легко синхронизировать оба экземпляра Note в отдельных потоках.

Я оставлю репозиторий github на тот случай, если он может кому-нибудь помочь: https://github.com/touseefbsb/MultiWindowBindingSync

P.S: Отдельное спасибо Martin Zikmund , который мне очень помог в разработке этого решения.

0 голосов
/ 10 сентября 2018

Лучшим решением в этом случае было бы иметь отдельный набор INotifyPropertyChanged экземпляров для каждого окна и использовать какое-то решение для обмена сообщениями, такое как EventHub в MvvmLight, которое публикует сообщение об изменении базовой модели и всех заинтересованных сторон. следует обновить свои экземпляры.

Другой вариант - создать базовый класс модели, который содержит словарь INotifyPropertyChanged экземпляров для каждого потока пользовательского интерфейса (таким образом, это будет Dictionary<Dispatcher, YourModelClass>. Теперь родительский элемент будет подписываться на событие PropertyChanged каждого дочернего элемента. экземпляр и после его выполнения будет передавать событие другим потомкам, используя соответствующий Dispatcher.

Также есть очень интересный служебный класс ViewSpecificBindableClass Мариана Долинского на его GitHub , который потенциально может быть решением, которое позволит вам иметь «один» класс в нескольких представлениях, осведомленный о множестве диспетчеров. , Я еще не пробовал, но это выглядит многообещающе.

...