2010-01-29 4 views
0

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

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

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

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

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

Итак, после того, как он установлен, он активирует OnValueChanged, который устанавливает Часы в 1, и это немедленно вызывает OnTimeChanged for Hours. На данный момент он получает Часы (1) Минуты (60) Секунды (0) и устанавливает значение в значение (которое интерпретируется как 2:00:00).

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

+0

Я решил это так, но он не выглядит элегантным вообще: if (control.Minutes == 60) {// делать минуты сначала} else if (control.Seconds == 60) {// do Секунды сначала} else {// делать часы сначала} – Brandon

ответ

0

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

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

+0

Это работает только в случае минут, а не секунд. У меня есть решение (комментарий к моему сообщению), но он не очень изящный (и по тем же направлениям, что вы сказали). – Brandon

+0

Возможно, вы захотите добавить свойство TimeSpan в TimeControl, чтобы сразу установить все значения. Свойство TimeSpan должно устанавливать значения непосредственно и только вызывать событие TimeChanged _after_, все 3 значения изменились. Таким образом, вам не нужно заботиться о том, в каком порядке значения обновляются, вы просто выполняете control.TimeSpan = (TimeSpan) e.NewValue; – dbemerlin

+0

Будет ли это работать? Я прошу, чтобы установить все 3 значения, которые вы вызываете установщиком для каждого отдельного компонента, и сразу после завершения сеттера вызывается OnTimeChanged. – Brandon

0

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

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

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

+0

Да, я согласен, что это не поможет (в данном случае) поставить их в сеттеры. Это фактически не работает, потому что часы отличаются (от 0 до 1), а затем он снова вызывает OnTimeChanged, а Minutes все еще равен 60. – Brandon