Archives de Catégorie: .NET

L’API Streaming de Twitter avec le HttpClient .net

La recherche et le streaming

Un des fonctionnalités intéressantes de l’API Twitter est l’utilisation du Streaming. Tout comme la recherche, cette API nous permet de spécifier un mot clé et de récupérer les tweets liés à ce mot clé. Cet article a pour but de vous montrer la différence entre la recherche et le streaming sur l’API Twitter ainsi que l’implémentation de ce streaming en C#.

La différence entre l’API de recherche et l’API de streaming réside en la manière de récupérer les tweets. Avec la première, vous envoyez une requête au serveur Twitter qui vous retourne alors page par page les tweets correspondant à la recherche dans une seule et même réponse. Le fonctionnement est celui d’un échange client-serveur classique :


(crédits image : https://dev.twitter.com )

Principe du streaming

Concernant le streaming, les tweets retournés ne vont pas l’être dans une seule et même réponse. En effet le serveur maintiendra la connexion indéfiniment et dès qu’un nouveau tweet concerné par la recherche faite va être soumis à Twitter, celui ci va nous être retourné dans la foulée. Ce cas d’utilisation est notamment intéressant dans des applications de type “live tweet” :

(crédits image : https://dev.twitter.com )

Implémentation en C#

L’url de requête sera https://stream.twitter.com/1.1/statuses/filter.json en POST. A cela s’ajoute un ou plusieurs paramètres. Pour une utilisation de base, nous ajouterons le paramètre “track” qui aura comme valeur votre recherche (un hashtag ou tout simplement un mot ).

Cette requête ne semble pas compliquée, mais l’extrait de code suivant, qui fonctionne dans le cas d’une simple recherche, va ici échouer. Le souci ne vient pas directement de ce code mais de la librairie OAuth que j’avais réalisée suite à un de mes précédents articles (code source disponible en une seule classe ici : https://skydrive.live.com/?cid=230FED47B214039C&id=230FED47B214039C%212851 )

static async void ComputeStreaming(string search)
{
    OAuthRequester.ComputeHmacSha1 = ComputeHMACSHA1Hash;
    string consumerKey = "yourConsumerKey";
    string consumerSecret = "yourConsumerSecret";
    string oauthToken = "yourOAuthToken";
    string oAuthTokenSecret = "yourTokenSecret";
    string searchUrl = 
"https://stream.twitter.com/1.1/statuses/filter.json"; Dictionary<string, string> contentParams = new Dictionary<string, string>(); contentParams.Add("track", search); var searchRequest = OAuthRequester.SignRequest(HttpMethod.Post,
searchUrl, consumerKey, consumerSecret, contentParams,
oauthToken, oAuthTokenSecret);
using (StreamReader reader = new StreamReader(
await (await searchRequest).Content.ReadAsStreamAsync())) { while (!reader.EndOfStream) { string result = reader.ReadLine(); JObject root = JObject.Parse(result); Console.WriteLine(root["text"].ToString()); } } }

Il n’y pas forcément d’erreur de conception dans ma librairie, l’erreur serait similaire avec l’utilisation de “WebClient” ou “WebHttpRequest”. De la même manière, la requête sera lancée, mais rien ne sera renvoyé et au bout d’une minute, vous recevrez un timeout. Le souci provient de la manière dont HttpClient traite la réponse par défaut, le client a bien commencé à recevoir la réponse, mais il attend d’avoir reçu tous le contenu avant de renvoyer votre objet “HttpResponseMessage”. Or, le principe du streaming est de laisser la connexion ouverte et de recevoir le contenu au fur et à mesure, donc HttpClient ne peut pas gérer le cas de figure de cette manière. Mais, heureusement, il suffit d’une petite modification pour gérer le cas particulier des réponses sous forme de flux et non de sous forme de contenu entier. Il suffit de modifier l’appel de la méthode “client.SendAsync(…)”, dans notre cas, nous utiliserons la surcharge suivante:

HttpResponseMessage response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead);

La différence réside en l’ajout du paramètre de type “HttpCompletionOption” qui, s’il est omis, prend par défaut la valeur “ResponseContentRead” qui signifie que HttpClient attend de recevoir tout le contenu de la réponse avant de la traiter. La valeur qui nous intéresse ici est “ResponseHeaderRead” qui signifie que la réponse sera traitée dès que les header seront reçus. Le contenu en lui même sera reçu plus tard, lors de l’appel de “ReadAsStreamAsync()”. Avec la modification de la requête, vous pouvez à présent tester le code un peu plus haut qui fonctionnera cette fois-ci (testez avec un sujet populaire du moment pour voir le streaming agir ).

Conclusion

Parfois complexe à utiliser dans certains SDK, l’API streaming de Twitter consiste en une simple requête en POST. La seule petite difficulté est de ne pas attendre de recevoir tous le contenu pour traiter la requête comme le font par défaut les clients HTTP en .net mais de la traiter dès réception des headers. HttpClient permet ce traitement particulier avec la modification du paramètre HttpCompletionOption.
La mise à jour de la classe helper OAuth 1.0 est disponible ici.

Par Mathieu Hollebecq

Migrer le LongListSelector vers Windows Phone 8

Le LongListSelector

Lors de la sortie du SDK Windows Phone 7 en 2010 certains contrôles natifs ont fait leur apparition comme l’application bar, d’autres managés hérités de Silverlight comme la ListBox. Ce dernier nous permettait d’afficher des éléments sous forme d’une liste déroulante. Mais la ListBox a été vite reconnue comme souffrant de problèmes de performances.

C’est en partie pour ces soucis de performances que le Silverlight Toolkit pour Windows Phone a intégré le contrôle LongListSelector qui nous permettait de même de faire un groupement d’éléments où la navigation d’un groupe à l’autre se faisait par l’intérmédiaire d’une “JumpList”.

Avec la sortie du SDK Windows Phone 8, celui ici intègre dorénavant directement le contrôle LongListSelector qui est à présent un contrôle natif, comme l’AppBar. Simultanément avec son arrivée  dans le SDK, il a été retiré du toolkit. Cet article a pour but de vous guider pour migrer vos projets Windows Phone 7 avec LongListSelector du toolkit vers un projet Windows Phone 8 avec LongListSelector du SDK.

Tout d’abord, les namespaces

Côté C#, aucun changement, le LongListSelector est toujours présent dans le namespace Microsoft.Phone.Controls mais si vous tentez de générer votre projet directement après le passage de votre projet en Windows Phone 8 sans mettre à jour le toolkit, le projet vous signalera un conflit de nommage. En effet, le LongListSelector sera présent deux fois dans le même namespace mais dans deux assemblies différentes. Commencez tout d’abord pour mettre à jour le tookit, vous pouvez le télécharger sur codeplex à cette adresse ou bien l’intégrer directement depuis NuGet.

Dans votre xaml, modifiez simplement le préfixe de namespace de vos LongListSelector pour passer de celui du toolkit (par défaut “toolkit:” à celui du sdk (par défaut “phone:”).

Templates et Styles

Globalement, les propriétés de styles et templates restent inchangées. La seule exception est l’ancienne propriété de template “GroupItemTemplate” qui n’existe plus et qui a été remplacée par une propriété Style “JumpListStyle” dont vous devez modifier les propriétés LayoutMode et  GridCellSize. En gros votre code passera de ceci :

<toolkit:LongListSelector.GroupItemTemplate>
   <DataTemplate>
      <Border Width="40" Height="40">
         <TextBlock Text="{Binding Key}" />
      </Border>
   </DataTemplate>
</toolkit:LongListSelector.GroupItemTemplate>

A ceci :

<phone:LongListSelector.JumpListStyle>
   <Style TargetType="phone:LongListSelector">
      <Setter Property="GridCellSize"  Value="113,113"/>
      <Setter Property="LayoutMode" Value="Grid" />
      <Setter Property="ItemTemplate">
         <Setter.Value>
            <DataTemplate>
               <Border Width="40" Height="40">
                  <TextBlock Text="{Binding Key}" />
               </Border>
            </DataTemplate>
         </Setter.Value>
      </Setter>
   </Style>
</phone:LongListSelector.JumpListStyle>

Activer le groupement

Pour utiliser le groupement dans vos LongListSelector, deux propriétés doivent être renseignées : LayoutMode et IsGroupingEnabled. La première indique si les éléments sont placés sous forme de liste dans une seule dimension ou sous forme de grille (un peu comme le controle GridView sous Windows 8). La seconde doit être obligatoirement mise à “true” pour pouvoir grouper vos éléments.

Vous devrez OBLIGATOIREMENT renseigner la propriété JumpListStyle, sous peine de faire crasher votre page (notamment lié au fait de ne pas avoir renseigné la propriété GridCellSize).

Vous pouvez, si vous le désirez, mettre la valeur “true” à la propriété “HideEmptyGroups” pour ne pas afficher les groupes qui n’ont aucun sous-élément.

Modèle supportant le groupement

Avec le LongListSelector du Toolkit, le contrôle supportait d’assembler les éléments d’un groupe dans une collection héritant directement de IEnumerable<T>, ainsi nous utilisions directement le résultat de l’appel de la methode GroupBy(…). Avec la nouvelle version du contrôle, les éléments d’un sous-groupe doivent être dans une collection héritant de IList<T>, ceci empêche d’utiliser directement le résultat de l’appel de GroupBy mais il nous suffit de passer par une petite classe générique faisant l’intermédiaire telle que celle-ci:

public class Group<Tkey, TElement> : List<TElement>
{
    public Tkey Key { get; set; }
    public Group(IGrouping<Tkey, TElement> group)
        : base(group)
    {
        this.Key = group.Key;
    }
}

Il vous suffira ensuite de suivre votre GroupBy par un Select qui vous retourne des éléments “Group” et le tour est joué pour votre liaison de données Sourire

Conclusion

Même si Visual Studio s’occupe de migrer votre projet Windows Phone 7.1 vers Windows Phone 8, certaines modification manuelles restent à faire, notamment lorsque des librairies externes au SDK sont incluses, c’est le cas pour le Windows Phone Toolkit qui n’implémente plus le LongListSelector qui est maintenant implémenté directement dans le SDK. Mais des choix de développement différents ont été fait et des petites modifications mineures restent à faire. Néanmoins nous pouvons profiter maintenant d’un LongListSelector directement présent dans le SDK et de manière native, ce qui apporte des performance améliorées.

Par Mathieu Hollebecq

Pimp ma listbox

On va découvrir ici comment éditer et transformer une ListBox. L’exemple sera fait sur du Windows Phone, mais pourra être facilement répliqué en WPF, Silverlight, … En Xaml quoi !

Je vais rapidement rappeler les bases d’une ListBox et nous passerons ensuite sur quelques exemples concrets (ListBox horizontale, ListBox wrappée).

Pour cet article, j’ai utilisé Expression Blend, qui permet d’éditer les styles rapidement et facilement :
Un clic droit sur la ListBox, et dans le menu additionnal template vous pourrez éditer facilement les éléments que je signale.

Structure d’une ListBox

Comme son nom l’indique, une ListBox sert à afficher une liste d’éléments, une collection d’objets. En plus de son template, elle est constituée de trois autres templates qui permettent de la personnaliser plus précisément. C’est à eux que l’on va s’intéresser.

Illustrons ces différents templates, avec un exemple simple : une boîte de bonbons :

  • ItemTemplate : La forme sous laquelle vont s’afficher les objets contenus dans la collection. Le bonbon brut.
  • ItemContainerStyle : Le conteneur de l’item. le papier autour du bonbon. C’est sur lui qu’on gèrera l’espacement entre chaque item.
  • ItemsPanel : Le conteneur général. La boîte.

ListBox Horizontale

En partant de cette base, comment faire une ListBox Horizontale ?

  1. On édite l’ItemsPanel, on veut un conteneur horizontal. On passe donc la propriété Orientation du StackPanel sur “Horizontal”;
  2. On revient à notre ListBox, on édite les propriétés liées au Layout, on passe HorizontalScrollBarVisibility sur “Auto” ou “Visible” et VerticalScrollBarVisibility sur “Disabled” (Si vous êtes sur Blend, et que vous ne trouvez pas ces propriétés, cliquez sur la petite flèche en bas de Layout);
  3. On édite l’ItemContainerStyle en mettant une marge à droite sur notre bordure (12px est le nombre magique en général) pour pas que les éléments soit collés;
  4. Il ne vous reste plus qu’à éditer l’ItemTemplate en quelques clics en fonction de vos goûts.

Et nous avons une belle ListBox horizontale !

ListBox Wrapped

Pour rappel, le wrap est le système qui permet à des éléments de revenir à la ligne automatiquement et de s’adapter à différentes tailles d’écrans :

image

(oui ce sont des carottes wrappées)

Donc si on a bien suivi, qu’a-t-on à faire ?

  1. Modifier l’ItemsPanel, oui ! On supprime le StackPanel qu’on remplace par le WrapPanel disponible dans le Silverlight Toolkit For Windows Phone (disponible via Nuggets);
  2. Régler l’HorizontalScrollBarVisibility et la VericalScrollBarVisibility en fonction du sens dans lequel vous voulez faire aller vos éléments (verticaux ou horizontaux);
  3. Editer l’ItemContainerStyle avec une marge en bas et à droite pour “décoller” les éléments entre eux;
  4. Et éditer l’ItemTemplate en fonction de vos goûts !

Conclusion

On a donc vu rapidement la structure d’une ListBox et comment faire facilement différents types de listes.

Par JC VASSELON

“DevFC.exe has stopped working”…

Récemment, lors d’un développement sur Windows Azure, lorsque je lançais mon rôle en mode debug, j’avais le droit systématiquement au message d’erreur suivant :

image

DevFC.exe étant le Compute Emulator des outils Windows Azure, il m’était  impossible d’exécuter mon rôle sur l’émulateur Windows Azure et donc de tester… L’erreur étant vraiment gênante et bloquante, je n’ai pas pu “abandonner” sans chercher plus loin, surtout que DevFC.exe fonctionnait correctement il y a quelque temps.

Tout d’abord, j’ai fait un petit tour dans les fichiers de log, se trouvant dans C:\Users\username\AppData\Local\dftmp\DevFCLogs. Après une petite analyse, je vois l’erreur suivante:

“ Error, 201523225, Exception occurred when trying to open service host. {0}, {1}`There is already a listener on IP endpoint 127.0.0.1:12001.  Make sure that you are not trying to use this endpoint multiple times in your application and that there are no other applications listening on this endpoint”

Clairement, cela signifie que le port 12001 que veut utiliser DevFC.exe est déjà en cours d’utilisation, je suis donc allé dans le Resource Monitor (perfmon.exe, puis “Open Resource Monitor”), dans la partie “Network”, nous pouvons voir les “listening ports”, après une recherche rapide je me suis rendu compte que le port 12001 est utilisé par “vmware-hostd.exe”. Ce processus est en fait l’outil d’accès à distance pour la gestion des machines virtuelles sous vmware 8, le port 12001 n’étant pas utilisé par la version 7 de VMware Worksation Il ne me restais plus qu’à aller dans les services (services.msc), de trouver “VMware Workstation Server” et d’arrêter ce service. Ceci étant fait, j’ai pu de nouveau debugger de manière tout à fait normale mon rôle dans l’émulateur Windows Azure Sourire

Par Mathieu Hollebecq

Programmation en Small Basic (partie 1)

Qu’est ce que Small Basic ?

Small Basic est un langage de programmation exploitant le framework .net 3.5. Il a été annoncé à la PDC (professional Developer Conference) 2008. Que nous apportait ce nouveau langage? Pour nous développeurs, rien du tout Sourire . Celui-ci se destine tout d’abord aux personnes peu ou pas initiées au développement.

La syntaxe, proche du VB.net, est assez simple, les mot-clés sont peu nombreux (donc rapidement mémorisés). L’environnement de développement est visuellement agréable à regarder avec ses couleurs et ses grosses icones. Mais surtout, il reste simple à utiliser, seules les fonctions essentielles ont été gardées. Quant au code en lui même, dès les premières lignes on peut vraiment “réaliser” quelque chose.

Ce langage est donc un langage pédagogique, très adapté pour un enfant niveau primaire ou collège voulant s’initier à la programmation. Mais il peut très bien aussi se destiner à des adultes étrangers au monde du développement et souhaitant acquérir une logique algorithmique.

Prise en main de Small Basic

Pour le télécharger et l’installer, rendez-vous simplement sur le site de small basic : http://smallbasic.com/. L’installation consiste en un assistant tout ce qu’il y a de plus classique. Le seul point sur lequel nous pouvons prêter attention est la sélection des différentes langues d’aide que vous voulez installer. Une fois la courte installation terminée, vous vous trouvez face à un environnement de développement très simplifié dont le menu n’est pas sans nous rappeler le ruban d’office.

image

Comme vous le voyez, nous n’avons pas d’explorateur de solution car notre “programme” ne peut être construit qu’à partir d’un seul fichier source, pas de fenêtre propriété, pas de fenêtre de débogage, tout a été fait pour intégrer seulement le strict minimum afin de ne pas perdre un utilisateur néophyte.

Le code de Small Basic

Quant à la syntaxe, nous nous rapprochons du VB.net avec quelques particularités :

image

Chaque instruction se termine par un retour à la ligne, comme en VB.net, sauf qu’ici la notion de “type” n’existe pas vraiment. En VB.net, nous aurions du déclarer “name” en tant que “string” et lui assigner la valeur “3” aurait été impossible. Sur le code précédent, aucune erreur n’est relevée et tout s’exécute sans problème. L’image suivante représente le résultat du code donné. Etant donné que nous assignons la valeur 3 à “name”, le programme écrira bien sur à chaque fois “Hello 3”.

image

Cet article étant une simple introduction, je ne détaillerais pas plus la syntaxe ici, néanmoins si vous arrivez à “bidouiller” et à produire un code qui vous convient, vous pouvez facilement le partager avec vos amis ou collègues grâce au bouton “Publier”. A ce moment là, votre code sera uploadé sur le site de Small basic et un lien vous sera fourni, celui-ci vous permettra d’afficher le résultat dans un navigateur grâce à Silverlight, suivi du code nécessaire pour réaliser votre “programme”. Avec Small Basic, pas de secret sur le code, chaque application publiée sera associée à son code. Si vous voulez “tester” mon code, naviguez sur http://smallbasic.com/program/?JPK986.

image

Sur la partie gauche, votre programme a été converti en Silverlight et peu ainsi être exécuté par n’importe quel utilisateur, à droite le code html pour publier votre création sur votre site web et en bas le code source.

N’oubliez pas de fouiller le site pour trouver des exemples de codes et d’applications bien réalisés, notamment un tetris et un simulateur de collisions.

Dans la prochaine partie, je vous donnerais un exemple de ce qu’on peut réaliser grâce à ce petit framework et ce langage facile à apprendre.

Par : Mathieu Hollebecq

Globalisation d’une application Windows Phone 7

La globalisation d’une application WP7 peut être un procédé laborieux. Nous allons voir dans cet article comment mettre en place l’affichage des textes en fonction de la langue de l’utilisateur.
Cet article est en grande partie un résumé illustré de la page MSDN (en anglais) suivante  : http://msdn.microsoft.com/en-us/library/ff637520%28v=VS.92%29.aspx.

Commencez par ajouter un dossier Resources à votre application qui contiendra un premier fichier Resources.resx qui sera le fichier par défaut.

Resources

Les fichiers *.resx contiennent toutes les chaînes de caractères de votre application. C’est lui qui permettra de stocker les différentes langues. Un fichier .resx correspond à une langue.
Ouvrez le fichier Resources.resx :
ResourcesOpened

La première colonne Name représente la clef de la valeur, c’est elle que vous allez binder aux propriétés qui nécessitent un texte traduit en fonction de l’utilisateurs. Elle doit être unique.
La valeur, c’est le texte qu’elle doit afficher.
Le commentaire vous permettra de savoir à quoi ça correspond.
Par exemple : Title | Titre | Titre de mon application

Pensez à mettre l’Access Modifier sur la valeur “Public” (menu déroulant entouré en bleu en haut à droite).

Ajoutez maintenant un fichier resx pour chaque langue. Votre fichier devra maintenant porter la syntaxe suivante [NomDeVotrePremierFichier].culture-language.resx
Dans notre cas, pour notre fichier Resource en français : Resources.fr-FR.resx. La liste des cultures est disponible ici : http://msdn.microsoft.com/en-us/library/ee825488%28v=cs.20%29.aspx
Bien sûr, pas la peine d’ajouter toutes les cultures. Si la langue de l’utilisateur n’est pas prévue, l’application sera dans la langue par défaut.

Faites maintenant un clic droit sur le nom de votre projet et rendez-vous dans les propriétés. Dans l’onglet Application, cliquez sur “Assembly Information…”.
En bas de la nouvelle fenêtre, dans la propriété “Neutral Language” sélectionnez la langue que traduit votre premier fichier Resources.resx. Dans notre cas “English”.
Assembly Options

Validez ces modifications et faites à nouveau un clic droit sur le nom de votre projet puis “Open folder in Windows Explorer”. Ouvrez le fichier .csproj dans un éditeur de texte puis ajoutez les différentes langues supportées entre les balises <SupportedCultures></SupportedCultures>. Dans notre cas : <SupportedCultures>en-EN;fr-FR;</SupportedCultures>

Le processus est presque terminé, il ne vous reste plus qu’à ajouter une classe Strings.cs dans le dossier des Resources qui permettra de binder les différentes valeurs à vos champs de texte :

public class Strings
{
public Strings()
{   }
private static Resources localizedresources = new Resources();
public Resources Localizedresources {
        get { return localizedresources; }
    }
}

Le nom de la classe Resources correspond bien sûr au nom de votre premier fichier Resources.

Ajoutons maintenant cette classe string dans notre feuille de style, ou dans les ressources de votre App.xaml :
<Resources:Strings x:Key= »Strings » />

Félicitation, la globalisation de votre application est terminée !
Vous pouvez binder vos chaînes de caractères globalisées de la manière suivante <TextBlock Text= »{Binding Localizedresources.Title, Source={StaticResource Strings}} »/>

Si vous souhaitez aller plus loin et globaliser vos converters, ça se passe par ici : http://blogs.dotnet-france.com/gregoryc/post/Net-e28093-Globalisation-de-la-valeur-de-ConverterParameter.aspx

Par JC VASSELON

L’impression multi-page en WPF

Vous avez déjà très certainement eu à imprimer des documents grâce à WPF. Cela se résumant souvent à un PrintDialog et à l’appel sa méthode PrintVisual. Mais cette dernière ne permet qu’une impression sur une seule et même page, si le contenu est trop grand, celui-ci risque d’être tronqué lors de l’impression.

Nous pourrions alors diviser par nous-même le contenu en plusieurs groupes de contrôles et appeler la méthode PrintVisual pour chaque “groupe”. Comme exemple, j’ai pris une ListView contenant 3000 éléments, et sur chaque page, je peux en contenir 45, imaginez que vous deviez imprimer les 67 pages par 67 appels de la méthode PrintVisual et donc autant de tâches d’impressions envoyées à l’imprimante. Ne serais-ce que pour son aspect bricolage, cette méthode est à proscrire.

Heureusement, il nous est possible d’imprimer sur plusieurs pages de manière plus élégante, nous utiliserons toujours une instance de PrintDialog, mais cette fois-ci nous appellerons la méthode PrintDocument, celle-ci a de même deux paramètres, une chaine de caractère représentant la description de l’impression et un objet de type DocumentPaginator. Si nous regardons la documentation sur le MSDN, nous voyons que cette classe est abstraite, nous allons donc créer une nouvelle classe dans notre projet que nous ferrons hériter de DocumentPaginator

public class OneFor4Paginator : DocumentPaginator
{
    public override DocumentPage GetPage(int pageNumber)
    {
        throw new NotImplementedException();
    }
    public override bool IsPageCountValid
    {
        get { throw new NotImplementedException(); }
    }
    public override int PageCount
    {
        get { throw new NotImplementedException(); }
    }
    public override Size PageSize
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            throw new NotImplementedException();
        }
    }
    public override IDocumentPaginatorSource Source
    {
        get { throw new NotImplementedException(); }
    }
}

Nous verrons l’implémentation détaillée des méthodes et propriétés un plus loin, mais ce qui va surtout nous intéresser ici est la propriété PageCount et la méthode GetPage(…). Comme leurs nom l’indiquent, la propriété PageCount doit retourner le nombre de pages à imprimer et la méthode GetPage retournera un objet de type DocumentPage qui représentera physiquement une page imprimée. Un petit coup d’œil sur la documentation MSDN nous montre que le constructeur de DocumentPage prend en paramètre un Visual. En regardant le diagramme de classe, on voit que UIElement hérite de Visual,  nous allons donc ici simplement utiliser un UserControl pour représenter le visuel de notre DocumentPage, celui ci contiendra uniquement une GridView avec deux colonnes dans notre cas, l’extrait de code suivant montre le conteneur parent de notre UserControl.

<Grid>
   <ListView Margin= »50″ x:Name= »liste » Grid.Row= »1″ ItemsSource= »{Binding Path=ListeItem, ElementName=Page} » ScrollViewer.VerticalScrollBarVisibility= »Hidden »>
      <ListView.View>
         <GridView>
            <GridViewColumn Header= »Id » DisplayMemberBinding= »{Binding Id} » Width= »50″ />
            <GridViewColumn Header= »Nom » DisplayMemberBinding= »{Binding Nom} » Width= »200″ />
         </GridView>
      </ListView.View>
   </ListView>
</Grid>
 

Chaque page sera totalement indépendante et recevra à sa construction les seuls éléments qu’elle doit imprimer. Le code-behind de notre UserControl sera donc le suivant :

public partial class OneFor4Page : UserControl, INotifyPropertyChanged
{
    private const double PageMargin = 15;
    private const double ElementHeight = 24;
    private IEnumerable<ModelOneFor4> _listeItem;
    public IEnumerable<ModelOneFor4> ListeItem
    {
        get { return _listeItem; }
        private set
        {
            _listeItem = value;
            this.RaisePropertyChanged(« ListeItem »);
        }
    }
    public OneFor4Page(IEnumerable<ModelOneFor4> lstItems)
    {
        ListeItems = lstItems;
        this.Margin = new Thickness(PageMargin);
        InitializeComponent();
    }
    public static int RowPerPage(double height)
    {
        return (int)Math.Floor((height – (2 * PageMargin)) / ElementHeight);
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

 

La propriété constante ElementHeight nous servira ici dans la méthode statique RowPerPage retournant le nombre d’éléments que peut contenir une page, par soucis de simplicité, nous divisons juste la hauteur disponible par la hauteur d’un élément pour obtenir ce nombre.

A présent que nous avons notre “page” retournons à notre OneFor4Paginator. Nous commençons par ajouter deux champs à notre classes, _pageSize de type System.Windows.Size et _rowsPerPage de type int. A chaque modification de la propriété PageSize, nous mettons à jour _pageSize et nous appelons la méthode de classe RowPerPage pour mettre de même à jour notre champ _rowsPerPage. Nous nous occupons ensuite de remplir les propriétés IsPageCountValid et PageCount, dans cet article, nous retournerons toujours la valeur true pour la validation du nombre de pages, quant aux nombres de pages, nous retournons simplement le nombre d’éléments à imprimer par le nombre de d’éléments par page ( vous suivez jusque là Sourire ).

Quant à la méthode GetPage, nous récupérons la ligne par laquelle nous devons commencer à imprimer grâce au numéro de page passé en paramètre ( plus par pratique que par logique, ce numéro démarre à 0 ), nous calculons le nombres d’éléments à imprimer en prenons soin de vérifier s’il reste assez d’éléments pour remplir une page et éviter une belle “ArgumentException”, puis nous appelons le constructeur de notre page. Avant de passer notre UserControl en paramètre du DocumentPage à retourner, il nous faut appeller successivement les méthodes du framework Measure et Arrange sur notre UserControl, celle-ci s’occuperons de placer tout le contenu de notre UserControl, sans elles, nous imprimerons des pages entièrement blanches…

class OneFor4Paginator : DocumentPaginator
{
    private Size _pageSize;
    IEnumerable<ModelOneFor4> _listeItems;
    private int _rowPerPage;
    public OneFor4Paginator(Size pageSize, IEnumerable<ModelOneFor4> listeItems)
    {
        this._listeItems = listeItems;
        this.PageSize = pageSize;
    }
    public override DocumentPage GetPage(int pageNumber)
    {
        int currentRow = _rowPerPage * pageNumber;
        OneFor4Page page;
        int printableRowCount = Math.Min(_rowPerPage, _listeItems.Count() – currentRow);
        page = new OneFor4Page(_listeItems.ToList().GetRange(currentRow, printableRowCount));
        page.Measure(PageSize);
        page.Arrange(new Rect(new Point(0, 0), PageSize));
        return new DocumentPage(page);
    }
    public override bool IsPageCountValid
    {
        get { return true; }
    }
    public override int PageCount
    {
        get { return (int)Math.Ceiling(_listeItems.Count() / (double)_rowPerPage); }
    }
    public override System.Windows.Size PageSize
    {
        get
        {
            return _pageSize;
        }
        set
        {
            _pageSize = value;
            _rowPerPage = OneFor4Page.RowPerPage(PageSize.Height);
        }
    }
    public override IDocumentPaginatorSource Source
    {
        get { return null; }
    }
}

Il ne nous reste plus qu’à instancier un PrintDialog et appeller la méthode PrintDocument avec en paramètre un OneFor4Paginator créée précédemment.

PrintDialog dialog = new PrintDialog();
bool? dialogResult = dialog.ShowDialog();
if (dialogResult.HasValue && dialogResult.Value)
{
    dialog.PrintDocument(new OneFor4Paginator(new Size(dialog.PrintableAreaWidth, dialog.PrintableAreaHeight), _listeItems), « Impression OneFor4″);
}

 

Voilà donc une façon simple d’imprimer des documents de plusieurs pages grâce à WPF, par souci de rapidité, nous avons créé notre page en code XAML, ce qui a comme “défaut” d’imprimer quelque chose ressemblant à de simples impression écran de notre logiciel. Mais nous aurions pu tout aussi bien ré implémenter la méthode OnRender de notre UserControl et “dessiner” notre page à la main grâce au DrawingContext, avec un peu d’habitude et de la refactorisation de code, cette manière de faire peut s’avérer tout aussi rapide que la création en code xaml tout en imprimant un “vrai” document sans la sensation d’impression écran…

Aucun arbre n’a du être coupé pour tester le code de cet article, par respect de la nature, utilisez au maximum les imprimantes virtuelles pour vos tests. Microsoft XPS Document Writer ne donne certes pas toujours des aperçus réalistes mais d’autres logiciels comme PDF Créator nous permettent d’obtenir un aperçu très proche des vrais impressions.

Les recherches pour cet article ont été faites dans le cadre d’un stage pour l’entreprise SACEO et son logiciel Opisto : www.opisto.fr

Par Mathieu Hollebecq