2010-10-04 5 views
10

Utilisation de Silverlight 4 & WPF 4, j'essaie de créer un style de bouton qui modifie la couleur du texte de tout texte contenu lorsque le bouton est mouseover'd. Depuis que je suis en train de faire ce compatible avec Silverlight & WPF, j'utilise le gestionnaire de l'état visuel:Dans le modèle de contrôle d'un bouton, comment puis-je définir la couleur du texte contenu?

<Style TargetType="{x:Type Button}"> 
<Setter Property="Template"> 
    <Setter.Value> 
     <ControlTemplate TargetType="Button"> 
      <Border x:Name="outerBorder" CornerRadius="4" BorderThickness="1" BorderBrush="#FF757679"> 
       <VisualStateManager.VisualStateGroups> 
        <VisualStateGroup x:Name="CommonStates"> 
         <VisualState x:Name="Normal" /> 
         <VisualState x:Name="MouseOver"> 
          <Storyboard> 
           <ColorAnimation Duration="0" To="#FFFEFEFE" 
               Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)" 
               Storyboard.TargetName="contentPresenter"/> 
          </Storyboard> 
         </VisualState> 
        </VisualStateGroup> 
       </VisualStateManager.VisualStateGroups> 
       <Grid> 
        <Border x:Name="Background" CornerRadius="3" BorderThickness="1" BorderBrush="Transparent"> 
         <Grid> 
          <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"/> 
         </Grid> 
        </Border> 
       </Grid> 
      </Border> 
     </ControlTemplate> 
    </Setter.Value> 
</Setter> 

Puisque c'est un modèle pour un bouton régulier vieux, je sais qu'il n'y a pas garantir qu'il y a même un bloc de texte à l'intérieur, et au début je n'étais pas sûr que c'était même possible. Curieusement, la couleur du texte ne changement si le bouton est déclarée comme:

<Button Content="Hello, World!" /> 

mais ne changement si le bouton est déclarée comme:

<Button> 
    <TextBlock Text="Hello, World!" /> <!-- Same result with <TextBlock>Hello, World </TextBlock> --> 
</Button> 

Même si l'arbre visuel (quand inspecté dans snoop) est identique (Button -> ContentPresenter -> TextBlock), avec la mise en garde que le bloc de texte créé dans la 1ère version a son contexte de données défini sur "Hello, World", alors que le bloc de texte dans la seconde ensemble de propriétés de texte. Je présume que cela a quelque chose à voir avec l'ordre de création du contrôle (la première version du bouton crée le TextBlock, dans la deuxième version, le bloc de texte peut être créé en premier (vraiment pas sur ce point). Dans le cadre de cette recherche, j'ai vu des solutions qui fonctionnent dans Silverlight (comme le remplacement du ContentPresenter par un ContentControl), mais cela ne fonctionnera pas dans WPF (le programme se bloque réellement). Puisque ceci est dans le modèle de contrôle du bouton, et si je veux utiliser le VSM si possible, je pense que cela exclut également la modification explicite de la propriété Foreground de Button (je ne sais pas comment j'accèderais à partir de dans le modèle?)

J'apprécierais vraiment n'importe quelle aide, conseil que n'importe qui pourrait donner.

+0

Je suis surpris que cela a fonctionné du tout compte tenu que TextElement n'a rien à voir avec TextBlock. – Denis

Répondre

5

Ainsi, après un peu plus la pensée, la solution que je suis finalement arrivé à est d'ajouter une propriété attachée à l'élément ContentPresenter au sein du modèle de contrôle du bouton. La propriété attachée accepte une couleur et lorsqu'elle est définie examine l'arborescence visuelle du présentateur de contenu pour tous les TextBlocks, et définit à son tour leurs propriétés de premier plan à la valeur transmise. Cela peut évidemment être étendu/fait pour gérer des éléments supplémentaires pour ce dont j'ai besoin.

public static class ButtonAttachedProperties 
    { 
     /// <summary> 
     /// ButtonTextForegroundProperty is a property used to adjust the color of text contained within the button. 
     /// </summary> 
     public static readonly DependencyProperty ButtonTextForegroundProperty = DependencyProperty.RegisterAttached(
      "ButtonTextForeground", 
      typeof(Color), 
      typeof(FrameworkElement), 
      new FrameworkPropertyMetadata(Color.FromArgb(255, 0, 0, 0), FrameworkPropertyMetadataOptions.AffectsRender, OnButtonTextForegroundChanged)); 

     public static void OnButtonTextForegroundChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) 
     { 
      if (e.NewValue is Color) 
      { 
       var brush = new SolidColorBrush(((Color) e.NewValue)) as Brush; 
       if (brush != null) 
       { 
        SetTextBlockForegroundColor(o as FrameworkElement, brush); 
       } 
      } 
     } 

     public static void SetButtonTextForeground(FrameworkElement fe, Color color) 
     { 
      var brush = new SolidColorBrush(color); 
      SetTextBlockForegroundColor(fe, brush); 
     } 

     public static void SetTextBlockForegroundColor(FrameworkElement fe, Brush brush) 
     { 
      if (fe == null) 
      { 
       return; 
      } 

      if (fe is TextBlock) 
      { 
       ((TextBlock)fe).Foreground = brush; 
      } 

      var children = VisualTreeHelper.GetChildrenCount(fe); 
      if (children > 0) 
      { 
       for (int i = 0; i < children; i++) 
       { 
        var child = VisualTreeHelper.GetChild(fe, i) as FrameworkElement; 
        if (child != null) 
        { 
         SetTextBlockForegroundColor(child, brush); 
        } 
       } 
      } 
      else if (fe is ContentPresenter) 
      { 
       SetTextBlockForegroundColor(((ContentPresenter)fe).Content as FrameworkElement, brush); 
      } 
     } 
    } 

