'WPF: How to generate screenshots of items within a ItemsControl (within a TabView)

I have a TabControl where each tab is a user control that has an ItemsControl in it. The TabControl is bound to a collection and has an ItemTemplate:

<DataTemplate x:Key="tabContentTemplate">
            <tabs:VisualizationTab x:Name="VisualizationTab"/>
        </DataTemplate>

<TabControl x:Name="tabControl" SelectionChanged="tabControl_SelectionChanged"
     ItemsSource="{Binding TabViewModels, Mode=TwoWay}" ItemHeaderTemplate="{StaticResource tabHeaderTemplate}"
     ItemTemplate="{StaticResource tabContentTemplate}"/>

Likewise, the ItemsControl in each tab is bound to data (complicated by a TemplateSelector).

    <UserControl.Resources>
        <DataTemplate x:Key="classOneTemplate">
            <charts:PlotOne DataContext="{Binding Converter={StaticResource plotOneViewModelConverter}}" />
        </DataTemplate>
        <DataTemplate x:Key="classTwoTemplate">
            <charts:PlotTwo DataContext="{Binding Converter={StaticResource plotTwoViewModelConverter}}" />
        </DataTemplate>
    <local:VisualizationTemplateSelector ClassOneTemplate="{StaticResource classOneTemplate}" ClassTwoTemplate="{StaticResource classTwoTemplate}"
                                                 x:Key="visualizationTemplateSelector" />
    </UserControl.Resources>

<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <ItemsControl x:Name="visualizationItemsControl" ItemsSource="{Binding Path=VisualizationSpecs}" ItemTemplateSelector="{StaticResource visualizationTemplateSelector}">
            <!-- ItemsPanelTemplate -->
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="{ Binding Path=ColumnCount}" Rows="{ Binding Path=RowCount}" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>

I'd like to get a collection of collection of images for all the items (to use as scaled down images in another part of UI). I am having difficulty for two reasons: 1) trying to access the items of the TabControl/ItemsControl gives the dataitems not the visual items and 2) the visual elements of the TabControl are only created for the currently selected tab.

My thought was to capture each Tab when the Tab is selected and store this with something like:

private void tabControl_SelectionChanged(object sender, TabControlSelectionChangedEventArgs e)
{
    WorkspaceViewModel workspaceViewModel = tabControl.SelectedItem as WorkspaceViewModel;
    if (workspaceViewModel is null)
        return;
    tabControl.Dispatcher.BeginInvoke(new Action(() =>
    {
        VisualizationTab visualizationTab = FindVisualChild<VisualizationTab>(tabControl);
        workspaceViewModel.SetVisualizationTab(visualizationTab);
    }));
}

Is there a better way of doing this?

And do I have to do the same thing with getting the visual children of the ItemsControl to get an image for each item?



Solution 1:[1]

Here is some code that I have used in the past to generate pictures from controls/screen (I usually wrap the control I am trying to take a picture of in a grid/border for simplicity)

using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public enum EncodingTypes : int
{
    BMP,
    GIF,
    JPEG,
    PNG,
    TIFF,
    WMP
}

public static class ImageHelper
{
    #region " FrameworkElementToBitmap "
    /// <summary>
    /// FrameworkElementToBitmap
    /// During signature capture, boundary does not get calculated correctly when user draws off the canvas.
    /// Use a hidden border to determine the canvas boundary when capturing signature.
    /// </summary>
    /// <param name="border"></param>
    /// <returns>byte[]</returns>
    public static byte[] FrameworkElementToBitmap(UIElement border, EncodingTypes encType, double scale = 1.0)
    {
        byte[] bitmapBytes;
        int width = (int)(border.RenderSize.Width * scale);
        int height = (int)(border.RenderSize.Height * scale);

        BitmapEncoder encoder;
        switch (encType)
        {
            case EncodingTypes.GIF:
                encoder = new GifBitmapEncoder();
                break;

            case EncodingTypes.JPEG:
                encoder = new JpegBitmapEncoder();
                break;

            case EncodingTypes.PNG:
                encoder = new PngBitmapEncoder();
                break;

            case EncodingTypes.TIFF:
                encoder = new TiffBitmapEncoder();
                break;

            case EncodingTypes.WMP:
                encoder = new WmpBitmapEncoder();
                break;

            default:
            case EncodingTypes.BMP:
                encoder = new BmpBitmapEncoder();
                break;
        }

        //render ink to bitmap
        RenderTargetBitmap renderBitmap = new RenderTargetBitmap(width, height, 96d, 96d, PixelFormats.Default);

        Rect bounds = VisualTreeHelper.GetDescendantBounds(border);

        //Use DrawingVisual to prevent having to move the control to the top of the screen to grab the bitmap
        DrawingVisual dv = new DrawingVisual();
        using (DrawingContext ctx = dv.RenderOpen())
        {
            ctx.PushTransform(new ScaleTransform(scale, scale));
            ctx.DrawRectangle(
                new VisualBrush(border), 
                null, 
                new Rect(new Point(), bounds.Size)); //new Rect(border.RenderSize));
        }

        renderBitmap.Render(dv);

        //save the ink to a memory stream
        encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
        using (MemoryStream ms = new MemoryStream())
        {
            encoder.Save(ms);

            //get the bitmap bytes from the memory stream
            bitmapBytes = ms.ToArray();
            //Uncomment to save images when debugging
            //#if DEBUG
            //              string savePath = Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);
            //              string testFileName = Path.Combine(savePath, string.Format("{0}.{1}", DateTime.Now.ToString("yyyyMMdd_hhmmss"), encType.ToString().ToLower()));
            //              File.WriteAllBytes(testFileName, bitmapBytes);
            //#endif
        }

        return bitmapBytes;
    }
    #endregion
}

Usage:

byte[] bytes = ImageHelper.FrameworkElementToBitmap(mygrid, EncodingTypes.JPEG);

Xaml:

<Grid x:Name="mygrid" Background="White">***controls you want to take pics of***</Grid>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kevin Cook