【问题标题】:Infinite recursion error using DependencyProperty (C#)使用 DependencyProperty (C#) 的无限递归错误
【发布时间】:2010-01-29 13:55:11
【问题描述】:

这里是直接来自网站 (http://jobijoy.blogspot.com/2007/10/time-picker-user-control.html) 的 C# 代码,当有人询问 WPF 的 TimePicker 时,每个人都会参考它,尽管我将它移动了一点以更有条理。 (请注意,如果您尝试运行此代码以使用它:您必须在显示小时、分钟和秒的 3 个网格上将此站点上的 XAML 代码从 KeyDown 更改为 PreviewKeyDown,并将 TextBlocks 更改为每个网格到文本框)

public partial class TimeControl : UserControl
{
    public TimeControl()
    {
        InitializeComponent();
    }

    public TimeSpan Value
    {
        get { return (TimeSpan)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register("Value", typeof(TimeSpan), typeof(TimeControl),
    new UIPropertyMetadata(DateTime.Now.TimeOfDay, new PropertyChangedCallback(OnValueChanged)));

    public int Hours
    {
        get { return (int)GetValue(HoursProperty); }
        set { SetValue(HoursProperty, value); }
    }
    public static readonly DependencyProperty HoursProperty =
    DependencyProperty.Register("Hours", typeof(int), typeof(TimeControl),
    new UIPropertyMetadata(0, new PropertyChangedCallback(OnTimeChanged)));

    public int Minutes
    {
        get { return (int)GetValue(MinutesProperty); }
        set { SetValue(MinutesProperty, value); }
    }
    public static readonly DependencyProperty MinutesProperty =
    DependencyProperty.Register("Minutes", typeof(int), typeof(TimeControl),
    new UIPropertyMetadata(0, new PropertyChangedCallback(OnTimeChanged)));

    public int Seconds
    {
        get { return (int)GetValue(SecondsProperty); }
        set { SetValue(SecondsProperty, value); }
    }
    public static readonly DependencyProperty SecondsProperty =
    DependencyProperty.Register("Seconds", typeof(int), typeof(TimeControl),
    new UIPropertyMetadata(0, new PropertyChangedCallback(OnTimeChanged)));

    private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        TimeControl control = obj as TimeControl;
        control.Hours = ((TimeSpan)e.NewValue).Hours;
        control.Minutes = ((TimeSpan)e.NewValue).Minutes;
        control.Seconds = ((TimeSpan)e.NewValue).Seconds;
    }

    private static void OnTimeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
            TimeControl control = obj as TimeControl;
            control.Value = new TimeSpan(control.Hours, control.Minutes, control.Seconds);
    }

    private void Down(object sender, KeyEventArgs args)
    {
        switch (((Grid)sender).Name)
        {
            case "sec":
                if (args.Key == Key.Up)
                    this.Seconds++;
                if (args.Key == Key.Down)
                    this.Seconds--;
                break;

            case "min":
                if (args.Key == Key.Up)
                    this.Minutes++;
                if (args.Key == Key.Down)
                    this.Minutes--;
                break;

            case "hour":
                if (args.Key == Key.Up)
                    this.Hours++;
                if (args.Key == Key.Down)
                    this.Hours--;
                break;
        }
    }
}

我对依赖或绑定还不是很好,我只是在学习它,这就是为什么我无法弄清楚。但问题是:当 Minutes 或 Seconds 超过 59/-59 时,就会出现无限循环。我会解释它的流程(至少我在这里学到了很多东西!):

假设 TimeControl 对象位于 0:59:00,我们在关注分钟文本框时按下向上键。因此,按照逻辑,它进入 PreviewKeyDown 事件,switch 语句将我们带到 this.Minutes++,它获取 Minutes 并看到 59,因此将分钟设置为 60。

这会触发 OnTimeChanged for Minutes,它会获取 Hours (0) Minutes (60) Seconds (0) 并将 Value 设置为该值。由于 Value 是 TimeSpan,因此它会将其解释为 1:00:00,这很好。

因此,一旦设置好,它就会触发 OnValueChanged,它将 Hours 设置为 1,这会立即回调 OnTimeChanged for Hours。此时,它获取小时 (1) 分钟 (60) 秒 (0) 并将 Value 设置为该值(解释为 2:00:00)。

现在我们有一个无限循环,直到 Hours 变得太大并引发异常。了解如何解决它有点过头了。什么是“适当的”修复?我知道可以使用 switch 语句中的 if 语句,甚至 OnTimeChanged/OnValueChanged 方法来修复它,但我确信有更好的方法来处理依赖项。

【问题讨论】:

  • 我已经这样解决了,但它看起来一点也不优雅: if (control.Minutes == 60) { // 先做分钟 } else if (control.Seconds == 60 ) { // 先做秒 } else { // 先做小时 }

标签: c# data-binding dependency-properties infinite-loop


【解决方案1】:

简单修复:更改它,使其首先重置分钟,然后更新小时。

// 免责声明:还没有阅读代码,所以我可能错了

【讨论】:

  • 这仅适用于分钟,而不是秒。我有一个解决方案(对我的帖子发表了评论),但它不是很优雅(和你所说的一样)。
  • 您可能希望将 TimeSpan 属性添加到 TimeControl,以便一次设置所有值。 TimeSpan 属性应直接设置值,并且仅在 所有 3 个值都更改后调用 TimeChanged 事件。这样您就不需要关心值的更新顺序,您只需执行 control.TimeSpan = (TimeSpan)e.NewValue;
  • 这行得通吗?我问是因为要设置所有 3 个值,您需要为每个单独的组件调用 setter,并且在 setter 完成后立即调用 OnTimeChanged。
【解决方案2】:

如果属性不同,则无需设置,请尝试以下操作:

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    TimeControl control = obj as TimeControl;
    var ts =  (TimeSpan)e.NewValue;
    if(ts.Hours != control.Hours) control.Hours = ts.Hours;
    if(ts.Minutes != control.Minutes) control.Minutes = ts.Minutes;
    if(ts.Seconds != control.Seconds) control.Seconds = ts.Seconds;
}

通常我会将这个逻辑放在设置器中,这在数据访问层中很常见......但我认为你的依赖调用仍然会在那里发生,所以最好在代码中的这个事件处理程序中进行。

【讨论】:

  • 是的,我同意(在这种情况下)将它们放入二传手是无济于事的。这实际上不起作用,因为 Hours 不同(从 0 到 1)然后它立即再次调用 OnTimeChanged,而 Minutes 仍然是 60。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多