将控件导出到 png WPF C# 时数据上下文不更新元素

Data Context not updating elements when exporting control to a png WPF C#

我正在开发一个可以为纸牌游戏打印纸牌的程序。在程序中,每种卡类型都有一个 class 和一个用户控件。我有一个包含我所有卡片的大清单。为了打印卡片,我在后台代码中动态创建控件,将它们添加到打印文档中,然后进行打印。这行得通!下面是我使用的控件的生成示例:

AttackCardControl cpTop = new AttackCardControl();
cpTop.DataContext = StateManager.CardsToPrint.ElementAt(i);

Viewbox vb = new Viewbox() { Width = 240, Height = 336 };
vb.Child = cpTop;

sp.Children.Add(vb);

sp 是我用来在页面上排列卡片的堆栈面板,i 是包含它的 for 循环的迭代器。我 运行 通过一系列 for 循环以及其他一些无关紧要的小东西,它工作得很好。

现在我正在创建一项新功能,允许用户将卡片导出为 PNG。我决定以与我打印卡片的方式非常相似的方式来做这件事。这是我使用的:

for (int i = 0; i < StateManager.CardsToPrint.Count; ++i)
{
    Canvas cv = new Canvas();
    cv.Width = 825;
    cv.Height = 1125;

    if (StateManager.CardsToPrint.ElementAt(i).GetType() == typeof(AttackCard))
    {
        AttackCardControl cardControl = new AttackCardControl();
        cardControl.DataContext = StateManager.CardsToPrint.ElementAt(i);
        cv.Children.Add(cardControl);
    }

    FileHandling.ExportToPng(new Uri(path + "/" + StateManager.CardsToPrint.ElementAt(i).Name + ".png"), cv);
}

这应该将我在列表中的所有攻击卡导出为 png,确实如此。然而,绑定到数据上下文的元素没有得到更新(名称、效果文本、风味文本等)。所以最后,我只得到一张有正确文件名的空白攻击卡。当我在调试时按照它进行操作时,DataContext 仍然填充了所有正确的数据,但是当它导出到 png 时,用户控件的 none 元素显示它们的值。这是我的 ExportToPng 方法的代码:

public static void ExportToPng(Uri path, Canvas surface)
{
    if (path == null) return;

    // Save current canvas transform
    Transform transform = surface.LayoutTransform;
    // reset current transform (in case it is scaled or rotated)
    surface.LayoutTransform = null;

    // Get the size of canvas
    System.Windows.Size size = new System.Windows.Size(surface.Width, surface.Height);
    // Measure and arrange the surface
    // VERY IMPORTANT
    surface.Measure(size);
    surface.Arrange(new Rect(size));

    // Create a render bitmap and push the surface to it
    RenderTargetBitmap renderBitmap =
        new RenderTargetBitmap(
        (int)size.Width,
        (int)size.Height,
        96d,
        96d,
        PixelFormats.Pbgra32);
    renderBitmap.Render(surface);

    // Create a file stream for saving image
    using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
    {
        // Use png encoder for our data
        PngBitmapEncoder encoder = new PngBitmapEncoder();
        // push the rendered bitmap to it
        encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
        // save the data to the stream
        encoder.Save(outStream);
    }

    // Restore previously saved layout
    surface.LayoutTransform = transform;
}

我只是不明白为什么它适用于打印,但不适用于导出到 PNG。任何帮助将不胜感激。

更新

我刚刚创建了一个有这个问题的示例项目,似乎找不到解决它的方法。示例项目有 3 个东西:MainWindow、UserControl1 和 Model.cs.

MainWindow 是我用来控制和显示问题的。后面的代码包含错误发生位置的所有逻辑。对于这个简单的例子,我没有理会 MVVM。 这是 XAML:

<Window x:Class="ExportTest.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:ExportTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel Name="sp" Margin="10">
        <Button Content="Export Control to PNG" Click="Button_Click" Width="150" Height="30"/>
    </StackPanel>
</Window>

下面是代码:

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

namespace ExportTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Model m = new Model("Testing");
            UserControl1 uc1 = new UserControl1();
            uc1.DataContext = m;
            sp.Children.Add(uc1);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var dialog = new System.Windows.Forms.FolderBrowserDialog();
            if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                string path = dialog.SelectedPath;
                Canvas cv = new Canvas();
                cv.Width = 825;
                cv.Height = 1125;

                Model m = new Model("Testing");
                UserControl1 uc1 = new UserControl1();
                uc1.DataContext = m;

                cv.Children.Add(uc1);

                // The card control is losing it's data context
                ExportToPng(new Uri(path + "/" + m.Name + ".png"), cv);
            }

        }

        public void ExportToPng(Uri path, Canvas surface)
        {
            if (path == null) return;

            //// Save current canvas transform
            //Transform transform = surface.LayoutTransform;
            //// reset current transform (in case it is scaled or rotated)
            //surface.LayoutTransform = null;

            // Get the size of canvas
            Size size = new Size(surface.Width, surface.Height);
            // Measure and arrange the surface
            // VERY IMPORTANT
            surface.Measure(size);
            surface.Arrange(new Rect(size));

            // Create a render bitmap and push the surface to it
            RenderTargetBitmap renderBitmap =
              new RenderTargetBitmap(
                (int)size.Width,
                (int)size.Height,
                96d,
                96d,
                PixelFormats.Pbgra32);
            renderBitmap.Render(surface);

            // Create a file stream for saving image
            using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
            {
                // Use png encoder for our data
                PngBitmapEncoder encoder = new PngBitmapEncoder();
                // push the rendered bitmap to it
                encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
                // save the data to the stream
                encoder.Save(outStream);
            }

            //// Restore previously saved layout
            //surface.LayoutTransform = transform;
        }
    }
}

这是 UserControl1(我们试图导出到 PNG 的用户控件)的 XAML:

<UserControl x:Class="ExportTest.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:ExportTest"
             mc:Ignorable="d">
    <StackPanel Orientation="Horizontal" Background="Red">
        <TextBlock Text="Name: "/>
        <TextBlock Text="{Binding Name}"/>
    </StackPanel>
</UserControl>

此用户控件不需要隐藏代码。

最后,我们有模型类。这将是 UserControl1 的数据上下文模型。这是 class:

namespace ExportTest
{
    public class Model
    {
        private string _Name;

        public string Name { get { return _Name; } set { _Name = value; } }

        public Model()
        {
            Name = "";
        }

        public Model(string name)
        {
            Name = name;
        }
    }
}

这将重现我的确切问题。在 Main Window 中,我们得到如下所示的内容:

如您所见,UserControl1 正在捕获 DataContext 并显示名称 Testing。但是,如果我单击“将控制导出到 PNG”按钮,这就是我导出的 PNG 文件的结果:

不知何故,我的数据丢失了。有什么线索吗?感谢您的帮助!

// Save current canvas transform
Transform transform = surface.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
surface.LayoutTransform = null;

之后

// Restore previously saved layout
surface.LayoutTransform = transform;

并不像您认为的那样工作。 Transform 是 class,不是结构。当您将 surface.LayoutTransform 设置为 null 时,您也将 transform 设置为 null。

尝试注释掉所有这些行,看看会发生什么。如果它有效但卡片被旋转,那么您必须深度克隆布局转换,或者只保存确实发生变化的属性。

所以我刚刚在这里找到了答案: Saving FrameworkElement with its DataContext to image file does no succeed

在我们将控件呈现为图像之前,需要调用 UpdateLayout() 来修复绑定。