Как приостановить рисование для элемента управления и его дочерних элементов?
У меня есть контроль, который я должен сделать большие изменения. Я хотел бы полностью предотвратить его перерисовку, пока я это делаю - SuspendLayout и ResumeLayout недостаточно. Как приостановить рисование для элемента управления и его дочерних элементов?
10 ответов:
на моей предыдущей работе мы боролись с тем, чтобы наше богатое приложение пользовательского интерфейса рисовалось мгновенно и плавно. Мы использовали стандартные элементы управления .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
ниже приведено то же самое решение 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's ответ.
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(); });
хорошее решение без использования interop:
Как всегда, просто включите DoubleBuffered=true на вашем CustomControl. Затем, если у вас есть какие-либо контейнеры, такие как FlowLayoutPanel или TableLayoutPanel, производные класса от каждого из этих типов и в конструкторах, включите двойную буферизацию. Теперь просто используйте производные контейнеры вместо окон.Формирует Контейнеры.
class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel { public TableLayoutPanel() { DoubleBuffered = true; } } class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel { public FlowLayoutPanel() { DoubleBuffered = true; } }
вот комбинация ceztko и 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
Я знаю, что это старый вопрос, уже ответил, но вот мой взгляд на это; я рефакторинг приостановки обновлений в 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(); } }
основываясь на ответе 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 }
это еще проще,и, возможно, hacky-как я вижу много мышц GDI на этой нити и очевидно только хорошо подходит для определенных сценариев. YMMV
в моем сценарии я использую то, что я буду называть "родительским" UserControl - и во время
Load
событие, я просто удаляю элемент управления для манипулирования из родительского.Controls
коллекция, а родительскаяOnPaint
заботится о том, чтобы полностью покрасить контроль ребенка в любом специальном путь.. полностью принимая возможности рисования ребенка в автономном режиме.теперь я передаю свою детскую рутину рисования в метод расширения, основанный на этом концепция от Mike Gold для печати windows forms.
здесь мне нужен подмножество меток для визуализации перпендикулярно к макету:
затем я освобождаю дочерний элемент управления от рисования, с этим кодом в
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-это все еще так весело, amiright?