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 le besoin de créer un contrôle. Toutefois, vous serez parfois amené à devoir créer un contrôle personnalisé dans certains cas. Cette rubrique traite des fonctionnalités qui limitent votre besoin de créer un contrôle personnalisé et des différents modèles de création de contrôle dans Windows Presentation Foundation (WPF). Cette rubrique montre également comment créer un nouveau contrôle.

Cette rubrique comprend les sections suivantes.

  • Alternatives à l'écriture d'un nouveau contrôle
  • Modèles pour création de contrôle
  • Notions de base de la création de contrôle
  • Rubriques connexes

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

Historiquement, si vous souhaitiez obtenir une expérience personnalisée à partir d'un contrôle existant, vous étiez limité à la modification des propriétés standards du contrôle, telles que la couleur d'arrière-plan, la largeur de bordure et la taille de police. Si vous souhaitiez étendre l'apparence ou le comportement d'un contrôle au-delà de ces paramètres prédéfinis, vous deviez créer un contrôle, généralement via l'héritage d'un contrôle existant et la substitution de la méthode responsable du dessin du contrôle. Bien que ce soit toujours possible, WPF vous permet de personnaliser des contrôles existants en utilisant son modèle, ses styles, ses modèles et ses déclencheurs au contenu riche. La liste suivante propose des exemples d'utilisation de ces fonctionnalités pour créer des expériences personnalisées et cohérentes sans devoir créer un contrôle.

  • Contenu riche. De nombreux contrôles WPF standard prennent en charge le contenu riche. Par exemple, la propriété de contenu d'un Button est de type Object, de sorte que, théoriquement, il est possible de tout afficher sur un Button. Si vous voulez qu'un bouton affiche une image et un texte, vous pouvez ajouter une image et un TextBlock à un StackPanel et assigner le StackPanel à la propriété Content. Comme les contrôles peuvent afficher des éléments visuels WPF et des données arbitraires, il est moins souvent 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 de Button et d'autres modèles de contenu dans WPF, consultez Modèle de contenu WPF.

  • Styles. Un Style est une collection de valeurs qui représentent les propriétés d'un contrôle. L'utilisation de styles vous permet de créer une représentation réutilisable de l'apparence et du comportement souhaités d'un contrôle sans écrire un nouveau contrôle. Par exemple, supposez que vous voulez que tous vos contrôles TextBlock aient une police rouge, 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. Chaque TextBlock que vous ajoutez à votre application a alors la même apparence.

  • Modèles de données. Un DataTemplate permet de personnaliser l'affichage des données sur un contrôle. Vous pouvez par exemple utiliser un DataTemplate pour définir l'affichage des données dans une ListBox. Pour obtenir un exemple, consultez Vue d'ensemble des modèles de données. En plus de personnaliser l'apparence des données, un DataTemplate peut inclure des éléments d'interface, ce qui vous offre une grande flexibilité dans les interfaces utilisateur personnalisées. Par exemple, vous pouvez créer une ComboBox dans laquelle chaque élément contient une case à cocher en utilisant un DataTemplate.

  • Modèles de contrôle. De nombreux contrôles dans WPF utilisent un ControlTemplate pour définir la structure et l'apparence de contrôle, qui sépare l'apparence d'un contrôle de ses fonctionnalités. Vous pouvez totalement modifier l'apparence d'un contrôle en redéfinissant son ControlTemplate. Imaginons par exemple que vous souhaitiez créer un contrôle ressemblant à un feu rouge. Ce contrôle possède une interface utilisateur et des fonctionnalités simples. Il est constitué de trois cercles, dont un seul peut être allumé à la fois. Après quelques instants de réflexion, vous réalisez qu'une RadioButton offre la possibilité d'effectuer une sélection unique. L'apparence par défaut de la RadioButton ne ressemble toutefois en rien aux voyants d'un feu rouge. Comme la RadioButton utilise un modèle de contrôle pour définir son apparence, il est facile de redéfinir ce ControlTemplate pour répondre aux exigences du contrôle et utiliser des cases d'option pour créer votre feu rouge.

    RemarqueRemarque

    Bien qu'une RadioButton puisse utiliser un DataTemplate, ce dernier ne suffit pas dans cet exemple.Le DataTemplate définit l'apparence du contenu d'un contrôle.Dans le cas d'une RadioButton, le contenu est l'élément qui apparaît à droite du cercle qui indique si la RadioButton est sélectionnée.Dans l'exemple du feu rouge, la case d'option doit juste être un cercle capable de "s'allumer". Comme l'apparence du feu rouge est très différente de l'apparence par défaut de la RadioButton, il est nécessaire de redéfinir le ControlTemplate.En général, un DataTemplate est utilisé pour définir le contenu (ou les données) d'un contrôle et un ControlTemplate pour définir sa structure.

  • Déclencheurs. Un Trigger vous permet de modifier dynamiquement l'apparence et le comportement d'un contrôle sans créer de contrôle. Par exemple, imaginons vous ayez plusieurs contrôles ListBox dans votre application et souhaitiez que les éléments de chaque ListBox apparaissent en caractères gras et rouges lorsqu'ils sont sélectionnés. Votre premier réflexe vous dicte de créer une classe qui hérite de ListBox et substitue la méthode OnSelectionChanged pour modifier l'apparence de l'élément sélectionné. La solution idéale consiste toutefois à 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 des propriétés ou de prendre des mesures sur la base de la valeur d'une propriété. Un EventTrigger vous permet de prendre des mesures lorsqu'un événement se produit.

