Archives de Catégorie: WPF

Récupérer et parser du HTML

Ma problématique était de récupérer des images depuis un site web. Il me fallait donc charger le contenu du site, puis chercher à l’intérieur du code source, les adresses des images qui m’intéressaient. Voyons tout cela plus en détail.

Récupérer une page nécessitant une authentification

Au départ, j’avais besoin de travailler sur les images des albums Facebook. Un album privé était donc difficilement accessible sans connexion. La manière la plus simple que j’ai trouvée a été d’utiliser le contrôle Web Browser en WPF.

J’ai donc créé un WebBrowser dans mon xaml que j’ai appelé WebBro. La navigation peut se faire depuis le code behind en une ligne :

WebBro.Navigate(VotreUrl);

Ensuite lorsque je suis sur la bonne page, je commence par récupérer et stocker le code HTML de la page :

dynamic dom = WebBro.Document;
string htmlText = dom.documentElement.innerHTML;

Le mot-clef dynamic simplifie le code mais vous pouvez très bien ajouter une référence à MSHTML et caster le WebBro.Document en mshtml.HTMLDocumentClass.
Nous avons donc récupéré le HTML de la page et l’avons stocké dans la string htmlText. Il nous restera plus qu’à travailler dessus pour rechercher les éléments que l’on souhaite. Nous verrons cela dans la troisième partie de cet article.

Récupérer une page “classique”

Pour une application WP7, il me fallait récupérer des images depuis un site mais pour cela il fallait tout automatiser. Le WebBrowser n’était donc pas imaginable.
J’ai utilisé un WebClient pour requêter le site et récupérer directement le HTML de la page.

_wc = new WebClient();
_wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(_wc_DownloadStringCompleted);
_wc.DownloadStringAsync(new Uri(VotreURL + _localAppItem.Name,
                                                UriKind.Absolute));

Vu que c’est du Windows Phone, tout est asynchrone, il faut donc s’abonner à l’évènement DownloadStringCompleted. Lorsque cet évènement est levé, nous pouvonsrécupérer le contenu de notre page HTML dans notre méthode _wc_DownloadStringCompleted(object o, DownloadStringCompletedEventArgs args).
Le code HTML de la page se trouvant dans la variable args.Result.

Parser le HTML grâce aux Regex

Les Regex… Ces petites bêtes que l’on souhaite éviter et qui ne se laisse pas dompter facilement.
Les Regex ou expressions régulières sont un moyen de chercher des éléments textuels respectant une certaine forme, un certain pattern.
Dans mon cas, pour chercher des images dans du code HTML, il me fallait récupérer toutes les chaines commençant par “src= »” et finissant avec une extension particulière “.jpg”, “.jpeg”, “.png”,…

Pour plus de lisibilités, j’ai donc défini deux chaînes, une pour le début et une pour la fin de la séquence à trouver :

string start = "src=\"";
string end = "\\.(jpg|png)";

Sur la chaîne start, le ‘\’ permet d’échapper le guillemet.
Sur la chaîne end, les ‘\\’ permettent d’échapper un ‘\’ qui permet d’échapper le ‘.’. En effet, dans une regex, le point est un caractère particulier.

Nous pouvons maintenant créer notre Regex :

Regex r = new Regex(start + "(.*?)" + end);

Nous créons donc une String que nous passons au constructeur de notre Expression Régulière. On retrouve notre chaîne de début et notre chaîne de fin avec au milieu cet assemblage étrange : « (.*?) » :

  • les parenthèse signifient un groupement de caractères
  • Le point signifie n’importe quel caractère
  • l’étoile signifie une à plusieurs fois le caractère précédent
  • le point d’interrogation permet de répéter l’expression précédente

Une fois cette Regex créée, il ne nous reste plus qu’à l’utiliser sur notre code html :

MatchCollection matches = r.Matches(htmlText);

A chaque fois qu’une occurrence sera trouvée, elle sera ajoutée à la collection matches qu’il nous suffit de parcourir avec un foreach pour récupérer les liens des images.

foreach (Match matche in matches)
            {
                VotreMethodePourTraiter(matche.Groups[1].Value);
            }

Bonus : Télécharger les images

Idem, je vais montrer avec deux possibilités. Synchrone en WPF et asynchrone pour WP7.

De manière synchrone

