2011-01-29 8 views
1

J'ai un UserControl de ClockFace qui expose un certain nombre de propriétés pour permettre aux utilisateurs de le styliser. L'horloge a deux objets Ellipse en tant que frontières; une bordure extérieure et une bordure intérieure.WPF DependencyProperty ... maintenant ça marche ... maintenant ça ne marche pas ... comment ça se fait?

<Ellipse Name="OuterBorder" Panel.ZIndex="5" StrokeThickness="{Binding BorderOuterThickness}" Stroke="{Binding BorderOuteBrush}" /> 
<Ellipse Name="InnerBorder" Panel.ZIndex="6" StrokeThickness="{Binding BorderInnerThickness}" Margin="{Binding StrokeThickness, ElementName=OuterBorder}" Stroke="{Binding BorderInnerBrush}"> 

public static readonly DependencyProperty BorderInnerBrushProperty = DependencyProperty.Register("BorderInnerBrush", typeof(Brush), typeof(ClockFace), new 

PropertyMetadata(new LinearGradientBrush(Color.FromRgb(118, 57, 57), Color.FromRgb(226, 185, 185), new Point(0.5, 0), new Point(0.5, 1)))); 

public Brush BorderInnerBrush 
{ 
    get { return (Brush)GetValue(BorderInnerBrushProperty); } 
    set { SetValue(BorderInnerBrushProperty, value); } 
} 

public static readonly DependencyProperty BorderOuterBrushProperty = DependencyProperty.Register("BorderOuterBrush", typeof(Brush), typeof(ClockFace), new 

PropertyMetadata(new LinearGradientBrush(Color.FromRgb(226, 185, 185), Color.FromRgb(118, 57, 57), new Point(0.5, 0), new Point(0.5, 1)))); 

public Brush BorderOuterBrush 
{ 
    get { return (Brush)GetValue(BorderOuterBrushProperty); } 
    set { SetValue(BorderOuterBrushProperty, value); } 
} 

Ces options peuvent être définies dans un style et seront mises à jour correctement lorsque les styles sont changés. J'ai pensé que je serais intelligent et ajouterais une propriété de raccourci appelée BorderBrush qui se passe à la propriété BorderOuterBrush et puis passe une copie de lui-même avec son dégradé inversé à la propriété BorderInnerBrush. Pour activer ce code lorsque la propriété a été définie après l'initialisation (en changeant de styles), j'ai dû implémenter une méthode PropertyChangedCallback qui appelle la méthode SetBorderBrushes.

public new static readonly DependencyProperty BorderBrushProperty = DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(ClockFace), new 

PropertyMetadata(OnBorderBrushChanged)); 

private static void OnBorderBrushChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 
{ 
    ((ClockFace)dependencyObject).SetBorderBrushes((Brush)e.NewValue); 
} 

public new Brush BorderBrush 
{ 
    get { return (Brush)GetValue(BorderBrushProperty); } 
    set { SetValue(BorderBrushProperty, value); } 
} 

private void SetBorderBrushes(Brush brush) 
{ 
    if (brush != null) 
    { 
     BorderOuterBrush = brush; 
     Brush innerBrush = BorderOuterBrush.Clone(); 
     if (brush.GetType() == typeof(LinearGradientBrush) || brush.GetType() == typeof(RadialGradientBrush)) 
     { 
      foreach (GradientStop gradientStop in ((GradientBrush)innerBrush).GradientStops) 
      { 
       gradientStop.Offset = 1 - gradientStop.Offset; 
      } 
     } 
     BorderInnerBrush = innerBrush; 
    } 
} 

Lorsque la propriété BorderBrush est définie dans un style, tout fonctionne très bien ... qui est jusqu'à ce que je passe le style au moment de l'exécution. Maintenant, la chose la plus étrange arrive ... laissez-moi vous expliquer.

J'ai quatre styles prédéfinis et individuellement, ils fonctionnent tous très bien. Trois d'entre eux utilisent les deux propriétés BorderInnerBrush et BorderOuterBrush et l'autre utilise la propriété BorderBrush du raccourci. Je peux basculer entre les styles en utilisant un ContextMenu et du code derrière qui accède aux styles xaml à partir de Ressources et les définit à la propriété Style de l'objet ClockFace.

