2

UWP 应用程序(mvvm 架构) 我有一个MainView,它的ViewModel中有一个集合,用于绑定到MainView上的 GridView ,每个项目都有一个TextBox,它具有 2 路数据绑定,具有类Note的Description属性。

每个 gridviewitem 的 TextBox 的 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));
    }        
}

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 路数据绑定。

问题

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

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

我知道 CoreDisptcher

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

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

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

OnPropertyChanged(propertyName);

但是INotify接口不允许我有一个返回 Task 的 Set<> 方法,它只需要返回一个对象,这是我卡住的地方,我不知道如何在这种情况下使用 Dispatcher,请让我知道是否有更好的方法可以做到这一点,看来这种方法可能效率不高。谢谢。

4

2 回答 2

5

在这种情况下,最好的解决方案是INotifyPropertyChanged为每个窗口设置一组单独的实例,并使用某种消息传递解决方案EventHub,例如 MvvmLight,它会发布底层模型已更改的消息,并且所有相关方都应该更新他们的实例。

另一种选择是创建一个基本模型类,它为每个 UI 线程维护一个实例字典(INotifyPropertyChanged因此它将是一个Dictionary<Dispatcher, YourModelClass>.PropertyChanged使用适当的Dispatcher.

ViewSpecificBindableClassMarian Dolinský在他的 GitHub 上还有一个非常有趣的实用程序类,它可能是一个解决方案,允许您在多个视图中拥有“单个”类,同时了解多个调度程序。我还没有尝试过,但它似乎很有希望。

于 2018-09-10T19:57:14.573 回答
1

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

我基本上将主页上的文本框传递到辅助页面(辅助视图),然后订阅其 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 路数据绑定,它不会导致任何异常,因为我对两个视图都使用了 2 个不同的Note实例。

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

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

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

PS:特别感谢Martin Zikmund,他帮助我解决了这个问题。

于 2018-09-18T20:46:13.377 回答