Archive

Archive for the ‘WPF’ Category

[WPF] Améliorer le chargement d’une collection d’images

Dans certaines applications où on est amené à afficher une collection d’images on peut avoir très vite des problèmes de performance dû au fait que les images ne sont pas dans un format compressé ou sont de grandes tailles et mettent du temps à se charger.

Comment améliorer cela ? La réponse est simple : créer des miniatures pour les images.

Mais il ne s’agit juste pas de redimensionner les contrôles Image autant que possible pour contenir les images voulues. En effet cela n’empêche en rien le chargement total de l’image en mémoire avant que le contrôle Image ne la redimensionne pour respecter la taille demandée.

La meilleure solution est d’utiliser les propriétés DecodePixelWidth et DecodePixelHeight de la classe BitmapImage comme suit :

<image width="100">
  <Image.Source>
    <!-- Utiliser DecodePixelWidth ou DecodePixelHeight. 
         N'utiliser les deux propriétés en même temps que 
         si vous ne voulez pas garder le ration de l'image  -->
    <BitmapImage DecodePixelWidth="100"  
                 UriSource="chemin vers le fichier image" />
  </Image.Source>
</Image>

Avec ces propriétés l’image est décodée en mémoire pour contenir exactement la taille demandée. Dans notre cas la largeur demandée est de 100 pixels et la hauteur est déduite en fonction de la ratio de la taille réelle de l’image. Cette méthode améliore la façon de charger une image et fournit une rapidité qui ne sera pas négligeable si nous avons affaire à une collection d’images 😉 .

Catégories :WPF Étiquettes : , ,

Partie 1 : WCF Data Services – Présentation du protocole OData

Ceci est la première partie d’une suite d’articles à travers lesquels nous essayerons de mettre en place un service de données OData (Open Data) qui sera consommé par différentes applications clientes telles que WPF, Silverlight et Windows Phone que nous mettrons en place.

Avant de s’attaquer aux objectifs nous allons d’abord parler tout au long de cette première partie du protocole Open Data. Les autres parties serviront à mettre en place le service de données et les applications clientes.

Open DATA aussi appelé OData est un protocole qui se base sur l’architecture REST (REpresentational State Transfert) pour faciliter le partage des données avec tout type de client quel que soit la technologie à condition que ce dernier sache manier les requêtes HTTP (POST, GET, PUT et DELETE) et manipuler les données au format XML ordinaire, Atom Pub (Atom Publishing Protocol) ou JSON (JavaScript Object Notation).
Chaque type de requête HTTP est associé joue un rôle précis dans l’architecture REST :

  • GET : permet de récupérer une ressource
  • POST : permet de créer une nouvelle ressosurce
  • PUT : permet de modifier une ressource
  • DELETE : permet de supprimer une ressource

Chaque donnée ou ressource exposée est identifiable à travers une URI (Uniform Resource Identifier) qui suit une convention bien définie et respectant le schéma suivant :

L'image provient du site odata.org

L'image provient du site odata.org

Plusieurs exemples de services de données se basant sur le protocole OData sont listés sur le site odata.org. Prenons l’exemple du service de données Northwind accessible via l’url suivante : http://services.odata.org/Northwind/Northwind.svc/. Cette url représente la partie ServiceRoot de notre service de données exemple. En y accédant nous avons une structure de données XML qui s’affiche. Cette structure représente ce qu’on appelle le Service Document qui représente toutes les collections pouvant être consultées et/ou être modifiable.

Service Document

Dans le service document précédent nous avons la liste des collections exposées par notre service de données. Pour accéder à une collection il suffit d’utiliser l’attribut « href » de l’élément « collection ». Cet attribut représente le chemin relatif pour accéder à la liste des entrées d’une collection. Ainsi pour accéder à la liste de toutes les catégories nous utiliserons l’url suivante : http://services.odata.org/Northwind/Northwind.svc/Categories. Nous avons alors un flux AtomPub représentant des entrées de la collection Categories comme suit :

Les entrées de la collection Category

Les entrées de la collection Category

Chaque élément Entry représente une catégorie et contient les différentes informations se rapportant à la catégorie dans le sous-élément Content.
La ResourcePath peut être complexe comme par exemple pour accéder à la liste des produits appartenant à une catégorie donnée nous utiliserons le chemin relatif Categories(1)/Products (la valeur mise entre parenthèse représente l’identifiant de la catégorie dont on veut afficher la liste des produits) définit dans l’attribut href de l’élément link dans notre précédent schéma.

Avec les chemins de ressources utilisés précédemment nous ne personnalisons pas nos résultats. C’est la partie QueryOptions qui permettra de personnaliser (filtrer, ordonner, choisir le format de données désiré etc.) des données renvoyées par le service. Les différentes options de personnalisations sont listées sur le site http://www.odata.org/developers/protocols/uri-conventions#QueryStringOptions. Nous pouvons en citer quelques unes utilisées :