Je peux basculer entre les trois styles qui n'utilisent pas la propriété raccourci sans problème. Je peux également passer au style qui utilise la propriété de raccourci et il semble très bien. C'est quand l'étrangeté commence. Après avoir basculé vers le style qui utilise la propriété BorderBrush, les propriétés BorderInnerBrush et BorderOuterBrush cessent tout simplement de fonctionner. Les objets Pinceau prédéfinis définis dans les différents styles ne sont plus définis sur les deux objets Ellipse. J'ai branché des méthodes PropertyChangedCallback aux propriétés de bordure intérieure et extérieure pour voir ce qui se passait. Lorsque je lance l'application pour la première fois, je peux passer aux trois styles qui n'utilisent pas la propriété de raccourci sans problème. J'ai mis des points d'arrêt sur les trois méthodes PropertyChangedCallback de la propriété border Brush et j'ai débogué le programme. Lorsque vous passez à chacun de ces trois styles, les points d'arrêt des méthodes de callback des propriétés de bordure intérieure et extérieure ont été atteints comme vous le souhaitez. Lorsque vous basculez vers le style qui utilise la propriété BorderBrush, le point d'arrêt de sa méthode de rappel a été atteint. La méthode SetBorderBrushes définit les deux autres objets Brush de bordure, de sorte que les points d'arrêt des méthodes de callback des propriétés de bordure interne et externe ont ensuite été frappés comme prévu.

Encore une fois, c'est la partie étrange. Lorsque vous passez ensuite à l'un des trois autres styles, les points d'arrêt des méthodes de callback des propriétés de bordure interne et externe ne sont plus affichés. Au lieu de cela, le point d'arrêt de la méthode de rappel attachée à la propriété BorderBrush est frappé et la valeur de e.NewValue est nulle. Comme les valeurs nulles sont ignorées dans la méthode SetBorderBrushes, aucun autre point d'arrêt n'est touché. Après une enquête plus poussée, j'ai découvert que e.NewValue était null car il n'y avait pas de valeur par défaut définie sur BorderBrushProperty DependencyProperty. En effet, après avoir ajouté un objet Brush par défaut à la déclaration, il s'agit du pinceau qui sera transmis dans e.NewValue dans la méthode de rappel. Bien que les points d'arrêt dans les méthodes de rappel des propriétés des deux frontières internes et externes soient atteints après cela, c'est uniquement parce qu'ils sont définis dans la méthode SetBorderBrushes. Les objets Brush définis dans les objets BorderInnerBrush et BorderOuterBrush dans les styles ne sont jamais transmis à ces propriétés après que la propriété BorderBrush a été utilisée une fois.Un dernier point à noter est que, tout comme la valeur par défaut de la propriété BorderBrushProperty est définie lorsqu'une valeur n'est pas explicitement définie dans un style, les valeurs par défaut des objets DependencyProperty de bordure interne et externe sont également définies lorsque leurs valeurs ne sont pas définis explicitement dans un style, mais uniquement lorsque la propriété BorderBrush n'est pas définie dans le style.

Je suis bloqué depuis des jours et même si une solution simple serait de supprimer la propriété de raccourci, je préfère savoir ce qui se passe et le réparer. J'espère avoir fourni suffisamment d'informations pour que je puisse résoudre ce problème. Si vous avez des idées ou des questions, n'hésitez pas à les partager. Merci beaucoup.

MISE À JOUR >>

la suggestion de Rick, je créé une autre paire de propriétés brosse et les mettre dans les méthodes de rappel attachées aux trois autres propriétés brosse. En utilisant cette configuration, je pourrais basculer entre tous les styles sans aucun problème visuel ... ou alors je pensais.

Je peux maintenant passer du style qui utilise la propriété BorderBrush (raccourci) aux styles qui définissent les deux propriétés BorderInner et BorderOuter et les mises à jour des bordures correctement, alors merci à Rick de me rapprocher. J'ai toujours un problème en passant du style qui définit la propriété BorderBrush au style qui ne définit explicitement aucune bordure Brush.

Par conséquent, j'ai toujours le même problème que les propriétés BorderInnerBrush et BorderOuterBrush ne fonctionnent pas directement après avoir été définies à partir du code derrière. Ce qui est nouveau, c'est que si je passe à un style qui définit les deux propriétés du pinceau, cela semble les "réactiver" ... si je passe ensuite au style qui ne définit pas de pinceau, les propriétés interne et externe définissent leur les valeurs par défaut correctement à nouveau. Ce n'est qu'après avoir utilisé un style qui définit les deux propriétés de la bordure du code qu'elles deviennent mortes en étant définies dans les styles ou en utilisant leurs valeurs par défaut. C'est tellement étrange ... quelqu'un peut-il s'en sortir?

MISE À JOUR 2 >>>

Tout d'abord, merci beaucoup pour votre temps et Rick exemple. Après avoir copié et collé le code d'exemple de Rick dans un nouveau projet, j'ai dû faire quelques modifications avant de le lancer ... peut-être parce que je n'ai pas Expression Blend 4? J'ai installé le SDK et ajouté les références aux DLLs mentionnées, mais a dû utiliser les déclarations d'espace de noms XML suivants au lieu:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
xmlns:ei="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions" 

