AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein GUI-Design mit VCL / FireMonkey / Common Controls Delphi Invalidate, Repaint, Refresh, Update, Paint... ?
Thema durchsuchen
Ansicht
Themen-Optionen

Invalidate, Repaint, Refresh, Update, Paint... ?

Ein Thema von Medium · begonnen am 14. Sep 2009 · letzter Beitrag vom 16. Sep 2009
Antwort Antwort
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#1

Invalidate, Repaint, Refresh, Update, Paint... ?

  Alt 14. Sep 2009, 12:45
Aloah!

Ich verzweifel gerade am simplen Neuzeichnen meiner selbst gebauten Komponenten. Diese sind direkt von TGraphicControl abgeleitet, und tragen einen kleinen Timer mit sich herum der sie auf Wunsch blinken lässt. Jeder "Blink" muss dabei dann natürlich neu gezeichnet werden, was ich bisher einfach über ein Repaint gemacht hatte.
Delphi-Quellcode:
procedure TMyGraphObject.BlinkProc(Sender: TObject);
begin
  FIsLit := not FIsLit;
  Repaint;
end;
Und im Paint wird dann anhand von FIsLit in der einen oder anderen Farbe gezeichnet. Nix spannendes.

Nun habe ich ein Projekt, bei dem sehr viele dieser (und anderer) Komponenten auf einem Formular liegen - so ~200-400 Stück. (Es ist ein Prozessabbild, da muss so viel rein ). Die Krux ist nun, dass offenbar jedes Repaint jeder Komponente ein komplettes Neuzeichnen des gesamten Formulars auslöst, wodurch das ganze merkbar lahm wird, wenn da mal so 50 Kästchen blinken sollen. (Und damit meine ich RICHTIG lahm, die Blink-Frequenz ist auf 750ms, und die werden nichtmal mehr geschafft. Dabei sind es alles nur einfache Figuren, keine aufwendigen Farbverläufe o.ä.)
Invalidate, Refresh, und auch ein InvalidateRect mit einem Rect in Größe der Kompo führen ebenfalls alle zu o.g. Verhalten.
Update löst überhaupt kein Neuzeichnen aus.

Abhilfe, und zwar massiv, schafft hier zwar ein Tauschen von Repaint mit Paint, allerdings werden dann natürlich z.B. Labels (generell alle TGraphicControls - also Parent-Canvas nutzende) die auf diesen Komponenten liegen natürlich einfach übernagelt. DAS darf nun aber auch nicht passieren!

Wie kann ich es erreichen, dass wirklich NUR die Elemente mit neu gezeichnet werden, die auch wirklich von meinem gerade übermalt worden sind (anstelle des kompletten Fensters)?
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#2

Re: Invalidate, Repaint, Refresh, Update, Paint... ?

  Alt 14. Sep 2009, 14:58
Ich hab bislang keine eingebaute Lösung dafür gefunden (weder in Delphi noch der WinAPI), und habe mir nun mit einem kleinen "Hack" geholfen. Für den Fall dass es mal jemand brauchen kann:

Delphi-Quellcode:
procedure TMyGraphObject.RepaintOverlapping;
var
  i, selfIndex: Integer;
  Bottom, Right: Integer;
  PBottom, PRight: Integer;
begin
  Bottom := Top+Height;
  Right := Left+Width;
  selfIndex := High(Integer);
  for i := 0 to Parent.ControlCount-1 do
  begin
    PBottom := Parent.Controls[i].Top+Parent.Controls[i].Height;
    PRight := Parent.Controls[i].Left+Parent.Controls[i].Width;
    if ((Bottom >= Parent.Controls[i].Top) and (Top <= PBottom) and
      (Right >= Parent.Controls[i].Left) and (Left <= PRight)) then
    begin
      if (Parent.Controls[i] = self) then selfIndex := i;
      if (i>selfIndex) and (Parent.Controls[i].Visible) then
        TMyGraphObject(Parent.Controls[i]).Paint;
    end;
  end;
