【问题标题】:Swapping out the object reference of a bound property using a BackgroundWorker使用 BackgroundWorker 交换绑定属性的对象引用
【发布时间】:2010-12-22 10:42:58
【问题描述】:

一点背景

我正在构建一个使用插件架构来访问特定设备的应用程序。这些设备具有我命名为属性的字段,可以读取、写入或两者兼而有之。这是使用在其自己的线程中运行的 WPF 前端和后端中的类似 MVVM 的结构来完成的,该结构包装特定插件以以通用方式公开其属性。

关于线程结构的一些细节

我有两个“宿主”对象,一个启动插件结构最终公开两个视图:可用插件的视图和(如果选择了插件)单个插件已知的所有属性的视图在。另一个最终启动 STA 线程并在 WPF 应用程序中运行主窗口。第二台主机使用 BackgroundWorkers 来执行选择、初始化、更新和提交任务。 DoWork 事件/委托(我相信它们被称为委托)在 ViewController 类中定义,该类位于 MVVM 结构中,并提供用于更新等的功能接口,并实现 INotifyPropertyChanged 接口。

更多细节

在创建主窗口时,它的上下文设置为视图控制器对象。 GUI 然后将两个列表框绑定到两个视图。

问题

当我从 UI 线程中调用 selectPlugin 方法时,它会冻结,直到连接操作完成并且每个属性都加载到 ViewModel 包含的列表中。但是它确实有效,然后更新 ListBox itemssource 绑定并显示所有属性。

但是,我不希望 UI 在每次操作时都冻结,因此我实现了后台工作程序。一切正常,对象得到更新,绑定源被新的 View 实例替换。但绑定本身不会更新。

我尝试使用 UI 线程中的 Dispatcher.Invoke 或 DoWorkComplete 事件分配不同的解决方案。我发现 PropertyChanged 事件本身使用以下属性设置器保持为空:

    public IAttributesViewModel AttributesView
    {
        get 
        {
                StaticLogger.WriteDebugLog(log,
                                           String.Format("Executing {0}",
                                                         System.Reflection.MethodBase.GetCurrentMethod()));
                return _currentAttributes;
        }
        set 
        {
            _currentAttributes = value;
            OnPropertyChanged("AttributesView");
        }
    }

INotifyPropertyChanged 的​​实现如下所示:

    #region INotifyPropertyChange implementation
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;
    /// <summary>
    /// Called when [property changed].
    /// </summary>
    /// <param name="name">The name.</param>
    protected void OnPropertyChanged(string name)
    {
        StaticLogger.WriteDebugLog(log, String.Format("Executing {0}", System.Reflection.MethodBase.GetCurrentMethod()));
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }
    #endregion

上面声明的 PropertyChanged 事件始终为空。这可能或应该与 XAML 中的实际绑定有关,但是我确实在 UI 线程中设置了主窗口的数据上下文,如下所示:

    private static void Host(object viewControler)
    {
        var controler = viewControler as IViewController;
        _instance._main = new MainWindow(controler) { DataContext = controler };
        var gui = new App();
        if (_instance._main == null) throw new ArgumentNullException("viewControler");
        gui.Run(_instance._main);
    }

我使用 UI 宿主对象之外的 Singleton 实例,以防万一。

在 XAML 中,我在包含 AttributesListbox 和一些样式代码的 UserControll 上使用自定义依赖属性。实际的 ListBox 绑定到此属性以获取它自己的 ItemSource 属性。不要认为这应该与直接使用列表框有什么不同,但以防万一这导致 PropertyChanged 事件为 null 的问题,并按以下方式实现:

C# /// /// AttributesListBox.xaml 的交互逻辑 /// 公共部分类 AttributesListBox : UserControl { 私有静态只读 UIPropertyMetadata _sourceMetadata = new UIPropertyMetadata(String.Empty, SourceChangedCallback);

    public AttributesListBox()
    {
        InitializeComponent();
    }

    /// <summary>
    /// The dependency property that gets or sets the source of the ListBox to render.
    /// </summary>
    public static DependencyProperty sourceProperty = DependencyProperty.Register(
        "Source", typeof(IEnumerable), typeof(AttributesListBox),_sourceMetadata);
    /// <summary>
    /// Gets or sets the nsource of the image to render.
    /// </summary>
    public IEnumerable Source
    {
        get { return (IEnumerable)GetValue(sourceProperty); }
        set { SetValue(sourceProperty, value); }
    }

    private static void SourceChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var box = (AttributesListBox) d;
        box.List.ItemsSource = e.NewValue as IEnumerable;
    }
}

