У меня есть элемент управления, с которым я должен вносить большие изменения. Я бы хотел полностью предотвратить его перерисовку, пока я это делаю - SuspendLayout и ResumeLayout недостаточно. Как приостановить рисование для элемента управления и его детей?
Как приостановить рисование для элемента управления и его детей?
Ответ 1
На моей предыдущей работе мы боролись с тем, чтобы наше богатое приложение пользовательского интерфейса рисовало мгновенно и плавно. Мы использовали стандартные элементы управления.Net, пользовательские элементы управления и элементы управления devexpress.
После многого использования googling и рефлектора я наткнулся на сообщение win32 WM_SETREDRAW. Это действительно прекращает рисование элементов управления, пока вы их обновляете, и его можно применять, 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 для С# и 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
Ответ 2
Следующее одно и то же решение 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();
}
}
Ответ 3
Обычно я использую небольшую измененную версию 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
. Следовательно, было бы неплохо сделать их общедоступными.
Ответ 4
Чтобы не забывать повторно использовать чертеж:
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();
});
Ответ 5
Хорошее решение без использования взаимодействия:
Как всегда, просто включите 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;
}
}
Ответ 6
Вот комбинация 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
Ответ 7
Я знаю, что это старый вопрос, уже ответили, но вот мой подход к этому; Я реорганизовал приостановку обновлений в 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();
}
}
Ответ 8
На основе ответа 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
}
Ответ 9
Это еще проще и, возможно, взломано, поскольку я вижу много мышц GDI в этом потоке и , очевидно, только подходит для определенных сценариев. YMMV
В моем сценарии я использую то, что я буду называть "родительским" UserControl, - и во время события Load
я просто удаляю управление, которое нужно манипулировать из коллекции Parent .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 так много веселья все еще, amiright?
Ответ 10
Или просто используйте Control.SuspendLayout()
и Control.ResumeLayout()
.