Jak zawiesić malowanie dla kontrolki i jej dzieci?
184
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?
czy ktoś może mi wyjaśnić, co w tym kontekście rysuje lub maluje? (Jestem nowy w .net) przynajmniej podaj link.
Mr_Green
1
To naprawdę wstyd (lub śmiech), że .Net jest dostępny od ponad 15 lat, a to wciąż stanowi problem. Gdyby Microsoft poświęcił tyle czasu na naprawę prawdziwych problemów, jak migotanie ekranu, jak powiedzmy: Pobierz złośliwe oprogramowanie dla systemu Windows X , to dawno temu by to zostało naprawione.
jww
@jww Naprawili to; nazywa się WPF.
philu
Odpowiedzi:
304
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:
Cóż za wspaniała odpowiedź i ogromna pomoc! Stworzyłem metody rozszerzeń SuspendDrawing i ResumeDrawing dla klasy Control, więc mogę je wywoływać dla dowolnej kontroli w dowolnym kontekście.
Zach Johnson
2
Miał kontrolkę przypominającą drzewo, która odświeżałaby odpowiednio węzeł tylko przy przestawianiu swoich dzieci, jeśli się zwali, a następnie rozszerzy, co doprowadziło do brzydkiego migotania. To działało idealnie, aby obejść ten problem. Dzięki! (Co zabawne, formant już zaimportował SendMessage i zdefiniował WM_SETREDRAW, ale tak naprawdę nie wykorzystał go do niczego. Teraz to robi.)
neminem
7
To nie jest szczególnie przydatne. To jest dokładnie to, co Controlklasa podstawowa dla wszystkich kontrolek WinForm już robi dla metod BeginUpdatei 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.
Cody Gray
13
@Cody Gray - TableLayoutPanels nie ma na przykład BeginUpdate.
TheBlastOne
4
Zachowaj ostrożność, jeśli kod umożliwia wywołanie tych metod przed wyświetleniem formantu - wywołanie Control.Handleto 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 SuspendDrawingwcześniej, Twój ruch będzie wolniejszy. Prawdopodobnie powinny mieć if (!parent.IsHandleCreated) returnkontrole w obu metodach.
oatsoda
53
Poniżej przedstawiono to samo rozwiązanie ng5000, ale nie używa P / Invoke.
publicstaticclassSuspendUpdate{privateconstint WM_SETREDRAW =0x000B;publicstaticvoidSuspend(Control control){Message msgSuspendUpdate =Message.Create(control.Handle, WM_SETREDRAW,IntPtr.Zero,IntPtr.Zero);NativeWindow window =NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgSuspendUpdate);}publicstaticvoidResume(Control control){// Create a C "true" boolean as an IntPtrIntPtr wparam =newIntPtr(1);Message msgResumeUpdate =Message.Create(control.Handle, WM_SETREDRAW, wparam,IntPtr.Zero);NativeWindow window =NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgResumeUpdate);
control.Invalidate();}}
Próbowałem, ale na etapie pośrednim między zawieszeniem a wznowieniem jest to po prostu nieważne. Może to zabrzmieć dziwnie, ale czy może po prostu utrzymać swój stan przed zawieszeniem w stanie przejściowym?
użytkownik
2
Wstrzymaj kontrolkę oznacza, że w obszarze kontrolnym nie zostanie w ogóle wykonany rysunek i możesz pobrać resztki z innych okien / kontrolek na tej powierzchni. Możesz spróbować użyć formantu z DoubleBuffer ustawionym na true, jeśli chcesz, aby poprzedni „stan” był rysowany, dopóki formant nie zostanie wznowiony (jeśli zrozumiałem, o co ci chodzi), ale nie mogę zagwarantować, że zadziała. W każdym razie myślę, że nie rozumiesz sensu używania tej techniki: ma ona na celu uniknięcie przez użytkownika oglądania postępującego i powolnego rysowania obiektów (lepiej widzieć wszystkie pojawiające się razem). W przypadku innych potrzeb użyj innych technik.
ceztko
4
Dobra odpowiedź, ale znacznie lepiej byłoby pokazać, gdzie Messagei gdzie NativeWindow; wyszukiwanie dokumentacji dla nazwanej klasy Messagenie jest wcale tak zabawne.
darda
1
@pelesl 1) użyj automatycznego importu przestrzeni nazw (kliknij symbol, ALT + Shift + F10), 2) oczekiwane zachowanie dzieci nie malowanych przez Invalidate (). Powinieneś zawiesić kontrolę rodzicielską tylko na krótki okres i wznowić ją, gdy wszystkie odpowiednie dzieci zostaną unieważnione.
ceztko
2
Invalidate()nie działa tak dobrze, jak i tak Refresh()po nim.
Eugene Ryabtsev,
16
Zwykle używam trochę zmodyfikowaną wersję ngLink za odpowiedź .
Umożliwia to zagnieżdżanie / zawieszanie połączeń. Musisz upewnić się, że każdy SuspendDrawingz nich pasuje do znaku ResumeDrawing. Dlatego prawdopodobnie nie byłoby dobrym pomysłem upublicznienie ich.
Pomaga to utrzymać oba połączenia zbilansowany: SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }. Inną opcją jest zaimplementowanie tego w IDisposableklasie i zawarcie części rysunkowej w usinginstrukcji. Uchwyt zostanie przekazany do konstruktora, który zawiesi rysowanie.
Olivier Jacot-Descombes
Stare pytanie, wiem, ale wysyłanie „fałszu” nie działa w najnowszych wersjach c # / VS. Musiałem zmienić wartość false na 0 i true na 1.
A kiedy action()rzuca wyjątek? (Spróbuj / w końcu)
David Sherret
8
Ł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.
Jest to z pewnością przydatna technika - którą często używam do wyświetlania listView - ale tak naprawdę nie zapobiega występowaniu przerysowań; wciąż zdarzają się poza ekranem.
Simon
4
Masz rację, to rozwiązuje problem migotania, a nie problem przerysowywania poza ekranem. Kiedy szukałem rozwiązania migotania, natknąłem się na kilka powiązanych wątków, takich jak ten, i kiedy go znalazłem, mogłem nie opublikować go w najbardziej odpowiednim wątku. Jednak gdy większość ludzi chce zawiesić malowanie, prawdopodobnie odnosi się to do malowania na ekranie, co często jest bardziej oczywistym problemem niż zbędne malowanie poza ekranem, więc nadal uważam, że inni widzowie mogą uznać to rozwiązanie za pomocne w tym wątku.
Eugenio De Hoyos,
Zastąp OnPaint.
6
W oparciu o odpowiedź ng5000 lubię używać tego rozszerzenia:
Pracuję z aplikacją WPF, która korzysta z formularzy WinFix zmieszanych z formularzami WPF i zajmuje się migotaniem ekranu. Jestem zdezorientowany, w jaki sposób należy wykorzystać ten kod - czy przejdzie to w oknie winform lub wpf? Czy to nie pasuje do mojej konkretnej sytuacji?
nocarrier 18.12.12
3
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 usinginstrukcji.
classSuspendDrawingUpdate:IDisposable{privateconstint WM_SETREDRAW =0x000B;privatereadonlyControl _control;privatereadonlyNativeWindow _window;publicSuspendDrawingUpdate(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);}publicvoidDispose(){var wparam =newIntPtr(1);// Create a C "true" boolean as an IntPtrvar 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 Loadwydarzenia po prostu usuwam kontrolę nad manipulacją z .Controlskolekcji Rodzica , a Rodzic OnPaintzajmuje się całkowitym malowaniem dziecka kontrolować w jakikolwiek specjalny sposób .. całkowicie wyłączając możliwości malowania dziecka w trybie offline.
Potrzebuję podzestawu etykiet do renderowania prostopadłego do układu:
Następnie wyłączam malowanie kontroli dzieci za pomocą tego kodu w ParentUserControl.Loadmodule obsługi zdarzeń:
PrivateSubParentUserControl_Load(sender AsObject, e AsEventArgs)HandlesMyBase.LoadSetStyle(ControlStyles.UserPaint,True)SetStyle(ControlStyles.AllPaintingInWmPaint,True)'exempt this control from standard painting:Me.Controls.Remove(Me.HostedControlToBeRotated)EndSub
Następnie w tym samym ParentUserControl malujemy kontrolkę, którą należy manipulować od podstaw:
ProtectedOverridesSubOnPaint(e AsPaintEventArgs)'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()EndSub
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?
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:
Są pełniejsze dyskusje na ten temat - Google dla C # i WM_SETREDRAW, np
C # Jitter
Zawieszanie układów
I do kogo może to dotyczyć, jest to podobny przykład w VB:
źródło
Control
klasa podstawowa dla wszystkich kontrolek WinForm już robi dla metodBeginUpdate
iEndUpdate
. 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 goSuspendDrawing
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.
źródło
Message
i gdzieNativeWindow
; wyszukiwanie dokumentacji dla nazwanej klasyMessage
nie jest wcale tak zabawne.Invalidate()
nie działa tak dobrze, jak i takRefresh()
po nim.Zwykle używam trochę zmodyfikowaną wersję ngLink za odpowiedź .
Umożliwia to zagnieżdżanie / zawieszanie połączeń. Musisz upewnić się, że każdy
SuspendDrawing
z nich pasuje do znakuResumeDrawing
. Dlatego prawdopodobnie nie byłoby dobrym pomysłem upublicznienie ich.źródło
SuspendDrawing(); try { DrawSomething(); } finally { ResumeDrawing(); }
. Inną opcją jest zaimplementowanie tego wIDisposable
klasie i zawarcie części rysunkowej wusing
instrukcji. Uchwyt zostanie przekazany do konstruktora, który zawiesi rysowanie.DllImport
deklarujewParam
jakobool
?Aby nie zapomnieć o ponownym włączeniu rysunku:
stosowanie:
źródło
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.
źródło
W oparciu o odpowiedź ng5000 lubię używać tego rozszerzenia:
Posługiwać się:
źródło
Oto kombinacja ceztko i ng5000 w celu uzyskania wersji rozszerzeń VB, która nie używa pinvoke
źródło
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.źródło
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 RodzicOnPaint
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ń:Następnie w tym samym ParentUserControl malujemy kontrolkę, którą należy manipulować od podstaw:
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?
źródło
Lub po prostu użyj
Control.SuspendLayout()
iControl.ResumeLayout()
.źródło