多个视图共享相同的数据,并在多个线程之间进行双向数据绑定

时间:2018-09-10 18:44:00

标签: c# multithreading uwp dispatcher multiviews

UWP应用程序(mvvm体系结构)我有一个 MainView ,它的 ViewModel 中有一个集合,用于绑定到 MainView 上的GridView每个项目都有一个文本框,该文本框具有2种方式的数据绑定,并具有类 Note Description 属性。

每个gridviewitem的文本框的Xaml。

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

用于绑定到gridview的ItemSource的Collection属性。

public ObservableCollection<Note> Notes { get; }

这是课程注意

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

可观察类用于两种方式的数据绑定帮助。

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 的同一对象。

此辅助视图中的文本框还具有与注释的描述的两种方式的数据绑定。

问题

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

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

我了解CoreDisptcher

我已经了解用于安全的跨线程通信的UWP的Dispatcher功能,我已经完成所有设置,并且如果我以常规方法使用它,则可以轻松地将其用于跨线程UI更新,并且完全可以使用。但是我的问题是以下行:

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

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

OnPropertyChanged(propertyName);

但是 INotify 接口不允许我使用Set <>方法来返回Task,而只需要返回一个对象,这就是我被困住的地方,我不知道该怎么做要在这种情况下使用Dispatcher,请让我知道是否有更好的方法可以执行此操作,但这种方法似乎效率不高。 谢谢。

2 个答案:

答案 0 :(得分:5)

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

另一种选择是创建一个基类,该基类为每个UI线程维护一个INotifyPropertyChanged实例的字典(因此它将是一个Dictionary<Dispatcher, YourModelClass>。现在,父级将订阅{{1 }}每个子实例的事件,一旦执行,事件就会使用适当的PropertyChanged传播给其他子实例。

此外,MarianDolinskýon his GitHub还有一个非常有趣的实用程序类Dispatcher,它可能是一个解决方案,可以让您在多个视图中拥有“单个”类,并知道多个调度程序。我还没有尝试过,但是看起来很有希望。

答案 1 :(得分:1)

所以我最终不得不采取一种完全不同的方法来集中处理MainView文本框的 TextChanged事件和辅助视图中的事件。

我基本上将主页上的文本框传递到了辅助页面(secondary view),然后订阅了其TextChanged事件。我还订阅了辅助视图上文本框的TextChanged事件,然后在反向 dispatchers 的帮助下,我可以在2个窗口之间同步文本而没有任何问题。

  

注意:始终确保在辅助窗口关闭时取消订阅事件,以防止内存泄漏。

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 ,他为我提供了很多解决方案。