Печать содержимого DocumentViewer в другом потоке пользовательского интерфейса - PullRequest
5 голосов
/ 07 сентября 2011

В моем приложении 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)
...

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

Есть идеи, как мне это решить? Я хотел бы сохранить окно в своей собственной теме.

Ответы [ 2 ]

7 голосов
/ 07 сентября 2011

После еще одного поиска в 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");
}

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

1 голос
/ 07 сентября 2011

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

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

  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».Это сделает ваш документ потокобезопасным для доступа из потока, отличного от того, который его создал.

...