Domanda

Nella mia app WPF, ho particolare Window che contiene, tra gli altri controlli, a DocumentViewer.

Quando questa finestra viene aperta e caricata, crea dinamicamente a FixedDocument con un indicatore di avanzamento, quindi lo visualizza in DocumentViewer. Funziona e, per migliorare l'esperienza dell'utente, eseguo questa finestra nel proprio thread, in modo che la finestra dell'applicazione principale sia ancora reattiva mentre il documento viene costruito.

In base ai suggerimenti a Questa pagina web, 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 contento di questa configurazione finora, ma ho appena riscontrato un problema.

MyDocumentViewerWindow Contiene un pulsante di stampa, che fa riferimento al comando di stampa integrato, mirato al documentViewer:

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

Prima di avere la finestra nel suo thread, ha funzionato bene. Ma ora, quando faccio clic, l'applicazione si blocca. Visual Studio 2010 mette in evidenza la seguente riga dal codice sopra come posizione di crash, con il messaggio 'Il thread chiamante non può accedere a questo oggetto perché un thread diverso lo possiede.':

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

La traccia dello stack inizia in questo modo:

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

La mia sospensione è che la finestra di dialogo di stampa si aprirà nel thread dell'interfaccia utente principale e sta cercando di accedere al documento creato e di proprietà del mio thread, da cui il crash.

Qualche idea su come posso risolverlo? Vorrei tenere la finestra nel suo thread.

È stato utile?

Soluzione

Dopo un po 'più di googling, mi sono imbattuto nel seguente thread, che sembra essere il problema esatto che sto riscontrando.

PrintDialog e un thread dell'interfaccia utente secondario grave problema

In quel thread, il ragazzo alla fine utilizza una classe di stampa personalizzata (il cui codice sorgente si trova qui), che è più o meno lo stesso di PrintDialog incorporato, ma con alcune modifiche per correggere questi bug incrociati (e sovrascrive anche lo scrittore di documenti XPS, che apparentemente si lega ancora più alla discussione dell'interfaccia utente dell'applicazione)

Ho copiato e incollato il codice per quella stampa personalizzata (e rinominato la classe ThreadSafePrintDialog), rimosso il comando del mio pulsante di stampa e invece usa 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.

Altri suggerimenti

Il tuo intuizione è corretto. Non è possibile accedere a questo oggetto sul thread dell'interfaccia utente quando è stato creato da un altro thread.

Credo che tu abbia alcune opzioni:

  1. È possibile creare questo documento sul thread dell'interfaccia utente, forse raccogliere le informazioni di cui hai bisogno in un thread di sfondo e quindi costruire effettivamente l'oggetto sul thread dell'interfaccia utente. Dipende da ciò che comporta la creazione del tuo documento. Potresti fare qualcosa come:

    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 al thread che crea questo altro documento? Aggrappati a questo thread e fai a thread.Invoke(printMethod)

  3. Potresti guardare dentro Oggetti congelabili. Guarda il fondo di questa pagina, dirigendo "Creazione della tua classe congelabile". Ciò renderebbe il thread-safe del documento per accedere da un thread diverso da quello che lo ha creato.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top