Laden...

GDI+ & OutOfMemoryException

Erstellt von PhilHol vor 15 Jahren Letzter Beitrag vor 15 Jahren 15.141 Views
P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren
GDI+ & OutOfMemoryException

Hallo,

Ich suche Tipps, wie ich oben genannte Exception ausmerzen könnte. Ich hab einiges gefunden, jedoch bin ich mir nicht sicher, ob es reicht.

Folgendes Problem stellt sich mir:

Ich hab ein umfangreiches C# Projekt in einem Zeitraum von 3/4 Jahr programmiert, welches Daten in verschiedene Arten darstellen soll (und auch tut 😉 ). Dazu zählen u.A. Diagramme, eine kleine Heightmap, etc. pp.

Die Endgrafik ist mit 3300x2400Pixel sowie 1,2mb schon ganz nett. (also eine Grafik von an die 30-40 die ich erstellen soll)

Nun das Projekt läuft wunderbar, wäre da nicht das Problem:

Es war gewünscht, dass man obrige Darstellungen sozusagen in "Batch" (automatisierter Durchlauf) erstellen kann. Man nehme ein paar 100MB Daten und viel Zeit und mache daraus zig Grafiken.

Funktioniert soweit auch super. Nur manchmal, mitten in der Nacht, kommt ein System.OutOfMemoryException Fehler.

So wie ich das sehe, scheint der PC manchmal überarbeitet zu sein (512 MB RAM, P4 2,8 GHZ)

Ich verwende größenteils die "DrawLine, SetPixel, .." Methoden.
Auslesen tu ich mittels OleDBConnection aus mehreren AccessTabellen.

Hierzu gleich eine Frage. Ich speichere die Connection in eine Klasse, welche ich jeder Zeichenfunktion übergebe um darauf zugreifen zu können (weil die Klasse wertet auch gleichzeitig einige wichtige Variablen aus) .. Ist das extrem Speicher + Performance Lastig ? Weil dann müsste ich stattdessen sehr sehr oft die Connection neu definieren.

Naja .. ich suche vorallem Tipps ..

Vielen Dank schonmal 🙂

Gelöschter Account
vor 15 Jahren

für mich hört es sich eher an als ob der gdi stackframe voll läuft.

hast du überall darauf geachtet alle gdiobjekte die idisposable implementieren in ein using einzugrenzen?

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Mhm ..

Meinst du mit GDI Objekte echt ALLE ? (Pen, Brush, ..)

Es sind wie gesagt eine Menge, und dazu müsste ich sie verschachteln.

Bzw. ganz ehrlich gesagt hab ich mit using noch nicht wirklich gearbeitet schäm

Also alle Objekte in using's packen bringt performance ? Weil dann schau ich mir das mal an

[edit]bzw. was meinst du mit GDI stackframe?

Gelöschter Account
vor 15 Jahren

oh je...
jetzt erwartet dich eine menge arbeit.

also:
jedes objekt, das die schnittstelle IDisposable implementiert, muss von hand wieder freigegeben werden, da es sich ansonsten ewig im speicher hält.
das macht man mit dem aufruf .Dispose()

using ist dabei mehr eine hilfe und etwas sicheres im bezug auf mögliche ausnahmen. wenn du den using-scope verlässt, wird automatisch die Dispose methode des eingebundenen objektes aufgerufen. von daher empfielt es sich mit using zu arbeiten.

wenn das nciht geht, implementiert man die IDisposable schnittstelle in der klasse in der man das objekt benutzt (z.b. wenn man einen pen in einer variable hat). in dieser dispose-methode ruft man dann Dispose für die entsprechenden objekte (z.b. für diesen pen) manuell auf.

bzw. was meinst du mit GDI stackframe

nun, für grafische handles hat man 1 mb speicher und wenn der voll ist dann gibt es auch die exception.
und da du kein dispose machst ist es mit garanite die ursache.

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Na servas ^^ ..

Also auf gut deutsch alle GDI Objekte die ich verwende muss ich wieder in die "weite welt" lassen .. gut gut ..

und kann ich demnach usings vereinen (Pen und brush) oder muss ich sie verschachteln ?

Was meinst du mit dem Interface IDisposable ? Jedes GDI Objekt hat die Methode Dispose .. aber was meinst du mit variable die pen enthält ? .. Kannst du mir vllt kurz ein Beispiel nennen?

Achja Wo wir gerade bei Performance sind hüstel .. Ich hab gehört, dass Try - Catch ein extremer Performance - Laster sind .. Ist dem echt so ? .. Weil dan ndarf ich mir noch was einfallen lassen lach (hab anfangs damit gearbeitet weil ich sozusagen den "error" log im laufenden betrieb wollte um im echt betrieb zu sehen ob ich fehler übersehen hab)

Gelöschter Account
vor 15 Jahren

Try - Catch ein extremer Performance - Laster sind

nein. kannst du ruhig verwenden. zu c++ zeiten waren exceptions extrem teuer.
du darfst nur exceptions nicht zu normalen abläufen verwenden. es sollen immer nur absolute ausnahmen bleiben.

bevor ich aber näher auf deine fragen eingehe solltest du dir unbedingt das da durchlesen:
Dispose implementieren und verwenden

dort wird beispiel und hintergrund erklärt.

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

OK,

Danke schön für deine Tipps & Hilfen

Werde mal meinen Code Reviewn 😉 ..

[edit] oh ja eine Frage hab ich noch... Wenn ich GDI Objekte innerhalb von einer Funktion erstelle, werden diese bei beendigung der funktion auch wieder zerstört ? oder soll ich auch da dispose vorher aufrufen ?

Weil im Normalfall sind diese Objekte immer nur innerhalb von Funktionen gültig

Bzw. Würde mir die IDisposable Implementierung eigentlich leider nichts bringen, da die GDI Objekte ja alle immer nur innerhalb von Funktionen gültig sind .. Oder seh ich das Falsch ? .. d.h. besser wäre es, wenn ich using verwende immer innerhalb der funktionen

[edit2]Bezüglich Exception .. Ich bin ein sehr "ängstlicher" Mensch und habe zumindest in jeder Funktion, die mit GDI+ Elementen arbeitet ein try-catch eingebaut .. Ist das sinnlos ? Soll ich annehmen dass sowas immer klappt oder doch drinnen lassen ?

P
48 Beiträge seit 2008
vor 15 Jahren

Hi PhilHol,

oh ja eine Frage hab ich noch... Wenn ich GDI Objekte innerhalb von einer Funktion erstelle, werden diese bei beendigung der funktion auch wieder zerstört ? oder soll ich auch da dispose vorher aufrufen ?

Dispose MUSS aufgerufen werden, wenn du Using nicht verwendest... auch innerhalb einer Funktion.
Normalfall ist sehr relativ ... du musst dir das so vorstellen. Wenn du zb new Pen sagst, trägt sich dieser Pen im hintergrund in einer Art Table ein um schnellen zugriff zu sichern und Speicher zu reservieren. Sagst du nun NICHT Dispose bleibt dieser Pen während der Laufzeit ewig in dieser Table ....

Bzw. Würde mir die IDisposable Implementierung eigentlich leider nichts bringen, da die GDI Objekte ja alle immer nur innerhalb von Funktionen gültig sind .. Oder seh ich das Falsch ? .. d.h. besser wäre es, wenn ich using verwende immer innerhalb der funktionen

Also ich habe selbst eine komplexe GDI Anwendung geschrieben und nach einigem Testen bin ich auf folgende Lösung was ZeichenObjekte wie Pens und Brushs angeht gekommen:

  • Instanziieren von Zeichen Objekten kosten recht viel Zeit, Speicher und Dispose muss beachtet werden: alle diese Probleme lösst eine statische BackroundHasttable.

Diese wird bei Programmstart instaziiert, bei Programmende verworfen. Während der Ausführung brauchen verschiedene Objekte zu verschiedenen Zeiten verschieden Pens / Brushes. Dazu gibt es dann die statische Methode GetBrushByColor(Color theColor). Diese macht nix anderes, als in der hashtable über die Color (key) zu schauen ob der Eintrag existiert und im erfolgfalls das Objekt ( Brush) zurückzuliefern oder wenn diese nicht existiert, einen neue Eintrag in der Hashtable zu machen, wo ein neuer Brush instaziiert wird (mit der entsprechenden Farbe). Der Vorteil: wenn verschiedene Objekte einen Brush zb mit der Farbe Blau benötigen, wird dieser Brush genau EINMAL instanziiert. danach erfolgt der Zugriff Just-In-Time. Über das Dispose brauchst du dir auch keine Gedanken zu machen, da dies ja erst zur Programmbeendigung nötig ist. Zudem wird ja nur eine definierte Anzahl von Objekten erstellt, welche du ggfs wiederverwendest.
Diese Methode ist äußerst effektiv, wenn du sichergehen kann, dass nur eine bestimmte Zahl von verschiedenfarbigen Brushes / Pens benötig wird. Zeichnest du mit 1000 verschiedenen farben, musst du dir selbst was einfallen lassen 🙂

