Imprimir o conteúdo de um DocumentViewer em um thread de IU diferente
-
27-10-2019 - |
Pergunta
Em meu aplicativo WPF, tenho um Window
particular que contém, entre outros controles, um DocumentViewer
.
Quando esta janela é aberta e carregada, ela cria dinamicamente um FixedDocument
com um indicador de progresso e, em seguida, exibe-o no DocumentViewer
. Funciona e, para melhorar a experiência do usuário, executo esta janela em seu próprio thread, de modo que a janela principal do aplicativo ainda responda enquanto o documento está sendo compilado.
Com base nas dicas em esta página da web , eu abro minha janela em um novo tópico como este:
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();
}
Estou feliz com essa configuração até agora, mas acabei de ter um problema.
MyDocumentViewerWindow
contém um botão de impressão, que faz referência ao comando Imprimir integrado, direcionado ao DocumentViewer:
<Button Command="Print" CommandTarget="{Binding ElementName=MyDocumentViewer}">Print</Button>
Antes de ter a janela em seu próprio thread, isso funcionava bem. Mas agora, quando clico nele, o aplicativo trava. O Visual Studio 2010 destaca a seguinte linha do código acima como o local da falha, com a mensagem ' O thread de chamada não pode acessar este objeto porque um thread diferente o possui. ':
System.Windows.Threading.Dispatcher.Run();
O rastreamento de pilha começa assim:
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)
...
Meu palpite é que a caixa de diálogo de impressão está abrindo no thread de interface do usuário principal e tentando acessar o documento criado e pertencente ao meu próprio thread, daí a falha.
Alguma ideia de como posso resolver isso? Eu gostaria de manter a janela em seu próprio segmento.
Solução
Depois de mais algumas pesquisas no Google, descobri o seguinte tópico, que parece ser exatamente o problema que estou tendo.
PrintDialog e uma IU secundáriathread grave problema
Nesse tópico, o cara eventualmente usa uma classe PrintDialog personalizada (o código-fonte da qual se encontra aqui ), que é muito parecido com o PrintDialog integrado, mas com alguns ajustes para corrigir esses bugs de thread cruzado (e também substitui o XPS Document Writer, que aparentemente se liga ainda mais ao thread de interface do usuário principal do aplicativo)
Copiei e colei o código desse PrintDialog personalizado (e renomeei a classe para ThreadSafePrintDialog
), removi o CommandTarget do botão Imprimir e, em vez disso, usei meu próprio método Print:
private void Print_Executed(object sender, ExecutedRoutedEventArgs args) {
var printDialog = new ThreadSafePrintDialog();
if (!printDialog.ShowDialog(this)) return;
printDialog.PrintDocument(DocumentViewer.Document.DocumentPaginator, "My Document");
}
Funciona perfeitamente.
Outras dicas
Seu palpite está correto.Você não pode acessar este objeto no thread de IU quando ele foi criado por outro thread.
Acredito que você tenha algumas opções:
-
Você pode criar este documento no thread de IU, talvez reunir as informações de que precisa em um thread de segundo plano e, então, construir o objeto no thread de IU.Depende do que envolve a criação do seu documento.Você poderia fazer algo como:
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) }); }); }
-
Você poderia enviar este comando para o thread que cria este outro documento?Segure este tópico e faça um
thread.Invoke(printMethod)
-
Você pode pesquisar Objetos Freezable .Olhe na parte inferior desta página, título "Criando sua própria classe congelável".Isso tornaria seu documento thread-safe para acessar de um thread diferente daquele que o criou.