[WPF] Augmenter les performances des ResourceDictionary

Le 17 juillet 2010 à 14:29

Quand vous déclarer un ResourceDictionary, cela crée une nouvelle instance de celui-ci. Mais si par exemple, vous avez des dizaines de contrôles personnalisés et que ceux-ci ont chacun un dictionnaire de ressource en commun, le même dictionnaire de ressource sera instancié plusieurs fois.

Pour remédier à ce problème, Christian Mosers, a crée la classe SharedResourceDictionary. Celle ci s’utilise de la même façon qu’un ResourceDictionary à la différence que cette fois ci, si un dictionnaire de ressource est instancié plusieurs fois, il sera en fait chargé qu’une seule fois.

Avec cette solution, il y aura un impact positif sur les performances et sur la mémoire !

[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml/presentation", 
"WPFTutorial.Utils")]
 
/// <summary>
/// The shared resource dictionary is a specialized resource dictionary
/// that loads it content only once. If a second instance with the same source
/// is created, it only merges the resources from the cache.
/// </summary>
public class SharedResourceDictionary : ResourceDictionary
{
    /// <summary>
    /// Internal cache of loaded dictionaries 
    /// </summary>
    public static Dictionary<Uri, ResourceDictionary> _sharedDictionaries =
        new Dictionary<Uri, ResourceDictionary>();
 
    /// <summary>
    /// Local member of the source uri
    /// </summary>
    private Uri _sourceUri;
 
    /// <summary>
    /// Gets or sets the uniform resource identifier (URI) to load resources from.
    /// </summary>
    public new Uri Source
    {
        get { return _sourceUri; }
        set
        {
            _sourceUri = value;
 
            if (!_sharedDictionaries.ContainsKey(value))
            {
                // If the dictionary is not yet loaded, load it by setting
                // the source of the base class
                base.Source = value;
 
                // add it to the cache
                _sharedDictionaries.Add(value, this);
            }
            else
            {
                // If the dictionary is already loaded, get it from the cache
                MergedDictionaries.Add(_sharedDictionaries[value]);
            }
        }
    }
}

Et pour l’utiliser :

<ResourceDictionary.MergedDictionaries>
   <SharedResourceDictionary Source="/MyControlLibrary;component/Themes/Brushes.xaml"  />
</ResourceDictionary.MergedDictionaries>

A bientôt !

[WPF] Appliquez des thèmes dynamiquement dans vos applications

Le 11 juillet 2010 à 07:24

Dans mon post précédent, j’ai fait part d’une solution pour localiser vos applications sans passer par l’outil LocBaml. J’utilisais des ResourceDictionary que j’affectais dynamiquement au moment de l’exécution. Cette méthode peut être utilisée dans plusieurs cas, et l’une d’entre elle : les thèmes.
Devoir gérer plusieurs thèmes dans les applications était auparavant un travail assez complexe (je pense à la création de contrôle personnalisée, que la technologie WPF nous a permis de faire beaucoup plus simplement), mais maintenant avec les ResourceDictionary, c’est devenu relativement “simple”. Bien sûr, ceci n’est qu’un exemple d’application et il reste assez basique à mettre en place, mais cela pourra vous inspirer.

Nous allons créer deux thèmes : “default” et “light”. Pour cela, comme expliquer ci-dessus, nous allons ajouter des ResourceDictionary (dictionnaire de ressources en français) :

image


Maintenant, nous allons remplir ces fichiers :

<!-- Default.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:System="clr-namespace:System;assembly=mscorlib">

    <System:String x:Key="ThemeName">Default</System:String>
    <SolidColorBrush x:Key="win_background" Color="DarkBlue"></SolidColorBrush>

    <Style TargetType="Button">
        <Setter Property="FontFamily" Value="Verdana"/>
        <Setter Property="FontSize" Value="10px"/>
        <Setter Property="FontWeight" Value="Bold"/>
        <Setter Property="Background" Value="LightBlue" />
    </Style>
</ResourceDictionary>



<!-- Light.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:System="clr-namespace:System;assembly=mscorlib">

    <System:String x:Key="ThemeName">Light</System:String>
    <SolidColorBrush x:Key="win_background" Color="LightBlue"></SolidColorBrush>

    <Style TargetType="Button">
        <Setter Property="FontFamily" Value="Trebuchet MS"/>
        <Setter Property="FontSize" Value="10px"/>
        <Setter Property="FontWeight" Value="Normal"/>
        <Setter Property="Background" Value="White" />
    </Style>
