D) GUI-Objekte als Bild speichern
Manchmal kommt man in die Verlegenheit, ein GUI-Objekt als Bild speichern zu wollen. Sei es, weil ein Screenshot der Anwendung oder einer bestimmten Componente erzeugt, eine aktuelle Visualisierung dargestellt, eine automatisierte Dokumentation mit Screenshots einer Anwendung generiert, oder etwas ganz anderes gemacht werden soll. Dieses Kapitel zeigt Ihnen, wie Sie dieses Problem lösen können.
Theorie
Die Theorie sieht wie folgt aus: Sie erzeugen ein neues BufferedImage
in der gewünschten Größe. Von diesem BufferedImage
holen Sie sich nun das Graphics2D
-Objekt, um in das Bild zeichnen zu können. Darauf lassen Sie dann die gewünschte GUI zeichnen. Hört sich einfach an, hat aber einige Besonderheiten, die beachtet werden müssen.
Ein sichtbares Objekt als Bild
Das Speichern eines sichtbaren Objekts, also einer GUI oder einem Teil einer GUI, die auf dem Bildschirm angezeigt wird, ist denkbar einfach. Sie müssen der paintAll
-Methode des zu zeichnenden Objekts einfach das Graphics2D
-Objekt des BufferedImage
übergeben. Schon wird alles auf das Bild gezeichnet. Eine entsprechende Methode könnte z. B. so aussehen:
public BufferedImage paintComponent(Component c) { BufferedImage img = new BufferedImage(c.getWidth(), c.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D g = img.createGraphics(); c.paintAll(g); g.dispose(); return img; }
Sehen Sie sich diese Testapplikation an, die einen Screenshot von sich als PNG abspeichert, sobald auf den „Drück mich“-Button geklickt wurde.
package de.jbb; import java.awt.Component; import java.awt.FlowLayout; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextField; public class CopyTest extends JFrame implements ActionListener { private static final long serialVersionUID = -5360063461409447639L; private JButton button = new JButton("Drück mich"); private JLabel label = new JLabel("Ich bin ein Text"); private JTextField field = new JTextField("Füllen Sie mich aus! "); public CopyTest() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getContentPane().setLayout(new FlowLayout()); getContentPane().add(this.label); getContentPane().add(this.field); getContentPane().add(this.button); this.button.addActionListener(this); pack(); setVisible(true); } public void actionPerformed(ActionEvent evt) { savePicture(paintComponent(this), "screen.png"); } public void savePicture(BufferedImage img, String file) { try { ImageIO.write(img, "png", new File(file)); } catch (IOException e) { e.printStackTrace(); } } public BufferedImage paintComponent(Component c) { BufferedImage img = new BufferedImage(c.getWidth(), c.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D g = img.createGraphics(); c.paintAll(g); g.dispose(); return img; } public static void main(String[] args) { new CopyTest(); } }
Ein daraus resultierender Screenshot könnte in etwa so aussehen:
Dabei fällt auf, dass der Button die Darstellung besitzt, wie wenn er gerade gedrückt wurde. Dies lässt sich umgehen, indem die Zeichnung erst dann angefertigt wird, wenn der Button wieder losgelassen wurde. Hierzu ergänzen Sie den Aufruf in der actionPerformed
-Methode um ein SwingUtilities.invokeLater
.
Ein
SwingUtilities.invokeLater
bewirkt, dass der darin eingebettete Code erst dann ausgeführt wird, wenn alle anderen GUI-Operationen ausgeführt wurden.
public void actionPerformed(ActionEvent evt) { SwingUtilities.invokeLater(new Runnable() { public void run() { savePicture(paintComponent(CopyTest.this), "screen.png"); } }); }
Nun sieht das erzeuge Bild doch gleich viel besser aus:
Ein unsichtbares Objekt als Bild
Versuchen Sie einmal auf diese Art und Weise ein unsichtbares GUI-Objekt auf einem Bild zu speichern. Dies wird Ihnen so nicht gelingen, da Sie entweder eine Fehlermeldung, oder einfach ein leeres, schwarzes Bild erhalten werden. Dies liegt daran, dass Java (aus Performancegründen) ein Objekt erst dann „zusammenbaut“, wenn es auch wirklich auf dem Bildschirm angezeigt werden soll. Eigentlich eine gute Sache, für unser Vorhaben aber eher hinderlich.
Java bietet jedoch die Möglichkeit zu simulieren, also ob der GUI-Ausschnitt irgendwo angezeigt werden würde. Hierzu gibt es die Methode paintComponent
in der Klasse SwingUtilities
. So ist es auch möglich, ein noch nicht dargestelltes Objekt zu zeichnen:
public BufferedImage paintNotVisibleComponent(Component c, Container con, Rectangle rect) { BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB); Graphics2D g = img.createGraphics(); SwingUtilities.paintComponent(g, c, con, rect); g.dispose(); return img; }
Dieser Methode wird zuerst das Graphics
-Objekt übergeben, auf welches gezeichnet werden soll. Anschließend die zu zeichnende Component
, gefolgt vom Container
, in dem die Component
pseudohaft dargestellt, und dem Bereich als Rectangle
, der gezeichnet werden soll.
Sehen Sie sich das noch einmal an einer einfachen Beispielapplikation an:
package de.jbb; import java.awt.Component; import java.awt.Container; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.SwingUtilities; public class PaintComponent { public static void main(String[] args) { PaintComponent pc = new PaintComponent(); JButton drawMe = new JButton("Ich bin ein Button"); BufferedImage img = pc.paintNotVisibleComponent(drawMe, new Container(), new Rectangle(drawMe.getPreferredSize())); pc.savePicture(img, "offScreen.png"); } public void savePicture(BufferedImage img, String file) { try { ImageIO.write(img, "png", new File(file)); } catch (IOException e) { e.printStackTrace(); } } public BufferedImage paintNotVisibleComponent(Component c, Container con, Rectangle rect) { BufferedImage img = new BufferedImage(rect.width, rect.height, BufferedImage.TYPE_INT_RGB); Graphics2D g = img.createGraphics(); SwingUtilities.paintComponent(g, c, con, rect); g.dispose(); return img; } }
Das Ergebnis: