OnMouseMove in UserControl- this.Invalidate() leistungshungrig?
-
Hallo!
Ich habe ein Usercontrol erstellt. Immer wenn es für den User sichtbare Änderungen gibt aktualisiere ich das UserControl mit this.Invalidate().
Leider beschert mir das eine CPU auslastung von 70%.
Gibt es eine andere Möglichkeit die Anzeige zu refreshen als die Invalidate Methode?
Wenn ich in meinem Usercontrol (Arbeitsebene) Elemente mit der Mouse herumziehe muss ja das Bild jedesmal (merh oder weniger die ganze Zeit) neu gezeichnet werden. Daher habe ich sobald ich etwas am Bildschirm bewege eine dauerhaft CPU Auslastung.Bewege ich hingegen Symbole am Desktop durch die Gegend merke ich überhaupt nichts davon in der CPU Auslastung.
Was also mache ich falsch??
-
nicht alles immer neuzeichnen. Du kannst bei Invalidate auch ein Rectangle übergeben welches nur den wirklich geänderten Bereicht beinhaltet. Ausserdem vieleicht mal prüfen ob sich wirklich bei jedem MouseMove was ändert, oder ob ein Invalidate nach nem MouseDown aussreicht. Ausserdem könntest du mal prüfen ob du deine Brushes / Pens bei jedem OnPaint neu erstellst... das ist auch relativ rechenintesiv.
-
Ich habe im onPaint event 4 forschleifen (nicht verschachtelt) in der in jeder bei jedem Durchlauf eine neu Pen (Brush) erstellt wird.
Wie war das genau mit dem rectangle gemeint, das check ich nicht ganz? Muss ich das Rectangle dann auch in meiner onPaint Methode berücksichtigen
-
THE_ONE schrieb:
Wie war das genau mit dem rectangle gemeint, das check ich nicht ganz? Muss ich das Rectangle dann auch in meiner onPaint Methode berücksichtigen
Du kannst mit dem Rechteck oder der Region sagen, welcher Bereich neu gezeichnet werden muss. Schau dir mal die Überladung von
Invalidate
an:
http://msdn.microsoft.com/en-us/library/system.windows.forms.usercontrol.invalidate.aspxDas
PaintEventArgs
Objekt beim EventPaint
, gibt dir dann einClipRectangle
an. So kannst du deine Zeichenfunktionen auf den Bereich einschränken.
http://msdn.microsoft.com/en-us/library/system.windows.forms.painteventargs.cliprectangle.aspxDu kannst auch im
Graphics
Objekt die Clip Region setzen. Ich weiss nicht, ob bei .Net diese bereits gesetzt ist oder man sie noch setzen muss. Aber das kannst du ja einfach mal abfragen lassen.
http://msdn.microsoft.com/en-us/library/system.drawing.graphics_members.aspxGrüssli
-
Und alle "managed resources", wie z.B. Pen, Brush, etc. solltest du NICHT im Paint-Ereignis erzeugen (und wieder disposen - ich hoffe mal wenigstens, du benutzt bisher "using ..."), sondern besser in einer Membervariablen ablegen (d.h. im Konstruktor erzeugen und dann im Paint nur noch benutzen).
Ansonsten nimmst du Windows u.U. viele "Handles" weg und der GC hat dann die Arbeit...Gleiches gilt natürlich auch für andere sich wiederholende Aufrufe, z.B. Timern etc.
-
Th69 schrieb:
Und alle "managed resources", wie z.B. Pen, Brush, etc. solltest du NICHT im Paint-Ereignis erzeugen ,
sondern besser in einer Membervariablen ablegen (d.h. im Konstruktor erzeugen und dann im Paint nur noch benutzen).
Ansonsten nimmst du Windows u.U. viele "Handles" weg und der GC hat dann die Arbeit...Danke werde ich machen! Habe diese, wie ja bereits geschrieben, immer wieder im OnPaint oder dort wo ich diese gebraucht habe erzeugt. Hier zwei Methode die im OnPaint aufgerufen werden, noch ungeändert(so wie ich sie bisher implementiert hatte):
private void DrawSelectionWindow(Graphics g) { Pen myPen = new Pen(System.Drawing.SystemBrushes.WindowFrame); myPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot; Rectangle rec = new Rectangle(Math.Min(pLeftMousedown.X,p2_SelectionWindow.X),Math.Min(pLeftMousedown.Y,p2_SelectionWindow.Y),Math.Abs(p2_SelectionWindow.X-pLeftMousedown.X),Math.Abs(p2_SelectionWindow.Y-pLeftMousedown.Y)); g.DrawRectangle(myPen, rec); }
eine andere Methode:
public void Draw(Graphics g) { foreach (Connection c in this.ConnectionList) { Pen linePen = new Pen(System.Drawing.Color.Black, (float)(2)); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.DrawLine(linePen, c.D1.GetConnectionPoint(c), c.D2.GetConnectionPoint(c)); } foreach (Device d in this) { if (!d.Selected) { //marked devices d.SetResolution(g.DpiX, g.DpiY); g.DrawImageUnscaled(d.Symbol, d.Position); } } foreach (Device d in this) { if (d.Selected) { //marked devices Pen myPen = new Pen(System.Drawing.SystemBrushes.ControlDarkDark); d.SetResolution(g.DpiX, g.DpiY); g.DrawImageUnscaled(d.Symbol, d.Position); g.DrawRectangle(myPen, d.PositionX, d.PositionY, d.Symbol.Width, d.Symbol.Height); } } }
Th69 schrieb:
Gleiches gilt natürlich auch für andere sich wiederholende Aufrufe, z.B. Timern etc.
Gehören da auch Rectangles dazu?? denn die müssen ja immer wieder erzeugt werden da sie immer unterschiedlich sind. Benutze Rectangles für ein SelectionWindow und dieses ist ja von der Mausposition abhängig und daher muss ja dauernd ein neues erstellt werden.
Th69 schrieb:
(und wieder disposen - ich hoffe mal wenigstens, du benutzt bisher "using ...")
Hmm, verstehe ich nicht ganz was du meinst? Warum soll ich mitten im Programmcode using verwenden. Könntest du mir das erklären.
Das mit Dispose wurde hier: Dispose in C# besprochen. Daher werde ich in Zukunft das Dispose für die Pens und die Brushes auch ausführen wenn die Methode von den Objekten angeboten wird.
Lg und besten Dank für eure Hilfe!
-
Du solltest
using
verwenden, weil es dafür sorgt dass deine Objekte disposed werden auch wenn eine Exception fliegt, so z.B:using(Pen pen = new ...) { // Zeichnen }
Das ganze wird dann umgesetzt was in etwa so aussieht:
Pen pen = new ... try { // Zeichnen } finally { pen.Dispose(); }
Wenn jetzt beim Zeichnen irgendetwas schief geht wird der Pen trotzdem freigeben und
using
sieht einfach besser aus als diese hässlichentry-finally
-Blöcke.Der Pen ist hier jetzt nur ein Beispiel. Für einen Pen wäre es sinnvoller ihn einmal zu erstellen und in einer Membervariable der Klasse zwischen zu speichern und beim nächsten Zeichnen wiederzuverwenden.
-
THE_ONE schrieb:
Pen linePen = new Pen(System.Drawing.Color.Black, (float)(2));
Sicher nur ein Detail und in dem Zusammenhang belanglos für die Performance, aber...
Anstatt
(float)(2)
wäre es besser einfach
2.0f
zu schreiben. Im ersten Fall erzeugst Du erst ein termporäres Objekt vom Type int und wandelst das dann in einen float um was keine triviale Konvertierung ist. Im zweiten Fall erzeugst Du einfach einen float...
also:
Pen linePen = new Pen(System.Drawing.Color.Black, 2.0f);
-
zuersteinmal zu 'loks' Aussage:
das ist völlig egal, ob 2.0f oder (float)2, da das der Compiler automatisch umwandelt... (du kannst auch (float)(1+1) schreiben)und nun wieder zu 'THE ONE':
hatte ich also richtig geraten, daß du die 'using'-Anweisung noch nicht kanntest (aber 'O.o' hat sie ja schon richtig beschrieben).Edit: nicht daß du es jetzt falsch verstehst, generell sollte man bei temporären Klassen, welche dispose() implementieren, 'using(...)' benutzen (besser als dispose() von Hand aufzurufen).
Für deine konstanten Pens, Brushes etc. sollten diese jedoch trotzdem als Membervariablen angelegt werden!!!Und ein Rectangle ist nur eine einfache Struktur, d.h. dort ist die Performance relativ unwichtig (wobei es schon sinnvoll ist, auch diese nur einmalig zu erstellen, sofern sie konstant ist - in deinem Beispiel ist das Rectangle aber abhängig von der Mausposition, daher mußt du es ja immer wier neu berechnen).
Außerdem kommt mir noch "foreach(Device d in this)" komisch vor, d.h. hast du keine Trennung zwischen Logik und GUI???
-
Das mit using glaube ich jetzt vestanden zu haben:
http://msdn.microsoft.com/de-de/library/yh598w02(VS.80).aspx
http://blog.schelian.de/2008/01/10/usingKurzErlaumlutertC.aspxTh69 schrieb:
Außerdem kommt mir noch "foreach(Device d in this)" komisch vor, d.h. hast du keine Trennung zwischen Logik und GUI???
Eigentlich versuche ich schon auf sowas zu achten, weil ich mal vom 3Tier Modell gehört habe! Nur irgendwo muss es doch die Schnittstelle zwischen GUI und Logik geben. (die erste Methode von vorher geört zur GUI die zweite gehört eigentlich zu einer anderen Klasse mit Logik). Wie man das am besten handled würde ich eh gerne mal wissen.
Außerdem hängt die GUI in meinem Fall sehr eng mit der Logik zusammen. Bisher besteht mein Programm eigentlich nur aus GUI und GUILogik. Die eigentliche Logik kommt erst später hinzu und ist DANN eigentlich ziemlich easy zu implementieren. Da alles was auf dem Bildschirm ist auch mit meiner Logik zu tun hat. Ändert sich was auf dem Schirm, ändert sich auch was in der Logik.Trotz all meines Geschwafels muss ich aber zugeben das ich wenig Programmiererfahrung habe und dass mir eigentlich noch nie jemand gesagt hat wie ich etwas besser machen könnte. Mein bisheriges Wissen habe ich alles aus Büchern.
Daher finde ich dieses Forum auch so hilfreich. Da wird man auf Dinge aufmerksam gemacht auf die man sonst gar nicht kommen würde. Bis vor kurzem kannte ich weder Dispose, Using, und noch so einiges mehr.
Daher Danke an alle die in diesem Forum so aktiv mitarbeiten und uns NOOBS helfen besseren Code zu schreiben.
-
Th69 schrieb:
das ist völlig egal, ob 2.0f oder (float)2, da das der Compiler automatisch umwandelt... (du kannst auch (float)(1+1) schreiben)
Dir ist schon bewusst das int nach float kein simpler cast ist sondern eine aufwendige Umrechnung?
btw, wenn man explizit den cast angeben muß dann ist das per Definition keine automatische Umwandlung.
-
O.o schrieb:
Für einen Pen wäre es sinnvoller ihn einmal zu erstellen und in einer Membervariable der Klasse zwischen zu speichern und beim nächsten Zeichnen wiederzuverwenden.
Wo führe ich dann das Dispose aus?? Muss ich da einen eigenen Destruktor schreiben wo ich dann die Membervariable dispose?
-
Es gibt dafür das sogenannte Dispose-Pattern, dass auch von Microsoft vorgeschlagen wird. Ob das jetzt die beste Lösung für das Problem ist, darüber lässt sich streiten, aber es ist auf jeden Fall ein guter Anfang:
class MyClass : IDisposable { private bool _disposed = false; ~MyClass() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if(!_disposed) { if(disposing) { // Managed Resourcen werden hier freigegeben Pens z.B. } // Unmanaged Resourcen werden hier freigegeben IntPtr z.B. _disposed = true; } } }
-
Hallo loks,
dann schau dir einfach mal den IL-Code aller drei Versionen an...
-
loks schrieb:
Th69 schrieb:
das ist völlig egal, ob 2.0f oder (float)2, da das der Compiler automatisch umwandelt... (du kannst auch (float)(1+1) schreiben)
Dir ist schon bewusst das int nach float kein simpler cast ist sondern eine aufwendige Umrechnung?
btw, wenn man explizit den cast angeben muß dann ist das per Definition keine automatische Umwandlung.
Du hast schon Recht, wenn du sagst, dass die Konvertierung nicht trivial ist. Allerdings ist der C#-Compiler inzwischen schlau genug und optimiert solche Schnitzer des Programmierers einfach weg.