【问题标题】:Binding to time-dependent properties绑定到时间相关的属性
【发布时间】:2011-05-25 11:49:37
【问题描述】:

前段时间我写了一个类似小部件的应用程序,它应该跟踪任务,每个任务都有一个指定为DateTime的截止日期,现在如果你想显示距离截止日期还有多少时间你可能想要绑定到“虚拟”(*诅咒virtual关键字*)属性,如下所示:

public TimeSpan TimeLeft
{
    get { return Deadline - DateTime.Now; }
}

从理论上讲,这个属性显然会在每一次滴答声中发生变化,并且您希望时不时地更新您的 UI(例如,通过定期为该属性抽出 PropertyChanged 事件)。

当我编写小部件时,我每分钟刷新整个任务列表,但这并不理想,因为如果用户与某些项目交互(例如,通过键入绑定到 Comments-property 的 TextBox),这将被严重中断并且源更新丢失。

如果您有这样的时间相关属性,那么更新 UI 的最佳方法是什么?

(顺便说一句,我不再使用那个应用程序了,只是觉得这是一个非常有趣的问题)

【问题讨论】:

  • 你比我更了解这些东西,所以我很好奇 - 除了 DP 和 INPC 之外,还有哪些事件或动作导致绑定系统重新评估当前 UI 中的所有绑定?有吗?如果是这样,是否可以依赖其中任何一个可靠地发生?
  • @Tim:想不到任何,好吧,当 DataContext 更改时,由于继承而导致子树中的更新(对于绑定到它的绑定)。在 WPF 中,您可以公开访问关联的 DataContextChanged 事件。由于某种原因,在 Silverlight 中它是内部的。
  • 作为 WPF 设计要求,所有属性都具有 setter 并且 getter 始终返回设置值可能值得说明。否则说像上面这样的功能代表 GUI 本质上是一个谎言。
  • @DaxFohl:我认为这不是一个好主意,您需要为可以即时检索的内容引入冗余的支持字段。此外,该属性也会对其内容撒谎,因为它不能准确,因为它只能经常刷新。
  • 您的问题是一般性和基本性的。时间是连续的。 UI 更新是严格离散的。因此,您需要从离散点的连续数据源更新您的 UI。因此,抽水是不可避免的。您可以使用计时器(您也可以订阅CompositionTarget.Rendering 事件,但我认为不需要)我正在制作一个允许此类绑定(以及其他东西)的库,但您的情况是简单,不会从中受益。

标签: wpf data-binding time dependencies notifications


【解决方案1】:

计时器是我能想到的唯一方法。由于这是一个有趣的问题,我将把我的 .02 放进去。我会像这样封装它:

public class CountdownViewModel : INotifyPropertyChanged
{
    Func<TimeSpan> calc;
    DispatcherTimer timer;

    public CountdownViewModel(DateTime deadline)
        : this(() => deadline - DateTime.Now)
    {
    }

    public CountdownViewModel(Func<TimeSpan> calculator)
    {
        calc = calculator;

        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(1);
        timer.Tick += timer_Tick;
        timer.Start();
    }

    void timer_Tick(object sender, EventArgs e)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs("CurrentValue"));
        }
    }

    public TimeSpan CurrentValue
    {
        get
        {
            var result = calc();
            if (result < TimeSpan.Zero)
            {
                return TimeSpan.Zero;
            }
            return result;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class MyViewModel
{
    public CountdownViewModel DeadlineCountdown { get; private set; }

    public DateTime Deadline { get; set; }

    public MyViewModel()
    {
        Deadline = DateTime.Now.AddSeconds(200);
        DeadlineCountdown = new CountdownViewModel(Deadline);
    }
}

然后你可以直接绑定到DeadlineCountdown.CurrentValue,或者创建一个CountdownView。如果需要,您可以将计时器移至CountdownView。您可以使用静态计时器,以便它们同时更新。

编辑

如果Deadline 发生变化,您必须像这样构建倒计时:

DeadlineCountdown = new CountdownViewModel(() => this.Deadline - DateTime.Now);

【讨论】:

  • 不错的努力,但有几件事:您不需要调度程序计时器,因为您不访问线程仿射对象,进一步封装可能具有调整倒计时变得更加复杂的缺点当截止日期更改时,因为属性不再像我的初始代码那样直接依赖于它。此外,类的构造对我来说似乎有点令人困惑,但这可能可以通过构造函数的一些文档来解决。
  • 关于调度程序计时器的要点 - 我正在考虑制作一个视图并进行“手动绑定”,所以我想我只是想到了调度程序计时器。我没有考虑更新截止日期 - 这使得第一个构造函数无用,但你仍然可以使用第二个。
  • 可能使用ref 让第一个拍摄截止日期也可以,不是吗?
  • 我不相信,但我以前错了:)。你不能use it in the lambda 也不能使用字段来保持“引用”。
【解决方案2】:

我认为您在代码示例之后的第一段中所说的是在 WPF 中使其工作的唯一合理方法。为TimeLeft 属性设置一个只调用PropertyChanged 的计时器。间隔会根据您的情况而有所不同(如果您正在谈论每周任务列表,您可能只需要每 5 分钟左右更新一次。如果您正在谈论接下来 30 分钟的任务列表,您可能需要每分钟或 30 秒更新一次。

该方法将避免您提到的刷新选项问题,因为只有TimeLeft 绑定会受到影响。如果你有数百万个这样的任务,我想性能损失会相当大。但是,如果您只有几十个或其他东西,那么每 30 秒左右更新一次这些绑定将是一个非常微不足道的问题,对吧?

我能想到的每一种可能性都使用计时器或动画。当您将任务添加到列表时,动画会太“沉重”。在 Timer 场景中,上面的场景似乎是最干净、最简单和最实用的一个。可能只是归结为它是否适用于您的特定场景。

【讨论】:

  • 我还认为使用 Timer 可能是最好的方法,一种改进可能是只为所有任务使用一个静态计时器,并在应用程序最小化时使计时器休眠。
  • 是的,肯定可以进行一些优化。这两个都是很好的建议。
  • 接受,直到魔术师停下来并提供了惊人的优越:P
【解决方案3】:

我阅读了您接受的答案,但我只是想知道......为什么不在“编辑”模式下禁用该特定任务的绑定,这样您就不会被打断?然后在完成或取消编辑后重新启用该绑定?这样即使你的计时器每秒更新一次,谁在乎呢?

至于如何在不分离它们的情况下禁用它们(从而重置它们的值),只需定义一个布尔标志,然后在要中断的所有 DP 中,在验证逻辑中检查该标志。如果标志为真并且它所应用的 DependencyObject 是您正在编辑的那个,则阻止对 DP 的更改。

无论如何,这只是突然出现在我的脑海中。还没有实际测试过,但应该很容易尝试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    • 2010-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多