web-dev-qa-db-ja.com

所有者が描画したWPFコントロールに、メジャーを実行したりパスを調整したりせずに更新/再描画するように手動で指示するにはどうすればよいですか?

コントロールサブクラスのOnRenderでカスタム描画を行っています。この描画コードは、外部トリガーとデータに基づいています。したがって、トリガーが起動するたびに、そのデータに基づいてコントロールを再レンダリングする必要があります。私たちがやろうとしているのは、レイアウトパス全体を経由せずに、コントロールを強制的に再レン​​ダリングする方法を見つけることです。

上記のように、私が見たほとんどの回答はVisualの無効化を中心に展開します。これにより、新しいメジャーを強制するレイアウトが無効になり、特に非常に複雑なビジュアルツリーの場合、非常にコストのかかるパスが配置されます。しかし、繰り返しになりますが、レイアウトはnotを変更し、VisualTreeも変更しません。唯一の処理は、別の方法でレンダリングされる外部データです。そのため、これは厳密に純粋なレンダリングの問題です。

繰り返しますが、OnRenderを再実行する必要があることをコントロールに通知する簡単な方法を探しています。新しいDependencyPropertyを作成し、それを「AffectsRender」に登録する「ハック」を1つ見たことがあります。これは、コントロールを更新するときに値を設定しただけですが、何が起こっているのかに興味がありますそれらのプロパティのデフォルト実装の内部:それらがその動作に影響を与えるために呼び出すもの。


更新:

まあ、AffectsRenderフラグでも内部的にアレンジパスが発生するので(以下のCodeNakedの回答に従って)、そのような呼び出しはないようですが、組み込みの動作を示す2番目の回答を投稿しましたまた、レイアウトパスコードがフラグとして単純なnull許容サイズで実行されないようにする回避策もあります。下記参照。

24
Mark A. Donohoe

OK、私はこれに答えて、CodeNakedの答えが正しい理由を人々に示しますが、可能であればアスタリスクを付け、回避策も提供します。しかし、優れたSO市民権では、彼の回答が私をここに導いたので、私はまだ彼を回答済みとしてマークしています。

更新:それ以来、私は2つの理由でここで受け入れられた回答をここに移動しました。 1つは、そこに知ってほしいisこれに対する解決策(ほとんどの人は承認された回答のみを読んで次に進む)と2つ、25Kの担当者がいることを考えると、彼はそうは思わないd取り戻せばいいのに! :)

これが私がしたことです。これをテストするために、このサブクラスを作成しました...

public class TestPanel : DockPanel
{
    protected override Size MeasureOverride(Size constraint)
    {
        System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
        return base.MeasureOverride(constraint);
    }

    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
        return base.ArrangeOverride(arrangeSize);
    }

    protected override void OnRender(System.Windows.Media.DrawingContext dc)
    {
        System.Console.WriteLine("OnRender called for " + this.Name + ".");
        base.OnRender(dc);
    }

}

...私はこのようにレイアウトしました(ネストされていることに注意してください):

<l:TestPanel x:Name="MainTestPanel" Background="Yellow">

    <Button Content="Test" Click="Button_Click" DockPanel.Dock="Top" HorizontalAlignment="Left" />

    <l:TestPanel x:Name="InnerPanel" Background="Red" Margin="16" />

</l:TestPanel>

ウィンドウのサイズを変更すると、これが表示されました...

MeasureOverride called for MainTestPanel.
MeasureOverride called for InnerPanel.
ArrangeOverride called for MainTestPanel.
ArrangeOverride called for InnerPanel.
OnRender called for InnerPanel.
OnRender called for MainTestPanel.

しかし、「MainTestPanel」(ボタンの「クリック」イベント)でInvalidateVisualを呼び出すと、代わりにこれを取得しました...

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

どの測定オーバ​​ーライドも呼び出されておらず、外部コントロールのArrangeOverrideのみが呼び出されていることに注意してください。

