Externalisation des styles WPF en assembly satellite

Les styles sont une des fonctionnalités majeures de WPF et permettent de modifier le rendu d’un objet. Les styles sont utilisés pour diverses raisons comme par exemple suivre une charte graphique ou obtenir une interface homogène dans une application. Les styles apportent une souplesse d’affichage en WPF qui n’existait pas dans les applications Windows traditionnelles. Dans la plupart des exemples, les styles sont déclarés et utilisés dans le contexte d’une page ou d’un contrôle. Dans une application, les styles sont généralement déclarés dans un dictionnaire de ressources global à l’application. Nous allons expliquer dans cet article comment externaliser un dictionnaire de ressources contenant les déclarations de style afin d’en faire un assembly satellite réutilisable dans plusieurs applications par simple référencement.

La solution est téléchargeable en pièce jointe de cet article (voir à la fin). 

Ci-dessous un exemple de style appliqué à un bouton :

<Window x:Class="StyledWinApp.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    Title="StyledWinApp" Height="300" Width="300"

    >

  <Window.Resources>

    <Style x:Key="BasicButtonStyle" TargetType="Button">

      <Setter Property="Control.Background" Value="LightBlue"/>

    </Style>

  </Window.Resources>

    <Grid>

      <Button Content="Hello!" Style="{StaticResource BasicButtonStyle}">

      </Button>

    </Grid>

</Window>

Dans la suite de l’article, j’utiliserai un style AquaGel dont la démonstration est téléchargeable ici : https://www.valil.com/AquaGelButton.zip sur le blog de Valentin Iliescu.

Dans un premier temps, nous allons créer un projet de type « Custom Control Library (WPF) » (nommé MTCParis.WPF.Themes.AquaGel pour l’exemple). Dans ce projet, il n’est pas nécessaire de conserver le contrôle utilisateur créé par défaut. Il convient d’ajouter un nouvel élément au projet de type ResourceDictionary (WPF). La déclaration du template du contrôle bouton est copiée puis collée dans le fichier XAML nouvellement créé que j’ai nommé AquaGelButtonStyle.xaml pour la démonstration. J’ai ajouté une déclaration d’un style encapsulant la déclaration du template.

<Style x:Key="AquaGelButtonTemplateStyle" TargetType="{x:Type Button}">

    <Setter Property="Template" Value="{DynamicResource AquaGelButtonTemplate}" />

  </Style>

Dans la démonstration téléchargée, chacun des 3 boutons est ensuite stylé avec différente couleur (bleu, rouge, jaune). Dans l’exemple d’externalisation et de réutilisation des ressources, je vais spécialiser un style pour le bouton rouge afin qu’il soit réutilisable simplement sur différentes applications de la manière suivante (remarquez l’attribut BasedOn qui permet d’hériter d’un autre style):

<Style x:Key="RedAquaGelButtonTemplateStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource AquaGelButtonTemplateStyle}">

      <Setter Property="Background">

        <Setter.Value>

          <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">

            <LinearGradientBrush.RelativeTransform>

              <TransformGroup>

                <TranslateTransform X="-0.5" Y="-0.5"/>

                <ScaleTransform ScaleX="1" ScaleY="1"/>

                <SkewTransform AngleX="0" AngleY="0"/>

                <RotateTransform Angle="90"/>

                <TranslateTransform X="0.5" Y="0.5"/>

                <TranslateTransform X="0" Y="0"/>

              </TransformGroup>

            </LinearGradientBrush.RelativeTransform>

            <LinearGradientBrush.GradientStops>

              <GradientStopCollection>

                <GradientStop Color="sc#1, 0.5, 0, 0" Offset="0"/>

                <GradientStop Color="sc#1, 1, 0.5, 0.5" Offset="1"/>

              </GradientStopCollection>

            </LinearGradientBrush.GradientStops>

          </LinearGradientBrush>

        </Setter.Value>

      </Setter>

    </Style>

 Afin que le dictionnaire de ressources de l’assembly satellite soit utilisable depuis une application, il est nécessaire d’indiquer un nom de classe que nous pourrons référencer. Cela se fait dans l’en-tête déclaratif du dictionnaire de ressource :

x:Class="MTCParis.WPF.Themes.AquaGel.AquaGelButtonResource"

Puis ajouter la déclaration partielle de la classe avec un constructeur qui effectue la phase d’initialisation (Ajouter une nouvelle classe du même nom dans le projet) :

namespace MTCParis.WPF.Themes.AquaGel

{

    public partial class AquaGelButtonResource

    {

        public AquaGelButtonResource()

        {

            this.InitializeComponent();

        }

    }

}

Note : Dans l’exemple, je n’ai pas trouvé d’autre solution que la déclaration partielle de la classe pour utiliser le style en externe.

Revenons maintenant à une application dans laquelle nous souhaitons utiliser notre librairie de style. La première étape est de référencer l’assembly satellite. Ensuite nous devons ajouter le code de chargement de la librairie et de fusion des ressources avec les ressources existantes de l’application. Je surcharge pour cela l’événement OnStartup dans le code du fichier de démarrage (App.xaml) :

protected override void OnStartup(StartupEventArgs e)

{

    base.OnStartup(e);

}

Puis, nous fusionnons le dictionnaire de ressources de l’assembly satellite aux ressources de l’application :

protected override void OnStartup(StartupEventArgs e)

{

   base.OnStartup(e);

   this.Resources.MergedDictionaries.Add(new MTCParis.WPF.Themes.AquaGel.AquaGelButtonResource());

}

Il reste à spécifier le style à utiliser sur le contrôle cible comme le montre l’exemple ci-dessous :

<Button VerticalAlignment="Top" Margin="179.5,171.4,385.5,0" Width="Auto" Height="40"

   x:Name="Button1"

  Style="{DynamicResource RedAquaGelButtonTemplateStyle}"

   RenderTransformOrigin="0.5,0.5" Content="Button1">

Il est possible de decliner les autres styles (vert et jaune ou bleu) de la même manière dans l’assembly satellite.

Ci-dessous un exemple avec la déclaration du style dans le contrôle Button1:

Button with no style

Après l'application du style:

Button with style

Cette externalisation permet d’isoler la déclaration des styles et améliorer leur réutilisabilité. Il nous vous reste plus qu’à créer vos propres librairies de styles réutilisables et à les publier J !

ExternalStyleSolution.zip