</ResourceDictionary>

Puis la déclaration dans le point d’entrée de l’application du thème par défaut :

<Application x:Class="WPFThemes.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes\Default.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Maintenant que tout cela est fait, on va implémenter la méthode qui va nous permettre de changer de thème dynamiquement dans le point d’entrée de notre application :

public partial class App : Application
{
    private ResourceDictionary obj;

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        foreach (ResourceDictionary item in this.Resources.MergedDictionaries)
        {
            if (item.Source != null && item.Source.OriginalString.Contains(@"Themes\"))
            {
                obj = item;
            }
        }
    }

    public void ChangeTheme(Uri dictionnaryUri)
    {
        if (String.IsNullOrEmpty(dictionnaryUri.OriginalString) == false)
        {
            ResourceDictionary objNewLanguageDictionary = 
                                   (ResourceDictionary)(LoadComponent(dictionnaryUri));

            if (objNewLanguageDictionary != null)
            {
                this.Resources.MergedDictionaries.Remove(obj);
                this.Resources.MergedDictionaries.Add(objNewLanguageDictionary);
            }
        }
    }
}

Il faut savoir que quand on utilise beaucoup de ResourceDictionary, il peut se produire un impact négatif sur les performances : ce sera mon prochain article :).

Maintenant pour pouvoir lié notre interface avec les styles :

<Window x:Class="WPFThemes.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        Background="{DynamicResource ResourceKey=win_background}">
    <Grid>
        <Button Content="Changer le thème" Height="51" HorizontalAlignment="Center" 
          Name="btnChangeTheme" VerticalAlignment="Center" Width="110" 
          Click="btnChangeTheme_Click" />
    </Grid>
</Window>

Pour télécharger les sources, ici.

A bientôt !

[WPF] Traduisez vos applications en plusieurs langues (sans LocBaml)

Le 5 juillet 2010 à 10:09

Il y a quelques mois, j’avais mis en ligne une façon de traduire ses applications en plusieurs langues, grâce à LocBaml. Nous allons voir maintenant comment traduire une application WPF mais sans cet outil.
La mise en place est très simple et il a l’avantage, par rapport à LocBaml, qu’on puisse traduire l’application « à la volée » sans relancer celle-ci. Par contre, il faut être plus rigoureux : là où LocBaml nous génère les clefs (x:Uid) des contenus de nos différents contrôles, ici il faut bien prendre soin de le faire soi-même.

Nous allons créer un dictionnaire de ressource pour la langue français et un autre pour la langue anglaise :

 

01
Pour plus de commodité, nous allons nommer les dictionnaires de ressources avec le code pays :

02


Dans ces fichiers, j’ajoute quelques balises afin de contenir les phrases dans la langue adéquate :

<!-- Fichier en-US.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:system="clr-namespace:System;assembly=mscorlib">

    <system:String x:Key="Culture">en-US</system:String>

    <system:String x:Key="Window">Window</system:String>
    <system:String x:Key="cmdExit">Exit</system:String>
    <system:String x:Key="lblWelcome">Hello</system:String>
    <system:String x:Key="World">World !</system:String>
    <system:String x:Key="lblChangeLangage">Change language</system:String>

</ResourceDictionary>


<!-- Fichier fr-FR.xaml -->
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:system="clr-namespace:System;assembly=mscorlib">

    <system:String x:Key="Culture">fr-FR</system:String>

    <system:String x:Key="Window">Fenêtre</system:String>
    <system:String x:Key="cmdExit">Quitter</system:String>
    <system:String x:Key="lblWelcome">Bonjour</system:String>
    <system:String x:Key="World">Tout le monde !</system:String>
    <system:String x:Key="lblChangeLangage">Changer de langue</system:String>

</ResourceDictionary>

Puis on va indiquer le ResourceDirectionary de la langue par défaut dans le point d’entrée de notre application (App.xaml), c’est cette ressource qui sera chargée au démarrage de l’application (bien sûr, ici ce n’est que pour exemple, il y aurait moyen de faire mieux dans le contexte où l’on retient le choix de la langue de l’utilisateur, qui peut être différent que le français :)) :