残念ながら、サブクラスのArrangeOverrideの内部に非常に重い計算があり(残念ながら私たちはそうしています)、まだ(再)実行されているようですが、少なくとも子供たちは同じ運命に陥りません。

ただし、子コントロールにAffectsParentArrangeビットが設定されたプロパティが含まれていないことがわかっている場合(ここでも同様)、Nullable Sizeをフラグとして使用して、ArrangeOverrideロジックを抑制できます。必要な場合を除いて、再入力します。

public class TestPanel : DockPanel
{
    Size? arrangeResult;

    protected override Size MeasureOverride(Size constraint)
    {
        arrangeResult = null;
        System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
        return base.MeasureOverride(constraint);
    }

    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        if(!arrangeResult.HasValue)
        {
            System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
            // Do your arrange work here
            arrangeResult = base.ArrangeOverride(arrangeSize);
        }

        return arrangeResult.Value;
    }

    protected override void OnRender(System.Windows.Media.DrawingContext dc)
    {
        System.Console.WriteLine("OnRender called for " + this.Name + ".");
        base.OnRender(dc);
    }

}

(MeasureOverrideの呼び出しのように)特別にアレンジロジックを再実行する必要がある場合を除いて、OnRenderのみを取得し、アレンジロジックを明示的に強制したい場合は、サイズをnullにして、InvalidateVisualを呼び出し、ボブの叔父さんを呼び出します! :)

お役に立てれば!

12
Mark A. Donohoe

残念ながら、内部的に InvalidateArrange を呼び出す InvalidateVisual を呼び出す必要があります。 OnRenderメソッドは配置フェーズの一部として呼び出されるため、WPFにコントロールを再配置するように(InvalidateArrangeが行う)、再描画する必要がある(InvalidateVisualが行う)ように指示する必要があります。

FrameworkPropertyMetadata.AffectsRenderオプションは、関連するプロパティが変更されたときにInvalidateVisualを呼び出すようにWPFに指示するだけです。

OnRenderをオーバーライドし、いくつかの子孫コントロールを含むコントロール(これをMainControlと呼びましょう)がある場合、InvalidateVisualを呼び出すには、子孫コントロールを再配置または再測定する必要がある場合があります。しかし、WPFには、使用可能なスペースが変更されていない場合に子孫のコントロールが再配置されないようにするための最適化が備わっていると思います。

レンダリングロジックを別のコントロール(NestedControlなど)に移動すると、これを回避できる場合があります。これは、MainControlの視覚的な子になります。 MainControlは、これを視覚的な子として自動的に追加することも、ControlTemplateの一部として追加することもできますが、Zオーダーで最下位の子にする必要があります。次に、NestedControlでInvalidateVisualを呼び出すMainControlでInvalidateNestedControlタイプのメソッドを公開できます。

19
CodeNaked

コントロールのサイズが変更されない限り、InvalidateVisual()を呼び出してはいけません。それでも、再レイアウトを引き起こす他の方法があります。

サイズを変更せずにコントロールのビジュアルを効率的に更新するため。 DrawingGroupを使用します。 DrawingGroupを作成し、OnRender()の実行中にDrawingContextに配置し、その後いつでもOpen()DrawingGroupを使用して、ビジュアル描画コマンドを変更できます。 、およびWPFは自動的におよび効率的に UIのその部分を再レンダリングします。 (毎回再描画するのではなく、段階的に変更できるビットマップが必要な場合は、RenderTargetBitmapでこの手法を使用することもできます)

これは次のようになります。

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

private void Render(DrawingContext drawingContext) {
    // put your render code here
}
2
David Jeske

ここに別のハックがあります: http://geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh--update-wpf-controls.aspx

つまり、DispatcherPriority.Renderの優先度でダミーのデリゲートを呼び出します。これにより、その優先度以上のものが呼び出され、再レンダリングが発生します。

1
GazTheDestroyer