Bezüglich Exception .. Ich bin ein sehr "ängstlicher" Mensch und habe zumindest in jeder Funktion, die mit GDI+ Elementen arbeitet ein try-catch eingebaut .. Ist das sinnlos ? Soll ich annehmen dass sowas immer klappt oder doch drinnen lassen ?

theoreitsch sollte es reichen, wenn du in der Hauptzeichenroutine gehst und dort mit try-catch arbeitest.

Grüße,

psy

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Super danke,

@background hashtable .. ich kann mir schon vorstellen wie du das meinst .. Danke 🙂

Bzw. noch ein beispiel für "using"

OleDBCommand Object:

Ich benötige desöfteren in einer Funktion dieses objekt .. soll ich es jedes mal mit using verwenden oder den gesamten "Block" mit einem using versehen ?

D
500 Beiträge seit 2007
vor 15 Jahren

Hallo PhilHol,

ich denke psy hat schon recht ausführlich beschrieben, was Dir bei Performance Problemen hilft. Vielleicht versuche ich noch einmal das aufzugreifen, was psy meint. Er meint mit Background Hashtable, dass Du Dir bspw. eine Klasse schreibst (enthält eine oder mehrere Hashtables), die eine Art Pool darstellt für wiederverwendbare Grafikobjekte , wie z.B. Pens, Brushes, aber soweit ich es in Erinnerung habe, kann es auch bei Fonts hilfreich sein. Ich persönlich würde das Dictionary<TKey, TType) der Hashtable bevorzugen, da das Dictionary getypt ist, und somit Sicherheit bringt.

Gruß, DaMoe

  • Edit: Habe wohl ein paar Einträge übersehen.
P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Ok ok,

Verstehs jetzt eh schon 🙂 ..

Danke auf jeden Fall ..

Gelöschter Account
vor 15 Jahren

hashtable und arraylist sind altes eisen. statt dessen soll man, wie DaMoe80 angegeben hat, ein dictionary oder im falle der arraylist lieber eine List<T> verwenden.

was gdi objekte, di du nur innerhalb einer methode benötigst, so gehören diese unbedingt in ein using. der grund ist, das z.b. im falle einer ausnahme, der fall eintreten kann das man zum manuellen dispose garnicht kommen kann. beim using wird dieser jedoch automtisch auch im falle einer ausnahme aufgerufen. beispiel:


public void foo()
{
Pen p = new Pen(..);
// anweisung -> Exception! und pech. da hier das dispose nicht mehr aufgerufen wird.
p.Dispose();
}

public void foo()
{
using(Pen p = new Pen(...))
{
//anweisung -> exception! und dispose wird noch vor eintreten in den catchblock aufgerufen.
}
}

allgemein ist die idee mit der dictionary für pens sicherlich eine gute idee.
hierzu empfielt es sich das singleton-pattern umzusetzen.
ein guter link mit einigen variationen des singleton patterns:
http://www.yoda.arachsys.com/csharp/singleton.html

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Mhm ... interessanter Ansatz ..

