Création d'un contrôle avec une apparence personnalisable
Windows Presentation Foundation (WPF) vous permet de créer un contrôle dont l’apparence peut être personnalisée. Par exemple, vous pouvez modifier l’apparence d’un CheckBox paramètre au-delà de ce que feront les propriétés en créant un nouveau ControlTemplate. L’illustration suivante montre un CheckBox qui utilise une valeur par défaut ControlTemplate et un CheckBox qui utilise un personnalisé ControlTemplate.
CheckBox qui utilise le modèle de contrôle par défaut
CheckBox qui utilise un modèle de contrôle personnalisé
Si vous suivez les parties et le modèle d’états lorsque vous créez un contrôle, l’apparence de votre contrôle est personnalisable. Les outils de concepteur tels que Blend pour Visual Studio prennent en charge les parties et le modèle d’états. Par conséquent, lorsque vous suivez ce modèle, votre contrôle sera personnalisable dans ces types d’applications. Cette rubrique décrit les parties et le modèle d’états et explique comment le suivre lorsque vous créez votre propre contrôle. Cette rubrique utilise un exemple de contrôle personnalisé, NumericUpDown
pour illustrer la philosophie de ce modèle. Le NumericUpDown
contrôle affiche une valeur numérique, qu’un utilisateur peut augmenter ou diminuer en cliquant sur les boutons du contrôle. L’illustration suivante montre le NumericUpDown
contrôle abordé dans cette rubrique.
Un contrôle NumericUpDown personnalisé
Cette rubrique contient les sections suivantes :
Prérequis
Cette rubrique suppose que vous savez comment créer un ControlTemplate contrôle existant, connaître les éléments d’un contrat de contrôle et comprendre les concepts abordés dans Créer un modèle pour un contrôle.
Remarque
Pour créer un contrôle qui peut avoir son apparence personnalisée, vous devez créer un contrôle qui hérite de la Control classe ou de l’une de ses sous-classes autres que UserControl. Un contrôle qui hérite UserControl est un contrôle qui peut être créé rapidement, mais il n’utilise pas un ControlTemplate contrôle et vous ne pouvez pas personnaliser son apparence.
Modèles de parties et d’états
Le modèle de parties et d’états spécifie comment définir la structure visuelle et le comportement visuel d’un contrôle. Pour suivre les parties et le modèle d’états, vous devez effectuer les opérations suivantes :
Définissez la structure visuelle et le comportement visuel dans le ControlTemplate contrôle.
Suivez certaines bonnes pratiques lorsque la logique de votre contrôle interagit avec des parties du modèle de contrôle.
Fournissez un contrat de contrôle pour spécifier ce qui doit être inclus dans le ControlTemplate.
Lorsque vous définissez la structure visuelle et le comportement visuel dans le ControlTemplate contrôle, les auteurs d’applications peuvent modifier la structure visuelle et le comportement visuel de votre contrôle en créant un nouveau ControlTemplate au lieu d’écrire du code. Vous devez fournir un contrat de contrôle qui indique aux auteurs d’applications quels FrameworkElement objets et états doivent être définis dans le ControlTemplate. Vous devez suivre certaines bonnes pratiques lorsque vous interagissez avec les parties dans le ControlTemplate cas où votre contrôle gère correctement un élément incomplet ControlTemplate. Si vous suivez ces trois principes, les auteurs d’applications pourront créer un ControlTemplate contrôle aussi facilement que possible pour les contrôles fournis avec WPF. La section suivante décrit chacune de ces recommandations en détail.
Définition de la structure visuelle et du comportement visuel d’un contrôle dans un ControlTemplate
Lorsque vous créez votre contrôle personnalisé à l’aide du modèle de parties et d’états, vous définissez la structure visuelle et le comportement visuel du contrôle au ControlTemplate lieu de sa logique. La structure visuelle d’un contrôle est le composite des FrameworkElement objets qui composent le contrôle. Le comportement visuel est la façon dont le contrôle apparaît lorsqu’il est dans un certain état. Pour plus d’informations sur la création d’un ControlTemplate élément qui spécifie la structure visuelle et le comportement visuel d’un contrôle, consultez Créer un modèle pour un contrôle.
Dans l’exemple du NumericUpDown
contrôle, la structure visuelle comprend deux RepeatButton contrôles et un TextBlock. Si vous ajoutez ces contrôles dans le code de son NumericUpDown
constructeur, par exemple, les positions de ces contrôles ne sont pas modifiables. Au lieu de définir la structure visuelle et le comportement visuel du contrôle dans son code, vous devez le définir dans le ControlTemplate. Ensuite, un développeur d’applications pour personnaliser la position des boutons et TextBlock spécifier le comportement qui se produit lorsqu’il Value
est négatif, car il ControlTemplate peut être remplacé.
L’exemple suivant montre la structure visuelle du NumericUpDown
contrôle, qui inclut une RepeatButton augmentation, une RepeatButton pour Value
diminuer Value
et une TextBlock pour afficher Value
.
<ControlTemplate TargetType="src:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="7,2,2,2" Grid.RowSpan="2"
Background="#E0FFFFFF"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock Name="TextBlock"
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type src:NumericUpDown}},
Path=Value}"/>
</Border>
<RepeatButton Content="Up" Margin="2,5,5,0"
Name="UpButton"
Grid.Column="1" Grid.Row="0"/>
<RepeatButton Content="Down" Margin="2,0,5,5"
Name="DownButton"
Grid.Column="1" Grid.Row="1"/>
<Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2"
Stroke="Black" StrokeThickness="1"
Visibility="Collapsed"/>
</Grid>
</Grid>
</ControlTemplate>
Un comportement visuel du NumericUpDown
contrôle est que la valeur se trouve dans une police rouge si elle est négative. Si vous modifiez le ForegroundTextBlock code lorsque le Value
code est négatif, il NumericUpDown
affiche toujours une valeur négative rouge. Vous spécifiez le comportement visuel du contrôle dans le ControlTemplate contrôle en ajoutant des VisualState objets au ControlTemplate. L’exemple suivant montre les VisualState objets pour les états et Negative
les Positive
états. Positive
et Negative
sont mutuellement exclusifs (le contrôle est toujours dans exactement l’un des deux), de sorte que l’exemple place les VisualState objets dans un seul VisualStateGroup. Lorsque le contrôle passe à l’état Negative
, le ForegroundTextBlock rouge devient rouge. Lorsque le contrôle est dans l’état Positive
, le Foreground retour à sa valeur d’origine. La définition d’objets VisualState dans un objet ControlTemplate est décrite plus loin dans Créer un modèle pour un contrôle.
Remarque
Veillez à définir la VisualStateManager.VisualStateGroups propriété jointe à la racine FrameworkElement du ControlTemplate.
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
Utilisation de parties du ControlTemplate dans le code
Un ControlTemplate auteur peut omettre FrameworkElement ou VisualState objets, de manière délibérée ou par erreur, mais la logique de votre contrôle peut nécessiter que ces parties fonctionnent correctement. Le modèle de parties et d’états spécifie que votre contrôle doit être résilient à un ControlTemplate objet manquant ou VisualState manquantFrameworkElement. Votre contrôle ne doit pas lever d’exception ou signaler une erreur si un FrameworkElement, VisualStateou VisualStateGroup est manquant dans le ControlTemplatefichier . Cette section décrit les pratiques recommandées pour interagir avec FrameworkElement les objets et gérer les états.
Anticiper les objets FrameworkElement manquants
Lorsque vous définissez des FrameworkElement objets dans la ControlTemplatelogique de votre contrôle, vous devrez peut-être interagir avec certains d’entre eux. Par exemple, le NumericUpDown
contrôle s’abonne à l’événement des Click boutons pour augmenter ou diminuer Value
et définir la Text propriété sur Value
TextBlock . Si un bouton personnalisé ControlTemplate omet les boutons ou les TextBlock boutons, il est acceptable que le contrôle perde certaines de ses fonctionnalités, mais vous devez être sûr que votre contrôle n’entraîne pas d’erreur. Par exemple, si un ControlTemplate bouton ne contient pas les boutons à modifier Value
, la NumericUpDown
perte de cette fonctionnalité, mais une application qui utilise le ControlTemplate fichier continuera à s’exécuter.
Les pratiques suivantes garantissent que votre contrôle répond correctement aux objets manquants FrameworkElement :
Définissez l’attribut
x:Name
pour chaque FrameworkElement référence dans le code.Définissez des propriétés privées pour chacune FrameworkElement dont vous avez besoin pour interagir.
Abonnez-vous à tous les événements gérés par votre contrôle dans l’accesseur défini de la FrameworkElement propriété.
Définissez les propriétés que vous avez définies à l’étape FrameworkElement 2 de la OnApplyTemplate méthode. Il s’agit du plus tôt que le FrameworkElementControlTemplate contrôle soit disponible. Utilisez le
x:Name
fichier FrameworkElement pour l’obtenir à partir du ControlTemplate.Vérifiez que le n’est FrameworkElement pas
null
avant d’accéder à ses membres. Si c’estnull
le cas, ne signalez pas d’erreur.
Les exemples suivants montrent comment le NumericUpDown
contrôle interagit avec FrameworkElement les objets conformément aux recommandations de la liste précédente.
Dans l’exemple qui définit la structure visuelle du NumericUpDown
contrôle dans le ControlTemplate, l’attribut RepeatButton augmente Value
a la x:Name
valeur UpButton
. L’exemple suivant déclare une propriété appelée UpButtonElement
qui représente celle RepeatButton déclarée dans le ControlTemplate. L’accesseur set
se désinscrit d’abord de l’événement du Click bouton si UpDownElement
ce n’est pas null
le cas, il définit la propriété, puis s’abonne à l’événement Click . Il existe également une propriété définie, mais pas affichée ici, pour l’autre RepeatButton, appelée DownButtonElement
.
private RepeatButton upButtonElement;
private RepeatButton UpButtonElement
{
get
{
return upButtonElement;
}
set
{
if (upButtonElement != null)
{
upButtonElement.Click -=
new RoutedEventHandler(upButtonElement_Click);
}
upButtonElement = value;
if (upButtonElement != null)
{
upButtonElement.Click +=
new RoutedEventHandler(upButtonElement_Click);
}
}
}
Private m_upButtonElement As RepeatButton
Private Property UpButtonElement() As RepeatButton
Get
Return m_upButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_upButtonElement IsNot Nothing Then
RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
m_upButtonElement = value
If m_upButtonElement IsNot Nothing Then
AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
End Set
End Property
L’exemple NumericUpDown
suivant montre le OnApplyTemplate contrôle. L’exemple utilise la GetTemplateChild méthode pour obtenir les FrameworkElement objets de l’objet ControlTemplate. Notez que l’exemple protège contre les cas où GetTemplateChild trouve un FrameworkElement nom spécifié qui n’est pas du type attendu. Il est également recommandé d’ignorer les éléments qui ont le type spécifié x:Name
, mais qui sont de type incorrect.
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
En suivant les pratiques affichées dans les exemples précédents, vous vous assurez que votre contrôle continuera à s’exécuter quand il ControlTemplate manque un FrameworkElement.
Utiliser VisualStateManager pour gérer les états
Le VisualStateManager suivi des états d’un contrôle et effectue la logique nécessaire à la transition entre les états. Lorsque vous ajoutez VisualState des objets à l’objet ControlTemplate, vous les ajoutez à une VisualStateGroup propriété jointe et ajoutez-les VisualStateGroup à la VisualStateManager.VisualStateGroups propriété jointe afin que l’accès VisualStateManager soit à ceux-ci.
L’exemple suivant répète l’exemple précédent qui montre les VisualState objets qui correspondent aux états et Negative
aux Positive
états du contrôle. Les Storyboard tournures ForegroundNegative
VisualState du TextBlock rouge. Lorsque le NumericUpDown
contrôle est dans l’état Negative
, le storyboard dans l’état Negative
commence. Ensuite, l’état StoryboardNegative
s’arrête lorsque le contrôle revient à l’état Positive
. Il Positive
VisualState n’est pas nécessaire de contenir une Storyboard valeur, car lorsque l’arrêt StoryboardNegative
est arrêté, le Foreground retour à sa couleur d’origine.
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
Notez que le TextBlock nom est donné, mais qu’il TextBlock n’est pas dans le contrat de contrôle car NumericUpDown
la logique du contrôle ne fait jamais référence à l’élément TextBlock. Les éléments référencés dans les ControlTemplate noms ont des noms, mais n’ont pas besoin de faire partie du contrat de contrôle, car un nouveau ControlTemplate pour le contrôle n’a peut-être pas besoin de référencer cet élément. Par exemple, quelqu’un qui crée un nouveau ControlTemplate pour NumericUpDown
peut décider de ne pas indiquer qu’il Value
est négatif en modifiant le Foreground. Dans ce cas, ni le code ni les ControlTemplate références par TextBlock nom.
La logique du contrôle est responsable de la modification de l’état du contrôle. L’exemple suivant montre que le NumericUpDown
contrôle appelle la GoToState méthode pour entrer dans l’état Positive
lorsque Value
la valeur est 0 ou supérieure, et que l’état Negative
est Value
inférieur à 0.
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
La GoToState méthode effectue la logique nécessaire pour démarrer et arrêter les storyboards de manière appropriée. Lorsqu’un contrôle appelle GoToState pour modifier son état, procédez VisualStateManager comme suit :
VisualState Si le contrôle va avoir un Storyboard, le storyboard commence. Ensuite, si le VisualState contrôle provient d’un Storyboard, le storyboard se termine.
Si le contrôle est déjà dans l’état spécifié, GoToState n’effectue aucune action et retourne
true
.Si l’état spécifié n’existe pas dans le ControlTemplate
control
fichier , GoToState n’effectue aucune action et retournefalse
.
Meilleures pratiques pour l’utilisation de VisualStateManager
Il est recommandé d’effectuer les opérations suivantes pour maintenir les états de votre contrôle :
Utilisez des propriétés pour suivre son état.
Créez une méthode d’assistance pour passer d’un état à l’autre.
Le NumericUpDown
contrôle utilise sa Value
propriété pour suivre s’il est dans l’état ou Negative
dans l’étatPositive
. Le NumericUpDown
contrôle définit également les états et UnFocused
les Focused
états, qui effectue le suivi de la IsFocused propriété. Si vous utilisez des états qui ne correspondent pas naturellement à une propriété du contrôle, vous pouvez définir une propriété privée pour suivre l’état.
Une méthode unique qui met à jour tous les états centralise les appels vers le VisualStateManager code et conserve votre code gérable. L’exemple suivant montre la NumericUpDown
méthode d’assistance du contrôle. UpdateStates
Lorsqu’il Value
est supérieur ou égal à 0, il Control est dans l’état Positive
. Quand Value
la valeur est inférieure à 0, le contrôle est dans l’état Negative
. Quand IsFocused c’est true
le cas, le contrôle est dans l’état Focused
; sinon, il est dans l’état Unfocused
. Le contrôle peut appeler UpdateStates
chaque fois qu’il doit modifier son état, quel que soit l’état qui change.
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
Private Sub UpdateStates(ByVal useTransitions As Boolean)
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
If IsFocused Then
VisualStateManager.GoToState(Me, "Focused", useTransitions)
Else
VisualStateManager.GoToState(Me, "Unfocused", useTransitions)
End If
End Sub
Si vous passez un nom d’état lorsque GoToState le contrôle est déjà dans cet état, ne fait rien, GoToState vous n’avez donc pas besoin de case activée pour l’état actuel du contrôle. Par exemple, si Value
les modifications d’un nombre négatif à un autre nombre négatif, le storyboard pour l’état Negative
n’est pas interrompu et l’utilisateur ne voit pas de modification dans le contrôle.
Les VisualStateManager objets utilisent VisualStateGroup des objets pour déterminer l’état à quitter lorsque vous appelez GoToState. Le contrôle est toujours dans un état pour chaque VisualStateGroup qui est défini dans son ControlTemplate et laisse uniquement un état lorsqu’il passe dans un autre état du même VisualStateGroup. Par exemple, le ControlTemplateNumericUpDown
contrôle définit les objets et Negative
VisualState les Positive
objets dans un etVisualStateUnfocused
Focused
les objets d’un VisualStateGroup autre. (Vous pouvez voir la section Exemple complet de cette rubrique etVisualStateUnfocused
la Focused
définir lorsque le contrôle passe de l’état Positive
à l’étatNegative
, ou inversement, le contrôle reste dans l’état ou Unfocused
dans l’étatFocused
.
Il existe trois endroits typiques où l’état d’un contrôle peut changer :
Lorsque l’objet ControlTemplate est appliqué à l’objet Control.
Lorsqu’une propriété change.
Lorsqu’un événement se produit.
Les exemples suivants illustrent la mise à jour de l’état du NumericUpDown
contrôle dans ces cas.
Vous devez mettre à jour l’état du contrôle dans la OnApplyTemplate méthode afin que le contrôle apparaisse dans l’état correct lorsque celui-ci ControlTemplate est appliqué. L’exemple suivant appelle UpdateStates
OnApplyTemplate pour s’assurer que le contrôle se trouve dans les états appropriés. Par exemple, supposons que vous créez un NumericUpDown
contrôle, puis définissez-le Foreground sur vert et Value
sur -5. Si vous n’appelez UpdateStates
pas lorsque le ControlTemplate contrôle est appliqué NumericUpDown
, le contrôle n’est pas dans l’état Negative
et la valeur est verte au lieu de rouge. Vous devez appeler UpdateStates
pour placer le contrôle dans l’état Negative
.
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
Vous devez souvent mettre à jour les états d’un contrôle lorsqu’une propriété change. L’exemple suivant montre la méthode entière ValueChangedCallback
. Étant donné qu’elle ValueChangedCallback
est appelée lorsque Value
des modifications sont apportées, la méthode appelle UpdateStates
en cas Value
de changement positif à négatif ou inversement. Il est acceptable d’appeler UpdateStates
lorsque Value
les modifications restent positives ou négatives, car dans ce cas, le contrôle ne change pas les états.
private static void ValueChangedCallback(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
NumericUpDown ctl = (NumericUpDown)obj;
int newValue = (int)args.NewValue;
// Call UpdateStates because the Value might have caused the
// control to change ValueStates.
ctl.UpdateStates(true);
// Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(
new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,
newValue));
}
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
ByVal args As DependencyPropertyChangedEventArgs)
Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
Dim newValue As Integer = CInt(args.NewValue)
' Call UpdateStates because the Value might have caused the
' control to change ValueStates.
ctl.UpdateStates(True)
' Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub
Vous devrez peut-être également mettre à jour les états lorsqu’un événement se produit. L’exemple suivant montre que les NumericUpDown
appels sur le Control serveur de gestion de l’événement GotFocusUpdateStates
.
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
UpdateStates(true);
}
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
UpdateStates(True)
End Sub
Vous VisualStateManager pouvez ainsi gérer les états de votre contrôle. En utilisant le VisualStateManager, vous assurez que votre contrôle passe correctement entre les états. Si vous suivez les recommandations décrites dans cette section pour utiliser le VisualStateManagercode de votre contrôle, le code de votre contrôle reste lisible et gérable.
Fourniture du contrat de contrôle
Vous fournissez un contrat de contrôle afin que ControlTemplate les auteurs sachent quoi placer dans le modèle. Un contrat de contrôle comporte trois éléments :
les éléments visuels utilisés par la logique du contrôle ;
les états du contrôle et le groupe auquel appartient chaque état ;
les propriétés publiques qui affectent visuellement le contrôle.
Quelqu’un qui crée un nouveau ControlTemplate doit savoir quels FrameworkElement objets la logique du contrôle utilise, le type de chaque objet et son nom. Un ControlTemplate auteur doit également connaître le nom de chaque état possible dans lequel le contrôle peut se trouver et dans lequel VisualStateGroup il se trouve.
En retournant à l’exemple NumericUpDown
, le contrôle s’attend ControlTemplate à ce que les objets suivants FrameworkElement s’affichent :
UpButton
Appelé RepeatButton .Appelé RepeatButton
DownButton.
Le contrôle peut se trouver dans les états suivants :
Dans le
ValueStates
VisualStateGroupPositive
Negative
Dans le
FocusStates
VisualStateGroupFocused
Unfocused
Pour spécifier les FrameworkElement objets attendus par le contrôle, vous utilisez le TemplatePartAttribute, qui spécifie le nom et le type des éléments attendus. Pour spécifier les états possibles d’un contrôle, vous utilisez le TemplateVisualStateAttribute, qui spécifie le nom de l’état et auquel VisualStateGroup il appartient. Placez et placez la TemplatePartAttributeTemplateVisualStateAttribute définition de classe du contrôle.
Toute propriété publique qui affecte l’apparence de votre contrôle fait également partie du contrat de contrôle.
L’exemple suivant spécifie l’objet et les FrameworkElement états du NumericUpDown
contrôle.
[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
public static readonly DependencyProperty BackgroundProperty;
public static readonly DependencyProperty BorderBrushProperty;
public static readonly DependencyProperty BorderThicknessProperty;
public static readonly DependencyProperty FontFamilyProperty;
public static readonly DependencyProperty FontSizeProperty;
public static readonly DependencyProperty FontStretchProperty;
public static readonly DependencyProperty FontStyleProperty;
public static readonly DependencyProperty FontWeightProperty;
public static readonly DependencyProperty ForegroundProperty;
public static readonly DependencyProperty HorizontalContentAlignmentProperty;
public static readonly DependencyProperty PaddingProperty;
public static readonly DependencyProperty TextAlignmentProperty;
public static readonly DependencyProperty TextDecorationsProperty;
public static readonly DependencyProperty TextWrappingProperty;
public static readonly DependencyProperty VerticalContentAlignmentProperty;
public Brush Background { get; set; }
public Brush BorderBrush { get; set; }
public Thickness BorderThickness { get; set; }
public FontFamily FontFamily { get; set; }
public double FontSize { get; set; }
public FontStretch FontStretch { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
public Brush Foreground { get; set; }
public HorizontalAlignment HorizontalContentAlignment { get; set; }
public Thickness Padding { get; set; }
public TextAlignment TextAlignment { get; set; }
public TextDecorationCollection TextDecorations { get; set; }
public TextWrapping TextWrapping { get; set; }
public VerticalAlignment VerticalContentAlignment { get; set; }
}
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))>
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))>
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")>
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")>
Public Class NumericUpDown
Inherits Control
Public Shared ReadOnly TextAlignmentProperty As DependencyProperty
Public Shared ReadOnly TextDecorationsProperty As DependencyProperty
Public Shared ReadOnly TextWrappingProperty As DependencyProperty
Public Property TextAlignment() As TextAlignment
Public Property TextDecorations() As TextDecorationCollection
Public Property TextWrapping() As TextWrapping
End Class
Exemple complet
L’exemple suivant est l’intégralité ControlTemplate du NumericUpDown
contrôle.
<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VSMCustomControl">
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the control to its initial state by
return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
<VisualStateGroup Name="FocusStates">
<!--Add a focus rectangle to highlight the entire control
when it has focus.-->
<VisualState Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--Return the control to its initial state by
hiding the focus rectangle.-->
<VisualState Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="7,2,2,2" Grid.RowSpan="2"
Background="#E0FFFFFF"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock Name="TextBlock"
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:NumericUpDown}},
Path=Value}"/>
</Border>
<RepeatButton Content="Up" Margin="2,5,5,0"
Name="UpButton"
Grid.Column="1" Grid.Row="0"/>
<RepeatButton Content="Down" Margin="2,0,5,5"
Name="DownButton"
Grid.Column="1" Grid.Row="1"/>
<Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2"
Stroke="Black" StrokeThickness="1"
Visibility="Collapsed"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
L’exemple suivant montre la logique pour le NumericUpDown
.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace VSMCustomControl
{
[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
public NumericUpDown()
{
DefaultStyleKey = typeof(NumericUpDown);
this.IsTabStop = true;
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(int), typeof(NumericUpDown),
new PropertyMetadata(
new PropertyChangedCallback(ValueChangedCallback)));
public int Value
{
get
{
return (int)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
private static void ValueChangedCallback(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
NumericUpDown ctl = (NumericUpDown)obj;
int newValue = (int)args.NewValue;
// Call UpdateStates because the Value might have caused the
// control to change ValueStates.
ctl.UpdateStates(true);
// Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(
new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,
newValue));
}
public static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
typeof(ValueChangedEventHandler), typeof(NumericUpDown));
public event ValueChangedEventHandler ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
protected virtual void OnValueChanged(ValueChangedEventArgs e)
{
// Raise the ValueChanged event so applications can be alerted
// when Value changes.
RaiseEvent(e);
}
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
private RepeatButton downButtonElement;
private RepeatButton DownButtonElement
{
get
{
return downButtonElement;
}
set
{
if (downButtonElement != null)
{
downButtonElement.Click -=
new RoutedEventHandler(downButtonElement_Click);
}
downButtonElement = value;
if (downButtonElement != null)
{
downButtonElement.Click +=
new RoutedEventHandler(downButtonElement_Click);
}
}
}
void downButtonElement_Click(object sender, RoutedEventArgs e)
{
Value--;
}
private RepeatButton upButtonElement;
private RepeatButton UpButtonElement
{
get
{
return upButtonElement;
}
set
{
if (upButtonElement != null)
{
upButtonElement.Click -=
new RoutedEventHandler(upButtonElement_Click);
}
upButtonElement = value;
if (upButtonElement != null)
{
upButtonElement.Click +=
new RoutedEventHandler(upButtonElement_Click);
}
}
}
void upButtonElement_Click(object sender, RoutedEventArgs e)
{
Value++;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Focus();
}
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
UpdateStates(true);
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
UpdateStates(true);
}
}
public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);
public class ValueChangedEventArgs : RoutedEventArgs
{
private int _value;
public ValueChangedEventArgs(RoutedEvent id, int num)
{
_value = num;
RoutedEvent = id;
}
public int Value
{
get { return _value; }
}
}
}
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input
Imports System.Windows.Media
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))> _
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))> _
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")> _
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")> _
Public Class NumericUpDown
Inherits Control
Public Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
Me.IsTabStop = True
End Sub
Public Shared ReadOnly ValueProperty As DependencyProperty =
DependencyProperty.Register("Value", GetType(Integer), GetType(NumericUpDown),
New PropertyMetadata(New PropertyChangedCallback(AddressOf ValueChangedCallback)))
Public Property Value() As Integer
Get
Return CInt(GetValue(ValueProperty))
End Get
Set(ByVal value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
ByVal args As DependencyPropertyChangedEventArgs)
Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
Dim newValue As Integer = CInt(args.NewValue)
' Call UpdateStates because the Value might have caused the
' control to change ValueStates.
ctl.UpdateStates(True)
' Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub
Public Shared ReadOnly ValueChangedEvent As RoutedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
GetType(ValueChangedEventHandler), GetType(NumericUpDown))
Public Custom Event ValueChanged As ValueChangedEventHandler
AddHandler(ByVal value As ValueChangedEventHandler)
Me.AddHandler(ValueChangedEvent, value)
End AddHandler
RemoveHandler(ByVal value As ValueChangedEventHandler)
Me.RemoveHandler(ValueChangedEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
Protected Overridable Sub OnValueChanged(ByVal e As ValueChangedEventArgs)
' Raise the ValueChanged event so applications can be alerted
' when Value changes.
MyBase.RaiseEvent(e)
End Sub
#Region "NUDCode"
Private Sub UpdateStates(ByVal useTransitions As Boolean)
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
If IsFocused Then
VisualStateManager.GoToState(Me, "Focused", useTransitions)
Else
VisualStateManager.GoToState(Me, "Unfocused", useTransitions)
End If
End Sub
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
Private m_downButtonElement As RepeatButton
Private Property DownButtonElement() As RepeatButton
Get
Return m_downButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_downButtonElement IsNot Nothing Then
RemoveHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
End If
m_downButtonElement = value
If m_downButtonElement IsNot Nothing Then
AddHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
End If
End Set
End Property
Private Sub downButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Value -= 1
End Sub
Private m_upButtonElement As RepeatButton
Private Property UpButtonElement() As RepeatButton
Get
Return m_upButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_upButtonElement IsNot Nothing Then
RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
m_upButtonElement = value
If m_upButtonElement IsNot Nothing Then
AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
End Set
End Property
Private Sub upButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Value += 1
End Sub
Protected Overloads Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs)
MyBase.OnMouseLeftButtonDown(e)
Focus()
End Sub
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
UpdateStates(True)
End Sub
Protected Overloads Overrides Sub OnLostFocus(ByVal e As RoutedEventArgs)
MyBase.OnLostFocus(e)
UpdateStates(True)
End Sub
#End Region
End Class
Public Delegate Sub ValueChangedEventHandler(ByVal sender As Object,
ByVal e As ValueChangedEventArgs)
Public Class ValueChangedEventArgs
Inherits RoutedEventArgs
Public Sub New(ByVal id As RoutedEvent,
ByVal num As Integer)
Value = num
RoutedEvent = id
End Sub
Public ReadOnly Property Value() As Integer
End Class
Voir aussi
.NET Desktop feedback