Partager via


Vue d’ensemble de la création de contrôles

L’extensibilité du modèle de contrôle Windows Presentation Foundation (WPF) réduit considérablement la nécessité de créer un contrôle. Toutefois, dans certains cas, vous devrez peut-être toujours créer un contrôle personnalisé. Cette rubrique décrit les fonctionnalités qui réduisent votre besoin de créer un contrôle personnalisé et les différents modèles de création de contrôles dans Windows Presentation Foundation (WPF). Ce sujet montre également comment créer un nouvel élément de contrôle.

Alternatives à l’écriture d’un nouveau contrôle

Historiquement, si vous souhaitez obtenir une expérience personnalisée à partir d’un contrôle existant, vous avez été limité à modifier les propriétés standard du contrôle, telles que la couleur d’arrière-plan, la largeur de bordure et la taille de police. Si vous souhaitez étendre l’apparence ou le comportement d’un contrôle au-delà de ces paramètres prédéfinis, vous devez créer un contrôle, généralement en héritant d’un contrôle existant et en remplaçant la méthode responsable du dessin du contrôle. Bien qu’il s’agit toujours d’une option, WPF vous permet de personnaliser des contrôles existants à l’aide de son modèle de contenu enrichi, de styles, de modèles et de déclencheurs. La liste suivante fournit des exemples de la façon dont ces fonctionnalités peuvent être utilisées pour créer des expériences personnalisées et cohérentes sans avoir à créer un contrôle.

  • Contenu enrichi. La plupart des contrôles WPF standard prennent en charge du contenu enrichi. Par exemple, la propriété de contenu d’un Button type est de type Object, donc théoriquement tout peut être affiché sur un Button. Pour qu’un bouton affiche une image et un texte, vous pouvez ajouter une image et un TextBlock à une StackPanel et affecter la StackPanelContent propriété. Étant donné que les contrôles peuvent afficher des éléments visuels WPF et des données arbitraires, il est moins nécessaire de créer un contrôle ou de modifier un contrôle existant pour prendre en charge une visualisation complexe. Pour plus d'informations sur le modèle de contenu pour Button et d'autres modèles de contenu dans WPF, consultez WPF Content Model.

  • Styles. Un Style est une collection de valeurs qui représentent les propriétés d’un contrôle. En utilisant des styles, vous pouvez créer une représentation réutilisable d’une apparence et d’un comportement de contrôle souhaités sans écrire de nouveau contrôle. Par exemple, supposons que vous souhaitez que tous vos TextBlock contrôles aient une police rouge, une police Arial avec une taille de police de 14. Vous pouvez créer un style en tant que ressource et définir les propriétés appropriées en conséquence. Ensuite, chaque TextBlock ajout à votre application aura la même apparence.

  • Modèles de données. A DataTemplate vous permet de personnaliser la façon dont les données sont affichées sur un contrôle. Par exemple, un DataTemplate peut être utilisé pour spécifier la façon dont les données sont affichées dans un ListBox. Pour obtenir un exemple de ceci, consultez Vue d’ensemble de la création de modèles de données. Outre la personnalisation de l’apparence des données, un DataTemplate peut inclure des éléments d’interface utilisateur, ce qui offre une grande flexibilité dans les interfaces utilisateur personnalisées. Par exemple, à l’aide d’un DataTemplate, vous pouvez créer un ComboBox dans lequel chaque élément contient une case à cocher.

  • Modèles de contrôle. De nombreux contrôles dans WPF utilisent un ControlTemplate pour définir la structure et l’apparence du contrôle, ce qui sépare l’apparence d’un contrôle des fonctionnalités du contrôle. Vous pouvez changer considérablement l’apparence d’un contrôle en redéfinissant son ControlTemplate. Par exemple, supposons que vous souhaitiez un contrôle qui ressemble à un feu d’arrêt. Ce contrôle dispose d’une interface utilisateur et d’une fonctionnalité simples. Le contrôle se compose de trois cercles, dont un seul peut être allumé à la fois. Après avoir réfléchi, vous pouvez vous rendre compte qu’une RadioButton offre la fonctionnalité de ne pouvoir en sélectionner qu'un seul à la fois, mais l’apparence par défaut de RadioButton ne ressemble pas aux lumières sur un feu de circulation. Puisque le RadioButton utilise un modèle de contrôle pour définir son apparence, il est facile de redéfinir le ControlTemplate pour répondre aux exigences du contrôle et d’utiliser des boutons radio pour créer votre feu de signalisation.

    Remarque

    Bien qu’un RadioButton puisse utiliser un DataTemplate, un DataTemplate n’est pas suffisant dans cet exemple. Définit DataTemplate l’apparence du contenu d’un contrôle. Dans le cas d’un RadioButton, le contenu est ce qui apparaît à droite du cercle indiquant si le RadioButton est sélectionné. Dans l’exemple du feu de signalisation, le bouton radio doit simplement être un cercle qui peut s'illuminer. Étant donné que l’exigence d’apparence pour le feu de signalisation est tellement différente de l’apparence par défaut du RadioButton, il est nécessaire de redéfinir le ControlTemplate. En général, il DataTemplate est utilisé pour définir le contenu (ou les données) d’un contrôle, et il ControlTemplate est utilisé pour définir la structure d’un contrôle.

  • Déclencheurs A Trigger vous permet de modifier dynamiquement l’apparence et le comportement d’un contrôle sans créer de contrôle. Par exemple, supposons que vous disposiez de plusieurs ListBox commandes dans votre application et que les éléments de chaque ListBox soient en gras et de couleur rouge lorsqu’ils sont sélectionnés. Votre premier instinct peut être de créer une classe qui hérite de ListBox et de remplacer la méthode OnSelectionChanged pour modifier l’apparence de l’élément sélectionné, mais une meilleure approche consiste à ajouter un déclencheur à un style de ListBoxItem qui modifie l'apparence de l'élément sélectionné. Un déclencheur vous permet de modifier les valeurs de propriété ou d’effectuer des actions en fonction de la valeur d’une propriété. Une EventTrigger option vous permet d’effectuer des actions lorsqu’un événement se produit.

