Render a content control in another tab of TabControl

I have a program wherein there are multiple tabs added dynamically to a TabControl object programmatically. What I want to do is render the Content value of each of these tabs to a PNG. I am using a script I picked up elsewhere on StackOverflow or perhaps Google (lost the source). My code looks like this:

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
        }
    }
}

This code works as desired if I have gone through and viewed all the tabs that must be rendered before invoking this code. It goes ahead and creates filePrefix_1.png, filePrefix_2.png, etc. with the correct content rendered from TabContentControl. However, if I invoke the handler that uses this code before having viewed all the tabs, my code throws an exception at new RenderTargetBitmap(...) because content.RenderSize is {0.0, 0.0}. When I try to force the render size of an unviewed tab to one of the viewed once, my outputted PNG is of the correct dimensions but completely empty.

So I guess I need some way to force rendering of TabContentControl. Seems like the Render event is only run, as it should be, when the UIElement needs to be rendered. Is their any trickery that I can perform to get around this?

I have also tried to "trick" WPF into painting a tab content by adding the following code when the tabs are created, in a Page_Loaded event handler:

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;
}

When the last line in the Page_Loaded handler is commented out, the last tab is in focus and has the RenderSize property defined for its content. When the last line is not commented out, the first tab is in focus, with the same behavior. The other tabs do not have any rendering information.

Answers


Finally figured it out, thanks to this blog post. The solution involved creating an extension method for UIElement with a Refresh method that invoked an empty delegate with render priority. Apparently scheduling something with Render priority caused all other more important items to be executed, thus changing the tab.

Code replicated here in case blog is deleted:

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;
}

To use it, just include the extension namespace in the code file that you need to use this functionality and it will appear in the list of methods.


Need Your Help

How do I install Eclipse Marketplace in Eclipse Classic?

eclipse eclipse-plugin eclipse-marketplace

I'm running Eclipse 3.6.1 Classic, which does not come with the Eclipse Marketplace plugin by default. I've looked around the Eclipse website, but I don't see an available plugin for installing Ecl...

Is this method O(log N) or constant time?

java queue complexity-theory time-complexity priority-queue

I have a method that inserts elements into a priority queue. I want to know the performance time it has.