Laden...

Graphics-Object to Image

Erstellt von robmir vor 17 Jahren Letzter Beitrag vor 17 Jahren 3.348 Views
R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 17 Jahren
Graphics-Object to Image

Hallo,

wie kann ich ein Graphics-Object als Image abspeichern. Ich möchte aus Performance-Gründen das was ich auf einem Usercontrol gezeichnet habe, in einem Image zwischenspeichern. Dh. falls das Usercontrol verdeckt wird oder das ganzes Fenster minimiert wird, soll nicht neu gezeichnet werden, weil keine Änderungen zu verzeichnen sind, aber dafür das Image draufgelegt (gezeichnet).

Das zweite Problem was ich beobachten kann, ist beim zeichnen von vielen Objekten (Details) wird das OnPaint- Methode zwei mal aufgerufen. Dann habe ich bei einer kleinerer Zeichnung, wo ganz normal die OnPaint- Methode nur 1 mal aufgerufen wird, in der OnPaint- Methode kleine Verzögerung eingebaut und dann hat Windows auch die OnPaint- Methode 2 mal aufgerufen. Also es muss etwas mit dem Dauer der onPaint Ausführung zu tun haben. Hmmmm....???? vielleicht hat schon jemand da auch Probleme gehabt.

mfg

robmir

1.271 Beiträge seit 2005
vor 17 Jahren

Zu Ersterem schau dir mal Graphics in Bitmap konvertieren an.

A wise man can learn more from a foolish question than a fool can learn from a wise answer!
Bruce Lee

Populanten von Domizilen mit fragiler, transparenter Außenstruktur sollten sich von der Translation von gegen Deformierung resistenter Materie distanzieren!
Wer im Glashaus sitzt, sollte nicht mit Steinen werfen.

3.170 Beiträge seit 2006
vor 17 Jahren

Hallo,
ich habe für einen ähnlichen Fall folgendes Offscreen-Verfahren entwickelt:
Du kannst die gesamte Zeichenfunktion in eine Methode auslagern und diese dann bei Ereignissen, bei denen tatsächlich neu gezeichnet wird, aufrufen. Das Ergebnis schreibst du in ein Image im RAM. Im OnPaint() zeichnest du dann dieses OffscreenImage. Das heißt, da nach Überdeckungen usw. immer nur OnPaint() aufgerufen wird, daß in diesen Fällen nur das Image gezeichnet wird.


class MyWindow: System.Windows.Forms.Form
{
  Graphics g;
  Bitmap offscreen;
  
  /* Konstruktion usw. */

  void Draw()
  {
      if (offscreen == null) //Bitmap for doublebuffering 
      {
        offscreen = new Bitmap(ClientRectangle.Width, ClientRectangle.Height); 
        g = Graphics.FromImage(offscreen); 
      }
      g.Clear(this.BackColor);
     
     /* Code zum zeichnen hier, dabei das Graphics-Objekt g benutzen. */
  
    CreateGraphics().DrawImage(offscreen,ClientRectangle);

    /*denkbar wäre statt der letzten Zeile auch

    this.InvokePaint(this, new PaintEventArgs(CreateGraphics(),ClientRectangle));

    aber die erste Lösung ist schneller :-)\*/
  }

  protected override void OnPaint(PaintEventArgs e)
  {
    e.Graphics.DrawImage(offscreen, e.ClipRectangle ,e.ClipRectangle, GraphicsUnit.Pixel);
    base.OnPaint(e);
  }
  
  protected override void OnResize(EventArgs e)
  {
   //Hier den offscreen nullen, da die Bitmap eine andere Größe bekommt
    offscreen = null;
    Draw();
    base.OnResize(e);
  }

}

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 17 Jahren

Hi,

danke für die schnelle Antwort. Ich habe es so gemacht aber es funktoniert nicht ganz.


protected override void OnPaint(PaintEventArgs e)
{
    if (toDraw)
    {   
         toDraw = false;
         buffer = new Bitmap(this.Width, this.Height);
         g = Graphics.FromImage(buffer);
         foreach (KeyValuePair<int, IDrawerObject> satellites in satellitesDict)
         {
             satellites.Value.Paint(myCanvas);
         }
          CreateGraphics().DrawImage(buffer, this.Width, this.Height);
    }else{
                e.Graphics.DrawImage(buffer, e.ClipRectangle, e.ClipRectangle,                       GraphicsUnit.Pixel);
            }
}

toDraw wird überall dort auf true gesetzt, wo das datenModel sich ändert (auch beim OnResize).
Beim vergössen vom Fenster wird usercontrol neu gezeichnet aber nur weißes Hintergrund dargestellt.

3.170 Beiträge seit 2006
vor 17 Jahren

wer ist myCanvas und was passiert in sattelites.Value.Paint ?

wichtig ist: die OnPaint-Methode benutzt zum Zeichnen das Graphics-Objekt, welches in den EventArgs übergeben wird, du musst aber auf das Graphics-Objekt g zeichnen.
Deshalb muss g an denen Stellen verfügbar sein, an denen tatsächlich gezeichnet wird, sprich z.B. Aufrufe von DrawLine(...) oder DrawString(...) gemacht werden.
Diese Aufrufe müssen auf dem Objekt g ausgeführt werden, nicht auf den an OnPaint übergebenen Objekt!

