C# 如何从ViewModel异步更新UI元素
我在多个表单上使用标签,这些表单显示从WCF服务调用的天气数据。我希望每分钟更新一次,以显示更新的天气数据,而不干扰用户交互 我得到以下错误: “必须在与DependencyObject相同的线程上创建DependencySource。” 我有一个用于异步获取天气数据的视图模型,它从ViewModelBase继承来处理属性更改事件。ViewModel中的特性将绑定到标签 天气视图模型C# 如何从ViewModel异步更新UI元素,c#,wpf,mvvm,async-await,C#,Wpf,Mvvm,Async Await,我在多个表单上使用标签,这些表单显示从WCF服务调用的天气数据。我希望每分钟更新一次,以显示更新的天气数据,而不干扰用户交互 我得到以下错误: “必须在与DependencyObject相同的线程上创建DependencySource。” 我有一个用于异步获取天气数据的视图模型,它从ViewModelBase继承来处理属性更改事件。ViewModel中的特性将绑定到标签 天气视图模型 public class WeatherDataVM : ViewModelBase { private
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");
}
}
}
ViewModelBase
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}" />
每次计时器滴答作响时,天气标签都应该改变。相反,它会抛出一个错误。如果冻结背景线程,则可以在其上创建笔刷:
var brush = new SolidColorBrush(windState);
brush.Freeze();
WindState = brush;
但是如果您只是调用任务,那么使用分派器就没有多大意义。在勾选事件处理程序中运行
如果您的事件处理程序只创建画笔,而不直接操作任何UI元素(当然不应该,因为它是在视图模型中实现的),那么您可以使用。它的appead
事件排队等待在线程池线程上执行,您可以在该线程中查询服务而不阻塞UI。看起来您正在尝试在非UI线程上更新UI,这将出错,因为UI具有线程关联性。您应该等待一个任务lt t gt,其中t包含用户界面所需的数据。然后在ui线程上更新ui。您应该从ViewModel
中删除SolidColorBrush
,改为使用数字或字符串,然后通过样式或转换器应用颜色。然后,您可以根据需要检索信息。这就是为什么在ViewModel中使用UI元素是一个坏主意,它总是会起反作用!SolidColorBrush不是UI元素。它是一个可自由释放的,因此明确设计为在UI线程以外的线程中创建。在视图模型中使用它完全没有问题。
var brush = new SolidColorBrush(windState);
brush.Freeze();
WindState = brush;