Pour plus d'informations sur les styles, les modèles et les déclencheurs, consultez Application d'un 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 qu'il ait l'air différent, vous devez d'abord vous demander s'il est possible d'utiliser une des méthodes examinées de cette section pour modifier l'apparence du contrôle existant.

Modèles pour création de contrôle

Le modèle de contenu riche, les styles, les modèles et les déclencheurs réduisent la nécessité de créer des contrôles. 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 proposés dans WPF. À cet effet, WPF propose trois modèles généraux offrant chacun un jeu de fonctionnalités et un niveau de flexibilité différent. Les classes de base de ces trois modèles sont UserControl, Control et FrameworkElement.

Dériver de UserControl

La méthode la plus simple pour créer un contrôle dans WPF consiste à le dériver d'un 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 dans Extensible Application Markup Language (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 ressemble fortement au modèle utilisé pour le développement d'applications dans WPF.

Si la génération s'est déroulée correctement, un UserControl peut tirer parti des avantages d'un contenu riche, de styles et de 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 un ControlTemplate pour personnaliser son apparence. Il est nécessaire de dériver à partir de la classe Control ou d'une de ses classes dérivées (autre que UserControl) pour créer un contrôle personnalisé qui prend en charge des modèles.

Avantages de dériver de UserControl

Vous pouvez envisager de dériver de UserControl si toutes les conditions suivantes s'appliquent :

  • Vous voulez créer votre contrôle de la même manière que vous générez une application.

  • Votre contrôle contient uniquement des composants existants.

  • Vous n'avez pas besoin de prendre en charge une personnalisation complexe.

Dériver de Control

Dériver de la classe Control 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 classe Control, vous définissez son apparence à l'aide de modèles. En agissant de la sorte, 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 en utilisant des commandes et des liaisons au lieu des événements et en évitant le plus possible de référencer les éléments dans le ControlTemplate.  Si l'interface utilisateur et la logique de votre contrôle sont correctement découplées, un utilisateur de votre contrôle peut redéfinir son ControlTemplate pour personnaliser son apparence. Bien que la génération d'un Control personnalisé ne soit pas aussi simple que la génération d'un UserControl, un Control personnalisé fournit plus de flexibilité.

Avantages de dériver de Control

Vous pouvez envisager de dériver de Control au lieu d'utiliser la classe UserControl lorsqu'une ou plusieurs des conditions suivantes s'appliquent :

  • Vous voulez pouvoir personnaliser l'apparence de votre contrôle via le ControlTemplate.

  • Vous souhaitez que votre contrôle prenne en charge des thèmes différents.

Dériver de FrameworkElement