Mit Deiner Version unterläufst Du das Konzept, weil sattelites.Value.Paint mit Sicherheit mit den falschen Graphics zeichnet.

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 17 Jahren

aha ja, es sollte so sein:


protected override void OnPaint(PaintEventArgs e)
{
    if (toDraw)
    {  
         toDraw = false;
         buffer = new Bitmap(this.Width, this.Height);
         g = Graphics.FromImage(buffer);
         foreach (KeyValuePair<int, IDrawerObject> satellites in satellitesDict)
         {
             satellites.Value.Paint(g);
         }
          CreateGraphics().DrawImage(buffer, this.Width, this.Height);
    }else{
                e.Graphics.DrawImage(buffer, e.ClipRectangle, e.ClipRectangle,                       GraphicsUnit.Pixel);
            }
}


satellites ist ein Dictionary wo objekte abgelegt sind, die ein Interface IDrawerObject implementieren. Im IDrawerObject ist ein Paint-Methode, dh. jedes Objekt muss es implementieren und dort wird eigentlich gezeichnet. D.h so kann ich einen Satellit deaktivieren bzw. aktivieren oder in bestimmter Reihenfolge zeihnen (schichtweise).

Na ja und es funktioniert immer noch nicht (siehe oben Beitrag).

3.170 Beiträge seit 2006
vor 17 Jahren

Auch sehr schön.
Bei mir implementiert das Fenster übrigens auch ein Drawerinterface 🙂 und das wird dann an ein Objekt übergeben, in dem wieder mehrere Unterobjekte in gewünschter Reihenfolge zeichnen mit den Interface-Methoden zeichnen, nur bei mir sind diese Interfacemethoden dann Member der Form und ich kann direkt auf g zugreifen. Ein etwas anderer Ansatz für genau das gleiche Problem 🙂
Funktioniert der Code jetzt übrigens??

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

3.170 Beiträge seit 2006
vor 17 Jahren

Probiers mal so:


protected override void OnPaint(PaintEventArgs e)
{
    if (toDraw)
    {  
         toDraw = false;
         buffer = new Bitmap(this.Width, this.Height);
         g = Graphics.FromImage(buffer);
         foreach (KeyValuePair<int, IDrawerObject> satellites in satellitesDict)
         {
             satellites.Value.Paint(g);
         }
     }
     e.Graphics.DrawImage(buffer, e.ClipRectangle, e.ClipRectangle,                       GraphicsUnit.Pixel);
}

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 17 Jahren

nööö, leider geht es nicht. wenn das

e.Graphics.DrawImage(buffer, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);

ausgeführt wird, wird nichts gezeichnet.

ja ja, hast Du recht, mit Interface kann man schöne Sachen machen. Die Klasse was ich den Ausschnitt hier gezeigt habe leitet von UserControl und implementiert auch ein Interface (IView) und das ganze basiert auf MVC (Model-View-Controller) zu jedem View habe ich einen controlller zb. ein "haupt"-View und ein Übersicht-View (so etwas wie in der Kartografie verwendet wird) zum navigieren zusammen arbeitend darstellen. Also ein Controller hat ein Daten-Model aber mehrere Views. Und ein View kann ich belibieg mit den "Satelliten" konifigurieren 🙂 also dazu schalten und schichtweise beim zeichnen aufrufen.

3.170 Beiträge seit 2006
vor 17 Jahren

seltsam so sollte es eigentlich klappen... Ich habe allerdings ein bisschen mit deinem code experimentiert und ganz seltsame effekte erzeugt... Vielleicht solltest Du es mal so probieren, wie ich es zuerst beschrieben habe, das klappt bei mir einwandfrei.

Du musst lediglich die for-Schleife an die entsprechend kommentierte Stelle setzen, und wo Du bisher Invalidate() gerufen hast, Draw() aufrufen.

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

R
robmir Themenstarter:in
155 Beiträge seit 2005
vor 17 Jahren

vielen Dank für die Hilfe, ich werde weiter versuchen (nach Deinem Muster). Falls klappen sollte werde ich schreiben (es kann aber dauern, morgen wird gespielt 😉 ).

Das zweite Problem ist auch sehr merkwürdig, irgendwie wenn es zu "lange" gezeichnet wird, denkt Windows, dass neu gezeichnet werden muss (hmm.... werden die nachrichten überholt ⚠ ) so etwas kann man schlecht unterdrücken. (spy++ und, und) habe ich versucht herauszufinden wer es aufruft, na ja, es bleibt nur windows selbst, nur warum.

3.170 Beiträge seit 2006
vor 17 Jahren

Ich denke daß das Problem irgendwo im Event-Handling liegt. Aber wenn Du kein Invalidate() rufen mußt, wird auch kein Paint-Event verschickt. Die OnPaoint-Methode wird in meinem Beispiel überhaupt nur ausgeführt, wenn Resized wird (obwohl ich mir das da auch noch sparen könnte) oder wenn das Fenster irgendwie von extern den Befehl zum neuzeichnen erhält. Daher gibts dann auch keine Probleme.

Es ist einige Zeit herddaß ich das geschrieben habe, aber ich weiß auch daß ich einen Grund dafür hatte, dasPaint-Event zu unterlaufen, obwohl es mir eher darum ging, flimmern bei schnell aufeinanderfolgenden Updates (Sternkarte die man ziehen kann) zu verhindern.

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca