Mam kontrolę, do której muszę wprowadzać duże modyfikacje. Chciałbym całkowicie uniemożliwić przerysowanie, gdy to robię - SuspendLayout i ResumeLayout nie wystarczą. Jak zawiesić malowanie dla kontrolki i jej dzieci?
Mam kontrolę, do której muszę wprowadzać duże modyfikacje. Chciałbym całkowicie uniemożliwić przerysowanie, gdy to robię - SuspendLayout i ResumeLayout nie wystarczą. Jak zawiesić malowanie dla kontrolki i jej dzieci?
Odpowiedzi:
W mojej poprzedniej pracy mieliśmy problemy z uzyskaniem, aby nasza bogata aplikacja UI malowała natychmiast i płynnie. Używaliśmy standardowych kontrolek .Net, niestandardowych i devexpress.
Po wielu problemach z przeglądaniem i używaniem reflektora natrafiłem na komunikat win32 WM_SETREDRAW. To naprawdę zatrzymuje rysowanie kontrolek podczas ich aktualizacji i można je zastosować, IIRC do panelu nadrzędnego / zawierającego.
To jest bardzo, bardzo prosta klasa pokazująca, jak korzystać z tego komunikatu:
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();
}
}
Są pełniejsze dyskusje na ten temat - Google dla C # i WM_SETREDRAW, np
I do kogo może to dotyczyć, jest to podobny przykład w 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
klasa podstawowa dla wszystkich kontrolek WinForm już robi dla metod BeginUpdate
i EndUpdate
. Samo wysłanie wiadomości nie jest lepsze niż użycie tych metod do wykonania ciężkiego podnoszenia i na pewno nie może przynieść różnych rezultatów.
Control.Handle
to wymusi utworzenie uchwytu okna i może wpłynąć na wydajność. Na przykład, jeśli przesuwałeś formant na formularzu przed jego wyświetleniem, jeśli wywołasz go SuspendDrawing
wcześniej, Twój ruch będzie wolniejszy. Prawdopodobnie powinny mieć if (!parent.IsHandleCreated) return
kontrole w obu metodach.
Poniżej przedstawiono to samo rozwiązanie ng5000, ale nie używa 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
i gdzie NativeWindow
; wyszukiwanie dokumentacji dla nazwanej klasy Message
nie jest wcale tak zabawne.
Invalidate()
nie działa tak dobrze, jak i tak Refresh()
po nim.
Zwykle używam trochę zmodyfikowaną wersję ngLink za odpowiedź .
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();
}
}
}
Umożliwia to zagnieżdżanie / zawieszanie połączeń. Musisz upewnić się, że każdy SuspendDrawing
z nich pasuje do znaku ResumeDrawing
. Dlatego prawdopodobnie nie byłoby dobrym pomysłem upublicznienie ich.
SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }
. Inną opcją jest zaimplementowanie tego w IDisposable
klasie i zawarcie części rysunkowej w using
instrukcji. Uchwyt zostanie przekazany do konstruktora, który zawiesi rysowanie.
DllImport
deklaruje wParam
jako bool
?
Aby nie zapomnieć o ponownym włączeniu rysunku:
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();
}
stosowanie:
SuspendDrawing(myControl, () =>
{
somemethod();
});
action()
rzuca wyjątek? (Spróbuj / w końcu)
Ładne rozwiązanie bez użycia interop:
Jak zawsze, po prostu włącz DoubleBuffered = true w CustomControl. Następnie, jeśli masz jakieś kontenery, takie jak FlowLayoutPanel lub TableLayoutPanel, wyprowadź klasę z każdego z tych typów i w konstruktorach włącz podwójne buforowanie. Teraz wystarczy użyć pochodnych kontenerów zamiast Windows.Forms Containers.
class TableLayoutPanel : System.Windows.Forms.TableLayoutPanel
{
public TableLayoutPanel()
{
DoubleBuffered = true;
}
}
class FlowLayoutPanel : System.Windows.Forms.FlowLayoutPanel
{
public FlowLayoutPanel()
{
DoubleBuffered = true;
}
}
W oparciu o odpowiedź ng5000 lubię używać tego rozszerzenia:
#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
Posługiwać się:
using (this.BeginSuspendlock())
{
//update GUI
}
Oto kombinacja ceztko i ng5000 w celu uzyskania wersji rozszerzeń VB, która nie używa 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
Wiem, że to stare pytanie, na które już udzielono odpowiedzi, ale oto moje zdanie na ten temat; Zawiesiłem aktualizację zawieszenia na IDisposable - w ten sposób mogę zawrzeć instrukcje, które chcę uruchomić w using
instrukcji.
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();
}
}
Jest to jeszcze prostsze i być może hackerskie - ponieważ widzę w tym wątku dużo mięśni GDI i jest oczywiście dobre tylko dla niektórych scenariuszy. YMMV
W moim scenariuszu korzystam z tego, co nazywam „Kontrolą nadrzędną” UserControl - a podczas tego Load
wydarzenia po prostu usuwam kontrolę nad manipulacją z .Controls
kolekcji Rodzica , a Rodzic OnPaint
zajmuje się całkowitym malowaniem dziecka kontrolować w jakikolwiek specjalny sposób .. całkowicie wyłączając możliwości malowania dziecka w trybie offline.
Teraz przekazuję moją procedurę malowania dziecięcego metodzie rozszerzenia opartej na tej koncepcji autorstwa Mike'a Golda do drukowania formularzy Windows .
Potrzebuję podzestawu etykiet do renderowania prostopadłego do układu:
Następnie wyłączam malowanie kontroli dzieci za pomocą tego kodu w ParentUserControl.Load
module obsługi zdarzeń:
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
Następnie w tym samym ParentUserControl malujemy kontrolkę, którą należy manipulować od podstaw:
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
Kiedy już gdzieś hostujesz ParentUserControl, np. Formularz Windows - stwierdzam, że mój Visual Studio 2015 poprawnie renderuje formularz w czasie projektowania, a także w czasie wykonywania:
Teraz, ponieważ moja szczególna manipulacja obraca kontrolę dziecka o 90 stopni, jestem pewien, że wszystkie gorące punkty i interaktywność zostały zniszczone w tym regionie - ale problem, który rozwiązałem, dotyczył etykiety opakowania, która wymagała podglądu i wydrukowania, dla mnie dobrze.
Jeśli istnieją sposoby na przywrócenie gorących punktów i kontroli mojej celowo osieroconej kontroli - chciałbym się kiedyś o tym dowiedzieć (nie dla tego scenariusza, oczywiście, ale ... tylko po to, aby się uczyć). Oczywiście, WPF popiera takie szaleństwo OOTB .. ale ... hej .. WinForm wciąż jest tak fajny, prawda?