Pregunta

En mi aplicación WPF, tengo particular Window que contiene, entre otros controles, un DocumentViewer.

Cuando esta ventana se abre y 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, de modo que la ventana principal de la aplicación aún responde mientras el documento se está construyendo.

Basado en los consejos en esta página web, Abro mi ventana en un nuevo hilo 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();
}

He estado contento con esta configuración hasta ahora, pero me encontré con un problema.

MyDocumentViewerWindow Contiene un botón de impresión, que hace referencia al comando de impresión incorporado, dirigido a DocumentViewer:

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

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

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

La traza 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 presentimiento es que el diálogo de impresión se abre en el hilo de la interfaz de usuario principal e intenta acceder al documento creado y propiedad de mi propio hilo, de ahí el bloqueo.

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

¿Fue útil?

Solución

Después de un poco más de Google, me topé con el siguiente hilo, que parece ser el problema exacto que estoy teniendo.

Printdialog y un problema severo de hilo de interfaz de usuario secundario

En ese hilo, el tipo finalmente usa una clase de printdialog personalizada (cuyo código fuente se encuentra aquí), que es muy similar al de printdialog incorporado, pero con algunos ajustes para corregir estos errores de hilo cruzado (y también anula el escritor de documentos XPS, que aparentemente se relaciona aún más en el hilo de interfaz de usuario principal de la aplicación)

Copié y pegé el código para ese printdialog personalizado (y cambié el nombre de la clase a ThreadSafePrintDialog), eliminó el comandante de mi botón de impresión y, en su lugar, use 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.

Otros consejos

Tu presentimiento es correcto. No puede acceder a este objeto en el hilo de la interfaz de usuario cuando ha sido creado por otro hilo.

Creo que tienes algunas opciones:

  1. Puede crear este documento en el hilo de la interfaz de usuario, tal vez recopile la información que necesita en un hilo de fondo y luego construir el objeto en el hilo de la interfaz de usuario. Depende de lo que implique su creación de documentos. Podrías 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. ¿Quizás podría enviar este comando al hilo que crea este otro documento? Agárrate a este hilo y haz un thread.Invoke(printMethod)

  3. Podrías investigar Objetos congelables. Mire la parte inferior de esta página, encabezando "Creación de su propia clase congelable". Esto haría que su documento sea seguro de hilo para acceder desde un hilo diferente al que lo creó.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top