Отсутствующие изображения в FlowDocument сохранены как документ XPS

У меня возникают некоторые трудности с получением изображений, содержащихся в FlowDocument, для отображения, когда FlowDocument сохраняется как документ XPS.

Вот что я делаю:

  1. Создайте изображение с помощью Image управление WPF. Я установил источник изображения, заключенный в скобки с помощью вызовов BeginInit / EndInit.
  2. Добавьте изображение в FlowDocument, заключив его в BlockUIContainer .
  3. Сохраните объект FlowDocument в файл XPS, используя измененную версию этот код.

Если я затем просматриваю сохраненный файл в средстве просмотра XPS, изображение не отображается. Проблема в том, что изображения не загружаются до тех пор, пока они не будут отображены на экране WPF, поэтому они не сохраняются в файле XPS. Следовательно, есть обходной путь: если я сначала покажу документ на экране с помощью FlowDocumentPageViewer, а затем сохраните файл XPS, изображение загружается и отображается в файле XPS. Это работает, даже если FlowDocumentPageViewer скрыт. Но это ставит меня перед другой проблемой. Вот что я хочу сделать (в псевдокоде):

void SaveDocument()
{
    AddFlowDocumentToFlowDocumentPageViewer();
    SaveFlowDocumentToXpsFile();
}

Это, конечно, не работает, поскольку FlowDocumentPageViewer никогда не получает возможности показать свое содержимое до того, как документ будет сохранен в файл XPS. Я попытался обернуть SaveFlowDocumentToXpsFile при вызове Dispatcher.BeginInvoke, но это не помогло.

Мои вопросы:

  1. Могу ли я каким-то образом заставить изображения загружаться перед сохранением файла XPS без фактического отображения документа на экране? (Я пробовал возиться с BitmapImage.CreateOptions безуспешно).
  2. Если нет решения для вопроса №1, есть ли способ узнать, когда FlowDocumentPageViewer завершил загрузку своего содержимого, чтобы я знал, когда он сохраняется для создания файла XPS?

person Jakob Christensen    schedule 11.03.2010    source источник
comment
Вы нашли способ показать FlowDocument в средстве просмотра перед печатью? Я подумываю о подобном взломе, чтобы мой документ отображался правильно.   -  person Dennis    schedule 26.02.2012
comment
@DennisRoche: Нет, к сожалению, я не нашел лучшего решения, чем кратко показать документ на экране перед сохранением его в файл. Пожалуйста, дайте мне знать, если найдете лучшее решение.   -  person Jakob Christensen    schedule 26.02.2012
comment
У меня может быть одно возможное решение, использующее ContextualLayoutManager для перехода к логическому дереву. Я дам вам знать, если это сработает, я дам вам знать. В противном случае я прибегну к загрузке документа в программу просмотра, как это сделали вы, однако установлю расположение окна на X: 10 000 Y: 10 000, чтобы пользователь не видел его.   -  person Dennis    schedule 27.02.2012


Ответы (4)


В конечном итоге решение было таким же, как и у вас, а именно: поместить документ в средство просмотра и кратко показать его на экране. Ниже приведен вспомогательный метод, который я написал, чтобы сделать это за меня.

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).

Для людей, которые ищут и находят этот вопрос, я могу сказать вам, что нет другого способа принудительно отобразить документ.

HTH,

person Dennis    schedule 27.02.2012
comment
Спасибо за ваш ответ. Я пометил ваш ответ как принятый, хотя мне хотелось бы увидеть другое решение ;-). - person Jakob Christensen; 27.02.2012
comment
Мне хотелось бы лучшего решения, но я считаю, что это исчерпало все другие возможности. Это довольно простой обходной путь, поскольку пользователь никогда не увидит, как окно выглядит так, как будто оно находится за пределами экрана. - person Dennis; 27.02.2012

Пара вещей ... Вы уверены, что размер изображения был задан до того, как он был написан? Обычно вам нужно вызвать Measure для элемента управления, чтобы он мог соответствующим образом изменить свой размер (бесконечность позволяет элементу управления расширяться до его ширины и высоты)

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

Кроме того, иногда вам нужно подтолкнуть поток пользовательского интерфейса, чтобы все обновлялось в элементе управления.

Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>{}));
person Community    schedule 18.01.2011
comment
Использование этой уловки Dispatcher действительно работает для небольших изображений или изображений, которые уже кэшированы, но не для больших изображений, которые загружаются из Интернета в фоновом режиме. Есть идеи, как отложить попытку сохранения в XPS до завершения 1-2-секундной загрузки? Или лучше подождать, пока все изображения, которые может содержать FlowDocument, будут полностью загружены и отрисованы? - person BCA; 07.02.2017
comment
@BCA, вы могли бы попробовать DispatcherPriority.Idle? - person ; 07.02.2017
comment
да, я использовал DispatcherPriority.SystemIdle. Я полагаю, уловка с диспетчером не работает, если изображение загружается в фоновом режиме? - person BCA; 07.02.2017
comment
@BCA похоже на это. Вам нужно будет отслеживать, сколько изображений загружается, а затем продолжить после завершения. - person ; 07.02.2017
comment
Я бы хотел как-то 1) await сначала загрузить каждое изображение (если оно еще не находится в кеше изображений WPF) с помощью await / async и 2) поместить загруженное изображение в кеш, затем 3) загрузить мой FlowDocument . Теоретически, если все изображения уже находятся в кеше, это не сработает. У вас есть идеи, как достичь пунктов 1 и 2? - person BCA; 07.02.2017
comment
@BCA Нет. Похоже, хороший вопрос. Вам интересно, как узнать, когда ImageSource завершил загрузку? Вы можете преобразовать его в BitmapImage и посмотреть событие DownloadCompleted. Чтобы получить каждый из них, может потребоваться множество неприятных взломов. Возможно, добавить настраиваемый IValueConverter, который преобразует URL-адрес в BitmapImage, отслеживает их все и предоставляет какое-то статическое событие, чтобы указать, что все загрузки завершены? Вдохните этот свежий аромат хака. Пахнет навозом. Мммм. - person ; 07.02.2017

Вам не нужно отображать документ, чтобы изображения сохранялись в xps. Вы вызываете фиксацию в XpsSerializationManager?

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();
            }
        }
person jfin3204    schedule 12.03.2012
comment
Я попытался вызвать XpsSerializationManager.Commit, но это не дало желаемого эффекта. - person Jakob Christensen; 13.03.2012
comment
Извините, что не ответил раньше. Вот настоящая грязная версия, которая должна работать. Позвольте мне знать, как это получается - person jfin3204; 17.03.2012
comment
У меня не было возможности попробовать. Я вернусь к вам :-) - person Jakob Christensen; 31.03.2012
comment
У вас уже была возможность попробовать это? - person jfin3204; 03.08.2012

Я смог решить эту проблему, бросив потоковый документ в средство просмотра, а затем выполнив измерение / аранжировку.

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)));
person Brian    schedule 16.01.2020