I am attempting to create a png capture of a StackPanel
, however when I save, I am getting a distorted view where all the content is black rectangles, and the size is not correct. The width and height are correct in the image save, however all the content is forced to the top and squished together
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:POExpress.Views" x:Class="POExpress.MainWindow"
Title="My Window" Height="500" MinWidth="1000" Width="1000">
<Grid>
<TabControl>
<TabItem Header="My Epics">
<Grid Background="#FFE5E5E5">
<Border Margin="0,52,0,0" BorderThickness="1" BorderBrush="Black">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel x:Name="sp_ports" Orientation="Vertical"/>
</ScrollViewer>
</Border>
<Button x:Name="btn_capture" Content="Save to png" Margin="0,10,114,0" VerticalAlignment="Top" Height="31" Background="White" HorizontalAlignment="Right" Width="99" Click="Btn_capture_Click"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
public RenderTargetBitmap GetImage()
{
Size size = new Size(sp_ports.ActualWidth, sp_ports.ActualHeight);
if (size.IsEmpty)
return null;
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(sp_ports), null, new Rect(new Point(), size));
context.Close();
}
result.Render(drawingvisual);
return result;
}
public static void SaveAsPng(RenderTargetBitmap src)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.Filter = "PNG Files | *.png";
dlg.DefaultExt = "png";
if (dlg.ShowDialog() == true)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
using (var stream = dlg.OpenFile())
{
encoder.Save(stream);
}
}
}
private void Btn_capture_Click(object sender, RoutedEventArgs e)
{
SaveAsPng(GetImage());
}
What it should render as (with some info blacked out)
All UIElements
inherit from Visual
, so you can feed your StackPanel
to the Render
method directly.
public RenderTargetBitmap GetImage()
{
Size size = sp_ports.DesiredSize;
if (size.IsEmpty)
return null;
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
result.Render(sp_ports);
return result;
}
UPDATE
As pointed out by @Clemens, there are some subtle intricacies to using the UIElement
directly. His other comment, however, is the million dollar one.
Size size = uiElement.DesiredSize
Gives us the size of the visible portion of uiElement
.
Size size = new Size(uiElement.ActualWidth, uiElement.ActualHeight)
Returns the full size of uiElement
, also extending in the non-visible range.
Given you've run into this problem, you're after the latter. The main gotcha is you'll need to reevaluate the visual before rendering. Currently, you're projecting the full visual to the desired size (the visible part) of the UIElement
.
public RenderTargetBitmap GetImage(FrameworkElement element)
{
Size size = new Size(element.ActualWidth, element.ActualHeight);
if (size.IsEmpty)
return null;
element.Measure(size);
element.Arrange(new Rect(size));
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(element), null, new Rect(new Point(), size));
}
result.Render(drawingvisual);
return result;
}
I use FrameworkElement
to incorporate ActualWidth
and ActualHeight
.
UPDATE 2
As soon as I change the size of the stack panel, the screenshot gets hosed again. It seems to remember whatever the longest state was and squishes based on that.
After some fiddling around I was able to reproduce your issue. It occurs when the StackPanel
has to extend to fill any remaining space. The solution is to give the uiElement
infinite space to calculate its desired size, which relieves us from the dependency on actual sizes.
public RenderTargetBitmap GetImage(FrameworkElement element)
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(element.DesiredSize));
Size size = element.DesiredSize;
if (size.IsEmpty)
return null;
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(element), null, new Rect(new Point(), size));
}
result.Render(drawingvisual);
return result;
}
I've checked Expander
behavior (ref test app) but couldn't find anything funny going on there.
For completeness, here's my test app.
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public RenderTargetBitmap GetImage(FrameworkElement element)
{
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(element.DesiredSize));
Size size = element.DesiredSize;
if (size.IsEmpty)
return null;
RenderTargetBitmap result = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingvisual = new DrawingVisual();
using (DrawingContext context = drawingvisual.RenderOpen())
{
context.DrawRectangle(new VisualBrush(element), null, new Rect(new Point(), size));
}
result.Render(drawingvisual);
return result;
}
public static void SaveAsPng(RenderTargetBitmap src)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.Filter = "PNG Files | *.png";
dlg.DefaultExt = "png";
if (dlg.ShowDialog() == true)
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(src));
using (var stream = dlg.OpenFile())
{
encoder.Save(stream);
}
}
}
private void Btn_capture_Click(object sender, RoutedEventArgs e)
{
SaveAsPng(GetImage(sp_ports));
}
}
}
MainWindow.cs
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Top" Click="Btn_capture_Click">Take Pic</Button>
<StackPanel x:Name="sp_ports">
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Header="H1" Width="40"/>
<DataGridTextColumn Header="H2" Width="*"/>
</DataGrid.Columns>
</DataGrid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200" />
<RowDefinition Height="Auto" />
<RowDefinition Height="400" />
</Grid.RowDefinitions>
<StackPanel Background="Red"/>
<Expander Grid.Row="1" ExpandDirection="Down" IsExpanded="False">
<TabControl Height="400">
<TabItem Header="Tab 1">
<TextBox FontSize="50" TextWrapping="Wrap">Text for Tab 1</TextBox>
</TabItem>
<TabItem Header="Tab 2">
<TextBox FontSize="50" TextWrapping="Wrap">Text for Tab 1</TextBox>
</TabItem>
</TabControl>
</Expander>
<StackPanel Grid.Row="2" Background="Blue"/>
</Grid>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Header="H1" Width="40"/>
<DataGridTextColumn Header="H2" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DockPanel>
</Window>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With