FlowDocument保存为XPS文档时缺失图像。

7
我在将FlowDocument保存为XPS文档时遇到了一些困难,无法显示其中包含的图像。
我的操作步骤如下:
1.使用WPF的Image控件创建图像。我在调用BeginInit/EndInit之间设置图像源。
2.将图像添加到FlowDocument中并将其包装在BlockUIContainer中。
3.使用修改过的此代码将FlowDocument对象保存为XPS文件。
如果我使用XPS查看器查看保存的文件,则图像不会显示。问题在于,图片直到由WPF在屏幕上实际显示时才被加载,因此它们不会保存到XPS文件中。因此,有一个解决方法:如果我首先使用FlowDocumentPageViewer将文档显示在屏幕上,然后再保存XPS文件,那么图像就会被加载并显示在XPS文件中。即使FlowDocumentPageViewer被隐藏,这也可以工作。但这给我带来了另一个挑战。以下是我希望做的(伪代码):
void SaveDocument()
{
    AddFlowDocumentToFlowDocumentPageViewer();
    SaveFlowDocumentToXpsFile();
}

当然,这种方法行不通,因为在将文档保存到XPS文件之前,FlowDocumentPageViewer永远没有机会显示其内容。我尝试使用Dispatcher.BeginInvoke将SaveFlowDocumentToXpsFile包装起来,但没有帮助。
我的问题是:
  1. 是否可以在不实际显示文档的情况下强制加载图像以在保存XPS文件之前加载它们?(我尝试了调整BitmapImage.CreateOptions,但没有成功)。
  2. 如果问题#1没有解决方案,是否有一种方法可以告诉FlowDocumentPageViewer何时完成加载其内容,以便我知道何时创建XPS文件是安全的?

你找到了在打印之前在查看器中显示FlowDocument的方法吗?我正在考虑类似的“hack”来使我的文档正确呈现。 - Dennis
@DennisRoche:不幸的是,我从未找到比在保存到文件之前短暂地在屏幕上显示文档更好的解决方案。如果您找到更好的解决方案,请告诉我。 - Jakob Christensen
我可能有一个使用ContextualLayoutManager遍历逻辑树的解决方案。如果它有效,我会告诉你。否则,我将采用像你所做的那样在查看器中加载文档,但是我会将窗口位置设置为X:10,000 Y:10,000,以便用户看不到它。 - Dennis
4个回答

3
最终的解决方案与您想到的一样,即将文档放入查看器并在屏幕上简要显示。下面是我为此编写的帮助方法。
private static string ForceRenderFlowDocumentXaml = 
@"<Window xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
          xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
       <FlowDocumentScrollViewer Name=""viewer""/>
  </Window>";

public static void ForceRenderFlowDocument(FlowDocument document)
{
    using (var reader = new XmlTextReader(new StringReader(ForceRenderFlowDocumentXaml)))
    {
        Window window = XamlReader.Load(reader) as Window;
        FlowDocumentScrollViewer viewer = LogicalTreeHelper.FindLogicalNode(window, "viewer") as FlowDocumentScrollViewer;
        viewer.Document = document;
        // Show the window way off-screen
        window.WindowStartupLocation = WindowStartupLocation.Manual;
        window.Top = Int32.MaxValue;
        window.Left = Int32.MaxValue;
        window.ShowInTaskbar = false;
        window.Show();
        // Ensure that dispatcher has done the layout and render passes
        Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => {}));
        viewer.Document = null;
        window.Close();
    }
}

编辑:我刚刚在这个方法中添加了window.ShowInTaskbar = false,因为如果您快速操作,您会看到窗口出现在任务栏上。

用户将永远不会“看到”该窗口,因为它被放置在Int32.MaxValue的屏幕外,这是早期多媒体制作(例如Macromedia/Adobe Director)中常见的技巧。

对于搜索并找到这个问题的人们,我可以告诉你,没有其他方法来强制文档呈现。

希望有所帮助,


