WPF数据绑定是否会对UI线程进行更改?

时间:2021-07-23 20:58:22

I just noticed that when changing bound properties in my ViewModel (MVVM) from a background worker thread I do not get any exceptions and the view is updated correctly. Does this mean I can safely rely on wpf databinding marshalling all changes in the ViewModel to the UI Thread? I think I have read somewhere that one should make sure (in the ViewModel) that INotifyPropertyChanged.PropertyChanged is fired on the UI thread. Has this changed in 3.5 or something?

我刚刚注意到,当我从后台工作线程更改我的ViewModel(MVVM)中的绑定属性时,我没有得到任何异常,并且视图已正确更新。这是否意味着我可以安全地依赖wpf数据绑定将ViewModel中的所有更改编组到UI线程?我想我已经读过一个应该确保(在ViewModel中)在UI线程上触发INotifyPropertyChanged.PropertyChanged的地方。这有什么改变在3.5或什么?

2 个解决方案

#1


12  

Yes for scalars, no for collections. For collections, you'll need a specialized collection that marshals for you, or manually marshal to the UI thread yourself via the Dispatcher.

对于标量是肯定的,对于集合是没有的。对于集合,您需要一个专门为您编组的集合,或者通过Dispatcher自行编组到UI线程。

You may have read that INotifyCollectionChanged.CollectionChanged must fire on the UI thread, because it's simply not true of INotifyPropertyChanged.PropertyChanged. Below is a very simple example that proves WPF marshals property changes for you.

您可能已经读过INotifyCollectionChanged.CollectionChanged必须在UI线程上触发,因为它根本不适用于INotifyPropertyChanged.PropertyChanged。下面是一个非常简单的示例,证明了WPF为您调整属性更改。

Window1.xaml.cs:

Window1.xaml.cs:

using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace WpfApplication1
{
    public partial class Window1 : Window
    {
        private CustomerViewModel _customerViewModel;

        public Window1()
        {
            InitializeComponent();
            _customerViewModel = new CustomerViewModel();
            DataContext = _customerViewModel;

            var thread = new Thread((ThreadStart)delegate
            {
                while (true)
                {
                    Thread.Sleep(2000);
                    //look ma - no marshalling!
                    _customerViewModel.Name += "Appended";
                    _customerViewModel.Address.Line1 += "Appended";
                }
            });

            thread.Start();
        }
    }

    public abstract class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class CustomerViewModel : ViewModel
    {
        private string _name;
        private AddressViewModel _address = new AddressViewModel();

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        public AddressViewModel Address
        {
            get { return _address; }
        }
    }

    public class AddressViewModel : ViewModel
    {
        private string _line1;

        public string Line1
        {
            get { return _line1; }
            set
            {
                if (_line1 != value)
                {
                    _line1 = value;
                    OnPropertyChanged("Line1");
                }
            }
        }
    }
}

Window1.xaml:

Window1.xaml:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBox Text="{Binding Name}"/>
        <TextBox Text="{Binding Address.Line1}"/>
    </StackPanel>
</Window>

#2


1  

I believe that with 2.0 and previous incarnations of .NET you would have received an InvalidOperationException due to thread affinity when executing the aforementioned example (link posted by bitbonk is dated 2006).

我相信,对于.NET和以前的.NET版本,在执行上述示例时,由于线程关联性,您会收到InvalidOperationException(bitbonk发布的链接日期为2006年)。

Now, with 3.5, WPF does appear to marshal background thread property changes onto the dispatcher for you.

现在,使用3.5,WPF似乎确实为您调度后台线程属性更改到调度程序。

So, in short, depends which version of .NET you're targetting. Hopefully that clears up any confusion.

因此,简而言之,取决于您所针对的.NET版本。希望能够解决任何困惑。

One of my fellow Lab49'ers blogged about it here in 2007:

Lab49的一位同事在2007年在这里写了一篇博客:

http://blog.lab49.com/archives/1166

http://blog.lab49.com/archives/1166

#1


12  

Yes for scalars, no for collections. For collections, you'll need a specialized collection that marshals for you, or manually marshal to the UI thread yourself via the Dispatcher.

对于标量是肯定的,对于集合是没有的。对于集合,您需要一个专门为您编组的集合,或者通过Dispatcher自行编组到UI线程。

You may have read that INotifyCollectionChanged.CollectionChanged must fire on the UI thread, because it's simply not true of INotifyPropertyChanged.PropertyChanged. Below is a very simple example that proves WPF marshals property changes for you.

您可能已经读过INotifyCollectionChanged.CollectionChanged必须在UI线程上触发,因为它根本不适用于INotifyPropertyChanged.PropertyChanged。下面是一个非常简单的示例,证明了WPF为您调整属性更改。

Window1.xaml.cs:

Window1.xaml.cs:

using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace WpfApplication1
{
    public partial class Window1 : Window
    {
        private CustomerViewModel _customerViewModel;

        public Window1()
        {
            InitializeComponent();
            _customerViewModel = new CustomerViewModel();
            DataContext = _customerViewModel;

            var thread = new Thread((ThreadStart)delegate
            {
                while (true)
                {
                    Thread.Sleep(2000);
                    //look ma - no marshalling!
                    _customerViewModel.Name += "Appended";
                    _customerViewModel.Address.Line1 += "Appended";
                }
            });

            thread.Start();
        }
    }

    public abstract class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class CustomerViewModel : ViewModel
    {
        private string _name;
        private AddressViewModel _address = new AddressViewModel();

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        public AddressViewModel Address
        {
            get { return _address; }
        }
    }

    public class AddressViewModel : ViewModel
    {
        private string _line1;

        public string Line1
        {
            get { return _line1; }
            set
            {
                if (_line1 != value)
                {
                    _line1 = value;
                    OnPropertyChanged("Line1");
                }
            }
        }
    }
}

Window1.xaml:

Window1.xaml:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <TextBox Text="{Binding Name}"/>
        <TextBox Text="{Binding Address.Line1}"/>
    </StackPanel>
</Window>

#2


1  

I believe that with 2.0 and previous incarnations of .NET you would have received an InvalidOperationException due to thread affinity when executing the aforementioned example (link posted by bitbonk is dated 2006).

我相信,对于.NET和以前的.NET版本,在执行上述示例时,由于线程关联性,您会收到InvalidOperationException(bitbonk发布的链接日期为2006年)。

Now, with 3.5, WPF does appear to marshal background thread property changes onto the dispatcher for you.

现在,使用3.5,WPF似乎确实为您调度后台线程属性更改到调度程序。

So, in short, depends which version of .NET you're targetting. Hopefully that clears up any confusion.

因此,简而言之,取决于您所针对的.NET版本。希望能够解决任何困惑。

One of my fellow Lab49'ers blogged about it here in 2007:

Lab49的一位同事在2007年在这里写了一篇博客:

http://blog.lab49.com/archives/1166

http://blog.lab49.com/archives/1166