У мене є контроль, який я повинен робити великі модифікації. Я хотів би повністю запобігти її перемальовуванню, поки я це роблю - SuspendLayout та ResumeLayout недостатньо. Як призупинити малювання для контролю та його дітей?
У мене є контроль, який я повинен робити великі модифікації. Я хотів би повністю запобігти її перемальовуванню, поки я це роблю - SuspendLayout та ResumeLayout недостатньо. Як призупинити малювання для контролю та його дітей?
Відповіді:
На моїй попередній роботі ми боролися з тим, щоб наш багатий додаток інтерфейсу малював миттєво та гладко. Ми використовували стандартні елементи управління .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();
}
}
З цього приводу йде повніше обговорення - 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
долучити частину малюнка до -ставлення. Ручка буде передана конструктору, який зупинить малювання.
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()
кидає виняток? (Використовуйте спробу / нарешті)
Приємне рішення без використання interop:
Як завжди, просто увімкніть DoubleBuffered = true у своєму CustomControl. Потім, якщо у вас є контейнери типу 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
}
Ось комбінація 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
Я знаю, що це старе питання, вже відповів, але ось я сприймати це; Я відновив призупинення оновлень на ідентифікатор - таким чином я можу долучити до заяви заяви, які хочу запустити 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 все ще так весело, аміре?