INotifyPropertyChanged 已实施,但文本块仅更新一次

INotifyPropertyChanged implemented, but textblock only updates once

@grek40回答

我已经在我的项目中实现了 INotifyPropertyChanged,并且对于其他一切它都工作正常,但是对于这个变量绑定的文本块,它只在主 window 加载事件上更新一次。

我可能只是遗漏了一些小细节,请帮忙!

MainWindow.xaml

<TextBlock HorizontalAlignment="Left" Margin="317,161,0,0"  
TextWrapping="Wrap" Text="{Binding ish.IsDoingWork, Mode=OneWay, 
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top"/>

<Button  Command="{Binding hksVm.HentKundeStatus}" Content="Invoke" 
HorizontalAlignment="Left" Margin="704,139,0,0" VerticalAlignment="Top" 
Width="75"/>

MainWindow.xaml.cs(设置数据上下文)

public MainWindow()    
{
    if (!ValidationHandler.GrantAccess().Equals(3))
    {
        InitializeComponent();
        DataContext = new
        {
            hksVm = new HentKundeStatusVm(),
            ish = new InvocationServiceHandler()
        };
    }
    else
    {
        Close();
    }
}

ViewModel.cs

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;

namespace MyNamespace
{
    public class HentKundeStatusVm : IViewModel
    {
        private ICommand _hentKundeStatus;
        private readonly InvocationServiceHandler _invocationServiceHandler = new InvocationServiceHandler();


        public ICommand HentKundeStatus => HentKundeStatusCommand();

        public ICommand HentKundeStatusCommand()
        {
            if (ValidationHandler.GrantAccess() < 2)
            {
                return _hentKundeStatus ?? (_hentKundeStatus = new RelayCommand(param =>
                           ElapsedTime = _invocationServiceHandler.ExecuteAndTimeAction(
                               () =>
                               {
                                   //web API kaldes asynkront - husk: using System.Net.Http; 
                                   using (var client = new HttpClient().GetAsync("API-url"))
                                   {
                                       client.Result.Content.ReadAsStringAsync();
                                   }
                               }, AntalKald)));
            }
            return null;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string property)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
}

InvocationServiceHandler.cs

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using App005_WebServiceTestingTool_Domain.Annotations;

namespace App005_WebServiceTestingTool_Domain.Handlers
{
    public class InvocationServiceHandler : INotifyPropertyChanged
    {
        // This works on the main_window load event and set the textblock in the view
        private string _isDoingWork = "Currently not working";
        public string IsDoingWork
        {
            get => _isDoingWork;
            set
            {
                _isDoingWork = value;
                NotifyPropertyChanged(nameof(IsDoingWork));
            }
        }

        /// <summary>
        /// Method that invokes action parameter x times in multiple threads (parallel) and returns the elapsed time
        /// </summary>
        /// <param name="action"></param>
        /// <param name="antalKald"></param>
        /// <returns></returns>
        public string ExecuteAndTimeAction(Action action, string antalKald)
        {
            // Here is set the bound variable, and if I debug I can see it getting set to Working...
            IsDoingWork = "Working...";
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < Convert.ToInt32(antalKald); i++)
            {
               action.Invoke();
            }
            sw.Stop();
            // Here I am resetting the variable and again in debug I can see it change, but nothing happens in the view
            IsDoingWork = "";
            return $"Elapsed time: {sw.Elapsed}";
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        public void NotifyPropertyChanged(string property)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
}

您的逻辑没有问题,一切看起来都正确附加并且值按预期更新。唯一的问题是您的 UI 没有更新它,因为您正在主线程上执行 For 循环,不幸的是,它阻塞了您所有的 UI 更新。

所以你可以

  1. 运行 ExecuteAndTimeAction 作为后台操作使用 Backgroundworker/[Task Library][1].

2.Use 调度程序刷新您的 UI 消息,例如:

    /// <summary>
    /// Enters the message loop to process all pending messages down to the specified
    /// priority. This method returns after all messages have been processed.
    /// </summary>
    /// <param name="priority">Minimum priority of the messages to process.</param>
    public static void DoEvents(DispatcherPriority priority = DispatcherPriority.Background)
    {
        DispatcherFrame frame = new DispatcherFrame();
        Dispatcher.CurrentDispatcher.BeginInvoke(
            priority,
            new DispatcherOperationCallback(ExitFrame), frame);
        Dispatcher.PushFrame(frame);
    }
    private static object ExitFrame(object f)
    {
        ((DispatcherFrame)f).Continue = false;
        return null;
    }

并打电话

        /// <summary>
        /// Method that invokes action parameter x times in multiple threads (parallel) and returns the elapsed time
        /// </summary>
        /// <param name="action"></param>
        /// <param name="antalKald"></param>
        /// <returns></returns>
        public string ExecuteAndTimeAction(Action action, string antalKald)
        {
            // Here is set the bound variable, and if I debug I can see it getting set to Working...
            IsDoingWork = "Working...";
            DoEvent();//flushes the UI msg queue
            var sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < Convert.ToInt32(antalKald); i++)
            {
               action.Invoke();
            }
            sw.Stop();
            // Here I am resetting the variable and again in debug I can see it change, but nothing happens in the view
            IsDoingWork = "";
            DoEvent();//flushes the UI msg queue
            return $"Elapsed time: {sw.Elapsed}";
        }

第二种方法是破解,它仍然会冻结 UI 但会为您完成工作。 我建议你采用第一种方法, 它好多了,但需要努力实施。

基本上,您有两个 InvocationServiceHandler 实例。一个在 DataContext 内为 ish = new InvocationServiceHandler(),另一个在 MyViewModel 内为 private readonly InvocationServiceHandler _invocationServiceHandler = new InvocationServiceHandler();

因此显示 ish.IsDoingWork 并更新 hksVm._invocationServiceHandler.IsDoingWork 之类的内容。这不是很清楚,因为 HentKundeStatusVmMyViewModel 在问题中并不是一回事。

应该可以通过服务处理程序的构造函数注入来解决这种情况:

public class HentKundeStatusVm : IViewModel
{
    private readonly InvocationServiceHandler _invocationServiceHandler;

    public HentKundeStatusVm(InvocationServiceHandler ish)
    {
        _invocationServiceHandler = ish;
    }

    // the other stuff
}

然后

// In the MainWindow constructor
var ishInstance = new InvocationServiceHandler();
DataContext = new
{
    hksVm = new HentKundeStatusVm(ishInstance),
    ish = ishInstance
};

然后您就有了可用于绑定和执行的相同处理程序实例。