2010-01-29 4 views
0

Voici le code C# directement à partir du site Web (http://jobijoy.blogspot.com/2007/10/time-picker-user-control.html) auquel tout le monde se réfère lorsque quelqu'un pose des questions sur TimePicker pour WPF, même si je l'ai déplacé un peu pour être mieux organisé. (S'il vous plaît noter, si vous essayez d'exécuter ce code pour travailler avec elle: vous devez changer le code XAML sur ce site de KeyDown à PreviewKeyDown sur les 3 grilles où les heures, minutes et secondes affiche en direct, et changer les TextBlocks avec chaque grille à TextBoxes)Erreur de récursion infinie à l'aide de 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; 
     } 
    } 
} 

Je ne suis pas très bien avec la dépendance ou encore Binding, je suis en train d'apprendre ce, c'est pourquoi je ne peux pas comprendre. Mais voici le problème: Quand les Minutes ou les Secondes sont prises au-delà de 59/-59 il y a une boucle infinie. J'expliquerai le déroulement de celui-ci (au moins j'apprends tellement ici!):

Supposons que l'objet TimeControl soit à 0:59:00 et que nous appuyions sur la touche haut tout en mettant l'accent sur la TextBox de la minute. Donc, comme nous suivons la logique, il va à l'événement PreviewKeyDown, et l'instruction switch nous emmène à this.Minutes ++ qui obtient Minutes et voit 59, donc définit les minutes à 60.

Cela déclenche OnTimeChanged pour Minutes, qui obtient Heures (0) Minutes (60) Secondes (0) et définit la valeur à cela. Puisque Value est un TimeSpan, il l'interprète comme 1:00:00, ce qui est génial. Donc, une fois que ce paramètre est défini, il déclenche OnValueChanged, qui définit Hours à 1, ce qui rappelle immédiatement OnTimeChanged for Hours. À ce stade, il obtient Heures (1) Minutes (60) secondes (0) et définit la valeur à cela (qui est interprétée comme 2:00:00).

Maintenant, nous avons une boucle infinie jusqu'à ce que Heures devienne trop grande et jette une exception. C'est un peu sur ma tête de comprendre comment le réparer. Quelle serait la "bonne" solution? Je sais qu'il pourrait être corrigé avec des instructions if dans l'instruction switch, ou même avec les méthodes OnTimeChanged/OnValueChanged, mais je suis sûr qu'il y a une meilleure façon de le faire avec les dépendances.

+0

Je l'ai résolu comme ça, mais ça ne semble pas du tout élégant: if (control.Minutes == 60) {// fais les minutes en premier} else if (control.Seconds == 60) {// fais Secondes first} else {// do Hours first} – Brandon

Répondre

0

Correctif simple: modifiez-le pour qu'il réinitialise d'abord les minutes, puis mettez à jour l'heure.

// Disclaimer: Lus pas le code mais si je peux me tromper

+0

Cela ne fonctionne que dans le cas des minutes, pas des secondes. J'ai une solution (commentée à mon poste), mais ce n'est pas très élégant (et dans le même sens que ce que vous avez dit). – Brandon

+0

Vous pouvez ajouter une propriété TimeSpan à TimeControl pour pouvoir définir toutes les valeurs en même temps. La propriété TimeSpan doit définir les valeurs directement et appeler uniquement l'événement TimeChanged _after_ toutes les 3 valeurs ont changé. De cette façon, vous n'avez pas besoin de vous soucier de l'ordre dans lequel les valeurs sont mises à jour, il suffit de control.TimeSpan = (TimeSpan) e.NewValue; – dbemerlin

+0

Est-ce que cela fonctionnerait? Je demande parce que pour mettre toutes les 3 valeurs que vous appelez le setter pour chaque composant individuel, et immédiatement après qu'un setter est terminé OnTimeChanged est appelé. – Brandon

0

Pas besoin de définir les propriétés si elles ne sont pas différentes, essayer quelque chose comme ceci:

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

Normalement, je 'd mettre cette logique dans les setters, quelque chose que vous voyez commun avec les couches d'accès aux données ... mais je pense que vos appels de dépendance se passerait toujours là, donc mieux de le faire dans ce gestionnaire d'événements dans votre code.

+0

Oui, je suis d'accord que ça ne servirait à rien (dans ce cas) de les mettre dans les setters. Cela ne fonctionne pas, car Hours est différent (passant de 0 à 1), puis il appelle immédiatement OnTimeChanged et Minutes est toujours à 60. – Brandon

Questions connexes