Brauch ich bei unmanaged code (also den List<>'s) jetzt das IDispose Interface ? ..

Dispose kann ich ja in dem Fall nicht aufrufen, das bedeudet, dass ich das object auf null setze ..

Muss ehrlich sagen SO tief hab ich mich mit C# no net beschäftigt (ein großer fehler, wie ich gerade bemerke)

Gelöschter Account
vor 15 Jahren

wie kommst du jetzt auf unmanaged code?

P
48 Beiträge seit 2008
vor 15 Jahren

ich habe mit den neune Listen-Klassen noch nicht gearbeitet, deswegen weiß ich nicht ob die generischen Listen Dispose haben und für ihre enthaltenen Objekte auf IDisposable prüfen und ggfs aufrufen ....

Auf Nummer Sicher gehst du aber auf jedenfall, wenn du selbst die Listen-Elemente durchläufst und selbst Dispose für jedes einzelne Element aufrufst.

EDIT: unmanaged Code kann ich auch net erkenne 😁

Grüße,

psy

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

pardon .. Denkfehler ..

Die Generic Lists sind ja nur lists .. also sollte ich für jeden Listeneintrag Dispose aufrufen ?

Sprich ich mach nach dem singleton pattern eine klasse, die die Liste "managed" und in der Klasse erb ich das idisposable, und da drinnen dispose ich, wenn ich die klasse nicht mehr brauche, in dem ich die liste durchgehe und jedes element dispose und dann null setze ..

hab ich das jetzt richtig erfasst ?

Gelöschter Account
vor 15 Jahren

ja naja wobei das bei einem singleton eigendlich auch shcon egal ist, da es vom pattern her solange lebt wie die ganze applikation lebt. nach schließen der applikation wird so oder so alles wieder vom betriebssystem wieder freigegeben.

aber wenn du es sauber machen willst, solltest du im singleton dispose implementieren und alle elemente der liste freigeben. zusätlich wäre da noch ein destruktor, der dispose aufruft auch nicht verkehrt.

im prinzip braucht deine klasse dann:

  1. dictionary für pen
  2. dictionary für brush
  3. dictionary für font (evtl.)
  4. diverse methoden für deren zugriffe und erstellungen usw. also das eigendliche managen
  5. disposemethode
  6. destruktor

ps: "Image" hat auch ein dispose.

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Verstehe, verstehe ..

Da programiert man 1 jahr C# und merkt, dass man von tuten und blasen keine Ahnung hat Oo .. Toll ^^ ..

Naja, bin heilfroh dass ihr mir geholfen habt, das problem zu lösen, die klasse ist soweit sogar schon fertig, war dank eurer hilfe dann eh nicht mehr SO schwer 🙂

danke nochmal ! 🙂

bezgl. des destruktors ..

nach dem Dispose implementieren und verwenden ruft der destruktor disposable mit parameter false auf, das bedeudet er schließt nur unmanaged code.

In meinem Fall hab ich aber keinen unmanaged code.

Brauch ich deshalb trotzdem den destruktor ?

Gelöschter Account
vor 15 Jahren

code sagt mehr als tausend worte ^^

    public class myClass : IDisposable
    {
        private bool disposed = false;
        public myClass()
        {
            //init
        }

        #region IDisposable Members

        public void Dispose()
        {
            if(disposed) return;
            disposed = true;
            //aufräumarbeit
        }

        #endregion

        ~myClass()
        {
            this.Dispose();
        }
    }
P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

na gut na gut, bei deinem beispiel seh ich den bedarf des konstruktors deutlicher heraus ..

So und jetzt mach ich mal froh ans schaffen ^^ ..

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo PhilHol,

Brauch ich deshalb trotzdem den destruktor ?

nein, wenn du keine unverwalteten Ressourcen direkt in dem Objekt hast, brauchst du den Destruktor nicht. Man sollte in diesen Fällen den Destruktor nicht implementieren.

Hallo JAck30lena,

jedes objekt, das die schnittstelle IDisposable implementiert, muss von hand wieder freigegeben werden, da es sich ansonsten ewig im speicher hält.

Das ist nicht richtig. Erstmal gibt Dispose das Objekt (also seinen Speicher) selbst sowieso nicht frei, sondern nur dessen unverwaltete Ressourcen. Zum anderen wird jedes Objekt ganz normal vom GC aufgeräumt, egal ob es Dispose implementiert oder nicht. Und wenn Dispose richtig implementiert ist, werden zu diesem Zeitpunkt auch noch die unverwalteten Ressourcen freigegeben, wenn das vorher noch nicht passiert ist.

Es gibt also keine Speicherlecks durch vergessenes Dispose.

Dispose sollte man natürlich trotzdem so früh wie möglich aufrufen, damit die meist knappen unverwalteten Ressourcen so früh wie möglich anderweitig genutzt werden können. Aber eben auch nur deshalb. Den Speicher des Objekts kann man mit Dispose nicht freigeben. Entsprechend hat Dispose auch keinen Einfluss darauf wie lange das Objekt im Speicher gehalten wird. Das bestimmt alleine der GC.

herbivore

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

*lach*

ok, naja ich habs jetzt so implementiert .. gehen tut's halbwegs ^^ ..

Die Singleton Klasse is cool

Hab jetzt damit meine Loging geschichten und die brushes / Pens umgeschrieben .. DAS funktioniert zum glück schon ganz gut 🙂

Gelöschter Account
vor 15 Jahren

im prinzip hast du vollkomen recht herbivore und ich hätte mich auch nciht allgemein halten sollen. eigendlich bezog ich meine aussagen eher auf das thema pen, brush und font. also quasi die gdi-handle geschichten, die intern (so weit ich weiß) unverwaltete ressourcen beinhalten und daher IDisposable implementieren.

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Hi,

kann es sein, dass die Lösung zwar "sauberer" ist mit der Singleton Klasse, aber Ressourcenfressender ? ^^ ... Oder zumindest kaum unterschied gibt ? ..

Habs bei 1-2 aufrufen pro Funktion mittels


SingletonGraphic.Instance.GetBrush();

und bei >2 aufrufen pro Funktion mittels:


SingletonGraphic sgraph = SingletonGraphic.Instance;
sgraph.GetBrush();

gelöst

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo PhilHol,

kann es sein, dass die Lösung zwar "sauberer" ist mit der Singleton Klasse, aber Ressourcenfressender ? ^^ ... Oder zumindest kaum unterschied gibt ?

wenn das so ist, dann hast du vermutlich noch einen Implementierungsfehler.

Habs bei 1-2 aufrufen pro Funktion mittels ...

Die beiden Codevarianten machen keinen Unterschied.

herbivore

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

mhm ..

Ok ich bin mal so frei .. äh .. hier ist meine singleton Klasse


using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace Test
{
    /// <summary>
    /// Globale Singleton Klasse zur Speicherung von Brushes und Pens
    /// </summary>
    public sealed class GraphicHelper : IDisposable
    {
        #region Singleton Pattern
        // Zugriff über Singleton s=Singleton.getInstance();

        // Hilfsfeld für eine sichere Threadsynchronisierung
        private static Object m_lock = new Object();

        private GraphicHelper()
        {
            this.m_brushes = new Dictionary<int, Brush>();
            this.m_pens = new Dictionary<string, Pen>();
        }

        private static volatile GraphicHelper instance = null;

        /// <summary>
        /// Die Instanz von GraphicHelper
        /// </summary>
        public static GraphicHelper Instance
        {
            get
            {
                // DoubleLock 
                if (instance == null)
                    lock (m_lock) { if (instance == null) instance = new GraphicHelper(); }
                return instance;
            }
        }
        #endregion

        private Dictionary<int, Brush> m_brushes;
        private Dictionary<string, Pen> m_pens;

        /// <summary>
        /// Übergibt ein Brush-Objekt
        /// </summary>
        /// <param name="color">Farbe des Brushes</param>
        /// <returns>der Brush</returns>
        public Brush GetBrush(Color color)
        {
            if (!this.m_brushes.ContainsKey(color.ToArgb()))
                this.m_brushes.Add(color.ToArgb(), new SolidBrush(color));

            return this.m_brushes[color.ToArgb()];
        }

        /// <summary>
        /// Übergibt ein Pen-Objekt
        /// </summary>
        /// <param name="color">Farbe des Pens</param>
        /// <returns>der Pen</returns>
        public Pen GetPen(Color color)
        {
            if (!this.m_pens.ContainsKey(color.ToArgb().ToString() + "+1"))
                this.m_pens.Add(color.ToArgb().ToString() + "+1", new Pen(color));

            return this.m_pens[color.ToArgb().ToString() + "+1"];
        }

        /// <summary>
        /// Übergibt ein Pen-Objekt
        /// </summary>
        /// <param name="color">Farbe des Pens</param>
        /// <param name="size">Größe des Pens</param>
        /// <returns>der Pen</returns>
        public Pen GetPen(Color color, int size)
        {
            if (!this.m_pens.ContainsKey(color.ToArgb().ToString() + "+" + size.ToString()))
                this.m_pens.Add(color.ToArgb().ToString() + "+" + size.ToString(), new Pen(color, size));

            return this.m_pens[color.ToArgb().ToString() + "+" + size.ToString()];
        }

        internal Pen GetPen(Color color, float size)
        {
            if (!this.m_pens.ContainsKey(color.ToArgb().ToString() + "+" + size.ToString()))
                this.m_pens.Add(color.ToArgb().ToString() + "+" + size.ToString(), new Pen(color, size));

            return this.m_pens[color.ToArgb().ToString() + "+" + size.ToString()];
        }

        #region Dispose
        private bool disposed = false;

        /// <summary>
        /// Destruktor, ruft Dispose auf
        /// </summary>
        ~GraphicHelper()
        {
            Dispose();
        }

        /// <summary>
        /// Releases all Ressources used by this Total_Visualization_Program.GraphicHelper
        /// </summary>
        public void Dispose()
        {
            if (disposed) return;
            disposed = true;

            foreach (KeyValuePair<int, Brush> brush in this.m_brushes)
                brush.Value.Dispose();
            this.m_brushes = null;
            foreach (KeyValuePair<string, Pen> pen in this.m_pens)
                pen.Value.Dispose();
            this.m_pens = null;
        }
        #endregion


    }
}

Gelöschter Account
vor 15 Jahren

ich würde dir noch zusätzlich empfehlen, die dictionarys nach oben hin zu begrenzen, indem du eine logik einbaust, das z.b. alte pens/brushes freigegeben werden, sobald man z.b. den 20. brush/pen hinzufügen möchte. also ein klassischer caching - mechanismus mit begrenztem speicher.

nebenbei kannst du einiges an performance bei den get methoden rausholen, indem du z.b. nicht dauernd in string konvertierst um einen wert zu prüfen (und das 3 mal..) usw.

49.485 Beiträge seit 2005
vor 15 Jahren

Hallo PhilHol,

wenn du viele verschiedene Pens brauchst, kann das Cachen tatsächlich mehr Speicher und vor allem mehr Handles fressen, als wenn du den Pen jedes mal neu erzeugst und direkt nach der oder den Zeichenoperationen, die ihn benötigen, wieder freigibst (Dispose). So ein Caching bietet sich vor allem dann an, wenn man nur sehr weniger verschiedene Pens braucht. Dann kann man diese sogar einfach in den Instanzvariablen halten, wie das auch in [Tutorial] Zeichnen in Windows-Programmen (Paint/OnPaint, PictureBox) gemacht wird.

herbivore

P
48 Beiträge seit 2008
vor 15 Jahren

Hallo,

es geht noch speicherschonender, wenn du bei den Pens nur Farbe und Breite änderst und bei den Brushs nur die Farbe änderst. Dabei musst du aber deine Zeichenreihenfolge beachten.
Denn genau diese Properties kann du ändern, ohne das GDI-Objekt neu instanziieren zu müssen (im Gegensatz zum Font.Size, etc).
Du kannst also nur einen globalen Pen und einen globalen Brush anlegen und deine Get-Funktionen entsprechend anpassen.


        /// <summary>
        /// Übergibt ein Pen-Objekt
        /// </summary>
        /// <param name="color">Farbe des Pens</param>
        /// <returns>der Pen</returns>
        public Pen GetPen(Color color)
        {
            itsGlobalPen.Color = color;

            return itsGlobalPen;
        }

        /// <summary>
        /// Übergibt ein Pen-Objekt
        /// </summary>
        /// <param name="color">Farbe des Pens</param>
        /// <param name="size">Größe des Pens</param>
        /// <returns>der Pen</returns>
        public Pen GetPen(Color color, float size)
        {
            itsGlobalPen.Color = color;
            itsGlobalPen.Width = size;

            return itsGlobalPen;
        }

Der Nachteil:

Pen aRedPen = GetPen(Color.Red)
Pen aGreenPen = GetPen(Color.Green)

und dann zeichnen mit beiden Stiften geht NICHT. (beide wären grün)
Du musst deine Reihefolge beachten und immer den Pen neu holen, falls du mit einer anderen Farbe zeichnen willst.

Grüße,

psy

P
PhilHol Themenstarter:in
82 Beiträge seit 2007
vor 15 Jahren

Oh, einige Ideen, vielen Dank.

Am einfachsten für mich ist das Beispiel von psy, weil ich nie 2 gleiche Pens brauche 🙂 .. Perfekt !

Danke !