2011-09-07 47 views
5

在我的WPF應用程序中,我特別有Window,其中包含DocumentViewer以及其他控件。在不同的UI線程中打印DocumentViewer的內容

打開並加載此窗口時,它會動態構建帶有進度指示器的FixedDocument,然後將其顯示在DocumentViewer中。它可以工作,爲了改善用戶體驗,我在自己的線程中運行此窗口,以便在構建文檔時主應用程序窗口仍然可以響應。

根據在this web page的提示,我在這樣一個新的線程打開我的窗口:

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

我已經滿意這個設置,到目前爲止,但我只是遇到了一個問題。

MyDocumentViewerWindow包含打印按鈕,它引用內置的打印命令,定位於中的DocumentViewer:

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

之前,我曾在自己的線程窗口,這工作得很好。但是現在,當我點擊它時,應用程序崩潰。 Visual Studio 2010將以上代碼中的以下代碼行突出顯示爲崩潰位置,消息爲'調用線程不能訪問此對象,因爲不同的線程擁有它。「:

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

堆棧跟蹤開始是這樣的:

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

我的預感是,在打印對話框打開主UI線程,並試圖訪問所創建的文件和被佔用由我自己的線程,因此崩潰。

任何想法我可以解決這個問題?我想保持窗口在自己的線程中。

回答

6

經過一些谷歌搜索,我偶然發現了下面的線程,這似乎是我正在遇到的確切問題。

PrintDialog and a secondary UI thread severe problem

在該線程,這個傢伙最終還是使用了自定義PrintDialog類類(源代碼中發現here),這就像內置PrintDialog類相同,但有一些調整,以修復這些跨線程錯誤(它也覆蓋了XPS文檔編寫器,它顯然將自己與應用程序的主UI線程進一步聯繫)

我複製並粘貼了該自定義PrintDialog的代碼(並將該類重命名爲ThreadSafePrintDialog) ,刪除我的打印按鈕的CommandTarget,而是使用我自己的打印方法:

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

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

完美地工作。

1

您的預感是正確的。當它由另一個線程創建時,您無法在UI線程上訪問此對象。

我相信你有幾個選擇:

1)你可以創建在UI線程上這個文件,也許收集您在後臺線程需要,然後實際構建UI線程對象的信息。這取決於您的文檔創建需要什麼。你可以這樣做:

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)你也許可以發送這個命令到創建這個其他文檔的線程?堅持這個線程並做一個thread.Invoke(printMethod)

3)你可以看看Freezable Objects。看看這個頁面的底部,標題爲「創建你自己的Freezable Class」。這將使您的文檔線程安全地從不同於創建它的線程訪問。

+0

謝謝你。創建我的文檔包括實例化一個FixedDocument,添加FixedPage對象,用控件填充它們等等。由於FixedDocument是一個DispatcherObject,我無法在後臺線程中創建它,然後將其設置爲DocumentViewer的源,因爲這樣做也會創建跨線程違規。我發現我必須在與我的DocumentViewer相同的線程中創建我的文檔 - 即在UI線程上:-(但我只是找到解決方法來解決我的問題 - 我現在將其發佈。 – Ross