【问题标题】:How do I asycronously update a UI element from a ViewModel [duplicate]如何从 ViewModel 异步更新 UI 元素 [重复]
【发布时间】:2019-12-16 08:51:39
【问题描述】:

我在多个表单上使用标签,这些表单显示从 WCF 服务调用的天气数据。我希望每分钟更新一次,以显示更新后的天气数据,而不会干扰用户交互。

我收到以下错误:

“必须在与 DependencyObject 相同的线程上创建 DependencySource。”

我有一个视图模型,用于异步获取天气数据,它继承自 ViewModelBase 以处理属性更改事件。 ViewModel 的属性绑定到标签

天气视图模型

public class WeatherDataVM : ViewModelBase
{
    private string _windString;
    private SolidColorBrush _windState;
    private DispatcherTimer _timer;


    public WeatherDataVM()
    {
        _timer = new DispatcherTimer(DispatcherPriority.Render);
        _timer.Interval = TimeSpan.FromSeconds(10);
        _timer.Tick += async (sender, args) => {await Task.Run(() => GetWindAsync()); };
        //_timer.Tick += _timer_Tick;
        _timer.Start();
        GetWind();
    }

    private void GetWind()
    {
        var weatherFromService = Services.Instance.EmptyStackService.GetWeather();
        var windSpeed = Convert.ToDouble(weatherFromService.Windspeed);
        var maxGust = Convert.ToDouble(weatherFromService.Max_Gust_In_Last_Min);

        var windSpeedMPH = Math.Round(windSpeed * 1.15078, 1);
        var maxGustMPH = Math.Round(maxGust * 1.15078, 1);

        var windString = $"W/S: {windSpeedMPH}({maxGustMPH})";

        var windState = new Color();
        if (windSpeed >= 40)
            windState = Color.FromRgb(255, 64, 64);
        else if (windSpeed >= 24)
            windState = Color.FromRgb(255, 212, 128);
        else
            windState = Color.FromRgb(0, 255, 0);
        _windState = new SolidColorBrush(windState);

        _windString = windString;


    }

    private async Task GetWindAsync()
    {
        var weatherFromService = Services.Instance.EmptyStackService.GetWeather();
        var windSpeed = Convert.ToDouble(weatherFromService.Windspeed);
        var maxGust = Convert.ToDouble(weatherFromService.Max_Gust_In_Last_Min);

        var windSpeedMPH = Math.Round(windSpeed * 1.15078, 1);
        var maxGustMPH = Math.Round(maxGust * 1.15078, 1);

        var windString = $"W/S: {windSpeedMPH}({maxGustMPH})";

        var windState = new Color();
        if (windSpeed >= 40)
            windState = Color.FromRgb(255, 64, 64);
        else if (windSpeed >= 24)
            windState = Color.FromRgb(255, 212, 128);
        else
            windState = Color.FromRgb(0, 255, 0);

        WindState = new SolidColorBrush(windState);
        WindString = windString;

    }


    public string WindString
    {
        get { return _windString; }

        set
        {
            if (_windString == value)
                return;
            _windString = value;
            OnPropertyChanged("WindString");
        }
    }

    public SolidColorBrush WindState
    {
        get { return _windState; }

        set
        {
            if (_windState == value)
                return;
            _windState = value;
            OnPropertyChanged("WindState");
        }

    }
}

视图模型库

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

查看标签的 Xaml

<Label x:Name="lblWeather" Content="{Binding WindString}" Foreground="black" Background="{Binding WindState}" Style="{DynamicResource SmallLabel}"  />

构造函数中视图后面的代码

lblWeather.DataContext = new WeatherDataVM();

天气标签应在每次计时器滴答时发生变化。相反,它会引发错误。

【问题讨论】:

  • 看起来您正在尝试在非 UI 线程上更新 UI,这会出错,因为 UI 具有线程关联性。您应该等待一个 Task lt t gt ,其中 t 包含 UI 所需的数据。然后更新 ui,回到 ui 线程。
  • 您应该从您的ViewModel 中删除SolidColorBrush 并改用数字或字符串,然后通过样式或转换器应用颜色。然后,您可以根据需要检索信息。这就是为什么在 ViewModel 中包含 UI 元素是一个坏主意,它总是会反击!
  • SolidColorBrush 不是 UI 元素。它是一个 Freezable,因此明确设计为在 UI 线程以外的线程中创建。在视图模型中使用它完全没有问题。

标签: c# wpf mvvm async-await


【解决方案1】:

如果你冻结它,你可以在后台线程上创建一个画笔:

var brush = new SolidColorBrush(windState);
brush.Freeze();
WindState = brush;

但如果您只是在Tick 事件处理程序中调用Task.Run,那么使用DispatcherTimer 并没有多大意义。

如果您的事件处理程序只创建画笔并且不直接操作任何 UI 元素(当然不应该,因为它是在视图模型中实现的),您可以使用 System.Timer.Timer。它的Elapsed 事件排队等待在线程池线程上执行,您可以在其中查询服务而不会阻塞 UI。

【讨论】:

    猜你喜欢
    • 2013-06-08
    • 2021-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-14
    相关资源
    最近更新 更多