Linien und Pixel beim Programmstart



  • Mechanics schrieb:

    So macht man das nicht, das Graphics Objekt, das du mit CreateGraphics bekommst ist nicht zum Wiederverwenden gedacht. Beim Button Click funktioniert es auch mehr oder weniger zufällig. Die nächste WM_PAINT Nachricht wird es löschen.
    Am besten zeichnet man in der OnPaint Methode. Wenn das Zeichnen aufwändiger ist und du nicht immer zeichnen willst, kannst du dir das Bild in einem Bitmap cachen.

    Gibt es dazu irgendwo ein Beispiel?

    Gruß
    Michael



  • Ja, im Internet.



  • Ich habe es jetzt so abgeändert dass beide Linien beim Start sofort sichtbar sind. Aber wirklich verstanden habe ich es noch nicht, und im Internet weiss ich nicht wonach ich suchen soll. Ein einfaches Beispiel habe ich noch nicht gefunden. Ich brauche ein Bitmap um darin mit SetPixel() zeichnen zu können, und zusätzlich sollen auch Linien mit DrawLine() gezeichnet werden.
    Ein neues Problem ist jetzt, dass der Button beim Programmstart nicht dargestellt wird. Keine Ahnung warum, ich tappe im Moment völlig im Dunkeln.

    Gruß
    Michael

    namespace CS_Grafik_test
    {
        public partial class Form1 : Form
        {
            public Bitmap bitmap1 = new Bitmap(256, 256);
            public Graphics graphics1;
            Pen p = new Pen(Color.Black);
    
            public Form1()
            {
                InitializeComponent();
                graphics1 = Graphics.FromImage(bitmap1);
                graphics1.Clear(Color.White);     // weisser Hintergrund
                for (int i = 0; i < 256; i++)     // Linie mit Grau-Verlauf in Bitmap zeichnen
                    bitmap1.SetPixel(i, 100, Color.FromArgb(i, i, i));           
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                pictureBox1.Refresh();
            }
    
            private void pictureBox1_Paint(object sender, PaintEventArgs e)
            {
                Graphics g = e.Graphics;
                pictureBox1.Image = bitmap1;
                g.DrawLine(p, 0, 0, 100, 100);      
            }      
        }
    }
    


  • Wenn Windows meint, dass ein Fenster neugezeichnet werden muss, kriegt das Fenster eine WM_PAINT Nachricht. Das ist der Zeitpunkt, wo du den richtigen Kontext hast und zeichnen darfst. Das wird in .NET durch OnPaint und Graphics gekapselt.
    Die PictureBox reagiert auf das PaintEvent indem sie ihr Image zeichnet. Damit wird sicher das überzeichnet, was du im Konstruktor in das Graphics Objekt reingezeichnet hast, deswegen funktioniert es nicht.
    So wirklich schön schaut das ganze Konstrukt mit dem Bitmap und dem Graphics Objekt bei dir aber nicht aus.
    Was das mit dem Button zu tun hat, weiß ich nicht, sieht man in dem geposteten Code nicht.



  • Mechanics schrieb:

    Wenn Windows meint, dass ein Fenster neugezeichnet werden muss, kriegt das Fenster eine WM_PAINT Nachricht. Das ist der Zeitpunkt, wo du den richtigen Kontext hast und zeichnen darfst. Das wird in .NET durch OnPaint und Graphics gekapselt.
    Die PictureBox reagiert auf das PaintEvent indem sie ihr Image zeichnet. Damit wird sicher das überzeichnet, was du im Konstruktor in das Graphics Objekt reingezeichnet hast, deswegen funktioniert es nicht.

    Doch, die Grafik wird ja jetzt korrekt gezeichnet, und beide Linien sind auch beim Programmstart sichtbar.
    Nur der Button wird nicht dargestellt. Der erscheint erst dann, wenn man einmal draufgeklickt hat.

    Mechanics schrieb:

    So wirklich schön schaut das ganze Konstrukt mit dem Bitmap und dem Graphics Objekt bei dir aber nicht aus.
    Was das mit dem Button zu tun hat, weiß ich nicht, sieht man in dem geposteten Code nicht.

    Aber das ist das vollständige Programm (abgesehen von 8 using Zeilen die ich hier nicht reinkopiert habe). Auf dem Form gibt's nur die PictureBox und den Button, sonst nichts weiter. Ich wüsste nicht wie ich das Beispiel noch weiter vereinfachen kann.

    Gruß
    Michael



  • micha7 schrieb:

    Doch, die Grafik wird ja jetzt korrekt gezeichnet, und beide Linien sind auch beim Programmstart sichtbar.

    Ja, jetzt. Du hast geschrieben, du verstehst es immer noch nicht, deswegen hab ich versucht, genauer zu erklären, warum das vorher nicht funktioniert hat.

    Es gibt noch eine zusätzliche x.designer.cs oder so, wo das InitializeComponent implementiert ist. Die Datei wird vom GUI Designer automatisch erstellt. Schau erstmal, ob du nicht aus Versehen Visible auf false gestellt hast oder so, oder ob sich nicht irgendwas überdeckt. Ich seh jedenfalls nichts, was irgendwas mit dem Button zu tun hätte.



  • Ich habe jetzt einfach ein neues Projekt angelegt und den gleichen Code reinkopiert, und siehe da jetzt läuft es. Keine Ahnung woran das mit den Button gelegen hat.
    Was könnte man daran jetzt noch verbessern?
    Also in dem Bitmap sind die Dinge enthalten, die viel Zeit zum Zeichnen brauchen. Diese Dinge ändern sich nur selten. Deshalb soll das Bitmap gespeichert werden. Die Dinge die sich häufig ändern werden mit DrawLine darüber gezeichnet, das passiert in diesem Beispiel wenn auf den Button geklickt wird.

    Gruß
    Michael

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    
    namespace Grafik_test3
    {
        public partial class Form1 : Form
        {
            public Bitmap bitmap1 = new Bitmap(256, 256);
            public Graphics graphics1;
            Pen p = new Pen(Color.Black);
            int a = 0;
    
            public Form1()
            {
                InitializeComponent();
                graphics1 = Graphics.FromImage(bitmap1);
                graphics1.Clear(Color.White);     // weisser Hintergrund für
                                                  //  Hintergrundbild
                for (int i = 0; i < 256; i++)     // Linie mit Grau-Verlauf
                                                  //  in Bitmap zeichnen
                    bitmap1.SetPixel(i, 100, Color.FromArgb(i, i, i));
                pictureBox1.Refresh();            // Beim Programmstart anzeigen 
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                a++;
                pictureBox1.Refresh();
            }
    
            private void pictureBox1_Paint(object sender, PaintEventArgs e)
            {
                Graphics g = e.Graphics;
                pictureBox1.Image = bitmap1;    // unverändertes Hintergrundbild 
                g.DrawLine(p, 0, a, 100, a);    // darauf wir eine Linie
                                                //  gezeichnet, bei jedem Klick auf
                                                //  den Button wird die Linie um 1
                                                //  nach unten verschoben
            }
        }
    }
    


  • Du kannst mit Graphics.DrawImage das Bitmap zeichnen. Das wäre dann einheitlicher und nicht so befremdlich wie Image zuweisen und unabhängig davon auf dem Graphics zeichnen.
    Warum brauchst du das mit SetPixel überhaupt? Wie du wohl schon selber gemerkt hast, ist SetPixel langsam.



  • Mechanics schrieb:

    Du kannst mit Graphics.DrawImage das Bitmap zeichnen. Das wäre dann einheitlicher und nicht so befremdlich wie Image zuweisen und unabhängig davon auf dem Graphics zeichnen.
    Warum brauchst du das mit SetPixel überhaupt? Wie du wohl schon selber gemerkt hast, ist SetPixel langsam.

    Ja, ich weiss dass es langsam ist, aber ich muss das Bild aus einzelnen Pixeln mit beliebigen Grauwerten zusammensetzen. Die Grauwerte hängen von Parametern ab die sich ändern können, daher kann ich das Bild nicht im voraus berechnen und dann einlesen. Die Grauwert-Linie in meinem Programm war jetzt nur ein stark vereinfachtes Beispiel. Im richtigen Programm müssen 100000 Grauwerte berechnet werden. Ist aber kein Problem, dauert weniger als 1 Sekunde.

    Gruß
    Michael



  • Du kannst auch über LockBits mit ScanLines arbeiten, dafür muss es aber in den unsafe Block. Wär aber deutlich schneller.



  • Muss nicht unbedingt in einen unsafe Block.
    Man kann auch in ein Array schreiben und dann Marshal.Copy verwenden um das Array in die gelockte Bitmap zu kopieren.
    Läuft im Prinzip natürlich mehr oder weniger auf das selbe raus.



  • Das Problem ist leider noch nicht gelöst. Das Programm tut zwar was es soll, aber es braucht im Leerlauf 50% CPU-Leistung.
    Kann es sein dass sich pictureBox1_Paint() irgendwie rekursiv selbst aufruft?

    Gruß
    Michael

    P.S.
    Wenn ich
    pictureBox1.Image = bitmap1;
    ersetze durch
    pictureBox1.BackgroundImage = bitmap1;
    dann ist die CPU-Leistung nahe null. Aber warum ist das so?

    namespace Grafik_test3
    {
        public partial class Form1 : Form
        {
            public Bitmap bitmap1 = new Bitmap(256, 256);
            int a = 0;
    
            public Form1()
            {
                InitializeComponent();
                Graphics graphics1 = Graphics.FromImage(bitmap1);
                graphics1.Clear(Color.White);   // weisser Hintergrund 
                for (int i = 0; i < 256; i++)   // Linie mit Grau-Verlauf
                    bitmap1.SetPixel(i, 100, Color.FromArgb(i, i, i));
                pictureBox1.Refresh();  
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                a++;
                pictureBox1.Refresh();
            }
    
            private void pictureBox1_Paint(object sender, PaintEventArgs e)
            {
                Graphics g = e.Graphics;
                pictureBox1.Image = bitmap1;    // Hintergrundbild laden
                Pen p = new Pen(Color.Black);
                g.DrawLine(p, 0, a, 100, a);    // Linie zeichnen, wird
                                                // bei jedem Mausklick um
                                                // 1 nach unten verschoben
            }
        }
    }
    


  • Du weist in der Paint Methode der PictureBox ein neues Image zu. Kann gut sein, dass das intern zu einem Invalidate führt, was wieder zu einem Paint Event führt. Deswegen wärs besser das so zu machen, wie ich vorgeschlagen habe, also das Bitmap einfach in das Graphics zu zeichnen, mit Graphics.DrawImage.
    Man müsste mal schauen, wie BackgroundImage implementiert ist. Vielleicht führt es eben nicht zu einem Invalidate.



  • Mechanics schrieb:

    Deswegen wärs besser das so zu machen, wie ich vorgeschlagen habe, also das Bitmap einfach in das Graphics zu zeichnen, mit Graphics.DrawImage.

    OK, ich hab's entsprechend geändert und es funktioniert problemlos.
    Vielen Dank für deine Hilfe !

    Gruß
    Michael




Anmelden zum Antworten