【问题标题】:INotifyPropertyChanged implemented, but textblock only updates onceINotifyPropertyChanged 已实现,但文本块仅更新一次
【发布时间】:2018-06-15 06:07:52
【问题描述】:

@grek40 回答

我已经在我的项目中实现了INotifyPropertyChanged,其他一切都运行良好,但是对于这个变量绑定文本块,它只在主窗口加载事件上更新一次。

我可能只是在某个地方遗漏了一些小细节,请帮忙!

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 线程时才会更新 UI。您在执行操作时似乎一直阻塞 UI 线程?
  • 在动作运行后文本块不会更新吗?
  • 你如何运行ExecuteAndTimeAction?请完整显示minimal reproducible example(更多详细信息)和最小化(更少不需要的东西,例如 Binding Mode=OneWay, UpdateSourceTrigger=PropertyChanged 的组合,它永远不会触发 OneWay 的更新)
  • @grek40 - OneWay 是 OneWayToTarget 所以这部分看起来不错。

标签: c# wpf inotifypropertychanged


【解决方案1】:

基本上,您有两个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
};

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

【讨论】:

  • 嗨@grek40,谢谢你的回复。我已经编辑了我的问题,以便将 MyViewModel 重命名为 HentKundeStatusVm,对“MyViewModel”的编辑是一个错误,我深表歉意。至于实例,我必须将我的视图直接绑定到 InvocationServiceHandler,因为如果我要从 InvocationServiceHandler 更新我的 HentKundestatusVm 中的相同属性,我会得到一个递归错误。这可能是您认为的问题吗?
  • @BjørnFrierPedersen 老实说,我不明白关于递归错误的部分 - 这听起来像是当你编写不同的代码时发生的事情(但我在问题中看不到该代码)。无论如何,正如回答的那样,您只需要一个InvocationServiceHandler 实例,并且您需要绑定到该实例并使用它来调用您的ExecuteAndTimeAction。目前,您使用处理程序的两个不同实例来做这两件事。您在减少处理程序实例的数量方面有什么问题吗?
  • HentKundeStatusVm/vm.cs 从 InvocationServiceHandler.cs/ish.cs 调用 ExecuteAndTimeAction() 并且 IsDoingWork 变量从 ish.cs 绑定到视图。但是,如果我将 IsDoingWork 变量从 ish.cs 移动到 vm.cs 并从那里绑定它,只创建一个 ish.cs 的处理程序实例,则 vm.cs 将调用 ExecuteAndTimeAction 并更新自身,从而产生递归错误。这就是为什么我试图这样做。但如果这会给绑定带来这么多问题,也许我应该重新考虑我的应用程序的整个结构。
  • @BjørnFrierPedersen 不要让它变得复杂......看我的编辑。
  • 我并没有试图过度复杂化,只是想回答你之前的问题。你的编辑看起来不错,我会在我的 Visual Studio 安装再次运行时尝试实现它。
【解决方案2】:

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

所以你可以

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

2.使用 Dispatcher 刷新您的 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,但会为您完成工作。 建议你选择第一种方法 这要好得多,但需要努力实施。

【讨论】:

  • 这似乎是正确的解决方案。我将(尝试)将该功能实现为后台工作人员,看看是否可行。感谢您的回复!
  • 是的,我会确保这样做。
  • 你好@Dharani,我尝试了破解,但它仍然没有更新 UI,这是否意味着问题不在调度程序中?
  • 您好,请将修改后的代码贴在您的问题中,让我检查一下
猜你喜欢
  • 2019-08-18
  • 1970-01-01
  • 2012-07-12
  • 2021-04-11
  • 2021-10-02
  • 1970-01-01
相关资源
最近更新 更多