大きな変更を加えなければならないコントロールがあります。その間、完全に再描画されないようにしたいのですが、SuspendLayoutとResumeLayoutでは不十分です。コントロールとその子のペイントを一時停止するにはどうすればよいですか?
大きな変更を加えなければならないコントロールがあります。その間、完全に再描画されないようにしたいのですが、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のグーグル、例えば
そしてそれが関係するかもしれない人にとって、これは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
Control
、すべてのWinFormsコントロールの基本クラスがおよびメソッドに対して既に行っていることBeginUpdate
とまったく同じEndUpdate
です。メッセージを自分で送信することは、これらの方法を使用して重い作業を行うことと同じで、確かに異なる結果を生み出すことはできません。
Control.Handle
ウィンドウハンドルが強制的に作成され、パフォーマンスに影響を与える可能性があります。たとえば、フォームのコントロールが表示される前に移動していた場合SuspendDrawing
、これを事前に呼び出すと、移動が遅くなります。おそらくif (!parent.IsHandleCreated) return
両方の方法でチェックが必要です。
以下は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();
}
}
Message
とNativeWindow
しています。という名前のクラスのドキュメントを検索Message
することは、実際にはそれほど面白いものではありません。
Invalidate()
Refresh()
とにかく1つ続けないと、うまく機能しません。
私は通常、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
ます。したがって、それらを公開することはおそらく良い考えではありません。
SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }
。別のオプションは、これをIDisposable
クラスに実装し、描画部分を- using
ステートメントで囲むことです。ハンドルはコンストラクターに渡され、描画が中断されます。
DllImport
宣言wParam
はbool
?
描画を再度有効にするのを忘れないようにするには:
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();
});
action()
例外をスローする場合はどうですか?(try / finallyを使用)
相互運用機能を使用しない優れたソリューション:
いつものように、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
}
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
私はこれが古い質問であることを知っていますが、すでに回答されていますが、これは私の考えです。更新の一時停止を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
を完全にペイントします特別な方法で制御します。完全に子のペイント機能をオフラインにします。
今、私は子供のフォームルーチンをMike Goldのこのコンセプトに基づいた拡張メソッドに引き渡し、Windowsフォームを印刷します。
ここでは、レイアウトに垂直にレンダリングするためにラベルのサブセットが必要です。
次に、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がデザイン時と実行時の両方でフォームを正しくレンダリングすることがわかります。
今、私の特定の操作で子コントロールを90度回転させるので、すべてのホットスポットと対話機能がその領域で破壊されたと確信しています-しかし、私が解決していた問題はすべて、プレビューと印刷に必要なパッケージラベルに関するものでした。私にとってはうまくいきました。
ホットスポットとコントロール性を意図的に孤立したコントロールに再導入する方法がある場合-私はいつかそのことについて学びたいです(もちろん、このシナリオではなく、単に学ぶために)。もちろん、WPFはそのような狂気のOOTBをサポートしています。でも、ちょっと.. WinFormsはまだとても楽しいですよね?