コントロールとその子のペイントを一時停止するにはどうすればよいですか?


184

大きな変更を加えなければならないコントロールがあります。その間、完全に再描画されないようにしたいのですが、SuspendLayoutとResumeLayoutでは不十分です。コントロールとその子のペイントを一時停止するにはどうすればよいですか?


3
誰かがこの文脈でここで何を描いているか、何を描いているかを私に説明してもらえますか?(私は.netを初めて使用します)少なくともリンクを提供します。
Mr_Green

1
.Netが15年以上使用されていないのは本当に残念(または笑える)で、これはまだ問題です。画面のちらつきなどの実際の問題の修正にMicrosoftが「Windows Xマルウェアを入手する」と同じくらいの時間を費やした場合、これはずっと前に修正されていたはずです。
jww

@jww彼らはそれを修正しました。WPFと呼ばれます。
philu

回答:


304

以前の仕事では、リッチなUIアプリを瞬時にスムーズにペイントするのに苦労していました。標準の.Netコントロール、カスタムコントロール、およびdevexpressコントロールを使用していました。

多くのグーグルとリフレクターの使用後、WM_SETREDRAW win32メッセージに遭遇しました。これにより、コントロールの描画中にコントロールの描画が停止し、IIRCを親/包含パネルに適用して適用できます。

これは、このメッセージの使用方法を示す非常に単純なクラスです。

class DrawingControl
{
    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);

    private const int WM_SETREDRAW = 11; 

    public static void SuspendDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
    }

    public static void ResumeDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
        parent.Refresh();
    }
}

これに関するより完全な議論があります-C#とWM_SETREDRAWのグーグル、例えば

C#ジッタ

レイアウトの一時停止

そしてそれが関係するかもしれない人にとって、これはVBでの同様の例です:

Public Module Extensions
    <DllImport("user32.dll")>
    Private Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Boolean, ByVal lParam As IntPtr) As Integer
    End Function

    Private Const WM_SETREDRAW As Integer = 11

    ' Extension methods for Control
    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control, ByVal Redraw As Boolean)
        SendMessage(Target.Handle, WM_SETREDRAW, True, 0)
        If Redraw Then
            Target.Refresh()
        End If
    End Sub

    <Extension()>
    Public Sub SuspendDrawing(ByVal Target As Control)
        SendMessage(Target.Handle, WM_SETREDRAW, False, 0)
    End Sub

    <Extension()>
    Public Sub ResumeDrawing(ByVal Target As Control)
        ResumeDrawing(Target, True)
    End Sub
End Module

44
なんて素晴らしい答えであり、途方もない助けです!Controlクラス用にSuspendDrawingおよびResumeDrawing拡張メソッドを作成したので、どのコンテキストのどのコントロールに対してもそれらを呼び出すことができます。
ザックジョンソン

2
折りたたんで展開した場合に子の再配置時にノードを適切に更新するだけのツリー状のコントロールがあり、醜いちらつきが発生していました。これはそれを回避するために完全に機能しました。ありがとう!(おもしろいことに、コントロールは既にSendMessageをインポートし、WM_SETREDRAW 定義していますが、実際には何も使用していません。現在は使用しています。)
neminem

7
これは特に便利ではありません。これはControlすべてのWinFormsコントロールの基本クラスがおよびメソッドに対して既に行っていることBeginUpdateとまったく同じEndUpdateです。メッセージを自分で送信することは、これらの方法を使用して重い作業を行うことと同じで、確かに異なる結果を生み出すことはできません。
コーディグレイ

13
@Cody Grey-たとえば、TableLayoutPanelsにはBeginUpdateがありません。
TheBlastOne

4
コントロールが表示される前にコードでこれらのメソッドを呼び出すことができる場合は注意してください-を呼び出すとControl.Handleウィンドウハンドルが強制的に作成され、パフォーマンスに影響を与える可能性があります。たとえば、フォームのコントロールが表示される前に移動していた場合SuspendDrawing、これを事前に呼び出すと、移動が遅くなります。おそらくif (!parent.IsHandleCreated) return両方の方法でチェックが必要です。
oatsoda 2014