et j'ai modifié le modèle comme ceci:

<ContentPresenter x:Name="contentPresenter" 
        ContentTemplate="{TemplateBinding ContentTemplate}" 
        local:ButtonAttachedProperties.ButtonTextForeground="{StaticResource ButtonTextNormalColor}" /> 
+0

Ceci est une bonne solution aussi, je suis curieux de savoir quels storyboards d'animation vous seriez capable d'utiliser puisque vous êtes lié à une source statique. – Jeremiah

+0

J'ai dû utiliser un ObjectAnimationUsingKeyFrames, par opposition à un ColorAnimation ou quelque chose de similaire. – Jordan0Day

4

Ceci est difficile. Le premier plan est une propriété du bouton transmis aux contrôles créés par le présentateur de contenu. Comme il s'agit d'une propriété de dépendance et non d'une propriété des contrôles disponibles dans le modèle, il est difficile de l'animer à l'aide de xaml pur.

MSDN A quelques exemples sur la façon de changer la couleur de premier plan. Cependant, il ne semble pas que vous vouliez faire ce travail à partir du code. (http://msdn.microsoft.com/en-us/library/system.windows.controls.button(VS.95).aspx)

Le modèle par défaut du bouton vous retient. Comme vous l'avez remarqué, le bouton est un contrôle sans contenu; ce qui signifie que le concepteur peut pousser un objet visuel aléatoire à l'intérieur de celui-ci. Le fait d'être sans contenu oblige la propriété de premier plan à être membre du modèle plutôt que du contrôle car il n'y a pas de composant garanti pour définir la couleur. C'est pourquoi il y a un présentateur de contenu à l'intérieur de celui-ci.

Vous avez maintenant deux options. 1. Facile mais pas flexible (pur xaml), 2. Créez votre propre contrôle qui fait tout ce qu'un bouton fait (nécessite du code et beaucoup de tests)

Je vais mettre en œuvre n ° 1 pour vous.

Si vous modifiez le modèle du bouton et supprimez le présentateur de contenu, vous pouvez placer à l'intérieur de celui-ci deux blocs de texte. Un avec la couleur normale, l'autre avec votre souris sur la couleur.

<TextBlock x:Name="RedBlock" Text="{TemplateBinding Content}"  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Foreground="Red" Visibility="Collapsed"/>  
<TextBlock x:Name="BlueBlock" Text="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Foreground="#FF0027FF"/> 

Notez que les propriétés Text des blocs de texte sont liées au contenu du bouton. Cela devient un problème s'il y a un besoin de se lier à autre chose que du texte. TextBlocks ne peut simplement afficher que des valeurs AlphaNumeric.

Notez également que par défaut j'ai rétréci la visibilité sur le RedBlock.

Puis, dans mon MouseOver VisualState's Storyboard je peux animer le Redblock être visible et l'invisible BlueBlock d'être:

<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="RedBlock"> 
    <DiscreteObjectKeyFrame KeyTime="0"> 
     <DiscreteObjectKeyFrame.Value> 
      <Visibility>Visible</Visibility> 
     </DiscreteObjectKeyFrame.Value> 
    </DiscreteObjectKeyFrame> 
</ObjectAnimationUsingKeyFrames> 
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="BlueBlock"> 
    <DiscreteObjectKeyFrame KeyTime="0"> 
     <DiscreteObjectKeyFrame.Value> 
      <Visibility>Collapsed</Visibility> 
     </DiscreteObjectKeyFrame.Value> 
    </DiscreteObjectKeyFrame> 
</ObjectAnimationUsingKeyFrames> 

Il se sent comme un hack, et je ne reviendrais probablement pas mettre en œuvre ce bouton dans beaucoup d'endroits . Je voudrais créer mon propre bouton avec de bonnes DependencyProperties à lier. Un chacun pour HighlightForeground et Text. Je voudrais également cacher ou au moins jeter une exception si quelqu'un a essayé de définir le contenu à autre chose que des valeurs AlphaNumeric. Cependant, le XAML serait le même, j'aurais différents blocs de texte pour différents états visuels.

J'espère que cela aide.

Passez une bonne journée.

-Jeremiah

+0

Salut Jérémie, c'est une très bonne solution et fonctionne bien dans une situation où le bouton ne contiendrait que du texte. Dans mon cas, j'aimerais avoir une solution plus générique, afin que tous les enfants de blocs de texte obtiennent leurs couleurs de premier plan. (N'importe quelle chose intéressante à remarquer est comment fonctionne Button quand vous définissez sa propriété Foreground explicitement - n'importe quel enfant TextBlocks reçoit la couleur appropriée, peu importe comment imbriqué dans les panneaux/contrôles ils sont dans le contenu du bouton. Je suis finalement arrivé à utiliser les propriétés attachées dans ce fil – Jordan0Day

0

Je l'utilise dans mon modèle et il fonctionne très bien

<ControlTemplate TargetType="Button"> 
       <Border 
        Margin="{TemplateBinding Margin}" 
        BorderBrush="{StaticResource BorderBrush}" 
        BorderThickness="{TemplateBinding BorderThickness}" 
        CornerRadius="2" 
        > 

        <TextBlock 
         Margin="{TemplateBinding Padding}" 
         Text="{TemplateBinding Content}" 
         Foreground="White" 
         HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
         VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> 
       </Border> 

Vous devez insérer ce code dans votre création de modèle de bouton mais je l'utilise quand je mets du texte dans le contenu du bouton et si vous voulez pour ajouter d'autres contrôles sur elle utiliser le style normal

Questions connexes