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.