53

以下はng5000の同じソリューションですが、P / Invokeを使用しません。

public static class SuspendUpdate
{
    private const int WM_SETREDRAW = 0x000B;

    public static void Suspend(Control control)
    {
        Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgSuspendUpdate);
    }

    public static void Resume(Control control)
    {
        // Create a C "true" boolean as an IntPtr
        IntPtr wparam = new IntPtr(1);
        Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
            IntPtr.Zero);

        NativeWindow window = NativeWindow.FromHandle(control.Handle);
        window.DefWndProc(ref msgResumeUpdate);

        control.Invalidate();
    }
}

試してみましたが、サスペンドとレジュームの中間段階では無効になっています。奇妙に聞こえるかもしれませんが、一時的な状態で一時停止する前にその状態を保持できますか?
ユーザー、

2
コントロールを一時停止すると、コントロール領域では描画がまったく実行されず、そのサーフェスの他のウィンドウやコントロールから残り物が描画される可能性があります。DoubleBufferをtrueに設定してコントロールを使用しようとすると、コントロールが再開されるまで前の "状態"を描画したい場合(私が意図したことを理解した場合)、それが機能することを保証できません。とにかく、このテクニックを使用するポイントが足りないと思います。これは、ユーザーがオブジェクトのプログレッシブで遅い描画を見るのを避けるためです(すべてのオブジェクトが一緒に表示されるのを見るのが良いです)。他のニーズについては、他の手法を使用してください。
ceztko 2012

4
ニースの答えが、場所を示すためにはるかに良いだろうMessageNativeWindowしています。という名前のクラスのドキュメントを検索Messageすることは、実際にはそれほど面白いものではありません。
darda 2014年

1
@pelesl 1)自動名前空間インポートを使用する(シンボルをクリックする、ALT + Shift + F10)、2)Invalidate()によってペイントされていない子が予期される動作です。親コントロールを一時停止し、関連するすべての子が無効になったときに再開する必要があります。
ceztko 2014年

2
Invalidate()Refresh()とにかく1つ続けないと、うまく機能しません。
Eugene Ryabtsev

16

私は通常、ngLinkの回答を少し変更したバージョンを使用します

public class MyControl : Control
{
    private int suspendCounter = 0;

    private void SuspendDrawing()
    {
        if(suspendCounter == 0) 
            SendMessage(this.Handle, WM_SETREDRAW, false, 0);
        suspendCounter++;
    }

    private void ResumeDrawing()
    {
        suspendCounter--; 
        if(suspendCounter == 0) 
        {
            SendMessage(this.Handle, WM_SETREDRAW, true, 0);
            this.Refresh();
        }
    }
}

これにより、一時停止/再開の呼び出しをネストできます。それぞれSuspendDrawingを必ずと一致させる必要がありResumeDrawingます。したがって、それらを公開することはおそらく良い考えではありません。


4
これにより、両方の通話のバランスを保つことができますSuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }。別のオプションは、これをIDisposableクラスに実装し、描画部分を- usingステートメントで囲むことです。ハンドルはコンストラクターに渡され、描画が中断されます。
Olivier Jacot-Descombes 2014

古い質問ですが、最近のバージョンのc#/ VSでは「false」を送信しても機能しないようです。私は1に0に偽を変更しなければならなかったし、真
モーリーマーコウィッツ

@MauryMarkowitzあなたのDllImport宣言wParambool
Ozgur Ozcitak 2015

こんにちは、この回答への反対投票は事故でした。申し訳ありません。
ジェフ、

13

描画を再度有効にするのを忘れないようにするには:

public static void SuspendDrawing(Control control, Action action)
{
    SendMessage(control.Handle, WM_SETREDRAW, false, 0);
    action();
    SendMessage(control.Handle, WM_SETREDRAW, true, 0);
    control.Refresh();
}

