none
PictureBox, Graphics; Kreis erscheint nicht RRS feed

  • Frage

  • Hey,

    so sieht mein Code derzeit aus. Eigentlich sollte auf der PictureBox pb ein Kreis erscheinen, was er aber nicht tut. Ich hab bereits etwas rumgesucht und gesehn, dass bei solchen Sachen in der Regel mit onPaint-Ereignissen gearbeitet wird. Wie genau müsste ich den Code umbauen damit er funktioniert?

    Hier mein derzeitiger Code-Schnippsel:

    public void zeichneForm(PictureBox pb, int maßstab)
            {
                if (form == "Kreis")
                {
                    pb.BackColor = Color.Transparent;// Hintergrundfarbe der PictureBox

                    Pen objPen = new Pen(color);
                    Graphics objG = pb.CreateGraphics();

                    Rectangle r = new Rectangle(0,0, laenge * maßstab, breite * maßstab); // Rechteck für Ellipse definieren
                   
                    objG.DrawEllipse(objPen, r); //zeichnen
                }
                ....
            }

    Freitag, 29. Juni 2012 15:06

Antworten

  • Hallo zusammen,

    Wenn man in ein Control zeichnet, dann im Paint-Ereignis, wie Elmar das bereits beschrieben hat. Aber es macht einfach keinen Sinn in ein PictureBox-Control zu  zeichnen. Das ist kein VB 6.0! Dafür wurde PictureBox einfach nicht konstruiert. PictureBox ist ein Control das Images anzeigt. Also sollte man besser in ein Image zeichnen und dann das Image PictureBox.Image zuweisen:

    private void buttonDrawEllipse_Click(object sender, EventArgs e)
    {
        Image image = new Bitmap(
            pictureBox1.ClientRectangle.Width, 
            pictureBox1.ClientRectangle.Height);
    
        using (Graphics g = Graphics.FromImage(image))
        {
            g.InterpolationMode = InterpolationMode.High;
            g.Clear(Color.Black);
            float penWidth = 10f;
            using (SolidBrush brush = new SolidBrush(color))
            {
                using (Pen pen = new Pen(brush, penWidth))
                {
                    RectangleF paintRectangle = 
                        new RectangleF((penWidth / 2f), 
                            (penWidth / 2f), 
                            ((this.pictureBox1.ClientRectangle.Width - penWidth) * maßstab), 
                            ((this.pictureBox1.ClientRectangle.Height - penWidth) * maßstab));
    
                    g.DrawEllipse(pen, paintRectangle);
                }
            }
        }
    
        pictureBox1.Image = image;
    }
    

    Übrigens: Wenn man in ein Control zeichnet, müssen Zeichen-Operationen und Steuerungs-Operationen getrennt werden. Gezeichnet wird im Paint()-Ereignis, gesteuert wird anderswo, z.B. in einem Button-Handler (über Setzen von Flags und letztendlich Aufrufen von [Control].Invalidate()).

    Gruß
    Marcel

    Freitag, 29. Juni 2012 17:02
    Moderator
  • Hallo,

    malen sollte immer im Paint-Ereignisses erfolgen. Wenn Du dafür Variablen brauchst, so kannst Du sie als Klassenvariablen definieren:

                
            private string form = "Kreis";
            private int laenge = 30;
            private int breite = 40;
            private float maßstab = 1.5f;
            private Color farbe = Color.Red;
    
            private void pictureBox1_Paint(object sender, PaintEventArgs e)
            {
                if (form == "Kreis")
                {
                    using (var pen = new Pen(farbe))
                    {
                        var rect = new RectangleF(0, 0, (laenge * maßstab), (breite * maßstab));
                        e.Graphics.DrawEllipse(pen, rect);
                    }
                }
            }
    

    wobei Du das Paint-Ereignis abonnieren musst, entweder über den Designer einstellen oder nach InitializeComponent eintragen:

    this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint);

    Wenn Du unterschiedliche Formen zeichnen möchtest, wäre es sinnvoll Klassen zu definieren,
    in denen Du die einzelnen Variablen (wie form, laenge, breite, maßstab, farbe) zusammenfaßt.

    Gruß Elmar

    Freitag, 29. Juni 2012 16:06
    Beantworter