end;
Das rufe ich nun nach jedem Paint innerhalb meiner Komponente auf. Es jodelt durch alle Komponenten des Parents, und veranlasst ein Neuzeichnen überlappender Controls wenn diese sichtbar sichtbar sind, und in der Z-Order über dem eigenen liegen.
Der "Hack" hierbei besteht in dem harten Cast auf TMyGraphObject. Da Paint protected ist, und ein Repaint das selbe Problem hervorruft wie oben beschrieben, nötige ich zum Aufruf von Paint. Das klappt weil Paint virtuell ist, und eigentlich sollte diese Methode wohl jede visuelle Komponente implementieren. Wenn es mal eine nicht tut knallt es natürlich, aber was nutzt eine Kompo ohne Darstellung - und sei es nur das Designtime-Icon

Das klappt natürlich nicht mehr, wenn eine "dritte Schicht" dazu kommt und die "zweite Schicht" eine fremde Kompo ist, die dies so nicht implementiert hat. Kommt in meinem Fall jedoch nicht vor, daher bleibt das mal offen. Auch mit teildurchsichtigen Controls wird es Probleme geben, so diese sich nicht hart über Regions begrenzen (im Falle von WinControls - GraphicControls wie TLabel mit Transparent = true sind da z.B. unproblematisch).

Schönen Gruß,
Medium

Edit: Hmm, okay. So ganz rund ist das echt nicht, da damit u.U. trotzdem die Darstellung nicht mehr der Z-Order entspricht. Das zu Lösen würde entweder eine Endlosschleife oder doch wieder einen verhältnismäßig aufwendigen Prozess nach sich ziehen, in dem zunächst erst alle potenziell betroffenen Controls gesammelt werden müssten, und dann anhand ihrer Z-Order neugezeichnet. Fies ist, dass man sich durch entsprechende Überlappungsketten ganz schöne Rattenschwänze an Graphen einhandeln kann
Für meine Anwendung ist das nicht weiter schlimm, aber SO allgemeingültig ist o.g. Workaround dann wohl leider nicht.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#3

Re: Invalidate, Repaint, Refresh, Update, Paint... ?

  Alt 14. Sep 2009, 15:34
Letzter Beitrag. Das Problem mit der Z-Order und nicht eigenen Komponenten lässt sich durch eine kleine nette Rekursion ja doch recht elegant lösen. Anbei die etwas bereinigte und rekusrsive Form:

Delphi-Quellcode:
procedure TMyGraphObject.RepaintOverlapping(item: TControl);
var
  i, itemIndex: Integer;
begin
  itemIndex := High(Integer);
  for i := 0 to Parent.ControlCount-1 do
  begin
    if (Parent.Controls[i] = item) then itemIndex := i;
    if (i > itemIndex) and (Parent.Controls[i] is TGraphicControl) and (Parent.Controls[i].Visible) and
      (((item.Top+item.Height) >= Parent.Controls[i].Top) and (item.Top <= (Parent.Controls[i].Top+Parent.Controls[i].Height)) and
      ((item.Left+item.Width) >= Parent.Controls[i].Left) and (item.Left <= (Parent.Controls[i].Left+Parent.Controls[i].Width))) then
    begin
      TMyGraphObject(Parent.Controls[i]).Paint;
      RepaintOverlapping(Parent.Controls[i]);
    end;
  end;
end;
Aufgerufen wird es aus der eigenen Komponente mit dem Parameter "self".

Die Rekursion terminiert, weil man keinen "Ring" aus sich jeweils an einer Stelle überlappenden Controls erzeugen kann, da sonst einem Control mehrere Z-Orders zukommen müssten. Daher ist dieses Vorgehen sicher.

Edit: Prüfung auf TGraphicControl eingebracht. Somit knallt man auch bei überlappenden WinControls nicht in eine Wand, da sie eben keine Paint-Methode haben. Diese sind zudem ohnehin kein Problem, da sie einen eigenen Canvas haben, der immer über GraphicControls des darunter befindlichen Parents gezeichnet wird.
Der Cast muss allerdings so bleiben, da TGraphicControl Paint ja noch nicht implementiert. D.h. man muss in seiner Komponente dies auf jeden Fall machen. Ich hab's mal mit diversen 3rd-Party Komponenten auf Basis von TGraphicControl probiert, und es ging mit bisher jedem.