使用法:

SuspendDrawing(myControl, () =>
{
    somemethod();
});

1
action()例外をスローする場合はどうですか?(try / finallyを使用)
David Sherret

8

相互運用機能を使用しない優れたソリューション:

いつものように、CustomControlでDoubleBuffered = trueを有効にします。次に、FlowLayoutPanelやTableLayoutPanelなどのコンテナーがある場合は、これらの各タイプからクラスを派生させ、コンストラクターでダブルバッファリングを有効にします。ここで、Windows.Formsコンテナの代わりに、派生したコンテナを使用します。

class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
    public TableLayoutPanel()
    {
        DoubleBuffered = true;
    }
}

class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
    public FlowLayoutPanel()
    {
        DoubleBuffered = true;
    }
}

2
これは確かに便利な手法です-私がListViewsでよく使う手法ですが、実際には再描画の発生を妨げるものではありません。彼らはまだオフスクリーンで発生します。
Simon

4
そうです、画面の再描画の問題ではなく、ちらつきの問題を解決します。ちらつきの解決策を探していたときに、このような関連するスレッドをいくつか見つけました。見つかったとき、最も関連性の高いスレッドに投稿していなかったかもしれません。ただし、ほとんどの人がペイントを一時停止したい場合、おそらく画面上でのペイントを参照しています。これは、多くの場合、画面外の冗長なペイントよりも明らかな問題です。そのため、他の視聴者もこのソリューションがこのスレッドで役立つと思うかもしれません。
Eugenio De Hoyos 2010年

OnPaintをオーバーライドします。

6

ng5000の答えに基づいて、私はこの拡張機能を使用するのが好きです:

        #region Suspend
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
        private const int WM_SETREDRAW = 11;
        public static IDisposable BeginSuspendlock(this Control ctrl)
        {
            return new suspender(ctrl);
        }
        private class suspender : IDisposable
        {
            private Control _ctrl;
            public suspender(Control ctrl)
            {
                this._ctrl = ctrl;
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, false, 0);
            }
            public void Dispose()
            {
                SendMessage(this._ctrl.Handle, WM_SETREDRAW, true, 0);
                this._ctrl.Refresh();
            }
        }
        #endregion

使用する:

using (this.BeginSuspendlock())
{
    //update GUI
}

4

pinzokeを使用しないVB拡張バージョンを提供するためのceztkoとng5000の組み合わせは次のとおりです

Imports System.Runtime.CompilerServices

Module ControlExtensions

Dim WM_SETREDRAW As Integer = 11

''' <summary>
''' A stronger "SuspendLayout" completely holds the controls painting until ResumePaint is called
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub SuspendPaint(ByVal ctrl As Windows.Forms.Control)

    Dim msgSuspendUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, System.IntPtr.Zero, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgSuspendUpdate)

End Sub