Un service de données expose aussi des métadonnées permettant de décrire la structure des données du service et de ses ressources permettant ainsi aux bibliothèques clientes de générer un proxy et par la suite faciliter la communication de votre application avec la source de données. Pour accéder à ces métadonnées il faut ajouter $metadata juste après le ServiceRoot. Ainsi dans l’exemple précédent on accédera aux métadonnées à travers l’url suivant : http://services.odata.org/Northwind/Northwind.svc/$metadata.

Vous pouvez mettre en place votre propre service de données OData pour cela vous devez respecter les spécifications définies sur le site www.odata.org. Cette tâche pouvant devenir fastidieuse alors plusieurs API serveurs et clients référencés sur le site du protocole ODATA permettent de créer et consommer des données exposées via ce protocole avec une facilité non négligeable.

Pour ce qui est de la technologie .Net, nous avons :

  • L’API serveur : pour mettre en place rapidement un service de données.
  • Les APIs clients : pour permettre à notre application .Net de communiquer avec notre service de données. Nous avons plusieurs APIs clients dépendants de l’application qui va consommer le service. Il existe une bibliothèque cliente WCF pour la ou les groupes de technologies .Net :
    • Silverlight
    • Windows Phone : bien qu’on développe avec du Silverlight pour cette plateforme une bibliothèque cliente à part entière lui est dédiée
    • Pour le reste : WPF, Windows Forms, ASP.Net etc.….

Voilà terminée la première partie qui explique comment fonctionne, sans entrer trop dans les détails, le protocole OData. Pour plus d’informations vous pouvez vous connecter sur le site odata.org qui constitue une mine d’informations sur ce protocole.

Dans la deuxième partie nous allons voir comment mettre en place notre service de données OData en utilisant la bibliothèque serveur WCF Data Services du framework .Net.

Renvoyer une nouvelle instance d’une ressource à chaque appel

WPF nous offre plusieurs manières de définir un objet en tant ressource dans notre application, en utilisant :

  • notre fichier App.xaml,
  • un dictionnaire de ressource
  • un style
  • la propriété Resources de notre fenêtre, contrôles intégrés à WPF et contrôles personnalisés (User Control et Custom Control)

La liste ci-dessus n’est pas exhaustive.
Dans notre exemple nous allons définir notre ressource au niveau de notre fenêtre comme suit :

<Window x:Class="WpfApplicationSharedResource.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <ObjectDataProvider x:Key="Random"
                            MethodName="Next"
                            ObjectType="{x:Type sys:Random}">
            <ObjectDataProvider.MethodParameters>
                <sys:Int32>100</sys:Int32>
            </ObjectDataProvider.MethodParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <UniformGrid Rows="1" Columns="2">
        <TextBlock Text="{Binding Path=., Source={StaticResource ResourceKey=Random}}" Background="AliceBlue"
                   FontSize="20"
                   VerticalAlignment="Stretch"
                   HorizontalAlignment="Stretch"
                   TextAlignment="Center" />
        <TextBlock Text="{Binding Path=., Source={StaticResource ResourceKey=Random}}" Background="Cyan"
                   FontSize="20"
                   VerticalAlignment="Stretch"
                   HorizontalAlignment="Stretch"
                   TextAlignment="Center"/>
    </UniformGrid>
</Window>

Dans le code XAML ci-dessus notre ressource utilise un objet de type ObjectDataProvider qui nous permet d’instancier la classe Random et d’appeler sa méthode Next en lui fournissant un entier comme paramètre. Ensuite nous définissons deux objets de type TextBlock qui récupèrent chacun la valeur renvoyer par notre ressource et l’affiche à l’écran.

On sait que la méthode Next de la classe Random est censée renvoyée une valeur aléatoire à chaque appel donc à l’exécution de notre exemple on peut s’attendre à ce que nos deux TextBlock aient des valeurs différentes. Mais si vous l’exécutez, vous verrez bien que ce n’est pas le cas. A chaque exécution les deux TextBlock afficheront la même valeur comme dans l’image ci-dessous :

Comment cela fonctionne ?
Les objets définis en ressource sont évalués lorsque nous essayons d’y accéder pour la première fois. A l’issue de ce premier accès le résultat est stocké en mémoire. Lorsque nous essayons d’y accéder à nouveau, ce sera le résultat stocké en mémoire qui nous sera retourné.
Donc dans notre exemple, l’accès à la ressource par notre premier TextBlock entraîne l’instanciation de la classe Random et l’appel de la méthode Next qui nous renvoie une valeur et c’est cette dernière qui est stockée en mémoire. Notre deuxième TextBlock essayant d’y accéder n’entraînera pas la réévaluation de notre ressource mais recevra tout simplement la valeur stockée en mémoire.