Pour plus d’informations sur les styles, les modèles et les déclencheurs, consultez Style et Création de modèles.

En règle générale, si votre contrôle reflète les fonctionnalités d’un contrôle existant, mais que vous souhaitez que le contrôle ait une apparence différente, vous devez d’abord déterminer si vous pouvez utiliser l’une des méthodes décrites dans cette section pour modifier l’apparence du contrôle existant.

Modèles pour la création de contrôles

Le modèle de contenu enrichi, les styles, les modèles et les déclencheurs réduit le besoin de créer un contrôle. Toutefois, si vous devez créer un contrôle, il est important de comprendre les différents modèles de création de contrôle dans WPF. WPF fournit trois modèles généraux pour la création d’un contrôle, chacun d’eux fournissant un ensemble différent de fonctionnalités et de niveau de flexibilité. Les classes de base pour les trois modèles sont UserControl, Controlet FrameworkElement.

Dérivation de UserControl

La façon la plus simple de créer un contrôle dans WPF consiste à dériver de UserControl. Lorsque vous générez un contrôle qui hérite de UserControl, vous ajoutez des composants existants au UserControl, nommez les composants et référencez les gestionnaires d’événements en XAML. Vous pouvez ensuite référencer les éléments nommés et définir les gestionnaires d’événements dans le code. Ce modèle de développement est très similaire au modèle utilisé pour le développement d’applications dans WPF.

S’il est correctement généré, un UserControl peut tirer parti des avantages du contenu enrichi, des styles et des déclencheurs. Toutefois, si votre contrôle hérite de UserControl, les personnes qui utilisent votre contrôle ne pourront pas utiliser un DataTemplate ou ControlTemplate pour personnaliser son apparence. Il est nécessaire de dériver de la Control classe ou de l’une de ses classes dérivées (autres que UserControl) pour créer un contrôle personnalisé qui prend en charge les modèles.

Avantages de la dérivation de UserControl

Envisagez de dériver à partir de UserControl si tous les éléments suivants s’appliquent :

  • Vous souhaitez créer votre contrôle de la même façon que vous générez une application.

  • Votre contrôle se compose uniquement de composants existants.

  • Vous n’avez pas besoin de prendre en charge la personnalisation complexe.

Dérivation du contrôle