''' <summary>
''' Resume from SuspendPaint method
''' </summary>
''' <param name="ctrl"></param>
''' <remarks></remarks>
<Extension()>
Public Sub ResumePaint(ByVal ctrl As Windows.Forms.Control)

    Dim wparam As New System.IntPtr(1)
    Dim msgResumeUpdate As Windows.Forms.Message = Windows.Forms.Message.Create(ctrl.Handle, WM_SETREDRAW, wparam, System.IntPtr.Zero)

    Dim window As Windows.Forms.NativeWindow = Windows.Forms.NativeWindow.FromHandle(ctrl.Handle)

    window.DefWndProc(msgResumeUpdate)

    ctrl.Invalidate()

End Sub

End Module

4
私はwpfフォームと混合したwinformを使用するWPFアプリで作業しており、画面のちらつきを処理しています。私はこのコードをどのように活用すべきか混乱しています-これはwinformまたはwpfウィンドウに行きますか?または、これは私の特定の状況に適していませんか?
nocarrier

3

私はこれが古い質問であることを知っていますが、すでに回答されていますが、これは私の考えです。更新の一時停止をIDisposableにリファクタリングしましたusing。これにより、実行するステートメントをステートメントで囲むことができます。

class SuspendDrawingUpdate : IDisposable
{
    private const int WM_SETREDRAW = 0x000B;
    private readonly Control _control;
    private readonly NativeWindow _window;

    public SuspendDrawingUpdate(Control control)
    {
        _control = control;

        var msgSuspendUpdate = Message.Create(_control.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);

        _window = NativeWindow.FromHandle(_control.Handle);
        _window.DefWndProc(ref msgSuspendUpdate);
    }

    public void Dispose()
    {
        var wparam = new IntPtr(1);  // Create a C "true" boolean as an IntPtr
        var msgResumeUpdate = Message.Create(_control.Handle, WM_SETREDRAW, wparam, IntPtr.Zero);

        _window.DefWndProc(ref msgResumeUpdate);

        _control.Invalidate();
    }
}

2

これはさらに単純で、おそらくハックです。このスレッド多くのGDIマッスルを確認できるため、特定のシナリオに適していること明らかです。YMMV

私のシナリオでは、「親」UserControlと呼ぶものを使用します。Loadイベント中に、操作するコントロールを親の.Controlsコレクションから削除し、親のOnPaintを完全にペイントします特別な方法で制御します。完全に子のペイント機能をオフラインにします。

今、私は子供のフォームルーチンをMike Goldのこのコンセプトに基づいた拡張メソッドに引き渡し、Windowsフォームを印刷します

ここでは、レイアウトに垂直にレンダリングするためにラベルのサブセットが必要です。

Visual Studio IDEの簡単な図

次に、ParentUserControl.Loadイベントハンドラーに次のコードを使用して、子コントロールの描画を免除します。

Private Sub ParentUserControl_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    SetStyle(ControlStyles.UserPaint, True)
    SetStyle(ControlStyles.AllPaintingInWmPaint, True)

    'exempt this control from standard painting: 
    Me.Controls.Remove(Me.HostedControlToBeRotated) 
End Sub

次に、同じParentUserControlで、操作されるコントロールをゼロからペイントします。

Protected Overrides Sub OnPaint(e As PaintEventArgs)
    'here, we will custom paint the HostedControlToBeRotated instance...

    'twist rendering mode 90 counter clockwise, and shift rendering over to right-most end 
    e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    e.Graphics.TranslateTransform(Me.Width - Me.HostedControlToBeRotated.Height, Me.Height)
    e.Graphics.RotateTransform(-90)
    MyCompany.Forms.CustomGDI.DrawControlAndChildren(Me.HostedControlToBeRotated, e.Graphics)

    e.Graphics.ResetTransform()
    e.Graphics.Dispose()

    GC.Collect()
End Sub

ParentUserControlをWindowsフォームなどのどこかにホストすると、Visual Studio 2015がデザイン時と実行時の両方でフォームを正しくレンダリングすることがわかります。 Windowsフォームまたは他のユーザーコントロールでホストされているParentUserControl

今、私の特定の操作で子コントロールを90度回転させるので、すべてのホットスポットと対話機能がその領域で破壊されたと確信しています-しかし、私が解決していた問題はすべて、プレビューと印刷に必要なパッケージラベルに関するものでした。私にとってはうまくいきました。

ホットスポットとコントロール性を意図的に孤立したコントロールに再導入する方法がある場合-私はいつかそのことについて学びたいです(もちろん、このシナリオではなく、単に学ぶために)。もちろん、WPFはそのような狂気のOOTBをサポートしています。でも、ちょっと.. WinFormsはまだとても楽しいですよね?


-4

または単にとを使用Control.SuspendLayout()Control.ResumeLayout()ます。


9
レイアウトとペイントは2つの異なるものです。レイアウトとは、子コントロールをコンテナに配置する方法です。
Larry
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.