private void GetPics()
        {
                int i = 0;
                foreach (string url in urlBrutImg)
                {
                    System.Drawing.Image myImg;
                    WebRequest req = WebRequest.Create(url);
                    WebResponse response = req.GetResponse();
                    myImg = System.Drawing.Image.FromStream(response.GetResponseStream());
                    myImg.Save("D:\\NomDuRepertoire\\" + i+".jpg");
                    i++;
                }
        }

Chaque fois qu’une de mes Regex étaient valide, j’ajoutais l’url à une liste. C’est cette liste, nommée urlBrutImg que je parcours dans le foreach.
Je fais une WebRequest sur l’url obtenue, je génère mon image avec le Stream que me retourne la WebRequest. Il ne me reste plus qu’à enregistrer cette image sur mon disque dur, et d’incrémenter i pour ne pas écraser la précédente !

De manière asynchrone

Ici, chaque fois que ma Regex matchait, j’ajoutai l’Uri obtenue dans une Queue, ce type de liste permet de retirer un élément lors de sa lecture. J’ai réutilisé le WebClient qui m’a permis de télécharger le code HTML de manière asynchrone pour télécharger les images. Il m’a juste fallu m’abonner à un nouvel évènement :

_wc.OpenReadCompleted += new OpenReadCompletedEventHandler(OpenReadCompleted);

J’ai ensuite ajouté une méthode Dequeue() pour retirer mes éléments au fur et à mesure que je récupérais mes images.

private void Dequeue()
        {
            if (_queuedItems.Count > 0)
            {
                Uri uri = _queuedItems.Dequeue();
                _wc.OpenReadAsync(uri, uri);
            }
        }

Et le code de ma méthode OpenReadCompleted pour enregistrer l’image :

private void OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            try
            {
                if (e.Error == null && e.Cancelled == false)
                {
                    Uri uri = e.UserState as Uri;
                    BitmapImage image = new BitmapImage();

                    image.SetSource(e.Result);
                    //Traitement de l’image                  
                }
            }
            catch (Exception ee)
            {}
            Dequeue();
        }

Une fois que vous avez votre BitmapImage, à vous de voir si vous voulez l’enregistrer dans l’IsolatedStorage ou l’afficher.
On fini par un Dequeue, pour continuer à vider notre collection et télécharger les images manquantes.

Conclusion

Dans cet article nous aurons vu plusieurs manières de récupérer du code HTML puis comment obtenir certains éléments similaires grâce aux Regex.

Par JC VASSELON

Publicités

Un modèle, une UI et… un pont

Lorsque l’on utilise WPF et son sympathique moteur de Binding, il peut arriver que l’on se retrouve avec une interface utilisateur trop proche du modèle de données.

Je m’explique, généralement, nous avons un élément d’interface lié à une propriété du modèle. Un problème qui peut se poser, c’est que notre modèle reflète l’interface utilisateur à tout instant, par exemple un Slider modifiera le modèle à chaque changement de valeurs alors que cela peut être inutile. Selon comment le modèle gère ses propriétés, nous pouvons nous retrouver avec des problèmes, par exemple si un traitement lourd est fait à chaque modification, ou bien si les modifications trop fréquentes ont des répercussions indésirables. Une des solutions très simple à mettre en place est d’indiquer au Binding qu’il ne doit mettre à jour la Source qu’à la perte du focus (UpdateSourceTrigger=LostFocus). Cela permet donc à l’utilisateur de modifier les valeurs depuis l’UI et de ne les valider qu’au moment où celui a vraiment fait son choix.

Mais imaginons que notre UI reflète plusieurs fois la même propriété. Prenons encore le cas d’un Slider (pour un changement de valeur rapide à la souris) mais cette fois associé à une TextBox (pour un changement précis). Si ces deux contrôles utilisent un Binding avec un UpdateSourceTrigger à LostFocus, la valeur de l’un ne sera mise à jour que lorsque la valeur de l’autre aura changé, le modèle avec. En bref, pour l’utilisateur ce n’est pas forcément l’idéal vu qu’il n’aura pas en direct l’information qu’il va valider.