XAML

<ListBox Name="List" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ItemsSource="{Binding Source}" BorderBrush="{x:Null}" />

第一个问题

我将 ViewController 对象与 UI 结合使用的方式有什么问题?因此,为什么 PropertyChanged 事件总是为空?这应该意味着我在某处搞砸了绑定过程不是吗?

第二个问题

如何获得 OnPropertyChanged 事件以通知 UI 线程,具体是 UserControll(AttributesListbox) 中的绑定?

事件遵循什么路线?

例如: 我使用 DoWork 委托,此方法直接从实例中更改属性,而不是在 UI 线程中。这是否会导致事件永远无法到达 UI 线程,因为它是在其他工作线程中引发的?

我无法理解事件冒泡/隧道,事件是否仅限于创建实例的线程或调用特定方法的线程(使用调度程序或其他)?

假设

我怀疑我的情况有两个问题: 1. PropertyChanged 处理程序保持为空,因为对象未绑定或未在正确的线程中创建。 2. 事件,如果它实际被触发,永远不会到达正确的线程,因为它卡在 BackgroundWorker 线程或后端“主机”线程中。

这是我在这里提出的第一个问题,所以如果你遗漏了一些拼图,请告诉我。

感谢您花时间阅读我的小问题情况,希望我们能找到解决方案。

【问题讨论】:

    标签: c# wpf xaml binding


    【解决方案1】:

    我认为您可能弄乱了列表框的绑定,即(为简单起见缩短了):

    <ListBox ItemsSource="{Binding Source}" />
    

    这个绑定意味着“在我当前的DataContext 中寻找一个名为Source 的属性”,我怀疑这不是你想要的。如果你想绑定到AttributesListBox 的属性Source(我想它承载了上面的列表框),你应该这样做:

    <ListBox ItemsSource="{Binding Source, RelativeSource={RelativeSource AncestorType={x:Type AttributesListBox}}}" />
    

    这意味着 - “在树上的第一个对象中查找名为 Source 的属性,该对象类型为 AttributesListBox”。当然,您仍然需要将 Source 属性绑定到控制器的正确内容,但我想您已经这样做了。

    对属性改变事件的处理是它必须从 UI(Dispatcher)线程引发,如果你从后台线程引发它,wpf 不会自动将它编组到 UI 线程。因此,请确保在后台工作完成后,在 UI 线程上设置需要更新的属性。

    【讨论】:

    • 感谢您的回复。该代码似乎仅适用于 {Binding Source} 但这可能是巧合,我可以看到您的绑定表达式如何更安全。并且 PropertyChanged 事件似乎以某种方式(可能是巧合)进入了 UI 线程。我不使用 Dispatcher 或 DoWorkComplete 事件来确保使用 UI 线程更新属性。这是一个安全的赌注还是值得一看?但是你确实澄清了我的第二个问题。
    • 我很惊讶 {Binding Source} 工作正常,ListBox 在运行时的 DataContext 是什么(您可以使用 codeplex 上的“snoop”工具检查它)?关于 INotifyPropertyChanged,我错了,我今天学到了一些新东西 =) 检查这个 stackoverflow.com/questions/2553333/…
    【解决方案2】:

    提出问题的好处在于,它可以迫使你真正思考问题的根源,因此稍事休息后,你可能最终会在两天后找到完全简单的解决方案。

    问题是一个 ParameterizedThreadStart 请求一个 void 方法(对象 arg),所以只要你传递一个对象,编译器就不会检查类型。我传递了错误的对象,因为我编辑了方法。使用“as”运算符的安全类型转换似乎很痛苦,它似乎吞下了一个 CastingException ,因此没有通知你这个小错误。

    那么简单的解决方案就是:

    private static void Host(object viewControler)
    {
        var controler = viewControler as IViewController;
        _instance._main = new MainWindow(controler) { DataContext = controler };
        var gui = new App();
        if (_instance._main == null) throw new ArgumentNullException("viewControler");
        gui.Run(controler);
    }
    

    我没想到我的 BackgroundWorker 可以 100% “开箱即用”地工作,但它似乎确实如此。我的代码是否是“最佳实践”是另一个单独的问题。

    让我朝那个方向思考的关键是: Short example: bad and good practice for databinding and object initialization

    在 Visaul Studio 中使用 DataBindings 时,还可以查看这篇小文章: How to show WPF trace

    【讨论】:

    • 很高兴你发现了问题!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-12
    • 1970-01-01
    • 2016-06-18
    • 2013-03-14
    相关资源
    最近更新 更多