2011-09-07 11 views
5

Nella mia app WPF, ho un particolare Window che contiene, tra gli altri controlli, un DocumentViewer.Stampa del contenuto di un DocumentViewer in un thread dell'interfaccia utente diverso

Quando questa finestra viene aperta e caricata, crea dinamicamente un FixedDocument con un indicatore di avanzamento, quindi lo visualizza nello DocumentViewer. Funziona e, per migliorare l'esperienza dell'utente, eseguo questa finestra nella sua stessa thread, in modo che la finestra principale dell'applicazione sia ancora reattiva mentre il documento è in fase di costruzione.

Sulla base delle punte a this web page, apro la mia finestra in un nuovo thread come questo:

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

Sono stato felice con questa configurazione fino ad ora, ma ho appena incontrato un problema.

MyDocumentViewerWindow contiene un pulsante di stampa, che fa riferimento al comando built-in di stampa, destinato al DocumentViewer:

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

Prima ho avuto la finestra nel proprio thread, questo ha funzionato bene. Ma ora, quando faccio clic, l'applicazione si arresta in modo anomalo. Visual Studio 2010 evidenzia la seguente riga dal codice precedente come posizione di arresto anomalo, con il messaggio 'Il thread chiamante non può accedere a questo oggetto perché lo possiede un thread diverso. ':

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

L'analisi dello stack inizia così:

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

mia impressione è che la finestra di dialogo di stampa si sta aprendo nel thread principale dell'interfaccia utente, e cercando di accedere al documento che viene creato e di proprietà dalla mia stessa discussione, da qui l'incidente.

Qualche idea su come posso risolvere questo problema? Mi piacerebbe mantenere la finestra con il suo thread.

risposta

6

Dopo un po 'di ricerche su Google, mi sono imbattuto nel thread successivo, che sembra essere il problema esatto che sto avendo.

PrintDialog and a secondary UI thread severe problem

In quel filo, il ragazzo alla fine utilizza una classe PrintDialog personalizzato (il codice sorgente del quale si trova here), che è molto simile a built-in PrintDialog, ma con qualche ritocco per fissare questi errori cross-thread (e sovrascrive anche XPS Document Writer, che apparentemente si lega ancora di più al thread principale dell'interfaccia utente dell'applicazione)

Ho copiato e incollato il codice per quel PrintDialog personalizzato (e rinominato la classe in ThreadSafePrintDialog) , rimosso CommandTarget del pulsante Stampa e utilizzo invece il mio metodo di stampa:

private void Print_Executed(object sender, ExecutedRoutedEventArgs args) { 
    var printDialog = new ThreadSafePrintDialog(); 
    if (!printDialog.ShowDialog(this)) return; 

    printDialog.PrintDocument(DocumentViewer.Document.DocumentPaginator, "My Document"); 
} 

Funziona perfettamente.

1

La tua impressione è corretta. Non è possibile accedere a questo oggetto sul thread dell'interfaccia utente quando è stato creato da un altro thread.

Credo di avere un paio di opzioni:

1) è possibile creare questo documento sul thread UI, forse raccogliere le informazioni di cui avete bisogno in un thread in background e poi effettivamente costruire l'oggetto sul thread dell'interfaccia utente. Dipende da ciò che comporta la creazione del tuo documento. Potresti fare qualcosa del tipo:

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) Potresti forse inviare questo comando alla discussione che crea questo altro documento? Prendi questo thread e fai un thread.Invoke(printMethod)

3) Potresti cercare su Freezable Objects. Guarda in fondo a questa pagina, intestazione "Creazione della tua classe Freezable". Ciò renderebbe al tuo documento sicuro per i thread l'accesso da un thread diverso da quello che lo ha creato.

+0

Grazie per quello. La creazione del mio documento implica l'istanziazione di un oggetto FixedDocument, l'aggiunta di oggetti FixedPage, il riempimento di controlli, ecc. Poiché il componente FixedDocument è DispatcherObject, non è stato possibile crearlo in un thread in background e quindi impostarlo come origine per DocumentViewer, così come creato una violazione cross-thread. Ho scoperto che devo creare il mio documento nello stesso thread del mio DocumentViewer - cioè su un thread dell'interfaccia utente :-(Ma ho appena trovato una soluzione al mio problema - lo posterò ora. – Ross

Problemi correlati