Ошибка бесконечной рекурсии с использованием DependencyProperty (C #) - PullRequest
0 голосов
/ 29 января 2010

Вот код C # непосредственно с веб-сайта (http://jobijoy.blogspot.com/2007/10/time-picker-user-control.html), на который все ссылаются, когда кто-то спрашивает о TimePicker для WPF, хотя я немного переместил его, чтобы быть более организованным. (Обратите внимание, если вы мы пытаемся запустить этот код для работы с ним: необходимо изменить код XAML на этом сайте с KeyDown на PreviewKeyDown в 3 сетках, где отображаются часы, минуты и секунды, и изменить TextBlocks с каждой сеткой на TextBoxes)

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;
        }
    }
}

Я пока не очень хорошо разбираюсь в зависимости и связывании, я только учусь этому, поэтому не могу понять. Но вот проблема: когда минуты или секунды взяты за пределы 59 / -59, возникает бесконечный цикл. Я объясню, как это происходит (по крайней мере, я многому здесь учусь!):

Допустим, объект TimeControl находится в 0:59:00, и мы нажимаем клавишу «вверх», в то время как фокусируемся на минутном TextBox. Итак, следуя логике, она переходит к событию PreviewKeyDown, и оператор switch переводит нас к этому. Minutes ++, который получает Minutes и видит 59, поэтому устанавливает минут на 60.

Это вызывает OnTimeChanged для минут, который получает Hours (0) Minutes (60) Seconds (0) и устанавливает для него значение. Поскольку Value - это TimeSpan, он интерпретирует это как 1:00:00, и это здорово.

Итак, как только это установлено, он переключает OnValueChanged, который устанавливает Hours на 1, и это немедленно вызывает OnTimeChanged для Hours. В этот момент он получает Hours (1) Minutes (60) Seconds (0) и устанавливает значение в значение (которое интерпретируется как 2:00:00).

Теперь у нас есть бесконечный цикл, пока Часы не станут слишком большими и не сгенерируют исключение. Это немного над моей головой, чтобы понять, как это исправить. Что будет правильным решением? Я знаю, что это можно исправить с помощью операторов if в операторе switch или даже с помощью методов OnTimeChanged / OnValueChanged, но я уверен, что есть лучший способ сделать это с зависимостями.

Ответы [ 2 ]

0 голосов
/ 29 января 2010

Нет необходимости устанавливать свойства, если они не отличаются, попробуйте что-то вроде этого:

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;
}

Обычно я бы помещал эту логику в сеттеры, что вы видите общим для слоев доступа к данным ... но я думаю, что ваши вызовы зависимостей все равно будут происходить там, поэтому лучше всего сделать это в этом обработчике событий в вашем коде. 1004 *

0 голосов
/ 29 января 2010

Простое исправление: измените его, чтобы сначала сбрасывать минуты, а затем обновлять час.

// Отказ от ответственности: еще не прочитал код, поэтому я могу ошибаться

...