5

FixedDocument、FlowDocument、PageContent、BlockUIContainerなどのwpf UI要素を使用して印刷プレビュー(長いもの)を生成する必要があります。UIの応答性を維持するために、この部分を別のThreadクラススレッドで実行しています(STAスレッドが必要なため、BackgroundWorkerは機能しません)。この時点まではすべてOKです。
しかし、印刷プレビューを表示した後、印刷する必要があります。生成されたプレビューの[印刷]アイコンをクリックすると、悪名高い「別のスレッドが所有しているため、呼び出し元のスレッドはこのオブジェクトにアクセスできません」がスローされます。例外。それで、回避する方法はありますか?

編集(コード):

Dispatcher.CurrentDispatcher.Invoke(new Action(() =>  
    {  
        Thread thread = new Thread(() =>  
            {  
                FixedDocument document = renderFlowDocumentTemplate(report);  
                PrintPreview preview = new PrintPreview();  
                preview.WindowState = WindowState.Normal;  
                preview.documentViewer.Document = document;  
                preview.ShowDialog();  
            });  
        thread.SetApartmentState(ApartmentState.STA);  
        thread.Start();  
    }));`

ここで、RenderFlowDocumentTemplate()は印刷プレビュー(UI要素を含む)を生成し、それらにレポートデータを入力します。PrintPreviewは、プレビューを実際に保持および表示するDocumentViewer要素を含むカスタムウィンドウであり、クリックするとPrintDialogウィンドウを表示するための印刷アイコンが含まれます。

編集(XAML):

<cw:CustomWindow x:Class="MyApp.Reports.PrintPreview"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cw="clr-namespace:MyApp.UI.CustomWindows;assembly=MyApp.UI.CustomWindows">    
    <DocumentViewer Margin="0,30,0,0" Name="documentViewer"></DocumentViewer>
</cw:CustomWindow>`
4

6 に答える 6

1

別の「トリック」もあります...

多くの場合、私は同じ問題に遭遇します。たとえば、をにバインドしてみFrameworkElementますContentPresenter。これに対する私の解決策はItemsControl、の代わりにを使用し、1つのアイテムだけでシングルをContentPresenterバインドします。この後、問題ありませんFrameworkElementObservableCollection<FrameworkElement>

于 2012-10-26T15:17:00.473 に答える
1

最も簡単な方法はです。

Action a = () =>
{
    //Code from another thread.
};
Dispatcher.BeginInvoke(a);
于 2012-07-06T08:47:04.987 に答える
1

私はこれを少し前に結びました-そして問題はprintpreviewダイアログがメインスレッド上にある必要があるということだと思います。

于 2012-07-06T11:19:18.883 に答える
1

まったく同じ問題を抱えている別の人を見つけました-別のUIスレッドでDocumentViewerのコンテンツを印刷します。同じ道をたどった。ここのコードは本当の救世主でした。
これで、Dispatcherスレッドからセカンダリスレッドで生成されたUI要素にアクセスしようとはしていません。代わりに、残りの印刷手順がセカンダリスレッドで実行されます。UI要素のクロススレッド「VerifyAccess」はなく、スムーズに動作しています。:)

于 2012-07-09T16:45:05.403 に答える
1

この単純なスニペットを作成しました。経験はありませんが、いくつかのテストを行ったところ、正常に機能しているようです。

/// <summary>
/// Creates UI element on a seperate thread and transfers it to
/// main UI thread. 
/// 
/// Usage; if you have complex UI operation that takes a lot of time, such as XPS object creation.
/// </summary>
/// <param name="constructObject">Function that creates the necessary UIElement - will be executed on new thread</param>
/// <param name="constructionCompleted">Callback to the function that receives the constructed object.</param>
public void CreateElementOnSeperateThread(Func<UIElement> constructObject, Action<UIElement> constructionCompleted)
{
    VerifyAccess();

    // save dispatchers for future usage.
    // we create new element on a seperate STA thread
    // and then basically swap UIELEMENT's Dispatcher.
    Dispatcher threadDispatcher = null;
    var currentDispatcher = Dispatcher.CurrentDispatcher;

    var ev = new AutoResetEvent(false);
    var thread = new Thread(() =>
        {
            threadDispatcher = Dispatcher.CurrentDispatcher;
            ev.Set();

            Dispatcher.Run();
        });

    thread.SetApartmentState(ApartmentState.STA);
    thread.IsBackground = true;
    thread.Start();

    ev.WaitOne();

    threadDispatcher.BeginInvoke(new Action(() =>
        {
            var constructedObject = constructObject();
            currentDispatcher.BeginInvoke(new Action(() =>
                {
                    var fieldinfo = typeof (DispatcherObject).GetField("_dispatcher",
                                                                       BindingFlags.NonPublic |
                                                                       BindingFlags.Instance);
                    if (fieldinfo != null)
                        fieldinfo.SetValue(constructedObject, currentDispatcher);

                    constructionCompleted(constructedObject);
                    threadDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
                }), DispatcherPriority.Normal);
        }), DispatcherPriority.Normal);
}

そして、ここに使用法があります:

 CreateElementOnSeperateThread(() =>
        {
            // running on new temp dispatcher.
            var loadsOfItems = new List<int>();
            for(var i = 0; i < 100000; i++)
                loadsOfItems.Add(i+12);


            var dataGrid = new DataGrid {ItemsSource = loadsOfItems, Width = 500, Height = 500};

            dataGrid.Measure(new Size(500, 500));
            dataGrid.Arrange(new Rect(0, 0, 500, 500));

            return dataGrid;
        }, result => SampleGrid.Children.Add(result));
于 2013-06-06T13:53:24.730 に答える
0

この場合、ディスパッチャクラスを使用します。DispatcherクラスにはinvokeメソッドとbeginInvokeメソッドがあります。これにより、ディスパッチプロセスを使用して現在のスレッドにリクエストを送信できます。あなたがする必要があるのは、デリゲートを使用して以下のような呼び出しを作成することです

  App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
//you code goes here.
 }));

begin invoke callは、呼び出しを非同期的に処理します。

于 2012-07-06T09:01:37.567 に答える