web-dev-qa-db-ja.com

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

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

178
Simon

以前の仕事では、リッチ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
294
ng5000

以下は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();
    }
}
51
ceztko

通常、ngLinkの answer を少し修正したバージョンを使用します。

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();
        }
    }
}

これにより、中断/再開呼び出しをネストすることができます。各SuspendDrawingResumeDrawingと必ず一致させる必要があります。したがって、それらを公開することはおそらく良い考えではないでしょう。

15
Ozgur Ozcitak

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

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();
});
13
Jonathan H

相互運用機能を使用しない素晴らしいソリューション:

いつものように、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;
    }
}
8

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
Koray

これはceztkoとng5000の組み合わせで、ピンボークを使用しないVB拡張機能バージョンをもたらします。

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
3
goughy000

これは古い質問であり、すでに答えられていることは知っていますが、これについての私の見解は次のとおりです。更新の一時停止を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();
    }
}
3
Scott Baker

これはさらにシンプルで、、おそらくハックです-このスレッドで多くのGDI筋肉を見ることができるように、そして明らかに、特定のシナリオにのみ適しています。YMMV

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

さて、私はこれをベースにした拡張メソッドに子のPaintルーチンを渡します Windowsフォームの印刷に関するMike Goldのコンセプト

ここでは、レイアウトに垂直に垂直を表示するラベルのサブセットが必要です。

simple diagram of your 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がデザイン時および実行時にフォームを正しくレンダリングすることを発見しています: ParentUserControl hosted in a Windows Form or perhaps other user control

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

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

2
bkwdesign