2010-01-09 4 views
2

J'ai un programme dans lequel plusieurs onglets sont ajoutés dynamiquement à un objet TabControl par programmation. Ce que je veux faire est de rendre la valeur de contenu de chacun de ces onglets à un PNG. J'utilise un script que j'ai récupéré ailleurs sur StackOverflow ou peut-être Google (perdu la source). Mon code ressemble à ceci:Rendu d'un contrôle de contenu dans un autre onglet de TabControl

if (tabPanel.Items.Count > 0) 
{ 
    SaveFileDialog fileDialog = new SaveFileDialog(); 
    fileDialog.Filter = "PNG|*.png"; 
    fileDialog.Title = "Save Tabs"; 
    fileDialog.ShowDialog(); 

    if (fileDialog.FileName.Trim().Length > 0) 
    { 
     try 
     { 
      string filePrefix = fileDialog.FileName.Replace(".png", ""); 
      int tabNo = 1; 
      foreach (TabItem tabItem in tabPanel.Items) 
      { 
       string filename = filePrefix + "_" + tabNo + ".png"; 

       TabContentControl content = tabItem.Content as TabContentControl; 
       Rect rect = new Rect(content.RenderSize); 
       RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right, (int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default); 
       rtb.Render(content); 

       BitmapEncoder pngEncoder = new PngBitmapEncoder(); 
       pngEncoder.Frames.Add(BitmapFrame.Create(rtb)); 

       System.IO.MemoryStream ms = new System.IO.MemoryStream(); 
       pngEncoder.Save(ms); 
       System.IO.File.WriteAllBytes(filename, ms.ToArray()); 
       ms.Close(); 

       tabNo++; 
      } 
     } 
     catch (Exception ex) 
     { 
      // log exception 
     } 
    } 
} 

Ce code fonctionne comme vous le souhaitez si je suis allé à travers et vu tous les onglets qui doivent être rendus avant d'invoquer ce code. Il va de l'avant et crée filePrefix_1.png, filePrefix_2.png, etc. avec le contenu correct rendu à partir de TabContentControl. Toutefois, si j'appelle le gestionnaire qui utilise ce code avant d'avoir consulté tous les onglets, mon code envoie une exception au new RenderTargetBitmap(...) car content.RenderSize est {0.0, 0.0}. Lorsque j'essaie de forcer la taille de rendu d'un onglet non consulté à l'une des vues une fois, mon PNG en sortie est de dimensions correctes mais complètement vide.

Donc je suppose que j'ai besoin d'un moyen de forcer le rendu de TabContentControl. On dirait que l'événement Render est seulement exécuté, comme il se doit, lorsque l'UIElement doit être rendu. Est-ce que leur tromperie que je peux effectuer pour contourner cela?

J'ai aussi essayé de « truc » WPF dans la peinture d'un contenu de l'onglet en ajoutant le code suivant lorsque les onglets sont créés, dans un gestionnaire d'événements PAGE_LOADED:

void Page_Loaded(object sender, RoutedEventArgs e) 
{ 
    // irrelevant code 
    foreach (// iterate over content that is added to each tab) 
    { 
     TabItem tabItem = new TabItem(); 
     // load content 
     tabPanel.Items.Add(tabItem); 
     tabItem.IsSelected = true; 
    } 
    // tabPanel.SelectedIndex = 0; 
} 

Lorsque la dernière ligne dans le gestionnaire Page_Loaded est mis en commentaire, le dernier onglet est en évidence et la propriété RenderSize est définie pour son contenu. Lorsque la dernière ligne n'est pas commentée, le premier onglet est en focus, avec le même comportement. Les autres onglets n'ont aucune information de rendu.

Répondre

2

Enfin compris, grâce à this blog post. La solution impliquait la création d'une méthode d'extension pour UIElement avec une méthode Refresh qui appelait un délégué vide avec la priorité de rendu. Apparemment, la planification de quelque chose avec la priorité Rendu a entraîné l'exécution de tous les autres éléments plus importants, modifiant ainsi l'onglet.

code reproduit ici dans le blog de cas est supprimé:

public static class ExtensionMethods 
{ 
    private static Action EmptyDelegate = delegate() { }; 

    public static void Refresh(this UIElement uiElement) 
    { 
     uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate); 
    } 
} 

void Page_Loaded(object sender, RoutedEventArgs e) 
{ 
    // irrelevant code 
    foreach (// iterate over content that is added to each tab) 
    { 
     TabItem tabItem = new TabItem(); 
     // load content 
     tabPanel.Items.Add(tabItem); 
     tabItem.IsSelected = true; 
     tabItem.Refresh(); 
    } 
    // tabPanel.SelectedIndex = 0; 
} 

Pour l'utiliser, il suffit d'inclure l'espace de noms d'extension dans le fichier de code que vous devez utiliser cette fonctionnalité et il apparaît dans la liste des méthodes.

1

Ce n'est pas vraiment une solution idéale, mais lorsque vous créez vos onglets et que vous les ajoutez au contrôle onglets, vous pouvez simplement stocker l'onglet actuellement ouvert, puis passer à l'onglet que vous venez de créer, puis basculer arrière. Pour l'utilisateur, il s'agirait juste d'un léger scintillement, mais en réalité, vous venez de tromper winforms (ou wpf en conséquence) dans le dessin de l'objet.

+0

J'ai essayé de faire quelque chose de similaire mais pour la vie de moi, je n'arrivais pas à comprendre comment changer l'onglet visible par programmation. Essayé de mettre 'TabControl.SelectedItem' dans' TabItem' et d'incrémenter la propriété 'TabControl.SelectedIndex'. – sohum

+0

En fait ... Je viens juste de réaliser que ce que je faisais était de changer les onglets après avoir cliqué sur le bouton de sauvegarde, depuis le gestionnaire ... donc je suppose qu'il n'y avait aucune possibilité de repeindre l'onglet? Je suppose que si je fais cela en ajoutant les onglets, ce ne sera pas un problème. – sohum

+0

Juste essayé, ça n'a pas fonctionné. WPF semble optimiser et ne peindre que le dernier onglet ouvert. – sohum

Questions connexes