【问题标题】:WPF MVVM share properties between UI/background threadsWPF MVVM 在 UI/后台线程之间共享属性
【发布时间】:2017-10-08 13:01:35
【问题描述】:

我找不到一个简单的例子来说明如何在我能理解的任何地方在线实现这一点。我也不想使用 MvvmLight。

我的视图包含一个填充颜色的矩形,该矩形绑定到我的视图模型中的 SolidColorBrush 属性。

我的模型还有一个 SolidColorBrush 属性和一个 UpdateBrushColor 方法。

我希望我的视图模型运行一个后台线程,该线程可以在模型中调用 UpdateBrushColor,然后更新其自己的 SolidColorBrush 以匹配模型画笔颜色,这将通过数据绑定更新 UI。

我在后台使用 Dispatcher.Invoke 来更新 UI,但仍然出现异常:“必须在与 DependencyObject 相同的线程上创建 DependencySource”。

查看(只是控件)

<StackPanel VerticalAlignment="Center">
  <Rectangle Height="50"
              Width="50"
              Fill="{Binding Brush}">
  </Rectangle>
  <CheckBox></CheckBox>
</StackPanel>

型号

class Model
{
    public SolidColorBrush Brush { get; set; } = Brushes.Black;

    public void UpdateBrushColor()
    {
        Thread.Sleep(100);
        Random rnd = new Random();
        byte r = (byte)rnd.Next(0, 255);
        byte g = (byte)rnd.Next(0, 255);
        byte b = (byte)rnd.Next(0, 255);
        Brush = new SolidColorBrush(Color.FromArgb(255, r, g, b));
    }
}

查看模型

class Vm : INotifyPropertyChanged
{
    public Model Model { get; set; }

    private SolidColorBrush brush;
    public SolidColorBrush Brush
    {
        get { return brush; }
        set { SetProperty(ref brush, value); }
    }

    public Vm()
    {
        Model = new Model();

        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                Model.UpdateBrushColor();

                Application.Current.Dispatcher.Invoke(() =>
                {                        
                    Brush = Model.Brush;    // why can't access model from here?
                });
            }
        });
    }

    // Data Binding Implementation
    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged(string propName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        NotifyPropertyChanged(propertyName);
        return true;
    }
}

【问题讨论】:

  • 请注意,在 UpdateBrushColor 方法中创建一个新的 Random 实例是没有意义的。它应该被创建一次(作为类成员)并在方法中重用。
  • 也就是说,Model.Brush 是在 Task 的线程中创建的(通过 UpdateBrushColor 调用),因此您不能将其分配给 Vm.Brush。你所有的代码都没有多大意义。为什么不在模型中创建一组 RGB 值(或颜色值),然后在视图模型中根据这些值创建画笔?对于任何周期性 UI 更新,您还应该使用 DispatcherTimer 而不是 Task。
  • 是的,我同意这在很多方面都没有多大意义。道歉。我快速编码以获得一个小的通用示例来解释我的问题;事后看来是个坏主意。我不知道线程亲和力是一回事,谢谢。

标签: c# wpf multithreading mvvm


【解决方案1】:

不要在模型中使用 Brush 类,因为它派生自 DispatcherObject,因此具有线程亲和性。在 Task 的线程中创建的实例不能在 UI 线程中使用。

您可以使用Color,它是一种没有线程关联的结构类型。

除此之外,您还可以使用简单的 DispatcherTimer 进行循环更新:

public class Model
{
    private readonly Random random = new Random();

    public Color Color { get; private set; }

    public void UpdateColor()
    {
        Color = Color.FromRgb(
            (byte)random.Next(256), (byte)random.Next(256), (byte)random.Next(256));
    }
}

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public Model Model { get; } = new Model();

    private Brush brush;
    public Brush Brush
    {
        get { return brush; }
        set
        {
            brush = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Brush)));
        }
    }

    public ViewModel()
    {
        var timer = new DispatcherTimer
        {
            Interval = TimeSpan.FromSeconds(0.1)
        };

        timer.Tick += (s, e) =>
        {
            Model.UpdateColor();
            Brush = new SolidColorBrush(Model.Color);
        };

        timer.Start();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-22
    • 1970-01-01
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 2011-12-24
    相关资源
    最近更新 更多