WPF StackPanel PNG 捕获无法正确呈现

WPF StackPanel PNG Capture not rendering correctly

我正在尝试创建一个 StackPanel 的 png 捕获,但是当我保存时,我得到一个扭曲的视图,其中所有内容都是黑色矩形,并且大小不正确。图像保存中的宽度和高度是正确的,但是所有内容都被强制到顶部并被压在一起

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

它应该呈现为什么(一些信息被涂黑了)

所有 UIElements 都继承自 Visual,因此您可以将 StackPanel 直接提供给 Render 方法。

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

更新

正如@Clemens 所指出的,直接使用 UIElement 有一些微妙的复杂之处。然而,他的另一个评论是百万美元。

Size size = uiElement.DesiredSize

给我们 uiElement.

可见部分的大小
Size size = new Size(uiElement.ActualWidth, uiElement.ActualHeight)

Returns uiElement 的完整尺寸,也在不可见范围内延伸。

鉴于您已经 运行 解决了这个问题,您正在寻找后者。主要问题是您需要在渲染之前重新评估视觉效果。目前,您正在将完整的视觉效果投影到 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;
}

我用FrameworkElement合并了ActualWidthActualHeight


更新 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.

经过一番折腾,我重现了您的问题。它发生在 StackPanel 必须扩展以填充任何剩余的 space 时。解决方案是给uiElement无限space来计算它想要的大小,这样就可以摆脱对实际大小的依赖。

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

我检查了 Expander 行为(参考测试应用程序),但没有发现任何有趣的事情。


为完整起见,这是我的测试应用程序。

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>