<Application x:Class="MultilangueSansLocBaml.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources\fr-FR.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Maintenant je vais ajouté une méthode qui permettra de changer la ressource de langue utilisée dans l’application :

 

Ici, nous allons chercher la ressource chargée, puis la supprimer afin d’ajouter la nouvelle ressource, puis changer la culture en cours. En effet, nous avons mis dans le XAML une ressource (fr-FR.xaml) chargée par défaut, il faut donc s’en “débarrassé” afin de mettre une autre ressource.
Pour savoir quelle est la ressource chargée, on va au démarrage de l’application récupérer le ResourceDictionary :

public void ChangeLangage(Uri dictionnaryUri)
{
    if (String.IsNullOrEmpty(dictionnaryUri.OriginalString) == false)
    {
        ResourceDictionary objNewLanguageDictionary = 
                   (ResourceDictionary)(LoadComponent(dictionnaryUri));

        if (objNewLanguageDictionary != null)
        {
            this.Resources.MergedDictionaries.Remove(obj);
            this.Resources.MergedDictionaries.Add(objNewLanguageDictionary);

            CultureInfo culture =
               new CultureInfo((string)Application.Current.Resources["Culture"]);
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = culture;
        }
    }
}

Maintenant passons à l’interface de notre application, pour exemple, nous allons utiliser cette fenêtre :

03

Au clique sur “Changer de langue”, l’interface se mettra automatiquement à jour avec la ressource anglaise.

private ResourceDictionary obj;

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    foreach (ResourceDictionary item in this.Resources.MergedDictionaries)
    {
        if (item.Source != null && item.Source.OriginalString.Contains(@"Resources\"))
        {
            obj = item;
        }
    }
}

Et le code pour changer la langue quand on clique sur le bouton :

<Window x:Class="MultilangueSansLocBaml.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="{DynamicResource Window}" Height="138" Width="203">
    <Grid>
        <StackPanel>
            <Button Content="{DynamicResource lblWelcome}" Click="Button_Click_1" />
            <Button Content="{DynamicResource lblChangeLangage}" Click="Button_Click" />
        </StackPanel>
    </Grid>
</Window>

Et pour charger un élément d’une ressource en C# :

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    MessageBox.Show((string) Application.Current.Resources["World"]);
}

04     05

Bien sûr, ceci n’est qu’une ébauche. On peut faire quelque chose de beaucoup plus complet, mais l’essentiel est là :).

Les sources du projet peuvent être téléchargés ici.

A bientôt !

[WPF] Interagir avec la nouvelle barre des tâches de Windows 7 avec .NET 4.0

Le 1 juillet 2010 à 16:04

Au niveau de l’interface utilisateur, Windows 7 a introduit plusieurs nouveautés. L’une d’entre elle c’est la nouvelle barre des tâches qui permet de communiquer l’état de l’application à l’utilisateur (par exemple Windows Media Player et les boutons “précédent”, “lire/suspendre” et “suivant”) et aussi d’exposer les tâches courantes (les fichiers récemment utilisés… etc).

 

image  image  image  image

Avec WPF 4.0, nous pouvons modifier le comportement de la barre des tâches pour y ajouter des tâches ou des actions.

Le type Jumplist permet d’ajouter des tâches associés au bouton de l’application. Par exemple, nous voulons ajouter une tâche dans la Jumplist :

JumpTask task = new JumpTask
                    {
                        Title = "Exemple",
                        Arguments = "exemple.txt",
                        Description = "La description de l'exemple",
                        CustomCategory = "La catégorie de mon exemple !",
                        IconResourcePath = "notepad.exe",
                        ApplicationPath = "notepad.exe"
                    };

JumpList jumpList = new JumpList();
jumpList.JumpItems.Add(task);
jumpList.ShowRecentCategory = true;
jumpList.ShowFrequentCategory = true;
JumpList.SetJumpList(Application.Current, jumpList);

On peut aussi très bien le faire en XAML (dans App.xaml, enfin dans le point de départ de votre application) :

<JumpList.JumpList>
    <JumpList ShowFrequentCategory="True" ShowRecentCategory="True">
        <JumpTask Title="Exemple"
                  Description="La description de l'exemple"
                  Arguments="exemple.txt"
                  CustomCategory="La catégorie de mon exemple !"
                  ApplicationPath="notepad.exe"
                  IconResourcePath="notepad.exe"/>
    </JumpList>
