尽管 PropertyChanged,WPF TextBlock 不会更新
WPF TextBlock won't update despite PropertyChanged
我正在编写一个 MVVM 应用程序,我正在尝试在底部包含一个状态栏。我已经设置了视图和视图模型,它们应该跟踪应用程序记录器的状态 class,这是一个易于使用的单例。
查看:
<UserControl x:Class="SynthEBD.UC_StatusBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SynthEBD"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:VM_StatusBar}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Path=DispString}" Foreground="{Binding Path=FontColor}" FontSize="18" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
查看模型:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace SynthEBD
{
public class VM_StatusBar : INotifyPropertyChanged
{
public VM_StatusBar()
{
this.DispString = "";
this.FontColor = new SolidColorBrush(Colors.Green);
this.SubscribedLogger = Logger.Instance;
this.SubscribedLogger.PropertyChanged += RefreshDisp;
}
public string DispString { get; set; }
private Logger SubscribedLogger { get; set; }
public SolidColorBrush FontColor { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void RefreshDisp(object sender, PropertyChangedEventArgs e)
{
this.DispString = SubscribedLogger.StatusString;
this.FontColor = SubscribedLogger.StatusColor;
}
}
}
记录器:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
namespace SynthEBD
{
public sealed class Logger : INotifyPropertyChanged
{
private static Logger instance;
private static object lockObj = new Object();
public event PropertyChangedEventHandler PropertyChanged;
public VM_RunButton RunButton { get; set; }
public string StatusString { get; set; }
public string LogString { get; set; }
public SolidColorBrush StatusColor { get; set; }
public SolidColorBrush ReadyColor = new SolidColorBrush(Colors.Green);
public SolidColorBrush WarningColor = new SolidColorBrush(Colors.Yellow);
public SolidColorBrush ErrorColor = new SolidColorBrush(Colors.Red);
public string ReadyString = "Ready To Patch";
private Logger()
{
this.StatusColor = this.ReadyColor;
this.StatusString = this.ReadyString;
}
public static Logger Instance
{
get
{
lock (lockObj)
{
if (instance == null)
{
instance = new Logger();
}
}
return instance;
}
}
public static void LogError(string error)
{
Instance.LogString += error + "\n";
}
public static void LogErrorWithStatusUpdate(string error, ErrorType type)
{
Instance.LogString += error + "\n";
Instance.StatusString = error;
switch (type)
{
case ErrorType.Warning: Instance.StatusColor = Instance.WarningColor; break;
case ErrorType.Error: Instance.StatusColor = Instance.ErrorColor; break;
}
}
public static void TimedNotifyStatusUpdate(string error, ErrorType type, int durationSec)
{
LogErrorWithStatusUpdate(error, type);
var t = Task.Factory.StartNew(() =>
{
Task.Delay(durationSec * 1000).Wait();
});
t.Wait();
ClearStatusError();
}
public static void ClearStatusError()
{
Instance.StatusString = Instance.ReadyString;
Instance.StatusColor = Instance.ReadyColor;
}
}
public enum ErrorType
{
Warning,
Error
}
}
我有意触发了 Logger.TimedNotifyStatusUpdate() 函数,尽管我可以看到 VM_StatusBar.RefreshDisp() 中到达了一个断点,但屏幕上的实际字符串和颜色永远不会改变(https://imgur.com/BhizinR).我没有看到任何失败的绑定,所以我不明白为什么视图没有更新。感谢您的任何建议!
编辑:我还尝试显式触发 PropertyChanged 事件,而不是依赖 PropertyChanged.Fody,如下所示,但屏幕上的结果是相同的。
public class VM_StatusBar : INotifyPropertyChanged
{
public VM_StatusBar()
{
this.DispString = "";
this.FontColor = new SolidColorBrush(Colors.Green);
this.SubscribedLogger = Logger.Instance;
this.SubscribedLogger.PropertyChanged += RefreshDisp;
}
public string DispString
{
get { return _dispString; }
set
{
if (value != _dispString)
{
_dispString = value;
OnPropertyChanged("DispString");
}
}
}
private string _dispString;
private Logger SubscribedLogger { get; set; }
public SolidColorBrush FontColor { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public void RefreshDisp(object sender, PropertyChangedEventArgs e)
{
this.DispString = SubscribedLogger.StatusString;
this.FontColor = SubscribedLogger.StatusColor;
string debugBreakHere = "";
}
}
您永远不应该在 Task
对象上调用 Task.Wait
。始终 await
它以允许它异步完成。从您发布的代码看来,您正在阻止 UI 线程,从而窃取了更新表面(渲染)所需的资源。 Task.Wait
是一张死锁票。
此外,更喜欢 Task.Run
而不是 Task.Factory
。
将阻塞代码变成非阻塞代码应该可以做到:
public static async Task TimedNotifyStatusUpdateAsync(string error, ErrorType type, int durationSec)
{
LogErrorWithStatusUpdate(error, type);
Task t = Task.Run(async () =>
{
// Calling Wait on the Task blocks this thread
//Task.Delay(durationSec * 1000).Wait();
// Instead 'await' the Task to free resources
await Task.Delay(durationSec * 1000);
});
// Await the Task to allow the UI thread to render the view
// in order to show the changes
await t;
ClearStatusError();
}
然后使用 await
:
从另一个 async Task
方法调用该方法
private async Task CallTimedNotifyStatusUpdateAsync()
=> await TimedNotifyStatusUpdateAsync();
请注意,将异步方法包装到 Task.Run
通常不是一个好主意。 TimedNotifyStatusUpdateAsync
的正确实现是:
public static async Task TimedNotifyStatusUpdateAsync(string error, ErrorType type, int durationSec)
{
LogErrorWithStatusUpdate(error, type);
// Await the Task to allow the UI thread to render the view
// in order to show the changes
await Task.Delay(durationSec * 1000);
ClearStatusError();
}
我正在编写一个 MVVM 应用程序,我正在尝试在底部包含一个状态栏。我已经设置了视图和视图模型,它们应该跟踪应用程序记录器的状态 class,这是一个易于使用的单例。
查看:
<UserControl x:Class="SynthEBD.UC_StatusBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SynthEBD"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:VM_StatusBar}"
d:DesignHeight="450" d:DesignWidth="800">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Path=DispString}" Foreground="{Binding Path=FontColor}" FontSize="18" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
查看模型:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace SynthEBD
{
public class VM_StatusBar : INotifyPropertyChanged
{
public VM_StatusBar()
{
this.DispString = "";
this.FontColor = new SolidColorBrush(Colors.Green);
this.SubscribedLogger = Logger.Instance;
this.SubscribedLogger.PropertyChanged += RefreshDisp;
}
public string DispString { get; set; }
private Logger SubscribedLogger { get; set; }
public SolidColorBrush FontColor { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void RefreshDisp(object sender, PropertyChangedEventArgs e)
{
this.DispString = SubscribedLogger.StatusString;
this.FontColor = SubscribedLogger.StatusColor;
}
}
}
记录器:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
namespace SynthEBD
{
public sealed class Logger : INotifyPropertyChanged
{
private static Logger instance;
private static object lockObj = new Object();
public event PropertyChangedEventHandler PropertyChanged;
public VM_RunButton RunButton { get; set; }
public string StatusString { get; set; }
public string LogString { get; set; }
public SolidColorBrush StatusColor { get; set; }
public SolidColorBrush ReadyColor = new SolidColorBrush(Colors.Green);
public SolidColorBrush WarningColor = new SolidColorBrush(Colors.Yellow);
public SolidColorBrush ErrorColor = new SolidColorBrush(Colors.Red);
public string ReadyString = "Ready To Patch";
private Logger()
{
this.StatusColor = this.ReadyColor;
this.StatusString = this.ReadyString;
}
public static Logger Instance
{
get
{
lock (lockObj)
{
if (instance == null)
{
instance = new Logger();
}
}
return instance;
}
}
public static void LogError(string error)
{
Instance.LogString += error + "\n";
}
public static void LogErrorWithStatusUpdate(string error, ErrorType type)
{
Instance.LogString += error + "\n";
Instance.StatusString = error;
switch (type)
{
case ErrorType.Warning: Instance.StatusColor = Instance.WarningColor; break;
case ErrorType.Error: Instance.StatusColor = Instance.ErrorColor; break;
}
}
public static void TimedNotifyStatusUpdate(string error, ErrorType type, int durationSec)
{
LogErrorWithStatusUpdate(error, type);
var t = Task.Factory.StartNew(() =>
{
Task.Delay(durationSec * 1000).Wait();
});
t.Wait();
ClearStatusError();
}
public static void ClearStatusError()
{
Instance.StatusString = Instance.ReadyString;
Instance.StatusColor = Instance.ReadyColor;
}
}
public enum ErrorType
{
Warning,
Error
}
}
我有意触发了 Logger.TimedNotifyStatusUpdate() 函数,尽管我可以看到 VM_StatusBar.RefreshDisp() 中到达了一个断点,但屏幕上的实际字符串和颜色永远不会改变(https://imgur.com/BhizinR).我没有看到任何失败的绑定,所以我不明白为什么视图没有更新。感谢您的任何建议!
编辑:我还尝试显式触发 PropertyChanged 事件,而不是依赖 PropertyChanged.Fody,如下所示,但屏幕上的结果是相同的。
public class VM_StatusBar : INotifyPropertyChanged
{
public VM_StatusBar()
{
this.DispString = "";
this.FontColor = new SolidColorBrush(Colors.Green);
this.SubscribedLogger = Logger.Instance;
this.SubscribedLogger.PropertyChanged += RefreshDisp;
}
public string DispString
{
get { return _dispString; }
set
{
if (value != _dispString)
{
_dispString = value;
OnPropertyChanged("DispString");
}
}
}
private string _dispString;
private Logger SubscribedLogger { get; set; }
public SolidColorBrush FontColor { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
public void RefreshDisp(object sender, PropertyChangedEventArgs e)
{
this.DispString = SubscribedLogger.StatusString;
this.FontColor = SubscribedLogger.StatusColor;
string debugBreakHere = "";
}
}
您永远不应该在 Task
对象上调用 Task.Wait
。始终 await
它以允许它异步完成。从您发布的代码看来,您正在阻止 UI 线程,从而窃取了更新表面(渲染)所需的资源。 Task.Wait
是一张死锁票。
此外,更喜欢 Task.Run
而不是 Task.Factory
。
将阻塞代码变成非阻塞代码应该可以做到:
public static async Task TimedNotifyStatusUpdateAsync(string error, ErrorType type, int durationSec)
{
LogErrorWithStatusUpdate(error, type);
Task t = Task.Run(async () =>
{
// Calling Wait on the Task blocks this thread
//Task.Delay(durationSec * 1000).Wait();
// Instead 'await' the Task to free resources
await Task.Delay(durationSec * 1000);
});
// Await the Task to allow the UI thread to render the view
// in order to show the changes
await t;
ClearStatusError();
}
然后使用 await
:
async Task
方法调用该方法
private async Task CallTimedNotifyStatusUpdateAsync()
=> await TimedNotifyStatusUpdateAsync();
请注意,将异步方法包装到 Task.Run
通常不是一个好主意。 TimedNotifyStatusUpdateAsync
的正确实现是:
public static async Task TimedNotifyStatusUpdateAsync(string error, ErrorType type, int durationSec)
{
LogErrorWithStatusUpdate(error, type);
// Await the Task to allow the UI thread to render the view
// in order to show the changes
await Task.Delay(durationSec * 1000);
ClearStatusError();
}