Печать содержимого документов в другом потоке пользовательского интерфейса
-
27-10-2019 - |
Вопрос
В моем приложении 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; writer, PrintTicket&amp; partialTrustPrintTicket, PrintQueue&amp; partialTrustPrintQueue, Double&amp; width, Double&amp; height, String jobDescription)
at System.Printing.PrintQueue.CreateXpsDocumentWriter(String jobDescription, PrintDocumentImageableArea&amp; documentImageableArea)
at System.Windows.Controls.Primitives.DocumentViewerBase.OnPrintCommand()
at System.Windows.Controls.Primitives.DocumentViewerBase.ExecutedRoutedEventHandler(Object target, ExecutedRoutedEventArgs args)
...
Я догадываюсь, что диалог печати открывается в основном потоке пользовательского интерфейса и пытается получить доступ к документу, который создан и принадлежит моей собственной ветке, отсюда и сбой.
Есть идеи, как я могу решить это? Я хотел бы сохранить окно в его собственной ветке.
Решение
После еще некоторых гуглей я наткнулся на следующую ветку, которая, кажется, является точной проблемой, которая у меня есть.
PrintDialog и вторичная нить пользовательского интерфейса серьезная проблема
В этой теме парень в конечном итоге использует пользовательский класс printdialog (исходный код которого найден здесь), который почти такой же, как встроенный printdialog, но с несколькими изменениями, чтобы исправить эти кросс-тренажеры (и также переопределяет автора документов XPS, что, по-видимому, еще больше связывается с основным потоком пользовательского интерфейса приложения)
Я скопировал и вставил код для этого пользовательского 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");
}
Работает отлично.
Другие советы
Ваш догад верен. Вы не можете получить доступ к этому объекту в потоке пользовательского интерфейса, когда он был создан другим потоком.
Я считаю, что у вас есть несколько вариантов:
Вы можете создать этот документ в потоке пользовательского интерфейса, возможно, собрать нужную информацию в фоновом потоке, а затем фактически построить объект в потоке пользовательского интерфейса. Это зависит от того, что влечет за собой создание вашего документа. Вы могли бы сделать что -то вроде:
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) }); }); }
Возможно, вы могли бы отправить эту команду в поток, который создает этот другой документ? Держись за эту ветку и сделайте
thread.Invoke(printMethod)
Вы могли бы посмотреть Замораживаемые объекты. Анкет Посмотрите на нижнюю часть этой страницы, направляя «Создание собственного замораживаемого класса». Это сделало бы ваш документ-поток для доступа из другого потока, чем тот, который создал его.