Alle Antworten

  • Hallo,

    malen sollte immer im Paint-Ereignisses erfolgen. Wenn Du dafür Variablen brauchst, so kannst Du sie als Klassenvariablen definieren:

                
            private string form = "Kreis";
            private int laenge = 30;
            private int breite = 40;
            private float maßstab = 1.5f;
            private Color farbe = Color.Red;
    
            private void pictureBox1_Paint(object sender, PaintEventArgs e)
            {
                if (form == "Kreis")
                {
                    using (var pen = new Pen(farbe))
                    {
                        var rect = new RectangleF(0, 0, (laenge * maßstab), (breite * maßstab));
                        e.Graphics.DrawEllipse(pen, rect);
                    }
                }
            }
    

    wobei Du das Paint-Ereignis abonnieren musst, entweder über den Designer einstellen oder nach InitializeComponent eintragen:

    this.pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox1_Paint);

    Wenn Du unterschiedliche Formen zeichnen möchtest, wäre es sinnvoll Klassen zu definieren,
    in denen Du die einzelnen Variablen (wie form, laenge, breite, maßstab, farbe) zusammenfaßt.

    Gruß Elmar

    Freitag, 29. Juni 2012 16:06
    Beantworter
  • Vielen Dank für die schnelle Antwort. Leider funktioniert es immernoch nicht ganz so wie gedacht.

    string form = "Kreis";
    int breite = 50;
    int laenge = 50;
    Color color = Color.Black;
    
    void mPicbox_Paint(object sender, PaintEventArgs e)
            {
                Graphics g = e.Graphics;
                
                if (form == "Kreis")
                {
                    var pen = new Pen(color);
                    Rectangle rect = new Rectangle(0, 0, laenge, breite);
                    g.DrawEllipse(pen, rect);
                }
            }


    Ich habe bei der if-Abfrage einen Breakpoint gesetzt, um zu sehn, ob das Programm dieses Teil überhaupt erreicht. Das ist der Fall. Dennoch erscheint die Ellipse nicht in der PictureBox. Selbstverständlich habe ich zuvor die Größe der PictureBox gesetzt und diese auch per Controls.Add zur Oberfläche hinzugefügt.

    MFG
    CurlyMB

    • Bearbeitet CurlyMB Freitag, 29. Juni 2012 16:40
    Freitag, 29. Juni 2012 16:37
  • Hallo zusammen,

    Wenn man in ein Control zeichnet, dann im Paint-Ereignis, wie Elmar das bereits beschrieben hat. Aber es macht einfach keinen Sinn in ein PictureBox-Control zu  zeichnen. Das ist kein VB 6.0! Dafür wurde PictureBox einfach nicht konstruiert. PictureBox ist ein Control das Images anzeigt. Also sollte man besser in ein Image zeichnen und dann das Image PictureBox.Image zuweisen:

    private void buttonDrawEllipse_Click(object sender, EventArgs e)
    {
        Image image = new Bitmap(
            pictureBox1.ClientRectangle.Width, 
            pictureBox1.ClientRectangle.Height);
    
        using (Graphics g = Graphics.FromImage(image))
        {
            g.InterpolationMode = InterpolationMode.High;
            g.Clear(Color.Black);
            float penWidth = 10f;
            using (SolidBrush brush = new SolidBrush(color))
            {
                using (Pen pen = new Pen(brush, penWidth))
                {
                    RectangleF paintRectangle = 
                        new RectangleF((penWidth / 2f), 
                            (penWidth / 2f), 
                            ((this.pictureBox1.ClientRectangle.Width - penWidth) * maßstab), 
                            ((this.pictureBox1.ClientRectangle.Height - penWidth) * maßstab));
    
                    g.DrawEllipse(pen, paintRectangle);
                }
            }
        }
    
        pictureBox1.Image = image;
    }
    

    Übrigens: Wenn man in ein Control zeichnet, müssen Zeichen-Operationen und Steuerungs-Operationen getrennt werden. Gezeichnet wird im Paint()-Ereignis, gesteuert wird anderswo, z.B. in einem Button-Handler (über Setzen von Flags und letztendlich Aufrufen von [Control].Invalidate()).

    Gruß
    Marcel

    Freitag, 29. Juni 2012 17:02
    Moderator
  • Danke! Jetzt funktioniert alles wie es soll. ;)

    Freitag, 29. Juni 2012 17:50
  • Hallo CurlyMB,

    da dürfte dann noch etwas fehlen oder Du hast den Breakpoint an der falschen Stelle, der oben gezeigte Code funktioniert bei mir in einer leeren PictureBox (mit ausreichender Größe).

    Beim Paint-Ereignis muss man mit Breakpoints vorsichtig sein: Wenn das Steuerelement von Visual Studio (oder einem anderen Fenster) verdeckt wird,so erfolgt das Zeichnen immer und immer wieder, da Paint immer ausgeführt wird, wenn Windows feststellt, dass der Fensterinhalt verdeckt wurde.

    Für die Grundlagen schau Dir mal die Webcast Serie an GDI+ mit dem .NET Framework und die MSDN Einführung: Grafik und Zeichnen in Windows Forms

    Gruß Elmar

    @Marcel:
    Man darf in eine PictureBox (oder Label, ...) malen... und das wir hier noch weit von einem ausgewachsenen Grafikprogramm entfernt sind und es sich um die ersten (noch etwas wackeligen) Schritte handelt, ist uns wohl klar ;-)

    Freitag, 29. Juni 2012 20:40
    Beantworter
  • Hallo CurlyMB und Elmar,

    Es gibt einige wenige Situationen, wo es sinnvoll erscheinen mag, in ein PictureBox zu zeichnen. Z.B. wenn man ein Overlay über das im PictureBox angezeigte Bild anzeigen möchte. Als Beispiel kann man sich diesen Beitrag ansehen, wo ich ein Fadenkreuz über eine PictureBox lege. Das sind Ausnahmen.

    Wenn man am Anfang steht, macht man oft den Fehler anzunehmen, dass man beim Zeichnen auf ein PictureBox direkt auf das geladene Image zeichnen würde. Dem ist leider nicht so. Die Pixel des geladenen Bildes bleiben beim Zeichnen auf eine PictureBox unangetastet. Der gezeichnete "Kreis" steht also nur auf dem Bildschirm, nicht im Image selbst.

    Das wäre genau so mit jedem anderen Control. Ich muss davon ausgehen, dass - wenn man schon ein PictureBox-Steuerelement verwendet - die Absicht besteht, ein Image darin anzuzeigen. Sonst könnte man genau so gut in ein Panel zeichnen. Ohne den PictureBox-Overhead.

    Gruß
    Marcel

    Samstag, 30. Juni 2012 07:34
    Moderator
  • Nur mal so aus Interesse gefragt. Als alter MFC Programmierer bin ich relativ neu in C# und habe da noch keinen Grafikzauber ver(a/u)nstaltet.

    Du benutzt für die GDI Objekte so ein using(...) Konstrukt, was wohl dazu dient ein explizites Dispose() einzusparen, wenn ich das Konzept richtig verstanden habe. Gilt das für alle GDI Ressourcen und gibt es sonst sowas wie memory leaks, o.ä.? Ich wundere mich, dass in einer sogenannten "managed" Umgebung sowas notwendig ist,

    Gruß,
    Hajü
    Montag, 2. Juli 2012 08:28
  • Hallo Hajü,

    using ist mehr als nur das Dispose, es funktioniert auch bei Ausnahmen, und der Compiler ergänzt den erforderlichen try ... catch Block, siehe Beschreibung in der MSDN.

    Bei Windows Forms sorgt Dispose (using) für das ReleaseDC, DeleteObject usw. in Windows API pur, denn darunter werkelt immer noch GDI(+) und das ist eben nicht managed.

    Wobei in den Fällen, in denen es der Programmierer vergisst, der Finalizer einspringt; nur dauert das bis zur nächsten Garbage Collection, so dass es zu keinen Leaks kommt.

    Und wer (wie ich) mit dem Windows 3.0 SDK angefangen ist, hat noch gelernt sparsam mit den Ressourcen umzugehen (und macht für einen Kringel keine Bitmap auf ;-)

    Gruß Elmar

    Montag, 2. Juli 2012 14:42
    Beantworter
  • Hallo Hajü und Elmar,

    Die CLR gibt keine Garantien dafür, dass der Finalizer überhaupt laufen wird. Und falls der Finalizer die Handles nicht freigibt, kommt es zu einem Leak von nativen Ressourcen. Darum, als Faustregel: Wenn eine Klasse IDisposable implementiert, dann muss Dispose() vom Entwickler aufgerufen werden. Im Fall von Graphics z.B., welches das IDisposable-Pattern implementiert, wird Dispose() beim Verlassen des using-Blocks aufgerufen, welches seinerseits Dispose(true) aufruft und GC.SuppressFinalize(this) um dem GC mitzuteilen, dass die Freigabe bereits erfolgt ist und es keinen Sinn mehr hat, die finialisierbare Instanz in der freachable Queue zu behalten. Das reduziert das Risiko eine Leaks.

    @Elmar: Zum Thema Bitmap "für einen Kringel" habe ich bereits alles gesagt, was zu sagen war. Ich kann's aber gerne wiederholen.

    Gruß
    Marcel

    Montag, 2. Juli 2012 14:58
    Moderator
  • Hallo Marcel,

    wenn in Anfänger-Threads über Spitzfindigkeiten diskutiert wird, so löst die Erinnerung an F. D. aus B. garantiert eine ThreadAbortException aus ;-)

    Gruß Elmar

    Montag, 2. Juli 2012 16:39
    Beantworter
  • Actioni contrariam semper et aequalem esse reactionem. Lass uns das Gleichgewicht suchen.
    Montag, 2. Juli 2012 17:24
    Moderator
  • Hallo Marcel und Elmar,

    Danke für eure Hintergrundinfos. Ich markiere die mal ausnahmsweise nicht als Antwort, da ich nicht der OP bin und es mit dem eigentlichen Thema nur am Rande zu tun hat. Hat mir aber was gebracht.

    Hajü

    Mittwoch, 4. Juli 2012 05:16
  • Hallo CurlyMB,

    Ich gehe davon aus, dass die Antworten Dir weitergeholfen haben.
    Solltest Du noch "Rückfragen" dazu haben, so gib uns bitte Bescheid.

    Grüße,
    Robert


    Robert Breitenhofer, MICROSOFT  Twitter Facebook
    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „Entwickler helfen Entwickler“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Donnerstag, 19. Juli 2012 11:53
    Moderator