</JumpList.JumpList>

Voici le résultat de notre exemple :

image

Vous avez certainement remarqué que dans Media Player (comme l’indique l’image plus haut) contient trois boutons et que vous pouvez directement interagir avec l’application depuis la barre des tâches pour effectuer des tâches “primaires” (suivant, précédent, arrêter) sur le lecteur. La mise en place de ce type de boutons n’est pas très compliqué, nous allons voir comment le faire en XAML.

Pour commencer, il faut ajouter le code suivant dans la fenêtre où l’on veut ajouter le bouton :

<Window.TaskbarItemInfo>
   <TaskbarItemInfo>
   </TaskbarItemInfo>
</Window.TaskbarItemInfo>

Ensuite, il faut ajouter à l’intérieur de la balise TaskBarItemInfo une collection de TumbButtonInfoCollection qui regrouperont nos ThumbButtonInfo (qui représentent les boutons) :

<Window.TaskbarItemInfo>
    <TaskbarItemInfo>
        <TaskbarItemInfo.ThumbButtonInfos>
            <ThumbButtonInfo Description="Hello" ImageSource="/about.png" />
        </TaskbarItemInfo.ThumbButtonInfos>
    </TaskbarItemInfo>
</Window.TaskbarItemInfo>

Maintenant, nous allons tout simplement ouvrir un MessageBox lors du click sur le bouton. Pour cela, nous avons l’évènement Click sur ThumbButtonInfo :

image

<ThumbButtonInfo Description="Hello" ImageSource="/about.png" 
  Click="ThumbButtonInfo_Click" />

Voici le résultat lors du click sur le bouton :

image

Nous allons maintenant voir comment indiquer l’avancement d’un traitement long. Quand vous copier un fichier, quand vous installez un logiciel… etc, l’état d’avancement du traitement peut être indiqué dans la barre des tâches.

image

Pour notre exemple, j’utilise un simple ProgressBar avec un Slider pour changer sa valeur facilement. Quand la valeur de la ProgressBar change, celle de la barre des tâches est mis à jour. Il va falloir utiliser les propriétés ProgressState et ProgressValue.

private void progressBar1_ValueChanged(object sender, 
             RoutedPropertyChangedEventArgs<double> e)
{
    double value =  e.NewValue;
    if (value != 0)
    {
        TaskbarItemInfo.ProgressValue = value / 100;
        TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal;
    }
    else
    {
        TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
    }
}

J’ai utilisé l’évènement ValueChanged pour pouvoir changer automatiquement la valeur de la barre des tâches quand la ProgressBar est mis à jour.

Cet article est un aperçu des différentes possibilités que nous offre la nouvelle barre des tâches de Windows Seven. Je vous invite à consulter MSDN pour avoir plus d’informations :).

Vous trouverez ici le projet exemple.

A bientôt !

Problème entre VS2008 et Office 2010 : freeze de l'interface en mode design

Le 7 juin 2010 à 10:10

Si vous travaillez avec Visual Studio 2008 sur un projet web et que vous avez installé Office 2010, vous avez peut-être un problème assez gênant lors du passage du code en mode design : freeze de Visual Studio.

imageLe passage en mode Design ou Fractionner gèle Visual Studio

Pour pouvoir à nouveau utiliser Visual Studio 2008 sans problème, il faut désinstaller le composant “Microsoft Visual Studio Web Authoring Component” :

image

Une fois le composant désinstallé, il faut se munir du DVD de Visual Studio et se rendre dans le dossier “WCU\WebDesignerCore” afin de lancer le setup du composant :

image

Une fois le composant réinstallé, vous ne devriez plus avoir de problème :).

A bientôt !

A propos de l'auteur

Mathieu Perrein - Software Solutions Manager, Software Architect, Trainer MCT, MSP de 2010 à 2012.

 

MSP

 

MSP

MSP

 MSPD

MCT

 

Facebook

 

Ce blog est strictement personnel et les opinions exprimées ici n'engagent donc que moi, et pas mon employeur.

Tags

Vous avez désactivé JavaScript ou bien vous possédez une ancienne version d'Adobe Flash Player. Téléchargez la dernière version de Flash Player.