大きな変更を加える必要があるコントロールがあります。それをしている間、再描画を完全に防ぎたいと思います-SuspendLayoutとResumeLayoutは十分ではありません。コントロールとその子の描画を一時停止するにはどうすればよいですか?
10 に答える
以前の仕事では、リッチ 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 については google です。
誰に関係があるかもしれませんが、これは 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, IntPtr.Zero)
If Redraw Then
Target.Refresh()
End If
End Sub
<Extension()>
Public Sub SuspendDrawing(ByVal Target As Control)
SendMessage(Target.Handle, WM_SETREDRAW, False, IntPtr.Zero)
End Sub
<Extension()>
Public Sub ResumeDrawing(ByVal Target As Control)
ResumeDrawing(Target, True)
End Sub
End Module
以下は 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();
}
}
私は通常、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();
}
}
}
これにより、中断/再開呼び出しをネストできます。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;
}
}
これは ceztko と ng5000 の組み合わせで、pinvoke を使用しない 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
これは古い質問であり、すでに回答されていることは知っていますが、これが私の見解です。更新の一時停止を 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();
}
}
または、 と を使用Control.SuspendLayout()
しControl.ResumeLayout()
ます。