Ce qui serait intéressant, c’est que l’utilisateur puisse voir en temps réel la propriété de la TextBox changer en même temps que le Slider, sans que le modèle ne soit modifié. Bien évidemment, quand le modèle est modifié, il faut que l’UI se mette à jour directement.
Les choses se corsent un tout petit peu. Mais la solution n’est pas forcément complexe. En effet, plutôt que de lier mon UI directement au modèle, je n’ai qu’à la lier à une autre propriété qui reflètera en permanence la valeur donnée par l’UI, et une fois l’interaction avec l’UI terminé, je n’aurais qu’à pousser la valeur dans le modèle.

Pour cela, j’ai mis au point ce que j’ai appelé un PropertyBridge. En gros, c’est ce composant qui va servir de pont entre le modèle et l’UI.

Le PropertyBridge est donc lié directement au modèle, tandis que l’UI est liée au PropertyBridge. Pour cet article, le modèle sera tout simple :

  1. public class Model : INotifyPropertyChanged
  2. {
  3.     public event PropertyChangedEventHandler PropertyChanged;
  4.  
  5.     private void NotifyPropertyChanged(string propName)
  6.     {
  7.         if (PropertyChanged != null)
  8.         {
  9.             PropertyChanged(this, new PropertyChangedEventArgs(propName));
  10.         }
  11.     }
  12.  
  13.     private string m_modelValue = "–";
  14.  
  15.     public string ModelValue
  16.     {
  17.         get { return m_modelValue; }
  18.         set { m_modelValue = value; NotifyPropertyChanged("ModelValue"); }
  19.     }
  20. }

Et tout aussi simple du côté de la vue :

  1. <Grid>
  2.     <Grid.DataContext>
  3.         <local:Model />
  4.     </Grid.DataContext>
  5.     <StackPanel>
  6.         <Slider Value="{Binding ModelValue}" />
  7.         <TextBlock><Run Text="Valeur du mod?le : " /><Run Text="{Binding ModelValue}" /></TextBlock>
  8.        
  9.         <local:PropertyBridge x:Name="Bridge" ModelProperty="{Binding ModelValue}" />
  10.         
  11.         <TextBox Text="{Binding ElementName=Bridge, Path=UIBridgeProperty, Mode=TwoWay}" />
  12.         <Slider Value="{Binding ElementName=Bridge, Path=UIBridgeProperty, Mode=TwoWay}">
  13.             <i:Interaction.Behaviors>
  14.                 <local:MouseUpBridgeBehavior Bridge="{Binding ElementName=Bridge}" />
  15.             </i:Interaction.Behaviors>
  16.         </Slider>
  17.     </StackPanel>
  18. </Grid>

Nous avons donc notre modèle dans le DataContext. Un Slider pour modifier directement le modèle (avec l’aperçu de la valeur directement en dessous), ainsi qu’un Slider et une TextBox pour modifier le modèle à travers le PropertyBridge.

Mais au final, il fait quoi ce PropertyBridge ? En fait, celui-ci est composé de deux propriétés. Une liée au modèle, et l’autre sur laquelle les contrôles UI sont liés, tout cela, en Binding TwoWay. Nous voilà donc avec un problème réglé : Tous les contrôles sont liés à la même propriété donc un changement dans l’un se répercutera dans l’autre, sans affecter le modèle.