Edit2: Bedingungen umgestellt. Ist jetzt noch eine kleine Ecke performanter durch Z-Order abfrage vor Überlappungstest, und ein paar Verschachtelungsebenen durch kombinierte Bedingungen gespart.
Vielleich wäre das ja sogar was für die CodeLib. Ich frage mich grad eh, warum Delphi das selbst nicht in ähnlicher Weise implementiert hat, und statt dessen da so sperrig mit DCs erzeugen und diversem anderen Overhead dran geht. Der Laufzeitunterschied ist ausgesprochen gewaltig.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Blup

Registriert seit: 7. Aug 2008
Ort: Brandenburg
1.464 Beiträge
 
Delphi 12 Athens
 
#4

Re: Invalidate, Repaint, Refresh, Update, Paint... ?

  Alt 15. Sep 2009, 12:47
procedure TMyGraphObject.RepaintOverlapping(item: TControl);

Der Hintergrund (Parent) wird nicht gezeichnet.
Steuerelemente die einen ItemIndex kleiner dem eigenen ItemIndex haben, werden überhaupt nicht gezeichnet.
Diese beiden Sachen sind schlecht, wenn das eigene Control zum Teil transparent ist.
Der rekursiven Aufruf kann dazu führen, daß Steuerelemente, mit einem ItemIndex größer als eigener ItemIndex + 1, mehrfach gezeichnet werden.
Besser ist es, ein ClipRect(TCanvas.ClipRect) zu setzen und wirklich nur die Steuerelemente zu zeichnen, die das ClipRect überschneiden. Damit entfällt die Rekursion.
Delphi-Quellcode:
procedure TMyGraphObject.RepaintOverlapping(AItem: TControl);
var
  Dummy: TRect;
  MyRgn: HRGN;
  Control: TControl;
  i: Integer;
begin
  with AItem.Boundsrect do
    MyRgn := CreateRectRgn(Left, Top, Right, Bottom);

  try
    SelectClipRgn(AItem.Parent.Canvas.Handle, MyRgn);
    {hier Parent-Hintergrund zeichnen}

    for i := 0 to AItem.Parent.ControlCount-1 do
    begin
      Control := AItem.Parent.Controls[i];
      if (Control is TGraphicControl) and Control.Visible and
         IntersectRect(Dummy, AItem.Boundsrect, Control.Boundsrect) then
      begin
        TMyGraphObject(Control).Paint;
      end;
    end;
  finally
    SelectClipRgn(AItem.Parent.Canvas.Handle, HRGN(nil));
    DeleteObject(MyRgn);
  end;
end;
200-400 Timer ?
Mal so richtig bei den Resourcen aus dem Vollen schöpfen...

Erstell dir besser eine zusätzliche Klasse mit einem Timer(im Prinzip ein Singleton) und realisiere ein Observer-Model.
Jede registrierte Komponente kann als Observer innerhalb des Events InvalidateRect aufrufen.
Dadurch sollte das Formular bei jedem Event nur einmal neu gezeichnet werden.
  Mit Zitat antworten Zitat
DerDan

Registriert seit: 15. Nov 2004
Ort: Donaueschingen
251 Beiträge
 
Delphi XE3 Professional
 
#5

Re: Invalidate, Repaint, Refresh, Update, Paint... ?

  Alt 15. Sep 2009, 13:02
Hallo,

mit einem zentralem Timer blinkt dann auch alles schön synchron..

mfg
DerDan
nichts ist so schön wie man es sich vorstellt
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#6

Re: Invalidate, Repaint, Refresh, Update, Paint... ?

  Alt 15. Sep 2009, 20:36
Das mit der ClipRegion klingt sehr interessant, und in der Tat müssen bei der Rekursion ggf. Elemente mehrfach gezeichnet werden. Das werde ich doch gleich morgen mal antesten!