Je devais aussi changer chaque déclaration de ChangePropertyAction légèrement:

<ei:ChangePropertyAction TargetName="clock" PropertyName="Style" Value="{StaticResource styleBrush}"/> 

Ensuite, , il a bien fonctionné pour moi et est une bonne représentation de cette partie de mon contrôle ClockFace réel, à deux exceptions près. La première est que mes styles sont passés du code derrière via un gestionnaire d'événement - cela ne cause évidemment pas mon problème. La seconde différence est que j'ai défini des valeurs de pinceau par défaut sur mes versions de ActualOuterBrush et ActualInnerBrush, de sorte que les utilisateurs du contrôle n'ont pas à fournir de pinceaux de bordure. Mon dernier problème est que les pinceaux de bordure interne et externe par défaut ne sont pas définis dans un style sans propriétés de bordure définies explicitement après la définition de la propriété BorderBrush dans un style. Rappelez-vous, les valeurs par défaut sont configurées jusqu'à ce point.Donc, j'ai expérimenté avec l'exemple de Rick et a ajouté quelques valeurs par défaut:

private static readonly DependencyPropertyKey ActualInnerBrushPropertyKey = DependencyProperty.RegisterReadOnly("ActualInnerBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata(Brushes.Teal)); 

private static readonly DependencyPropertyKey ActualOuterBrushPropertyKey = DependencyProperty.RegisterReadOnly("ActualOuterBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata(Brushes.Salmon)); 

Maintenant, ce projet a un problème similaire ... les valeurs par défaut ne sont jamais remis à zéro. Je suis tellement déconcerté par ce comportement.

J'ai passé si longtemps sur ce problème maintenant et bien que j'ai appris un certain nombre de choses utiles de Rick, j'ai toujours ce problème avec les valeurs par défaut. J'ai décidé que la chose la plus simple à faire est de supprimer la propriété du raccourci BorderBrush. Rick a tellement aidé que j'attribue sa réponse comme la bonne réponse, mais si quelqu'un qui lit ceci peut aider avec la valeur par défaut, je serais reconnaissant d'avoir des nouvelles d'eux.

Répondre

0

Une fois qu'une propriété a été définie, les styles ne seront plus appliqués. En définissant vous-même les propriétés de pinceau interne et externe dans votre gestionnaire modifié, le sous-système de propriété de dépendance ne sait pas que le changement était dû à un style, contrairement à ce que vous avez fait explicitement. Une solution consiste à exposer les propriétés protégées en lecture seule ActualInnerBorderBrush et ActualOuterBorderBrush en lecture seule et de faire en sorte que les trois propriétés définissables par l'utilisateur définissent ces valeurs réelles dans leurs gestionnaires de changement respectifs. De cette façon, les propriétés visibles par l'utilisateur peuvent toujours être "définies par l'utilisateur" sans interférer les unes avec les autres.

Edit:

Voici une mise en œuvre complète de travail des cinq propriétés:

public class Clock : StackPanel 
{ 
    public Brush Brush 
    { 
     get { return (Brush)GetValue(BrushProperty); } 
     set { SetValue(BrushProperty, value); } 
    } 

    public static readonly DependencyProperty BrushProperty = 
     DependencyProperty.Register("Brush", typeof(Brush), typeof(Clock), 
     new UIPropertyMetadata((d, e) => (d as Clock).OnBrushChanged(d, e))); 

    public void OnBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     ActualInnerBrush = e.NewValue as Brush; 
     ActualOuterBrush = e.NewValue as Brush; 
    } 

    public Brush InnerBrush 
    { 
     get { return (Brush)GetValue(InnerBrushProperty); } 
     set { SetValue(InnerBrushProperty, value); } 
    } 

    public static readonly DependencyProperty InnerBrushProperty = 
     DependencyProperty.Register("InnerBrush", typeof(Brush), typeof(Clock), 
     new UIPropertyMetadata((d, e) => (d as Clock).OnInnerBrushChanged(d, e))); 

    public void OnInnerBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     ActualInnerBrush = e.NewValue as Brush; 
    } 

    public Brush OuterBrush 
    { 
     get { return (Brush)GetValue(OuterBrushProperty); } 
     set { SetValue(OuterBrushProperty, value); } 
    } 

    public static readonly DependencyProperty OuterBrushProperty = 
     DependencyProperty.Register("OuterBrush", typeof(Brush), typeof(Clock), 
     new UIPropertyMetadata((d, e) => (d as Clock).OnOuterBrushChanged(d, e))); 

    public void OnOuterBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     ActualOuterBrush = e.NewValue as Brush; 
    } 

    public Brush ActualInnerBrush 
    { 
     get { return (Brush)GetValue(ActualInnerBrushProperty); } 
     private set { SetValue(ActualInnerBrushPropertyKey, value); } 
    } 

    private static readonly DependencyPropertyKey ActualInnerBrushPropertyKey = 
     DependencyProperty.RegisterReadOnly("ActualInnerBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata()); 
    public static readonly DependencyProperty ActualInnerBrushProperty = ActualInnerBrushPropertyKey.DependencyProperty; 


    public Brush ActualOuterBrush 
    { 
     get { return (Brush)GetValue(ActualOuterBrushProperty); } 
     private set { SetValue(ActualOuterBrushPropertyKey, value); } 
    } 

    private static readonly DependencyPropertyKey ActualOuterBrushPropertyKey = 
     DependencyProperty.RegisterReadOnly("ActualOuterBrush", typeof(Brush), typeof(Clock), new UIPropertyMetadata()); 
    public static readonly DependencyProperty ActualOuterBrushProperty = ActualOuterBrushPropertyKey.DependencyProperty; 
} 