Comment faire pour que notre ressource nous renvoie une nouvelle instance à chaque nouvel accès ?
Pour que notre ressource nous renvoie une nouvelle instance à chaque accès, il va falloir utiliser l’attribut x:Shared et mettre sa valeur à false. Cet attribut permet d’avoir une instance pour chaque demande au lieu de partager la même instance pour toutes les demandes. Inutile de vous dire qu’en mettant la valeur de cet attribut à true cela entraînera le même comportement que l’exemple définit précédemment.

Dans l’exemple précédent, si nous modifions la définition de notre ressource en y ajoutant cet attribut et mettre sa valeur à false , nous verrons que les deux TextBlock auront des valeurs différentes.

N.B : L’attribut Shared n’est pas supporté en Silverlight.

Catégories :Silverlight, WPF Étiquettes : , ,

Faire du MutliBinding sans utiliser la classe MutliValueConverter, c’est possible !

J’ai longtemps cru qu’il était impossible de faire du MultiBinding sans disposer au préalable d’un converter c’est à dire d’une instance d’une classe dérivant de la classe de base MultiValueConverter.
Il n’y a qu’à voir cette discussion où je mettais à défi les membres du forum pour me sortir un exemple de cas d’utilisation où l’instance d’un MutliValueConverter ne serait pas indispensable.
Le fait que j’ai lancé ce défi était dû à deux choses :

  1. La première est liée au fonctionnement même du MultiBinding. Ce dernier recevant plusieurs bindings en paramètre alors il doit savoir comment les afficher donc on avait besoin d’une classe dérivant de la classe de base MutliValueConverter pour combiner ces différents bindings et fournir un résultat final.
  2. Quand on oublie de spécifier un converter dans le MutliBinding, nous remarquons le message « Impossible de définir MutliBinding parce que MutliValueConverter doit être spécifié » nous est renvoyé pas Visual Studio.

Bon ! Après des recherches sur le net, j’en suis arrivé à la conclusion suivante : il est bien possible de faire du MultiBinding sans utiliser une classe dérivant de la classe MutliValueConverter. Il suffit d’utiliser la propriété StringFormat de l’instancce de notre MultiBinding..

Comme on le dit souvent du code vaut mieux qu’un long discours alors voici un exemple de cas d’utilisation très simple :

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
 
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
 
        <ComboBox x:Name="cb1" Grid.Column="0" Margin="2">
            <ListBoxItem Content="Item 0 - 1" />
            <ListBoxItem Content="Item 0 - 2" />
            <ListBoxItem Content="Item 0 - 3" />
            <ListBoxItem Content="Item 0 - 4" />
            <ListBoxItem Content="Item 0 - 5" />
        </ComboBox>
 
        <ComboBox x:Name="cb2" Grid.Column="1" Margin="2">
            <ListBoxItem Content="Item 1 - 1" />
            <ListBoxItem Content="Item 1 - 2" />
            <ListBoxItem Content="Item 1 - 3" />
            <ListBoxItem Content="Item 1 - 4" />
            <ListBoxItem Content="Item 1 - 5" />
        </ComboBox>
 
        <TextBlock Grid.ColumnSpan="2" Grid.Row="1">
            <TextBlock.Text>
                <MultiBinding StringFormat="cbOne : {0} ### cbTwo : {1}" FallbackValue="Deux items doivent être sélectionnés">
                    <Binding Path="SelectedItem.Content" ElementName="cb1" />
                    <Binding Path="SelectedItem.Content" ElementName="cb2" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Grid>

La propriété StringFormat du MultiBinding fonctionne comme la méthode statique Format de la classe String. Cette propriéte reçoit en paramètre le format à utiliser et les paramètres qui lui sont passés sont les valeurs des différents bindings

Avec le code XAML ci-dessus, vous verrez que Visual Studio devient muet et nous n’affiche plus le message d’erreur précédent. Je pense d’ailleurs que ce message devait plutôt être « Impossible de définir MutliBinding parce que MutliValueConverter ou StringFormat doit être spécifié ».

Catégories :WPF Étiquettes : , ,

Lancer son application au démarrage de Windows sans bidouiller avec la base de registre

Dans ce billet je vais vous présenter deux méthodes permettant de configurer son application .Net pour qu’elle soit lancée au démarrage de Windows. Ces deux méthodes nous évitent de toucher à la base de registre et ainsi nous n’allons pas oublier de nettoyer cette base s’il arrivait que l’utilisateur désinstalle notre application.

