尽管 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();
}