Печать содержимого DocumentViewer в другом потоке пользовательского интерфейса

В моем приложении WPF у меня есть конкретный Window, который содержит, среди других элементов управления, файл DocumentViewer.

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

На основе советов на этой веб-страницы, я открываю свое окно в новом потоке следующим образом:

public void ShowDocumentViewerWindow(params object[] data) {
    var thread = new Thread(() => {
        var window = new MyDocumentViewerWindow(new MyObject(data));
        window.Closed += (s, a) => window.Dispatcher.InvokeShutdown();
        window.Show();
        System.Windows.Threading.Dispatcher.Run();
    });
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
}

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

MyDocumentViewerWindow содержит кнопку печати, которая ссылается на встроенную команду «Печать», предназначенную для DocumentViewer:

<Button Command="Print" CommandTarget="{Binding ElementName=MyDocumentViewer}">Print</Button>

До того, как у меня было окно в отдельном потоке, это работало нормально. Но теперь, когда я нажимаю на нее, приложение вылетает. Visual Studio 2010 выделяет следующую строку из приведенного выше кода как место сбоя с сообщением «Вызывающий поток не может получить доступ к этому объекту, поскольку им владеет другой поток.»:

System.Windows.Threading.Dispatcher.Run();

Трассировка стека начинается так:

at System.Windows.Threading.Dispatcher.VerifyAccess()
at MS.Internal.Printing.Win32PrintDialog.ShowDialog()
at System.Windows.Controls.PrintDialog.ShowDialog()
at System.Printing.PrintQueue.GatherDataFromPrintDialog(PrintDialog printDialog, XpsDocumentWriter&amp;amp; writer, PrintTicket&amp;amp; partialTrustPrintTicket, PrintQueue&amp;amp; partialTrustPrintQueue, Double&amp;amp; width, Double&amp;amp; height, String jobDescription)
at System.Printing.PrintQueue.CreateXpsDocumentWriter(String jobDescription, PrintDocumentImageableArea&amp;amp; documentImageableArea)
at System.Windows.Controls.Primitives.DocumentViewerBase.OnPrintCommand()
at System.Windows.Controls.Primitives.DocumentViewerBase.ExecutedRoutedEventHandler(Object target, ExecutedRoutedEventArgs args)
...

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

Любые идеи, как я могу это решить? Я хотел бы сохранить окно в своем собственном потоке.


person Ross    schedule 07.09.2011    source источник


Ответы (2)


После еще нескольких поисков в Google я наткнулся на следующую тему, которая, похоже, является именно той проблемой, с которой я столкнулся.

Серьезная проблема с PrintDialog и дополнительным потоком пользовательского интерфейса

В этом потоке парень в конечном итоге использует собственный класс PrintDialog (исходный код которого находится здесь), который почти такой же, как встроенный PrintDialog, но с несколькими настройками для исправления этих межпоточных ошибок (и он также переопределяет XPS Document Writer, который, по-видимому, еще больше привязывается к основному потоку пользовательского интерфейса приложения)

Я скопировал и вставил код для этого пользовательского PrintDialog (и переименовал класс в ThreadSafePrintDialog), удалил CommandTarget моей кнопки «Печать» и вместо этого использовал свой собственный метод «Печать»:

private void Print_Executed(object sender, ExecutedRoutedEventArgs args) {
    var printDialog = new ThreadSafePrintDialog();
    if (!printDialog.ShowDialog(this)) return;

    printDialog.PrintDocument(DocumentViewer.Document.DocumentPaginator, "My Document");
}

Работает отлично.

person Ross    schedule 07.09.2011
comment
не могли бы вы поместить класс сюда, потому что ссылка мертва, я действительно борюсь с этой проблемой, и я думаю, что у нас точно такая же проблема - person Hakan Fıstık; 23.04.2018
comment
Я имею в виду, не могли бы вы поместить исходный код класса ThreadSafePrintDialog - person Hakan Fıstık; 23.04.2018

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

Я считаю, что у вас есть несколько вариантов:

  1. Вы можете создать этот документ в потоке пользовательского интерфейса, возможно, собрать необходимую информацию в фоновом потоке, а затем создать объект в потоке пользовательского интерфейса. Это зависит от того, что влечет за собой создание вашего документа. Вы можете сделать что-то вроде:

    public void CreateDocument(T inputDataForDocumentCreation) {
      var uiDispatcher = Dispatcher.CurrentDispatcher;
      ThreadPool.QueueUserWorkItem(_ => {
        // Load and create document components with yourDataForDocumentCreation
    
         dispatcher.BeginInvoke(DispatcherPriority.Normal, () => {
         //Actually create the document (this will happen on the UI thread, so it may be accessed from the UI thread)
      });
      });
    }
    
  2. Возможно, вы могли бы отправить эту команду в поток, который создает этот другой документ? Держитесь за эту тему и сделайте thread.Invoke(printMethod)

  3. Вы можете изучить Freezable Objects. Посмотрите внизу этой страницы, заголовок «Создание собственного класса Freezable». Это сделало бы ваш документ потокобезопасным для доступа из потока, отличного от того, который его создал.

person Kevek    schedule 07.09.2011
comment
Спасибо за это. Создание моего документа включает в себя создание экземпляра FixedDocument, добавление объектов FixedPage, заполнение их элементами управления и т. д. Поскольку FixedDocument является DispatcherObject, я не мог создать его в фоновом потоке, а затем установить в качестве источника для DocumentViewer, так как это также создает перекрестное нарушение. Я обнаружил, что должен создать свой документ в том же потоке, что и мой DocumentViewer, то есть в потоке пользовательского интерфейса :-( Но я только что нашел обходной путь для своей проблемы - я опубликую его сейчас. - person Ross; 07.09.2011