큰 수정을해야하는 컨트롤이 있습니다. 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에 대한 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, 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
위한 기본 클래스 의 모든 윈폼 컨트롤이 이미 수행 을 위해 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()
어쨌든 하나만 따르지 않는 한 제대로 작동하지 않습니다 .
나는 보통 약간 수정 된 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()
예외를 throw? (시도 / 마지막 사용)
interop을 사용하지 않는 멋진 솔루션 :
항상 그렇듯이 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
자식을 완전히 페인팅 할 수 있습니다. 특별한 방법으로 제어 할 수 있습니다. 자녀의 페인트 기능을 완전히 오프라인 상태로 만듭니다.
이제 아이 페인트 루틴을 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는 여전히 굉장히 재미 있습니다.