La dérivation de la Control classe est le modèle utilisé par la plupart des contrôles WPF existants. Lorsque vous créez un contrôle qui hérite de la Control classe, vous définissez son apparence à l’aide de modèles. En procédant ainsi, vous séparez la logique opérationnelle de la représentation visuelle. Vous pouvez également garantir le découplage de l’interface utilisateur et de la logique à l’aide de commandes et de liaisons au lieu d’événements et d’éviter de référencer des éléments dans la mesure du ControlTemplate possible. Si l'interface utilisateur ControlTemplate et la logique de votre contrôle sont correctement découplées, un utilisateur de votre contrôle peut redéfinir le contrôle pour en personnaliser l'apparence. Bien que la création d’un personnalisé Control ne soit pas aussi simple que la création d’un UserControl, une personnalisée Control offre la plus grande flexibilité.

Avantages tirés du contrôle

Envisagez de dériver au Control lieu d’utiliser la UserControl classe si l’un des éléments suivants s’applique :

  • Vous souhaitez que l’apparence de votre contrôle soit personnalisable via le ControlTemplate.

  • Vous souhaitez que votre contrôle soutienne différents thèmes.

Dérivation de FrameworkElement

Les contrôles qui dérivent de UserControl ou de Control s’appuient sur la composition d’éléments existants. Pour de nombreux scénarios, il s’agit d’une solution acceptable, car tout objet qui hérite FrameworkElement peut se trouver dans un ControlTemplate. Toutefois, il existe des moments où l’apparence d’un contrôle requiert plus que la simple fonctionnalité de composition des éléments. Baser un composant sur FrameworkElement est le bon choix pour ces scénarios.

Il existe deux méthodes standard pour la création de composants basés sur FrameworkElement : le rendu direct et la composition d'éléments personnalisés. Le rendu direct consiste à remplacer la méthode OnRender de FrameworkElement et à fournir des opérations DrawingContext qui définissent explicitement les visuels du composant. Il s’agit de la méthode utilisée par Image et Border. La composition d’élément personnalisé implique l’utilisation d’objets de type Visual pour composer l’apparence de votre composant. Pour obtenir un exemple, consultez Utilisation d’objets DrawingVisual. Track est un exemple de contrôle dans WPF qui utilise une composition d’élément personnalisée. Il est également possible de combiner le rendu direct et la composition d’élément personnalisée dans le même contrôle.

Avantages de la dérivation de FrameworkElement

Envisagez de dériver de FrameworkElement si l’un des éléments suivants s’applique :

  • Vous souhaitez avoir un contrôle précis sur l’apparence de votre contrôle au-delà de ce qui est fourni par une composition d’élément simple.

  • Vous souhaitez définir l’apparence de votre contrôle en définissant votre propre logique de rendu.

  • Vous voulez composer des éléments existants de nouvelles façons qui vont au-delà de ce qui est possible avec UserControl et Control.

Principes de base de la création de contrôles

Comme indiqué précédemment, l’une des fonctionnalités les plus puissantes de WPF est la possibilité d’aller au-delà de la définition des propriétés de base d’un contrôle pour modifier son apparence et son comportement, mais n’a pas encore besoin de créer un contrôle personnalisé. Les fonctionnalités de style, de liaison de données et de déclencheur sont rendues possibles par le système de propriétés WPF et le système d’événements WPF. Les sections suivantes décrivent certaines pratiques que vous devez suivre, quel que soit le modèle que vous utilisez pour créer le contrôle personnalisé, afin que les utilisateurs de votre contrôle personnalisé puissent utiliser ces fonctionnalités comme ils le feraient pour un contrôle inclus dans WPF.

Utiliser les propriétés de dépendance

Lorsqu’une propriété est une propriété de dépendance, il est possible d’effectuer les opérations suivantes :

  • Définissez la propriété dans un style.

  • Lier la propriété à une source de données.

  • Utilisez une ressource dynamique comme valeur de la propriété.

  • Animer la propriété.