La première méthode :
Dans le projet d’installation (projet de déploiement avec Windows Installer) suivre les étapes suivantes :

  1. Dans le File System (Système de fichiers) du projet on ajoute un dossier spécial appelé User’s Startup
  2. Folder (Dossier de démarrage de l’utilisateur)
  3. On crée un raccourci de la sortie de projet qui se trouve dans le dossier Application Folder (Dossier de l’application).
  4. On renomme ce raccourci pour lui donner un nom plus explicite.
  5. On coupe (CRTL+X) le raccourci qu’on vient de créer et on le colle (CRTL+V) dans le dossier User’s Startup Folder

Deuxième méthode :
Cette solution consiste à rendre configurable le démarrage automatique à partir du code de l’application. Pour cela j’ai créé deux fonctions : une pour l’activation et l’autre pour la désactivation.
N.B. : Pour que le code fonctionne, il faudra ajouter la référence à l’assembly COM Windows Script Host Object Model dans votre projet.
La fonction d’activation est la suivante :

// Cette méthode vérifie l'existence du raccourci
// Si le raccourci est déjà présent alors on ne fait rien
public void EnbaleApplicationStartup()
{
	string shortcutPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "MonRaccourci.lnk");
	if (System.IO.File.Exists(shortcutPath)) return;
 
	WshShell wshShell = new WshShellClass();
 
	IWshShortcut shortcut = (IWshShortcut)wshShell.CreateShortcut(shortcutPath);
 
	shortcut.TargetPath = Assembly.GetEntryAssembly().Location;
 
	shortcut.Description = "Mon premier raccourci";
 
	shortcut.Save();
}

La fonction de désactivation :

public void DisableApplicationStartup()
{
	string shortcutPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "MonRaccourci.lnk");
	if (!System.IO.File.Exists(shortcutPath)) return;
 
	System.IO.File.Delete(shortcutPath);
}
Catégories :WPF Étiquettes :

Mettre en place un SplashScreen sous WPF

Lorsqu’on développe son application on remarque souvent que cette dernière prend pas mal de temps lors du démarrage. Pour pallier à ce problème de lenteur lors du démarrage à froid beaucoup d’applications intègrent un splash screen (page de garde ou écran de démarrage). Le Splash screen affiche dans la majeure partie des applications juste une image à l’utilisateur le temps que la première fenêtre de l’application soit totalement chargée. une fois la première fenêtre chargée, le splash screen disparaît en fondu. Pour exemple on peut prendre le cas de Visual Studio 2010 qui affiche l’image ci-dessous comme splah screen :

Pour mettre en place un splash screen pour votre application, suivre les étapes ci-dessous :

  1. Ajouter une image existante (png, jpg, gif etc) à votre projet Visual Studio
  2. Dans les propriétés du fichier mettre la propriété « Action de génération » (Build Action) à la valeur SplashScreen
  3. Facile à mettre place avec 0 ligne de code. 😉

Catégories :WPF Étiquettes :

Installer votre application sans chercher les dépendances sur Internet

Lorsqu’on crée un projet d’installation (Setup) avec Windows Installer pour une application .Net, notre application aura besoin d’un ou plusieurs outils prérequis sur la machine cible. Ces outils sont indispensables pour que l’application puisse être installée ou pour qu’elle fonctionne correctement.

Parmi ces outils indispensables nous avons au minimum le SDK du Framework .NET qui est détecté et ajouté automatiquement lors de l’ajout d’une sortie de projet type .Net dans le projet d’installation.

Si nous regardons les propriétés définies pour cette dépendance (double-cliquez sur la dépendance pour afficher ses propriétés) nous pouvons voir que la propriété InstallUrl est configurée pour aller télécharger le SDK via une adresse URL définie.

Donc si la machine cible ne dispose pas d’internet alors impossible d’obtenir le prérequis et par conséquent l’installation ne pourra pas se poursuivre.

Deux solutions sont disponibles permettant d’éviter l’échec de l’installation du au fait qu’une ou plusieurs dépendances sont manquantes.

La première solution consiste à télécharger soi-même le SDK du Framework .Net sur une machine disposant d’internet et l’installer au final sur la machine cible. Cette solution peut devenir lassante si le nombre de machines cibles est conséquent.

La deuxième solution qui est la plus reposante, même si nous avons plusieurs machines cibles ne disposant pas d’internet, consiste juste à suivre les étapes ci-dessous :

  1. On télécharge la version du SDK du framework .net requise pour l’application
  2. On mettra ce fichier à la racine de notre setup (à la racine du dossier qui contient le msi et le setup.exe)
  3. Dans les proprietés de la dépendance du SDK, on modifie la propriété InstallUrl et on met le chemin relatif par rapport à notre Setup.

Avec cette deuxième solution, la connexion à Internet n’est pas requise pour télécharger le SDK. De plus l’installation manuelle du prérequis se fera automatiquement à partir du chemin relatif que nous avons spécifié.

Catégories :WPF Étiquettes :
%d blogueurs aiment cette page :