WPF 文本块不会更新,尽管属性已更改



我正在写一个MVVM应用程序,我试图在底部包括一个状态栏。我已经设置了视图和视图模型,它们应该跟踪应用程序的Logger类的状态,这是一个易于使用的单例。

视图:

<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://i.stack.imgur.com/9Baep.jpg)。我没有看到任何失败的绑定,所以我不明白为什么视图没有更新。谢谢你的建议!

编辑:我还尝试显式触发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();
}

最新更新