2011-09-07 39 views
5

En mi aplicación WPF, tengo Window que contiene, entre otros controles, un DocumentViewer.Imprimir el contenido de un DocumentViewer en un subproceso de interfaz de usuario diferente

Cuando esta ventana se abre y se carga, construye dinámicamente un FixedDocument con un indicador de progreso, y luego lo muestra en el DocumentViewer. Funciona, y para mejorar la experiencia del usuario, ejecuto esta ventana en su propio hilo, para que la ventana principal de la aplicación siga respondiendo mientras se está construyendo el documento.

Sobre la base de las puntas en this web page, abro la ventana en un nuevo hilo de esta manera:

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(); 
} 

he sido feliz con esta configuración hasta ahora, pero me acabo de encontrar con un problema.

MyDocumentViewerWindow contiene un botón de impresión, que hace referencia a la incorporada en el comando de impresión, dirigido a la DocumentViewer:

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

Antes de que tuviera la ventana en su propio hilo, esto funcionó bien. Pero ahora, cuando hago clic en él, la aplicación falla. Visual Studio 2010 resalta la siguiente línea del código anterior como la ubicación del bloqueo, con el mensaje 'El hilo de llamada no puede acceder a este objeto porque lo posee un hilo diferente. ':

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

El seguimiento de la pila comienza así:

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

Mi impresión es que el diálogo de impresión se está abriendo en el hilo principal de interfaz de usuario, y tratando de acceder al documento que se crea, y de propiedad por mi propio hilo, de ahí el choque.

¿Alguna idea de cómo puedo solucionar esto? Me gustaría mantener la ventana en su propio hilo.

Respuesta

6

Después de buscar en Google, me encontré con el siguiente tema, que parece ser el problema exacto que estoy teniendo.

PrintDialog and a secondary UI thread severe problem

En ese hilo, el tipo con el tiempo utiliza una clase PrintDialog personalizado (el código fuente de la que se encuentra here), lo que es lo mismo que una función de PrintDialog, pero con algunos retoques para fijar estos errores cruzados (y también anula el Escritor de documentos XPS, que aparentemente se vincula aún más con el subproceso principal de la aplicación)

Copié y pegué el código para ese PrintDialog personalizado (y renombré la clase a ThreadSafePrintDialog) , eliminé el CommandTarget de mi botón Imprimir y en su lugar uso mi propio método de impresión:

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

1

Tu corazonada es la correcta. No puede acceder a este objeto en el subproceso de interfaz de usuario cuando ha sido creado por otro subproceso.

Creo que tiene algunas opciones:

1) Se puede crear este documento en el hilo de interfaz de usuario, tal vez recopilar la información que necesita en un subproceso de fondo y luego en realidad construir el objeto en el subproceso de interfaz de usuario. Depende de lo que implique su creación de documento. Podría hacer 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) 
     }); 
    }); 
} 

2) ¿Podría enviar este comando al hilo que crea este otro documento? Aférrate a este hilo y haz un thread.Invoke(printMethod)

3) Podrías mirar en Freezable Objects. Mire la parte inferior de esta página, titulada "Creando su propia clase de Freezable". Esto haría que su documento sea seguro para la ejecución de subprocesos desde un subproceso diferente al que lo creó.

+0

Gracias por eso. Crear mi documento implica crear instancias de FixedDocument, agregar objetos FixedPage, rellenarlos con controles, etc. Debido a que FixedDocument es un DispatcherObject, no pude crearlo en una cadena de fondo y luego configurarlo como fuente para DocumentViewer, ya que también creó una violación entre hilos. Descubrí que tengo que crear mi documento en el mismo hilo que mi DocumentViewer, es decir, en un hilo de interfaz de usuario :(Pero encontré una solución a mi problema, la publicaré ahora. – Ross

Cuestiones relacionadas