Les contrôles qui dérivent de UserControl ou de Control reposent sur la composition d'éléments existants. Cette solution est acceptable dans de nombreux scénarios, car tout objet héritant de FrameworkElement peut appartenir à un ControlTemplate. Dans certains cas, l'apparence d'un contrôle requiert toutefois des fonctionnalités autres que celles de la composition d'éléments simples. Pour ces scénarios, baser un composant sur FrameworkElement est le bon choix.

Il existe deux méthodes standard pour générer des composants basés sur FrameworkElement : le rendu direct et la composition d'éléments personnalisés. Le rendu direct implique de substituer la méthode OnRender de FrameworkElement et de fournir des opérations DrawingContext qui définissent explicitement les visuels du composant. C'est la méthode utilisée par Image et Border. La composition d'éléments personnalisés implique l'utilisation d'objets de type Visual pour composer l'apparence du 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éments personnalisée. Il est également possible de combiner le rendu direct et la composition d'éléments personnalisés au sein du même contrôle.

Avantages de dériver de FrameworkElement

Vous pouvez envisager de dériver de FrameworkElement dans l'un des cas suivants :

  • Vous souhaitez avoir un contrôle précis sur l'apparence de votre contrôle allant au-delà des fonctionnalités proposées par la composition d'éléments simples.

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

  • Vous souhaitez composer des éléments existants de manière innovante au delà de ce qui est possible avec UserControl et Control.

Notions de base de la création de contrôle

Comme expliqué précédemment, l'une des fonctionnalités les plus puissantes de WPF est la possibilité d'aller au delà de la définition de propriétés de base d'un contrôle pour modifier son apparence et son comportement, tout en n'ayant toujours pas besoin de créer un contrôle personnalisé. Les styles, la liaison de données et les fonctionnalités du déclencheur sont rendus possibles par le système de propriétés WPF et le système d'événement WPF. Les sections suivantes décrivent certaines pratiques que vous devez respecter, indépendamment du 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 des propriétés de dépendance

Dans le cas d'une propriété de dépendance, il est possible d'effectuer les opérations suivantes :

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

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

  • Utiliser une ressource dynamique en tant que valeur de la propriété.

  • Animer la propriété.