Si vous souhaitez qu’une propriété de votre contrôle prend en charge l’une de ces fonctionnalités, vous devez l’implémenter en tant que propriété de dépendance. L’exemple suivant définit une propriété de dépendance nommée Value en procédant comme suit :

  • Définissez un DependencyProperty identificateur nommé ValueProperty en tant que publicstaticreadonly champ.

  • Inscrivez le nom de la propriété auprès du système de propriétés, en appelant DependencyProperty.Register, pour spécifier les éléments suivants :

    • Nom de la propriété.

    • Type de la propriété.

    • Type propriétaire de la propriété.

    • Métadonnées de la propriété. Les métadonnées contiennent la valeur par défaut de la propriété, a CoerceValueCallback et a PropertyChangedCallback.

  • Définissez une propriété wrapper CLR nommée Value, qui porte le même nom utilisé pour enregistrer la propriété de dépendance, en implémentant les accesseurs de la propriété get et set. Notez que les accesseurs get et set appellent uniquement GetValue et SetValue respectivement. Il est recommandé que les accesseurs des propriétés de dépendance ne contiennent pas de logique supplémentaire, car les clients et WPF peuvent contourner les accesseurs et appeler directement GetValue et SetValue. Par exemple, lorsqu’une propriété est liée à une source de données, l’accesseur de set la propriété n’est pas appelé. Au lieu d’ajouter une logique supplémentaire aux accesseurs get et set, utilisez les délégués ValidateValueCallback, CoerceValueCallback, et PropertyChangedCallback pour répondre ou vérifier la valeur lorsqu’elle change. Pour plus d’informations sur ces rappels, consultez Les rappels de propriété de dépendance et la validation.

  • Définissez une méthode nommée CoerceValueCallback pour le CoerceValue. CoerceValue garantit qu’il Value est supérieur ou égal à MinValue et inférieur ou égal à MaxValue.

  • Définissez une méthode pour le PropertyChangedCallback, nommé OnValueChanged. OnValueChanged crée un RoutedPropertyChangedEventArgs<T> objet et se prépare à déclencher l’événement ValueChanged routé. Les événements routés sont abordés dans la section suivante.