et voici un petit programme de test pour prouver que cela fonctionne:

<Grid> 
    <Grid.Resources> 
     <Style x:Key="styleBrush" TargetType="local:Clock"> 
      <Setter Property="Brush" Value="Red"/> 
     </Style> 
     <Style x:Key="styleInnerOnly" TargetType="local:Clock"> 
      <Setter Property="InnerBrush" Value="Green"/> 
     </Style> 
     <Style x:Key="styleInnerOuter" TargetType="local:Clock"> 
      <Setter Property="InnerBrush" Value="Blue"/> 
      <Setter Property="OuterBrush" Value="Yellow"/> 
     </Style> 
     <Style x:Key="styleEmpty" TargetType="local:Clock"/> 
    </Grid.Resources> 
    <StackPanel> 
     <local:Clock x:Name="clock" Orientation="Horizontal"> 
      <Rectangle Width="100" Height="100" Fill="{Binding ActualInnerBrush, ElementName=clock}"/> 
      <Rectangle Width="100" Height="100" Fill="{Binding ActualOuterBrush, ElementName=clock}"/> 
     </local:Clock> 
     <StackPanel Orientation="Horizontal"> 
      <Button Content="Default"> 
       <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Click"> 
         <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{x:Null}"/> 
        </i:EventTrigger> 
       </i:Interaction.Triggers> 
      </Button> 
      <Button Content="BrushOnly"> 
       <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Click"> 
         <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleBrush}"/> 
        </i:EventTrigger> 
       </i:Interaction.Triggers> 
      </Button> 
      <Button Content="InnerOnly"> 
       <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Click"> 
         <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleInnerOnly}"/> 
        </i:EventTrigger> 
       </i:Interaction.Triggers> 
      </Button> 
      <Button Content="InnerOuter"> 
       <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Click"> 
         <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleInnerOuter}"/> 
        </i:EventTrigger> 
       </i:Interaction.Triggers> 
      </Button> 
      <Button Content="Empty"> 
       <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Click"> 
         <ei:ChangePropertyAction TargetObject="{Binding ElementName=clock}" PropertyName="Style" Value="{StaticResource styleEmpty}"/> 
        </i:EventTrigger> 
       </i:Interaction.Triggers> 
      </Button> 
     </StackPanel> 
    </StackPanel> 
</Grid> 

Cet exemple utilise des comportements. Si vous n'êtes pas familier avec les comportements, installez le Expression Blend 4 SDK et ajouter ces espaces de noms:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 

et ajoutez System.Windows.Interactivity et Microsoft.Expression.Interactions à votre projet.

+0

J'ai essayé votre solution et je suis désolé de dire qu'elle n'a pas résolu mon problème de bordure. J'ai créé une autre paire de propriétés de pinceau et les ai placées de l'autre 3. En utilisant cette configuration, je pourrais basculer entre tous les styles sans n'importe quels problèmes visuels ... Le problème est que mes objets d'Ellipse se lient aux propriétés de pinceau, ainsi ils doivent être public ... alors un utilisateur pourrait les définir et j'aurais le même problème. – Sheridan

+0

Pas de problème: Rendez-le public en lecture seule! La seule raison que j'ai suggéré de protéger était de ne pas apparaître dans la liste IntelliSense de vos consommateurs. Pour créer une propriété de dépendance en lecture seule, utilisez DependencyProperty.RegisterReadOnly et utilisez un setter privé. –

+0

Le rappel est appelé uniquement lorsque la valeur change et si la propriété a une valeur, un sélecteur de style n'a aucun effet et la valeur reste inchangée. La raison pour laquelle la valeur est bloquée et la raison pour laquelle le gestionnaire de modification de propriété semble cesser d'être appelé est due à la même cause: les styles ne peuvent pas remplacer les valeurs définies explicitement. –