Was die Timer angeht: Nein, es sind natürlich nicht so viele Timer! Der wird natürlich erst erstellt sobald ein Element auf blinkend gesetzt wird, bzw. freigegeben wenn auf starre Füllung zurück geschaltet wird. Im Normalfall blinken maximal 2-5 Elemente gleichzeitig, im Idealfall 0 (es dient zur Fehleranzeige ). Dass ich nun auf einmal 50+ "Blinkis" hatte und dieses Problem überhaupt erst bemerkt habe, liegt daran dass ich zu Debuggingzwecken alles blinken lassen hab, was noch nicht vollständig parametrisiert ist. Das kommt im Anwendungsfall nie vor. Was jedoch vorkommt ist, dass mal so 20-50 Elemente auf einen Schlag ihre Farbe wechseln - insbesondere bei Programmstart - und da trifft das selbe Problem zu, weswegen ich es ganz gerne lösen wollte.

Als ich mit den Kompos angefangen hab, hatte ich auch eben diese Gedanken, aber letztlich davon abgesehen diese zusätzliche "Wurschtel" reinzubringen, da es sonst an anderen Stellen ggf. sehr unschön hätte werden können. (Es ist eine ganze Suite an Kompos, mit zugehöriger eigener Formularklasse, integrierter Datenbankanbindung und anderen Schweinereien.) Was aber letztlich das Totschlagargument war, war dass gleich im ersten Projekt in dem sie zum Einsatz kamen unterschiedlich schnell blinkende Elemente gefordert waren. Klar kann man das alles realisieren, und es ist hübsch und so, aber da hat dann Zeitdruck und "wer bezahlt das" lauter geschrien als mein Sinn für Ästhätik Synchrones Blinken hab ich damals auch auf den Tisch gebracht, das war aber ausser mir keinem wichtig Hauptsache blinkt rot wenn kaputt.
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Medium

Registriert seit: 23. Jan 2008
3.686 Beiträge
 
Delphi 2007 Enterprise
 
#7

Re: Invalidate, Repaint, Refresh, Update, Paint... ?

  Alt 16. Sep 2009, 10:05
Södale, gebaut und leider ein Problem festgestellt, welches der Grund war weshalb ich das überhaupt rekursiv gemacht hatte. Ein Bild sagt mehr als 1000 Worte, siehe Anhang.

Ich musste auch noch eine Kleinigkeit anpassen, da ein TControl selbst noch keinen Canvas hat. TButton z.B. hat auch keinen, weshalb das Ganze nicht gegangen wäre, wenn der Parent kein TGraphicControl oder TCustomControl Nachfahre ist. Kommt zwar doch eher selten vor, aber es geht ja auch so (DC selbst abholen):
Delphi-Quellcode:
var
  Dummy: TRect;
  MyRgn: HRGN;
  Control: TControl;
  i: Integer;
  DC: HDC;
begin
  with item.Boundsrect do
    MyRgn := CreateRectRgn(Left, Top, Right, Bottom);
  DC := GetDC(item.Parent.Handle);
  try
    SelectClipRgn(DC, MyRgn);
    //hier Parent-Hintergrund zeichnen
    for i := 0 to item.Parent.ControlCount-1 do
    begin
      Control := item.Parent.Controls[i];
      if (Control is TGraphicControl) and Control.Visible and
         IntersectRect(Dummy, item.Boundsrect, Control.Boundsrect) then
      begin
        TMyGraphObject(Control).Paint;
      end;
    end;
  finally
    SelectClipRgn(DC, HRGN(nil));
    DeleteObject(MyRgn);
    ReleaseDC(item.Parent.Handle, DC);
  end;
end;
Aber wie gesagt, im Anhang sichtbares Problem taucht damit leider wieder auf. Auch machte es den Eindruck etwas langsamer zu sein. (Mein PC "sirrt" seltsam wenn er unter Last ist, und das Sirren ist mit dieser Methode beim Blinken etwas lauter und länger. Nicht grad eine wissenschaftliche Messmethode, aber bisher immer zuverlässig! Mit der ganz alten Repaint-Version hat er z.B. durchgehend gesirrt... )
Miniaturansicht angehängter Grafiken
blink_883.gif  
"When one person suffers from a delusion, it is called insanity. When a million people suffer from a delusion, it is called religion." (Richard Dawkins)
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:33 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz