-2

How can I programmatically convert my very wide GridView to a bitmap image? I need all the data to show in one bitmap image.

Xaml:

<StackPanel>
    <telerik:RadGridView x:Name="GridView1" ItemsSource="{Binding MyProperties1}" AutoGenerateColumns="True" />
    <telerik:RadGridView x:Name="GridView2" ItemsSource="{Binding MyProperties2}" AutoGenerateColumns="True" />
</StackPanel>

enter image description here

Rod
  • 14,529
  • 31
  • 118
  • 230

2 Answers2

2

@Yanger Yang's answer is close, but you should use the element's DesiredSize instead of its ActualWidth/ActualHeight. This is, of course, assuming that you're not setting an explicit size on the Grid element itself. This approach should work with your telerik:RadGridView, but I do not have the library available for testing.


Here's a sample project containing a Grid with fifteen (15) columns inside a ScrollViewer.

WPF XAML designer

<Window x:Class="PrintableGridDemo.MainWindow"
        Title="Printable Grid" Height="600" Width="800">
    <DockPanel>
        <Button DockPanel.Dock="Top" 
                HorizontalAlignment="Right" 
                Margin="0 10 10 0" 
                Padding="50 10"
                Content="Print"
                Click="PrintButton_Click"/>
        <Border Margin="10" 
                BorderBrush="DarkGray" 
                BorderThickness="1">
            <ScrollViewer HorizontalScrollBarVisibility="Visible"
                          VerticalScrollBarVisibility="Visible">
                <Grid x:Name="MyGrid" 
                      Background="White">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                        <ColumnDefinition Width="100"/>
                    </Grid.ColumnDefinitions>
                </Grid>
            </ScrollViewer>
        </Border>
    </DockPanel>
</Window>

I've (ab)used the class's constructor to populate the Grid with sample data including the column number so that you can see the generated image contains all the columns in the grid.

public MainWindow()
{
    InitializeComponent();

    for (int col = 0; col < MyGrid.ColumnDefinitions.Count; col++)
    {
        var control = new TextBlock() { 
            Text = $"This is column number { col + 1 }. It is a column with almost no dynamic data. The only dynamic data is the number corresponding to this column's index in the grid plus one since users prefer one-indexed values while the framework uses zero-indexed values.",
            TextWrapping = TextWrapping.Wrap
        };
        MyGrid.Children.Add(control);
        Grid.SetColumn(control, col);
    }
}

Here's what the project looks like when it's running.

Running application

And here's the result of clicking the "Print" button. As you can see, using the DesiredSize of the grid has allowed us to see the contents of all columns (and has also removed the extra layout whitespace where the grid expanded to fill the ScrollViewer).

Image of the full grid


Here's what happens when you click the "Print" button:

private void PrintButton_Click(object sender, RoutedEventArgs e)
{
    // Set up the output file.
    string destination = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
    string fileName = $"GridImage_{DateTime.Now:yyyy-MMM-dd_hh-mm-ss-tt}.png";
    string filePath = Path.Combine(destination, fileName);
    
    // Render the control as a Bitmap.
    RenderTargetBitmap image = new((int)MyGrid.DesiredSize.Width, (int)MyGrid.DesiredSize.Height, 96, 96, PixelFormats.Pbgra32);
    image.Render(MyGrid);

    // Encode the Bitmap image as a PNG instead.
    PngBitmapEncoder encoder = new();
    encoder.Frames.Add(BitmapFrame.Create(image));

    // Save the PNG to the file.
    using FileStream stream = File.Create(filePath);
    encoder.Save(stream);
}

The key bit is here:

RenderTargetBitmap image = new(

    // The key bit.
    (int)MyGrid.DesiredSize.Width, 
    (int)MyGrid.DesiredSize.Height, 
    
    // Format specific parameters. You can ignore these.
    96, 96, PixelFormats.Pbgra32);

Because we're using the element's DesiredSize, the render will include parts of the element that would not normally be rendered (since they are not visible in the UI).

If you run into issues with clipping, you can call the element's Measure method to recalculate the element's current DesiredSize.

D M
  • 5,769
  • 4
  • 12
  • 27
  • Is the scroll viewer a requirement? – Rod Sep 23 '21 at 18:39
  • 1
    Yes, I believe so. The `Grid` will only desire to be as large as the space available to it. In other words, [layout is measured from the top down](https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.measure?view=net-5.0#remarks). – D M Sep 23 '21 at 19:49
  • 1
    Thanks so much for your efforts and sharing additional insights! – Rod Sep 23 '21 at 19:50
1

It is convenient to use DrawToBitmap in Windows Form to do what you want. In WPF, you can use RenderTargetBitmap, BitmapFrame and BmpBitmapEncoder to do that.

    void SaveToBmp(FrameworkElement control, string fileName)
    {
        var bmpEncoder = new BmpBitmapEncoder();
        SaveControlToImage(control, fileName, bmpEncoder);
    }

    void SaveControlToImage(FrameworkElement control, string fileName, BitmapEncoder encoder)
    {
        // Keep the current postion relative to the container
        UIElement container = VisualTreeHelper.GetParent(control) as UIElement;
        Point curPos = control.TranslatePoint(new Point(0.0, 0.0), container);

        RenderTargetBitmap bitmap = new RenderTargetBitmap((int)control.ActualWidth, (int)control.ActualHeight, 96, 96, PixelFormats.Pbgra32);
        Size visualSize = new Size(control.ActualWidth, control.ActualHeight);
        control.Measure(visualSize);
        control.Arrange(new Rect(visualSize));
        bitmap.Render(control);
        BitmapFrame frame = BitmapFrame.Create(bitmap);
        encoder.Frames.Add(frame);

        using (var stream = File.Create(fileName))
        {
            encoder.Save(stream);
        }

        control.Arrange(new Rect(curPos, visualSize));
    }

And using the following code to save your GridView to image file:

SaveToBmp(GridView1, @"D:\gridview1.bmp");

Refer to Easiest way of saving wpf Image control to a file and add some improvement.

Yanger Yang
  • 121
  • 4
  • The image doesn't show all the columns. See updated OP. – Rod Sep 22 '21 at 13:18
  • This may not even be possible the more and more I think about it: to show all 8 columns expanded in one image? – Rod Sep 22 '21 at 13:28