26

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

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

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


アップデート:

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

4

4 に答える 4

20

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

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

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

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

于 2011-10-18T06:09:35.207 に答える
12

わかりました、CodeNaked の回答が正しい理由を人々に示すためにこれに回答していますが、必要に応じてアスタリスクを付け、回避策を提供します。しかし、良いSO市民権では、彼の回答が私をここに導いたので、私はまだ彼を回答済みとしてマークしています。

更新:2つの理由から、受け入れられた回答をここに移動しました。1つは、これに対する解決策があることを人々に知ってもらいたい(ほとんどの人は、受け入れられた回答を読んで先に進むだけです). ! :)

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

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.

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

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

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

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

ただし、どの子コントロールにも AffectsParentArrange ビットが設定されたプロパティがないことがわかっている場合は (これも行います)、さらに良い方法で NullableSizeをフラグとして使用して、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 を呼び出して、ボブの叔父さんを呼び出してください! :)

お役に立てれば!

于 2011-10-18T07:43:05.150 に答える
1

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

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

于 2011-10-18T08:11:51.643 に答える