Si vous voulez qu'une propriété de votre contrôle prenne en charge l'une de ces fonctionnalités, vous devez l'implémenter en tant que propriété de dépendance. L'exemple suivant montre comment définir une propriété de dépendance appelée Value.

  • Définissez un identificateur DependencyProperty nommé ValueProperty en tant que champ public static readonly.

  • Enregistre le nom de la propriété avec le système de propriétés, en appelant DependencyProperty.Register, afin de spécifier les éléments suivants :

    • le nom de la propriété ;

    • le type de la propriété ;

    • le type propriétaire de la propriété ;

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

  • Définissez une propriété de wrapper CLR nommée Value, qui est le nom utilisé pour enregistrer la propriété de dépendance, en implémentant les accesseurs get et set de la propriété. Notez que les accesseurs get et set appellent uniquement GetValue et SetValue, respectivement. Il est conseillé de ne pas inclure de logique supplémentaire dans les accesseurs de propriétés de dépendance car les clients et WPF peuvent ignorer les accesseurs et appeler directement GetValue et SetValue. Par exemple, lorsqu'une propriété est liée à une source de données, l'accesseur set de 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 lorsque celle-ci change. Pour plus d'informations sur ces rappels, consultez Validation et rappels de propriétés de dépendance.

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

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

        ''' <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
/// <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);
}

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

Utiliser RoutedEvents

De même que 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 conseillé d'implémenter votre événement sous forme d'événement routé car un tel événement prend en charge le comportement suivant :

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

  • Des é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.

  • Il est possible d'utiliser des événements routé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 montre comment définir un événement routé :

  • Définissez un identificateur RoutedEvent nommé ValueChangedEvent en tant que champ public static readonly.

  • Enregistrez l'événement routé en appelant la méthode EventManager.RegisterRoutedEvent. L'exemple spécifie les informations suivantes lorsqu'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 que les gestionnaires d'événements sur les éléments parents de la source sont appelés dans l'ordre, 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>, qui est construit avec un type Decimal.

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

  • Déclarez un événement public nommé ValueChanged et incluez des déclarations d'accesseurs d'événement. L'exemple appelle AddHandler dans la déclaration d'accesseur add et RemoveHandler dans la déclaration d'accesseur remove afin d'utiliser les services d'événement 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 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
/// <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);
}

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

Utilisation de la liaison

Pour découpler l'interface utilisateur de votre contrôle de sa logique, pensez à utiliser la liaison de données. C'est particulièrement important si vous définissez l'apparence de votre contrôle à l'aide d'un ControlTemplate. L'utilisation de la liaison de données permet d'éviter de référencer des parties spécifiques de l'interface utilisateur au départ du code. Il est préférable d'éviter de référencer des éléments qui se trouvent dans le ControlTemplate parce que lorsque le code référence des éléments présents dans le ControlTemplate et que le ControlTemplate est modifié, l'élément référencé doit être inclus dans le nouveau ControlTemplate.

L'exemple suivant met à jour le TextBlock du contrôle NumericUpDown, en lui assignant un nom et référençant la zone de texte dans le code par son nom.

<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 Sub UpdateTextBlock()
            valueText.Text = Value.ToString()
        End Sub
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}

L'exemple suivant utilise la liaison pour faire 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 obtenir de l'aide pour les contrôles WPF personnalisés dans Concepteur WPF pour Visual Studio (par exemple, pour modifier des propriétés avec la fenêtre Propriétés), suivez les indications ci-après. Pour plus d'informations sur le développement pour Concepteur WPF, consultez Concepteur WPF.

Propriétés de dépendance

Veillez à implémenter des assesseurs get et set CLR comme décrit précédemment dans la section « Utiliser des 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, à l'instar de WPF et des clients du contrôle, ils ne sont pas tenus d'appeler les accesseurs lors de l'obtention ou de la définition de la propriété.

Propriétés attachées

Vous devez implémenter des propriétés attachées sur des contrôles personnalisés à l'aide des indications suivantes :

  • Préparez une DependencyProperty public static readonly de type NomPropriétéProperty créé à l'aide de l'une des méthodes RegisterAttached. Le nom de propriété passé à RegisterAttached doit correspondre à NomPropriété.

  • Implémentez deux méthodes public static CLR nommées SetNomPropriété et GetNomPropriété. Les deux méthodes doivent accepter une classe dérivée de DependencyProperty pour leur premier argument. La méthode SetNomPropriété accepte également un argument dont le type correspond au type de données enregistré pour la propriété. La méthode GetNomPropriété doit retourner une valeur du même type. Si la méthode SetNomPropriété fait défaut, la propriété est marquée en lecture seule.

  • SetPropertyName et GetPropertyName doivent router directement aux méthodes GetValue et SetValue sur l'objet de dépendance cible, respectivement. Les concepteurs peuvent accéder à la propriété attachée en l'appelant via le wrapper de méthode ou lançant un appel direct à l'objet de dépendance cible.

Pour plus d'informations sur les propriétés attachées, 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 l'intégrer dans un assembly séparé qui peut être utilisé dans plusieurs applications. Pour la plupart, les informations abordées dans cette rubrique sont valables, quelle que soit la méthode que vous utilisez. Toutefois, il existe une différence notable. 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. Cependant, un assembly qui ne contient que des contrôles n'est associé à aucun objet Application et aucun fichier App.xaml n'est donc 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 référence la ressource, puis il passe en revue les ressources du parent logique, et ainsi de suite, jusqu'à atteindre l'élément racine ;

  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 enregistrés dans un sous-dossier nommé Themes. Les fichiers contenus dans ce dossier correspondent aux thèmes. Par exemple, vous pouvez avoir Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml, etc. Vous pouvez également avoir un fichier nommé generic.xaml. Lorsque le système recherche une ressource au niveau des thèmes, il la cherche en premier lieu dans le fichier spécifique au thème puis 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 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 donner à votre fichier de ressources le nom de votre choix, 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 comme clés. L'exemple suivant crée un fichier de ressources LinearGradientBrush nommé Dictionary1.xaml.

<ResourceDictionary 
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://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, vous pouvez utiliser le XAML ou du code.

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

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

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

L'exemple de code suivant crée une classe qui retourne un ResourceDictionary partagé.

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 InitilizeComponent. SharedDictionaryManager.SharedDictionary étant une propriété statique, le ResourceDictionary n'est créé qu'une seule fois. Comme le dictionnaire de ressources a été fusionné avant l'appel de InitializeComponent, les ressources sont mises à la disposition du contrôle dans son fichier XAML.

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

}

Définition de ressources au niveau du thème

WPF permet de créer des ressources pour les différents thèmes Windows. En tant qu'auteur du contrôle, vous pouvez définir une ressource pour un thème spécifique afin de modifier l'apparence de votre contrôle selon le thème utilisé. Par exemple, l'apparence d'un Button dans le thème Windows Classic (thème par défaut de Windows 2000) diffère de celle d'un Button avec le thème Windows Luna (thème par défaut de Windows XP) parce que le Button utilise un ControlTemplate différent 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, dépendant du dossier qui contient le contrôle. Le tableau suivant répertorie les fichiers des dictionnaires de ressources et le thème associé à chaque fichier :

Nom de fichier du dictionnaire de ressources

Thème de Windows

Classic.xaml

Aspect habituel de Windows 9x/2000 sous Windows XP

Luna.NormalColor.xaml

Thème bleu par défaut sous Windows XP

Luna.Homestead.xaml

Thème vert olive sur Windows XP

Luna.Metallic.xaml

Thème argent sur Windows XP

Royale.NormalColor.xaml

Thème par défaut sous Windows XP Édition Media Center

Aero.NormalColor.xaml

Thème par défaut sous Windows Vista

Il n'est pas nécessaire de définir une ressource pour chaque thème. Si une ressource n'est pas définie pour un thème spécifique, le contrôle vérifie Classic.xaml pour la ressource. Si la ressource n'est pas définie dans le fichier correspondant 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 fichier generic.xaml se trouve dans le même dossier que les fichiers de dictionnaire de ressources spécifiques au thème. Bien que generic.xaml ne corresponde pas à un thème spécifique de Windows, il s'agit néanmoins d'un dictionnaire au niveau du thème.

Contrôle personnalisé NumericUpDown avec exemple de thème et de prise en charge d'UI Automation (page éventuellement en anglais) contient deux dictionnaires de ressources pour le contrôle NumericUpDown : l'un dans generic.xaml et l'autre dans Luna.NormalColor.xaml. Vous pouvez exécuter l'application et basculer entre le thème Argent de Windows XP et un autre thème afin de voir la différence entre les deux modèles de contrôle. (Si vous travaillez sous Windows Vista, vous pouvez renommer Luna.NormalColor.xaml en Aero.NormalColor.xaml et basculer entre deux thèmes, tels que Windows Classic et le thème par défaut de Windows Vista.)

Lorsque vous placez un ControlTemplate dans l'un 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) de la DefaultStyleKey, comme illustré dans l'exemple suivant.

        Shared Sub New()
            DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
        End Sub
static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}

Définition et référencement de clés pour les ressources de thème

Lorsque vous définissez une ressource au niveau de l'élément, vous pouvez assigner une chaîne comme 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 comme 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 comme 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 spécifiques aux thèmes

Pour rechercher les ressources relatives à un contrôle, l'application d'hébergement doit savoir quel l'assembly contient des ressources spécifiques au contrôle. Pour ce faire, vous pouvez ajouter le ThemeInfoAttribute à l'assembly qui contient le contrôle. Le ThemeInfoAttribute comporte une propriété GenericDictionaryLocation qui spécifie l'emplacement des ressources génériques, et une propriété ThemeDictionaryLocation qui spécifie l'emplacement des ressources spécifiques au thème.

L'exemple suivant affecte aux propriétés GenericDictionaryLocation et ThemeDictionaryLocation la valeur 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

Concepts

URI à en-tête pack dans WPF

Autres ressources

Concepteur WPF

Personnalisation des contrôles