/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
    get { return (decimal)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown control = (NumericUpDown)obj;			

    RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
        (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
    control.OnValueChanged(e);
}
''' <summary>
''' Identifies the Value dependency property.
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

''' <summary>
''' Gets or sets the value assigned to the control.
''' </summary>
Public Property Value() As Decimal
    Get
        Return CDec(GetValue(ValueProperty))
    End Get
    Set(ByVal value As Decimal)
        SetValue(ValueProperty, value)
    End Set
End Property

Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
    Dim newValue As Decimal = CDec(value)
    Dim control As NumericUpDown = CType(element, NumericUpDown)

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

    Return newValue
End Function

Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
    Dim control As NumericUpDown = CType(obj, NumericUpDown)

    Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
    control.OnValueChanged(e)
End Sub

Pour plus d’informations, consultez Propriétés de dépendance personnalisées.

Utiliser des événements routés

Tout comme les propriétés de dépendance étendent la notion de propriétés CLR avec des fonctionnalités supplémentaires, les événements routés étendent la notion d’événements CLR standard. Lorsque vous créez un contrôle WPF, il est également recommandé d’implémenter votre événement en tant qu’événement routé, car un événement routé prend en charge le comportement suivant :

  • Les événements peuvent être gérés par un parent de plusieurs éléments de contrôle. Si un événement est un événement propagatif, un seul parent dans l'arborescence des éléments peut s’abonner à l’événement. Les auteurs d’applications peuvent ensuite utiliser un gestionnaire pour répondre à l’événement de plusieurs contrôles. Par exemple, si votre contrôle fait partie de chaque élément d’un ListBox (car il est inclus dans un DataTemplate), le développeur d’applications peut définir le gestionnaire d’événements pour l’événement de votre contrôle sur le ListBox. Chaque fois que l’événement se produit sur l’un des contrôles, le gestionnaire d’événements est appelé.

  • Les événements routés peuvent être utilisés dans un EventSetter, ce qui permet aux développeurs d'applications de spécifier le gestionnaire d’un événement dans un style.

  • Les événements routés peuvent être utilisés dans un EventTrigger, ce qui est utile pour animer des propriétés à l’aide de XAML. Pour plus d’informations, consultez Vue d’ensemble de l’animation.

L’exemple suivant définit un événement routé en procédant comme suit :

  • Définissez un RoutedEvent identificateur nommé ValueChangedEvent en tant que publicstaticreadonly champ.

  • Inscrivez l’événement routé en appelant la EventManager.RegisterRoutedEvent méthode. L’exemple spécifie les informations suivantes quand il appelle RegisterRoutedEvent:

    • Le nom de l’événement est ValueChanged.

    • La stratégie de routage est Bubble, ce qui signifie qu’un gestionnaire d’événements sur la source (l’objet qui déclenche l’événement) est appelé en premier, puis les gestionnaires d’événements sur les éléments parents de la source sont appelés en succession, en commençant par le gestionnaire d’événements sur l’élément parent le plus proche.

    • Le type du gestionnaire d’événements est RoutedPropertyChangedEventHandler<T>, construit avec un Decimal type.

    • Le type propriétaire de l’événement est NumericUpDown.

  • Déclarez un événement public nommé ValueChanged et incluez des déclarations d’accesseur d’événements. L’exemple appelle AddHandler dans la déclaration d’accesseur add et RemoveHandler dans la déclaration d’accesseur remove pour utiliser les services d’événements WPF.

  • Créez une méthode virtuelle protégée nommée OnValueChanged qui déclenche l’événement ValueChanged .

/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble,
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
    RaiseEvent(args);
}
''' <summary>
''' Identifies the ValueChanged routed event.
''' </summary>
Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

''' <summary>
''' Occurs when the Value property changes.
''' </summary>
Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
    AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.AddHandler(ValueChangedEvent, value)
    End AddHandler
    RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.RemoveHandler(ValueChangedEvent, value)
    End RemoveHandler
    RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
    End RaiseEvent
End Event

''' <summary>
''' Raises the ValueChanged event.
''' </summary>
''' <param name="args">Arguments associated with the ValueChanged event.</param>
Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
    MyBase.RaiseEvent(args)
End Sub

Pour plus d’informations, consultez Vue d’ensemble des événements routés et créer un événement routé personnalisé.

Utiliser la liaison

Pour dissocier l’interface utilisateur de votre contrôle de sa logique, envisagez d’utiliser la liaison de données. Cela est particulièrement important si vous définissez l’apparence de votre contrôle à l’aide d’un ControlTemplate. Lorsque vous utilisez la liaison de données, vous pouvez être en mesure d’éliminer la nécessité de référencer des parties spécifiques de l’interface utilisateur à partir du code. Il est judicieux d’éviter de référencer des éléments qui se trouvent dans le ControlTemplate code, car lorsque le code fait référence à des éléments qui se trouvent dans le ControlTemplate code et que celui-ci ControlTemplate est modifié, l’élément référencé doit être inclus dans le nouveau ControlTemplate.

L’exemple suivant met à jour le TextBlockNumericUpDown contrôle, lui affectant un nom et référençant la zone de texte par nom dans le code.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

L’exemple suivant utilise un lien pour accomplir la même chose.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

    <!--Bind the TextBlock to the Value property-->
    <TextBlock 
        Width="60" TextAlignment="Right" Padding="5"
        Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type local:NumericUpDown}}, 
                       Path=Value}"/>

</Border>

Pour plus d’informations sur la liaison de données, consultez Vue d’ensemble de la liaison de données.

Conception pour les concepteurs

Pour recevoir la prise en charge des contrôles WPF personnalisés dans le Concepteur WPF pour Visual Studio (par exemple, modification de propriété avec la fenêtre Propriétés), suivez ces instructions. Pour plus d’informations sur le développement pour le concepteur WPF, consultez Design XAML dans Visual Studio.

Propriétés de dépendance

Veillez à implémenter CLR get et set accesseurs comme décrit précédemment, dans « Utiliser les propriétés de dépendance ». Les concepteurs peuvent utiliser le wrapper pour détecter la présence d’une propriété de dépendance, mais eux, comme WPF et les clients du contrôle, ne sont pas obligés d'appeler les accesseurs lors de l'obtention ou de la définition de la propriété.

Propriétés jointes

Vous devez implémenter des propriétés jointes sur des contrôles personnalisés à l’aide des instructions suivantes :

  • Avoir un publicstaticreadonlyDependencyPropertyformulaire PropertyNameProperty qui a été créé à l’aide de la RegisterAttached méthode. Le nom de propriété passé doit correspondre à RegisterAttachedPropertyName.

  • Implémentez une paire de publicstatic méthodes CLR nommées SetPropertyName et GetPropertyName. Les deux méthodes doivent accepter une classe dérivée de DependencyProperty comme leur premier argument. La Set méthode PropertyName accepte également un argument dont le type correspond au type de données inscrit pour la propriété. La Get méthode PropertyName doit retourner une valeur du même type. Si la Set méthode PropertyName est manquante, la propriété est marquée en lecture seule.

  • Set PropertyName et GetPropertyName doivent acheminer directement vers les méthodes sur GetValue et SetValue de l’objet de dépendance cible, respectivement. Les concepteurs peuvent accéder à la propriété jointe en appelant via le wrapper de méthode ou en effectuant un appel direct à l’objet de dépendance cible.

Pour plus d’informations sur les propriétés jointes, consultez Vue d’ensemble des propriétés jointes.

Définir et utiliser des ressources partagées

Vous pouvez inclure votre contrôle dans le même assembly que votre application, ou vous pouvez empaqueter votre contrôle dans un assembly distinct qui peut être utilisé dans plusieurs applications. Pour la plupart, les informations présentées dans cette rubrique s’appliquent quelle que soit la méthode que vous utilisez. Toutefois, il y a une différence qui vaut la peine de noter. Lorsque vous placez un contrôle dans le même assembly qu’une application, vous êtes libre d’ajouter des ressources globales au fichier App.xaml. Toutefois, un assembly qui contient uniquement des contrôles n’a pas d’objet Application associé à celui-ci. Par conséquent, un fichier App.xaml n’est pas disponible.

Lorsqu’une application recherche une ressource, elle examine trois niveaux dans l’ordre suivant :

  1. Niveau de l’élément.

    Le système commence par l’élément qui fait référence à la ressource, puis recherche les ressources du parent logique et ainsi de suite jusqu’à ce que l’élément racine soit atteint.

  2. Niveau de l’application.

    Ressources définies par l’objet Application .

  3. Niveau du thème.

    Les dictionnaires au niveau du thème sont stockés dans un sous-dossier nommé Thèmes. Les fichiers du dossier Thèmes correspondent aux thèmes. Par exemple, vous pouvez avoir Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml, et ainsi de suite. Vous pouvez également avoir un fichier nommé generic.xaml. Lorsque le système recherche une ressource au niveau des thèmes, elle la recherche d’abord dans le fichier spécifique au thème, puis la recherche dans generic.xaml.

Lorsque votre contrôle se trouve dans un assembly distinct de l’application, vous devez placer vos ressources globales au niveau de l’élément ou au niveau du thème. Les deux méthodes ont leurs avantages.

Définition de ressources au niveau de l’élément

Vous pouvez définir des ressources partagées au niveau de l’élément en créant un dictionnaire de ressources personnalisé et en le fusionnant avec le dictionnaire de ressources de votre contrôle. Lorsque vous utilisez cette méthode, vous pouvez nommer votre fichier de ressources tout ce que vous souhaitez, et il peut se trouver dans le même dossier que vos contrôles. Les ressources au niveau de l’élément peuvent également utiliser des chaînes simples en tant que clés. L’exemple suivant crée un LinearGradientBrush fichier de ressources nommé Dictionary1.xaml.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>
  
</ResourceDictionary>

Une fois que vous avez défini votre dictionnaire, vous devez le fusionner avec le dictionnaire de ressources de votre contrôle. Pour ce faire, utilisez XAML ou du code.

L’exemple suivant fusionne un dictionnaire de ressources à l’aide de XAML.

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

L’inconvénient de cette approche est qu’un ResourceDictionary objet est créé chaque fois que vous le référencez. Par exemple, si vous avez 10 contrôles personnalisés dans votre bibliothèque et que vous fusionnez les dictionnaires de ressources partagées pour chaque contrôle à l’aide de XAML, vous créez 10 objets identiques ResourceDictionary . Vous pouvez éviter cela en créant une classe statique qui fusionne les ressources dans le code et retourne le résultat ResourceDictionary.

L’exemple suivant crée une classe qui retourne un partage ResourceDictionary.

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml",
                                    System.UriKind.Relative);

                _sharedDictionary =
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

L’exemple suivant fusionne la ressource partagée avec les ressources d’un contrôle personnalisé dans le constructeur du contrôle avant d’appeler InitializeComponent. Comme il SharedDictionaryManager.SharedDictionary s’agit d’une propriété statique, celle-ci ResourceDictionary n’est créée qu’une seule fois. Étant donné que le dictionnaire de ressources a été fusionné avant InitializeComponent d’être appelé, les ressources sont disponibles pour le contrôle dans son fichier XAML.

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();
}

Définition des ressources au niveau du thème

WPF vous permet de créer des ressources pour différents thèmes Windows. En tant qu’auteur de contrôle, vous pouvez définir une ressource pour un thème spécifique afin de modifier l’apparence de votre contrôle en fonction du thème utilisé. Par exemple, l’apparence d’un Button thème Windows Classic (le thème par défaut pour Windows 2000) diffère d’un Button thème Windows Luna (le thème par défaut pour Windows XP), car il Button utilise un thème différent ControlTemplate pour chaque thème.

Les ressources spécifiques à un thème sont conservées dans un dictionnaire de ressources avec un nom de fichier spécifique. Ces fichiers doivent se trouver dans un dossier nommé Themes qui est un sous-dossier du dossier qui contient le contrôle. Le tableau suivant répertorie les fichiers de dictionnaire de ressources et le thème associé à chaque fichier :

Nom du fichier de dictionnaire de ressources Thème Windows
Classic.xaml Apparence classique de Windows 9x/2000 sur Windows XP
Luna.NormalColor.xaml Thème bleu par défaut sur Windows XP
Luna.Homestead.xaml Thème olive sur Windows XP
Luna.Metallic.xaml Thème Argent sur Windows XP
Royale.NormalColor.xaml Thème par défaut sur Windows XP Media Center Edition
Aero.NormalColor.xaml Thème par défaut sur Windows Vista

Vous n’avez pas besoin de définir une ressource pour chaque thème. Si une ressource n’est pas définie pour un thème spécifique, alors le contrôle vérifie la présence de la ressource dans Classic.xaml. Si la ressource n’est pas définie dans le fichier qui correspond au thème actuel ou dans Classic.xaml, le contrôle utilise la ressource générique, qui se trouve dans un fichier de dictionnaire de ressources nommé generic.xaml. Le generic.xaml fichier se trouve dans le même dossier que les fichiers de dictionnaire de ressources spécifiques au thème. Bien qu’il generic.xaml ne corresponde pas à un thème Windows spécifique, il s’agit toujours d’un dictionnaire au niveau du thème.

Le contrôle personnalisé NumericUpDown en C# ou en Visual Basic, avec un exemple illustrant le support du thème et de l'automatisation de l'interface utilisateur, contient deux dictionnaires de ressources pour le contrôle NumericUpDown : l'un dans generic.xaml et l'autre dans Luna.NormalColor.xaml.

Lorsque vous placez un ControlTemplate dans n'importe lequel des fichiers de dictionnaire de ressources spécifiques au thème, vous devez créer un constructeur statique pour votre contrôle et appeler la méthode OverrideMetadata(Type, PropertyMetadata) sur le DefaultStyleKey, comme illustré dans l'exemple suivant.

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
Définition et référencement des clés pour les ressources de thème

Lorsque vous définissez une ressource au niveau de l’élément, vous pouvez affecter une chaîne en tant que clé et accéder à la ressource via la chaîne. Lorsque vous définissez une ressource au niveau du thème, vous devez utiliser une ComponentResourceKey clé. L’exemple suivant définit une ressource dans generic.xaml.

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

L’exemple suivant référence la ressource en spécifiant la ComponentResourceKey clé.

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>
Spécification de l’emplacement des ressources de thème

Pour rechercher les ressources d’un contrôle, l’application d’hébergement doit savoir que l’assembly contient des ressources spécifiques au contrôle. Pour ce faire, ajoutez le ThemeInfoAttribute à l’assembly qui contient le contrôle. Il ThemeInfoAttribute possède une propriété GenericDictionaryLocation qui spécifie l’emplacement des ressources génériques ainsi qu'une propriété ThemeDictionaryLocation qui précise l’emplacement des ressources spécifiques au thème.

L'exemple suivant définit les propriétés GenericDictionaryLocation et ThemeDictionaryLocation à SourceAssembly, pour spécifier que les ressources génériques et spécifiques au thème se trouvent dans le même assembly que le contrôle.

[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
           ResourceDictionaryLocation.SourceAssembly)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>

Voir aussi