谢谢你的回答。虽然我本来想看到不同的解决方案,但我已经将你的答案标记为被接受的答案。 - Jakob Christensen
我本想有一个更好的解决方案,但我相信已经尝试了所有其他可能性。这是一个相当干净的解决方法,因为用户将永远不会看到窗口出现在屏幕之外。 - Dennis

1

有几件事情... 你确定图像在写入之前已经调整大小了吗?通常你需要调用控件上的Measure方法,以便它可以相应地调整大小(无限制让控件扩展到其宽度和高度)

image.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

有时候,您需要唤醒UI线程,以便控件中的所有内容都得到更新。
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>{}));

使用Dispatcher技巧确实适用于小图像或已缓存的图像,但对于在后台下载的大型图像则不适用。有没有想法如何延迟保存到XPS的尝试,直到1-2秒的下载完成?或者更好的是,等待FlowDocument可能包含的所有图像都完全下载和渲染? - BCA
@BCA 你可以尝试使用 DispatcherPriority.Idle 吗? - user1228
是的,我使用了 DispatcherPriority.SystemIdle。我猜如果图像在后台下载,调度程序技巧就不起作用了? - BCA
@BCA听起来是这样的。您将需要跟踪下载的图像数量,然后在完成后继续进行。 - user1228
我想要做的是以某种方式1)使用await/async等待每个图像的下载(如果它尚未在WPF图像缓存中),然后2)将下载的图像放入缓存,最后3)加载我的FlowDocument。理论上,如果所有图像都已经在缓存中,那么它将没有问题。你有任何想法如何完成#1和#2吗? - BCA
@BCA 不是的。听起来是个好问题。你想知道如何知道ImageSource何时完成下载吗?你可以将其转换为BitmapImage并观察DownloadCompleted事件。这可能需要大量的恶意攻击才能获得每个。也许添加一个自定义IValueConverter,将URL转换为BitmapImage,跟踪所有这些内容,并公开一些静态事件以指示所有下载已完成?闻到了新鲜的黑客气味。闻起来像粪便。嗯。 - user1228

0

您不必显示文档即可将图像保存到XPS中。您是否在XpsSerializationManager上调用了commit?

FlowDocument fd = new FlowDocument();

        fd.Blocks.Add(new Paragraph(new Run("This is a test")));

        string image = @"STRING_PATH";

        BitmapImage bi = new BitmapImage();
        bi.BeginInit();
        bi.UriSource = new Uri(image, UriKind.RelativeOrAbsolute);
        bi.CacheOption = BitmapCacheOption.OnLoad;
        bi.EndInit();
        MemoryStream ms = new MemoryStream();
        Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
        Uri pkgUri = bi.UriSource;

        PackageStore.AddPackage(pkgUri, pkg);


        Image img = new Image();
        img.Source = bi;

        BlockUIContainer blkContainer = new BlockUIContainer(img);

        fd.Blocks.Add(blkContainer);


        DocumentPaginator paginator = ((IDocumentPaginatorSource)fd).DocumentPaginator;

      using (XpsDocument xps = new XpsDocument(@"STRING PATH WHERE TO SAVE FILE", FileAccess.ReadWrite, CompressionOption.Maximum))
        {
            using (XpsSerializationManager serializer = new XpsSerializationManager(new XpsPackagingPolicy(xps), false))
            {
                serializer.SaveAsXaml(paginator);
                serializer.Commit();
            }
        }

我尝试调用XpsSerializationManager.Commit,但是它没有产生想要的效果。 - Jakob Christensen
抱歉没有及时回复。这是一个真正的脏版本,应该可以工作。让我知道它的结果如何。 - jfin3204
我还没有机会尝试它。我会回复你 :-) - Jakob Christensen
你有机会尝试过这个吗? - jfin3204

0

我通过将FlowDocument放入查看器中,然后进行测量/排列来解决了这个问题。

FlowDocumentScrollViewer flowDocumentScrollViewer = new FlowDocumentScrollViewer();
flowDocumentScrollViewer.Document = flowDocument;
flowDocumentScrollViewer.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
flowDocumentScrollViewer.Arrange(new Rect(new Point(0, 0), new Point(Double.MaxValue, Double.MaxValue)));

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