多个视图共享相同的数据与多个线程之间的双向数据绑定

Multiple views sharing same data with two-way data binding between multiple threads

UWP 应用程序(mvvm 架构)我有一个 MainView,它在其 ViewModel 中有一个集合,用于绑定到 GridView MainView 并且每个项目都有一个 TextBox,具有 2 种数据绑定方式 Description 属性 class .

Xaml 每个 gridviewitem 的 TextBox。

<TextBlock Text="{x:Bind Description,Mode=TwoWay}"

用于绑定到 gridview 的 ItemSource 的集合 属性。

public ObservableCollection<Note> Notes { get; }

这是class

public class Note : Observable
{
    private string _description;
    public string Description
    {
        get => _description;
        set => Set(ref _description, value, nameof(Description));
    }        
}

Observable class 用于双向数据绑定帮助。

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));
}

Now everything uptil this point works perfectly, when I change the text in textbox, it changes the value of Description as well.

第二个视图

现在我有一个功能,其中每个 GridViewItem 都有一个 按钮,它可以在 new window 中打开注释。而这个新 window 只有 1 个 TextBox,所以现在辅助视图和打开该视图的 GridViewItem 使用相同的对象 Note

辅助视图中的此 TextBox 还具有与注释的 Description 的 2 种数据绑定方式。

问题

我想要的是,无论是编辑 gridview 中的文本框还是辅助视图中的文本框,描述的值都必须在这两个文本框之间保持同步,这就是为什么我尝试将它们绑定到同一对象的两种方式注意,因此相同的 Description 对象绑定到它们。

这里的错误是我预料到的 编组线程错误,所以每当我尝试更改任何文本框的值时,它都会尝试在其他视图上更新 UI (这是另一个线程)当然是不允许的。

我知道 CoreDisptcher

我已经知道用于安全跨线程通信的 UWP 的 Dispatcher 功能,我已经完成了所有设置,如果我从正常方法使用它,我可以轻松地将它用于跨线程 UI 更新和它完全有效。但我的问题是以下一行:

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

当它试图调用 PropertyChanged 时发生异常我试图在我的 Dispatcher 中包含以下行:

OnPropertyChanged(propertyName);

但是 INotify 接口不允许我有一个 Set<> 方法,它 return 是一个 Task 而它需要 return 只是一个对象,这是我卡住的地方,我不知道如何在这种情况下使用 Dispatcher,请告诉我是否有更好的方法来执行此操作,似乎这种方法可能效率不高。 谢谢。

在这种情况下,最好的解决方案是为每个 window 单独设置一组 INotifyPropertyChanged 个实例,并使用某种消息传递解决方案,例如 MvvmLight 中的 EventHub,它发布基础模型已更改且所有相关方应更新其实例的消息。

另一种选择是创建一个基础模型 class,它为每个 UI 线程维护一个包含 INotifyPropertyChanged 个实例的字典(因此它将是一个 Dictionary<Dispatcher, YourModelClass> . 现在父级会订阅每个子实例的 PropertyChanged 事件,一旦它执行就会使用适当的 Dispatcher.

将事件传播给其他子实例

还有一个非常有趣的实用程序 class ViewSpecificBindableClass,作者是 Marian Dolinský on his GitHub,它可能是一个解决方案,可以让您拥有 "single" class 在多个视图中,了解多个调度程序。我还没有尝试过,但看起来很有希望。

所以我最终不得不采取一种完全不同的方法来集中 TextChanged 事件 MainView 文本框和 secondaryview 上的事件。

我基本上将主页上的文本框传递到辅助页面(辅助视图),然后订阅了它的 TextChanged 事件。我还在辅助视图上订阅了文本框的 TextChanged 事件,然后在反向 dispatchers 的帮助下,我能够在 2 windows 之间同步文本,没有任何问题。

Note : always make sure to unsubscribe to events when the secondary window closes to prevent memory leaks.

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 种方式的数据绑定,它不会导致任何异常,因为我为两个视图使用了 2 个不同的 Note 实例。

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

但是因为我在两个文本框上都有双向数据绑定,所以我可以轻松地让两个 Note 实例在不同的线程上保持同步。

我会保留 github 回购以防它可以帮助其他人:https://github.com/touseefbsb/MultiWindowBindingSync

P.S:特别感谢 Martin Zikmund,他帮助我找到了这个解决方案。