3

TabControlプログラムでオブジェクトに動的に追加される複数のタブがあるプログラムがあります。私がやりたいのは、これらの各タブのコンテンツ値をPNGにレンダリングすることです。StackOverflowまたはおそらくGoogle(ソースを失った)の他の場所で取得したスクリプトを使用しています。私のコードは次のようになります。

if (tabPanel.Items.Count > 0)
{
    SaveFileDialog fileDialog = new SaveFileDialog();
    fileDialog.Filter = "PNG|*.png";
    fileDialog.Title = "Save Tabs";
    fileDialog.ShowDialog();

    if (fileDialog.FileName.Trim().Length > 0)
    {
        try
        {
            string filePrefix = fileDialog.FileName.Replace(".png", "");
            int tabNo = 1;
            foreach (TabItem tabItem in tabPanel.Items)
            {
                string filename = filePrefix + "_" + tabNo + ".png";

                TabContentControl content = tabItem.Content as TabContentControl;
                Rect rect = new Rect(content.RenderSize);
                RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right, (int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);
                rtb.Render(content);

                BitmapEncoder pngEncoder = new PngBitmapEncoder();
                pngEncoder.Frames.Add(BitmapFrame.Create(rtb));

                System.IO.MemoryStream ms = new System.IO.MemoryStream();
                pngEncoder.Save(ms);
                System.IO.File.WriteAllBytes(filename, ms.ToArray());
                ms.Close();

                tabNo++;
            }
        }
        catch (Exception ex)
        {
            // log exception
        }
    }
}

このコードを呼び出す前にレンダリングする必要のあるすべてのタブを確認して確認した場合、このコードは希望どおりに機能します。先に進み、TabContentControlからレンダリングされた正しいコンテンツを使用してfilePrefix_1.png、filePrefix_2.pngなどを作成します。ただし、すべてのタブを表示する前にこのコードを使用するハンドラーを呼び出すと、コードはで例外をスローnew RenderTargetBitmap(...)content.RenderSizeます{0.0, 0.0}。表示されていないタブのレンダリングサイズを一度表示されたタブの1つに強制しようとすると、出力されたPNGは正しいサイズですが、完全に空です。

したがって、TabContentControlのレンダリングを強制する方法が必要だと思います。Renderイベントは、UIElementをレンダリングする必要がある場合にのみ実行されるようです。これを回避するために私が実行できるトリックはありますか?

また、Page_Loadedイベントハンドラーでタブの作成時に次のコードを追加することにより、WPFを「だまして」タブコンテンツをペイントしようとしました。

void Page_Loaded(object sender, RoutedEventArgs e)
{
    // irrelevant code
    foreach (// iterate over content that is added to each tab)
    {
        TabItem tabItem = new TabItem();
        // load content
        tabPanel.Items.Add(tabItem);
        tabItem.IsSelected = true;
    }
    // tabPanel.SelectedIndex = 0;
}

ハンドラーの最後の行Page_Loadedがコメント化されると、最後のタブにフォーカスがRenderSize置かれ、そのコンテンツに対してプロパティが定義されます。最後の行がコメントアウトされていない場合、最初のタブにフォーカスがあり、同じ動作をします。他のタブにはレンダリング情報がありません。

4

2 に答える 2

4

このブログ投稿のおかげで、ついにそれを理解しました。このソリューションには、レンダリング優先度で空のデリゲートを呼び出すRefreshメソッドを使用してUIElementの拡張メソッドを作成することが含まれていました。どうやら、レンダリング優先度で何かをスケジュールすると、他のすべてのより重要なアイテムが実行され、タブが変更されたようです。

ブログが削除された場合に備えて、ここに複製されたコード:

public static class ExtensionMethods
{
   private static Action EmptyDelegate = delegate() { };

   public static void Refresh(this UIElement uiElement)
   {
      uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
   }
}

void Page_Loaded(object sender, RoutedEventArgs e)
{
    // irrelevant code
    foreach (// iterate over content that is added to each tab)
    {
        TabItem tabItem = new TabItem();
        // load content
        tabPanel.Items.Add(tabItem);
        tabItem.IsSelected = true;
        tabItem.Refresh();
    }
    // tabPanel.SelectedIndex = 0;
}

これを使用するには、この機能を使用する必要があるコードファイルに拡張名前空間を含めるだけで、メソッドのリストに表示されます。

于 2010-02-12T19:00:21.593 に答える
1

これは実際には理想的なソリューションではありませんが、タブを作成してタブコントロールに追加すると、現在開いているタブを保存し、作成したばかりのタブに切り替えてから、元に戻すことができます。ユーザーにとってはわずかなちらつきになりますが、実際には、winforms(またはそれに応じてwpf)をだましてオブジェクトを描画しているだけです。

于 2010-01-11T02:51:10.657 に答える