ฉันมีการควบคุมซึ่งฉันต้องทำการปรับเปลี่ยนขนาดใหญ่ ฉันต้องการป้องกันไม่ให้วาดใหม่ทั้งหมดในขณะที่ทำเช่นนั้น - SuspendLayout และ ResumeLayout ไม่เพียงพอ ฉันจะระงับการระบายสีเพื่อการควบคุมและลูก ๆ ของมันได้อย่างไร
ฉันมีการควบคุมซึ่งฉันต้องทำการปรับเปลี่ยนขนาดใหญ่ ฉันต้องการป้องกันไม่ให้วาดใหม่ทั้งหมดในขณะที่ทำเช่นนั้น - SuspendLayout และ ResumeLayout ไม่เพียงพอ ฉันจะระงับการระบายสีเพื่อการควบคุมและลูก ๆ ของมันได้อย่างไร
คำตอบ:
ที่งานก่อนหน้าของฉันเราพยายามต่อสู้กับการใช้แอพ UI ที่หลากหลายของเราในการวาดภาพได้อย่างราบรื่นและทันที เราใช้การควบคุมแบบ. Net มาตรฐาน, การควบคุมแบบกำหนดเองและการควบคุม devexpress
หลังจากการใช้งาน googling และ reflector มากมายฉันเจอข้อความ 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();
}
}
มีการพูดคุยเกี่ยวกับเรื่องนี้อย่างเต็มที่ - google สำหรับ 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()
เว้นแต่จะตามมาด้วยใครอยู่ดี
ผมมักจะใช้รุ่นที่ปรับเปลี่ยนเล็ก ๆ น้อย ๆ ของ 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
-statement หมายเลขอ้างอิงจะถูกส่งผ่านไปยังตัวสร้างซึ่งจะระงับการวาด
DllImport
ประกาศwParam
ว่าเป็นbool
อย่างไร
เพื่อช่วยในการไม่ลืมที่จะวาดภาพ reenable:
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()
มีข้อยกเว้น (ลองใช้ / ในที่สุด)
ทางออกที่ดีโดยไม่ใช้การเชื่อมต่อ:
และเช่นเคยเพียงเปิดใช้งาน DoubleBuffered = true ใน CustomControl ของคุณ จากนั้นหากคุณมีคอนเทนเนอร์เช่น FlowLayoutPanel หรือ TableLayoutPanel ให้หาคลาสจากแต่ละประเภทเหล่านี้และใน Constructor ให้เปิดใช้งานการกำหนดบัฟเฟอร์คู่ ตอนนี้เพียงแค่ใช้คอนเทนเนอร์ที่ได้รับของคุณแทน Windows.Forms Containers
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
}
นี่คือการรวมกันของ ceztko's และ ng5000 เพื่อนำมาเป็นส่วนขยายของ VB ซึ่งไม่ได้ใช้ pinvoke
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
ฉันรู้ว่านี่เป็นคำถามเก่าตอบแล้ว แต่นี่คือสิ่งที่ฉันทำ ฉัน refactored ระงับการปรับปรุงใน 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
ดูแลภาพเด็กอย่างสมบูรณ์ ควบคุมด้วยวิธีการพิเศษใด ๆ
ตอนนี้ผมมือปิดของฉันสีประจำเด็กวิธีขยายตามออกนี้แนวคิดจากไมค์ทองสำหรับการพิมพ์รูปแบบหน้าต่าง
ที่นี่ฉันต้องการชุดย่อยของฉลากเพื่อตั้งฉากกับเค้าโครง:
จากนั้นฉันได้รับการยกเว้นการควบคุมเด็กจากการทาสีด้วยรหัสนี้ใน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 ยังสนุกอยู่มาก
หรือเพียงแค่ใช้และControl.SuspendLayout()
Control.ResumeLayout()