【问题标题】:Bound property not always updated to the control绑定属性并不总是更新到控件
【发布时间】:2023-03-26 09:24:01
【问题描述】:

我的视图模型中有以下代码。它有三种不同的状态,令我困惑的是,第一个和最后一个状态显示在控件中,但进度没有。

public void DoStuffForAWhile(int limit)
{
  TheBoundProperty = "This is not shown.";

  DateTime start = DateTime.Now;
  TimeSpan elapsed;
  do
  {
    elapsed = new TimeSpan(DateTime.Now.Ticks - start.Ticks);
    TheBoundProperty = "Neither is this.";
  } while (elapsed.TotalSeconds < limit);

  TheBoundProperty = "But this is!";
}

绑定设置正确,因为我在创建视图模型时看到了起始文本。我看到了更新程序的最后一条文本。另外,属性的set方法被命中,值为未显示的字符串。

关于为什么不显示这些值的任何提示?如何解决问题?

【问题讨论】:

  • 您更新属性的频率太高,其更新正在丢失。
  • @KonradViltersten 您的循环在 UI 线程上运行并阻止它,TheBoundProperty 引发的事件在能够这样做之前不会被处理。在后台线程上运行长时间运行的任务并通过调度程序引发属性更改事件
  • @Marko 我不确定你是否正确。我已经测试过添加延迟,但这并没有影响任何事情。我怀疑这与 dkozl 提到的有关。
  • @dkozl 建议如何解决呢?

标签: c# wpf xaml data-binding


【解决方案1】:

这是因为所有普通代码在主 UI 线程上运行在 DispatcherPriority.Normal,所有渲染代码在 DispatcherPriority.Render 运行,priority list 上较低。

在 .Normal 上运行的所有内容都会在 .Render 上的任何内容之前执行,因此当 UI 更新时,只有最终消息存在。

您要做的是将字符串设置为加载,然后启动另一个线程来执行您长时间运行的进程。长时间运行的过程完成后,您想再次更新标签。

我希望您的最终代码看起来像这样:

public void DoStuff()
{
    TheBoundProperty = "Loading ..";

    Task.Factory.StartNew(() => 
    {
        RunSomething();
    })
    .RunWorkerCompleted(() => 
    {
        TheBoundProperty = "Load Completed";
    });
}

如果您想要定期更新,我希望使用调度程序将更新发送到主 UI 线程。

private void UpdateLoadingLabel(int percent)
{
    Application.Dispatcher.BeginInvoke(DispatcherPriority.Background,
    new Action(delegate() { 
         TheBoundProperty = string.Format("Loading {0}/100..", percent);
     }));
}

我不完全确定我是否掌握了所有这些正确的语法,但你应该明白这一点。有关此示例代码的更详细说明,请查看this answer

【讨论】:

  • 对此有两个问题。 (1) 你的意思是应该从另一个线程的进程中调用 UpdateLoadingLabel 方法,对吗? (2) 您的建议与该线程中的其他答案有何关联?我无法决定哪个最适合我 - 他的更短,但你的似乎更深刻。
  • 是的,UpdateLoadingLabel 将从后台线程调用。在这里使用 Dispatcher 的主要原因是 WPF 中的对象无法从未创建它们的线程中修改,因此为了更新您的加载标签,您需要将调用发送回主 UI 线程。另一个答案是使用await,这是一个较新的关键字,允许您在另一个线程上排队以下代码,而无需实际创建任务/线程。它的主要问题是如果您必须从等待代码中更新在主 UI 线程上创建的项目。
【解决方案2】:

使用延迟。

public async void DoStuffForAWhile(int limit)
{
  TheBoundProperty = "This is not shown.";

  DateTime start = DateTime.Now;
  TimeSpan elapsed;
  do
  {
    elapsed = new TimeSpan(DateTime.Now.Ticks - start.Ticks);
    TheBoundProperty = "Neither is this.";
    await Task.Delay(1000); //1 sec delay
  } while (elapsed.TotalSeconds < limit);

  TheBoundProperty = "But this is!";
}

更新

如果你坚持使用Task..

public async void Foo(int limit){
    await DoStuffForAWhile(limit);
    TheBoundProperty = "But this is!";
}

public async Task DoStuffForAWhile(int limit)
{
  TheBoundProperty = "This is not shown.";

  DateTime start = DateTime.Now;
  TimeSpan elapsed;
  do
  {
    elapsed = new TimeSpan(DateTime.Now.Ticks - start.Ticks);
    TheBoundProperty = "Neither is this.";
    await Task.Delay(1000); //1 sec delay
  } while (elapsed.TotalSeconds < limit);
}

但是完全没必要使用Task或者Dispatcher

【讨论】:

  • 此代码与其他回复有何关联?当我使用你的时,它解决了我的问题,但我担心我只是隐藏了问题。你会说使用 asyncawaitTask 更可取吗?或者更确切地说 - 有什么好处和坏处?
  • 只是你不需要使用一个Task,使用“async void”就是射击和忘记,这就是你所需要的。
  • 双重建议+1。
  • 使用这个实现要注意的主要事情是 WPF 中的对象只能在创建它们的线程上修改,因此在 async 中修改或创建对象可能会遇到一些麻烦代码。对于像Task.Delay 这样简单的东西,这很好。
  • TheBoundProperty = "这也不是。"; - 此语句在创建异步操作的线程上执行 - 除非明确初始化新线程,否则当前实现不会出现任何问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-11
  • 2012-01-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多