WPF StackPanel PNG截图无法正确渲染

8
我正在尝试创建一个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());
}

这里输入图片描述

渲染后的效果(有一些信息被隐藏了) 这里输入图片描述


屏幕上看起来怎么样?很难想象所有的内容都应该放在那个小图像中并且看起来好看。 - redcurry
我一直在复制和粘贴“mainGrid”,试图找到可以渲染的组件。 - JeremyK
@redcurry添加了正确显示内容的捕获。 - JeremyK
我应该注意,如果我在滚动查看器级别或更高级别进行渲染,则可以正确地进行渲染,但仅限于可见区域。 - JeremyK
1
这是你的全部代码吗?我用起来很好用。你检查了 ActualWidthActualHeight 是否有预期值了吗?顺便提一下,当使用 using 块释放时,你不需要调用 context.Close() - Clemens
显示剩余2条评论
1个回答

7

所有UI元素都继承自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)

返回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来整合ActualWidth和ActualHeight。
更新2

一旦我改变了StackPanel的大小,截图就会再次出现问题。它似乎记住了最长状态,并根据那个状态进行压缩。

经过一些尝试,我成功地重现了您的问题。当StackPanel必须扩展以填充任何剩余空间时,这种情况就会发生。解决方案是给uiElement无限的空间来计算其所需的大小,从而使我们摆脱了对实际大小的依赖。
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>

1
只有当元素的边距为零时,此方法才能正常工作。使用VisualBrush通常是更好的方法。 - Clemens
运行得非常好,谢谢你挽救了这一天。在这个问题上我一直很困扰。 - JeremyK
似乎我说得太早了。一旦我改变堆栈面板的大小,屏幕截图再次出现问题。它似乎记住了最长状态,并基于该状态进行压缩。 - JeremyK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接