Voici donc le code du PropertyBridge. Notez qu’il hérite de FrameworkElement pour pouvoir être ajouté à l’arbre visuel (si quelqu’un connait un moyen d’éviter ça 🙂 ).

  1. public class PropertyBridge : FrameworkElement
  2. {
  3.     public object ModelProperty
  4.     {
  5.         get { return (object)GetValue(ModelPropertyProperty); }
  6.         set { SetValue(ModelPropertyProperty, value); }
  7.     }
  8.  
  9.     // The DependencyProperty that is bound the model
  10.     public static readonly DependencyProperty ModelPropertyProperty =
  11.         DependencyProperty.Register(    "ModelProperty",
  12.                                         typeof(object),
  13.                                         typeof(PropertyBridge),
  14.                                         new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
  15.  
  16.     public object UIBridgeProperty
  17.     {
  18.         get { return (object)GetValue(UIBridgePropertyProperty); }
  19.         set { SetValue(UIBridgePropertyProperty, value); }
  20.     }
  21.  
  22.     // Using a DependencyProperty as the backing store for UIBridgeProperty.  This enables animation, styling, binding, etc…
  23.     public static readonly DependencyProperty UIBridgePropertyProperty =
  24.         DependencyProperty.Register("UIBridgeProperty", typeof(object), typeof(PropertyBridge),
  25.         new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
  26.         {
  27.             DefaultUpdateSourceTrigger = System.Windows.Data.UpdateSourceTrigger.Explicit
  28.         });
  29. }

 

Nous avons donc juste deux propriétés avec quelques configurations de base. Les deux propriétés utilisent par défaut un Binding TwoWay. Petite particularité de la propriété côté UI, celle-ci utilise un Binding avec l’UpdateSourceTrigger à Explicit.

Bien évidemment, en l’état actuel, le modèle ne sera jamais modifié… Bref, pour l’instant, ça ne sert à rien. On pourrait dire que nous avons commencé à construire un pont, sans relier les deux rives.

Nous avons donc une propriété spécifique à l’UI qui sera mise à jour directement par l’UI sur laquelle vont se lier tous les contrôles permettant de modifier la valeur du modèle. L’autre propriété elle est directement relié au modèle. Si nous regardons dans l’ensemble, nous avons un pont avec d’un côté une synchronisation avec le modèle, et de l’autre une synchronisation avec l’UI.

Il nous faut donc créer le lien entre ces deux parties en respectant certaines conditions :

  • Les mises à jour du modèles sont immédiatement répercutées dans l’UI.
  • Les mises à jour de l’UI ne sont poussées dans le modèle que de manière explicite.

    L’intérêt du Binding Explicit est donc immédiat. Il nous faut seulement définir un Binding à l’intérieur du PropertyBridge, la propriété de l’UI devant être lié à la partie Modèle, le tout en permettant au PropertyBridge de mettre à jour la source du Binding de manière explicite :

    1. public PropertyBridge()
    2. {
    3.     Binding internalBinding = new Binding();
    4.     internalBinding.Source = this;
    5.     internalBinding.Path = new PropertyPath("ModelProperty");
    6.  
    7.     this.SetBinding(PropertyBridge.UIBridgePropertyProperty, internalBinding);
    8. }
    9.  
    10. public void PushUIBridgePropertyToModel()
    11. {
    12.     this.GetBindingExpression(PropertyBridge.UIBridgePropertyProperty).UpdateSource();
    13. }

    Il nous reste au final une seule chose : Comment l’UI va dire au PropertyBridge « Tu peux mettre à jour le modèle » ? Etant donné que le but est d’être au plus proche du contrôle, il y a peu de possibilités. Soit refaire un contrôle qui sera capable de se brancher sur le PropertyBridge, soit en passant par un Behavior qui sera branché sur le PropertyBridge. Dans mon cas, j’ai opté pour la deuxième solution. L’avantage étant d’éviter d’hériter d’un contrôle juste pour y ajouter un comportement assez spécifique, et de pouvoir éventuellement utiliser le même Behavior pour plusieurs contrôles.

    1. public class MouseUpBridgeBehavior : Behavior<FrameworkElement>
    2. {
    3.     public PropertyBridge Bridge
    4.     {
    5.         get { return (PropertyBridge)GetValue(BridgeProperty); }
    6.         set { SetValue(BridgeProperty, value); }
    7.     }
    8.  
    9.     // Using a DependencyProperty as the backing store for Bridge.  This enables animation, styling, binding, etc...
    10.     public static readonly DependencyProperty BridgeProperty =
    11.         DependencyProperty.Register("Bridge", typeof(PropertyBridge), typeof(MouseUpBridgeBehavior), new UIPropertyMetadata(null));
    12.  
    13.         
    14.  
    15.     protected override void OnAttached()
    16.     {
    17.         base.OnAttached();
    18.  
    19.  
    20.         AssociatedObject.PreviewMouseUp += new System.Windows.Input.MouseButtonEventHandler(AssociatedObject_PreviewMouseUp);
    21.     }
    22.  
    23.     void AssociatedObject_PreviewMouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    24.     {
    25.         if (Bridge != null && AssociatedObject.IsMouseCaptureWithin)
    26.             Bridge.PushUIBridgePropertyToModel();
    27.     }
    28.  
    29.     protected override void OnDetaching()
    30.     {
    31.         AssociatedObject.PreviewMouseUp -= new System.Windows.Input.MouseButtonEventHandler(AssociatedObject_PreviewMouseUp);
    32.         base.OnDetaching();
    33.     }
    34. }

  • 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