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

StackOverflow https://stackoverflow.com/questions/487661

  •  20-08-2019
  •  | 
  •  

質問

大規模な変更を加える必要があるコントロールがあります。その際、再描画を完全に防ぎたいのですが、SuspendLayout と ResumeLayout だけでは十分ではありません。コントロールとその子の描画を一時停止するにはどうすればよいですか?

役に立ちましたか?

解決

私の前の仕事で、私たちは即座にかつスムーズにペイントする豊富な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のためにグーグル

<のhref = "https://web.archive.org/web/20160324214157/http://blog.bee-eee.com/2008/04/18/c-getting-rid-of-the-ジッタ/」REL = "noreferrer"> 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

他のヒント

以下はng5000の同じ溶液であるが、P /呼び出しを使用していない。

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

私は通常ngLinkの<のhref = "https://stackoverflow.com/questions/487661/how-do-i-suspend-painting-for-a-control-and-its-childrenの少し変更したバージョンを使用します/ 487757#487757" >答えでます。

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と一致することを確認しなければなりません。したがって、おそらくそれらを公開するのは良いアイデアではありません。

を再び有効図面に忘れないで支援するために:

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

相互運用機能を使用することなく、素敵な解決策ます:

いつものように、単にあなたの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;
    }
}

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
}

ここ

のPInvokeを使用していない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

私は、これは古い質問、すでに答えているが、ここではこれが私の感想です知っています。私は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();
    }
}

これはさらに単純ですが、 そしておそらくハッキーです - このスレッドには多くの GDI の要素が見られるので、, 、 そして 明らかに、特定のシナリオにのみ適しています。 YMMV

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

ここで、子ペイント ルーチンをこれに基づいた拡張メソッドに渡します。 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 は今でもとても楽しいです、そうですか?

それともControl.SuspendLayout()Control.ResumeLayout()を使用します。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top