Inhalt |
|
|
|
|
|
|
|
|
|
Fonts, Anti-Aliasing und Graphics2D | |
|
Wir haben bisher rein text-basierte Programme geschrieben. Nun wollen wir Grafiken erzeugen. Es ist nicht schwer, ein Grafikfenster zu öffnen.
import javax.swing.*; public class Test { public static void main (String args[]) { JFrame F = new JFrame("Test Frame"); F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); F.setSize(600, 600); F.setLocationRelativeTo(null); F.setVisible(true); } }
Das Programm zeigt lediglich ein leeres Fenster. Das Fenster kann allerdings schon verschoben, vergrößert, maximiert oder geschlossen werden. Man kann das Schließen des Fensters und alle anderen Operationen selbst überwachen. Im Moment ist es einfacher, die CloseOperation zu setzen.
Die Position des Fensters wird mit null in die Mitte des Bildschirms gesetzt. Das sollte erst geschehen, nachdem das Fenster eine gute Größe hat. Das Fenster erscheint erst, wenn setVisible(true) aufgerufen wird.
Man beachte die Importe. Wir importieren einfach alle Klassen aus java.swing.
Wir wollen nun einen String in das Fenster zeichnen. Dazu könnte man den String einfach in main zeichnen lassen.
import java.awt.*; import javax.swing.*; public class Test { public static void main (String args[]) { JFrame F = new JFrame("Test Frame"); F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); F.setSize(600, 600); F.setLocationRelativeTo(null); F.setVisible(true); Graphics G = F.getGraphics(); G.drawString("Hallo!",300,300); } }
Für das Zeichnen benötigen wir die Klassen in java.awt, die wir zusätzlich importieren.
Das eben gezeigte Programm funktioniert aber nicht zuverlässig! Möglicherweise erscheint der Schriftzug "Hallo!" sogar bei einigen Systtem. Das Fenster wird aber nicht neu gezeichnet, wenn es von einem anderen Fenster kurzzeitig verdeckt wurde.
Man muss daher einen völlig anderen Programmierstil entwickeln. Ein Programm mit einer graphischen Benutzeroberfläche wird von Ereignissen gesteuert. Eines dieser Ereignisse ist die Aufforderung, das Fenster neu zu zeichnen. Am einfachsten entspricht man dieser Aufforderung, indem man die paint-Methode von Frame, bzw. JFrame überlagert. Dazu benötigt man eine Kindklasse.
import javax.swing.*; import java.awt.*; class MyCanvas extends Canvas { @Override public void paint (Graphics g) { int w = getWidth(), h = getHeight(); g.setColor(Color.gray.darker()); g.fillRect(0, 0, w, h); g.setColor(Color.gray.brighter()); g.fillRect(20, 20, w - 40, h - 40); g.setColor(Color.red.darker()); g.setFont(new Font("SansSerif", Font.PLAIN, 20)); g.drawString("Hello!", w / 2 - 40, h / 2); } } class TestFrame extends JFrame { public TestFrame() { super("Test Frame"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); add(new MyCanvas()); } } public class Test { public static void main (String args[]) { JFrame F = new TestFrame(); F.setSize(600, 600); F.setLocationRelativeTo(null); F.setVisible(true); } }
Hier einige Erklärungen zum ereignisgesteuerten Programmstil und den verwendeten Swing-Elementen.
Im Hauptprogramm main erzeugen wir einen TestFrame, der ein Kind von JFrame ist. Wir setzen dort lediglich die Größe und die Position, und starten die Anzeige dieses Fensters.
Die Klasse TestFrame ist ein JFrame, bei dem wir MyCanvas, einen Canvas (das ist eine Zeichenfläche), hinzugefügt haben. Das Layout ist so, dass dieses Canvas die ganze Fläche des Fensters einnimmt.
Die Klasse MyCanvas hat nun eine Methode paint, die die Default-Neuzeichenroutine von Canvas überschreibt. sie wird jedesmal aufgerufen, wenn ein Neuzeichnen des Fensters erforderlich ist.Dazu erhält sie ein Graphics-Objekt, mit dem sie arbeiten kann.
Wir ermitteln in paint zunächst die Größe des Canvas. Dann wird die gesamte Fläche dunkelgrau und ein inneres Rechteck grau gefärbt, mit 20 Pixel Rand. Ins Zentrum (40 Pixel nach links verschoben) zeichnen wir einen roten Schriftzug in einem 20 Pixel großem Font.
Die Methode paint wird beim Öffnen des Fensters aufgerufen, weil ja ein Neuzeichnen notwendig ist. Man kann das aber mit
repaint();
jederzeit selbst forcieren. Dies ruft paint nicht direkt auf, sondern erzeugt ein Ereignis, das angibt, dass ein Neuzeichnen notwendig ist. Das System initiiert das Neuzeichnen.
Nun können wir durch Ändern der paint-Methode komplexere Zeichnungen anfertigen. Als Beispiel dient ein Farbspiel, das jedoch nur auf System mit genügend vielen Farben zur Geltung kommt.
class MyCanvas extends Canvas
{
public void paint (Graphics g)
{
Dimension d = getSize();
int w = d.width, h = d.height;
int i = 0;
Color C;
while (i <= w - 1 - i && i <= h - 1 - i)
{
// Beschaffe neue Farbe:
if (2 * i < 256)
C = new Color(2 * i, 255 - 2 * i, 255);
else
C = new Color(255, 0, 255);
g.setColor(C); // setze Farbe
g.drawRect(i, i, w - 1 - 2 * i, h - 1 - 2 * i);
i++;
}
}
}
Das Farbmodell, das hier verwendet wird, setzt Farben aus ihren Rot-, Grün- und Blauanteilen zusammen. Von außen nach innen nimmt also Rot ab und Grün zu. Ganz im innern wird die Farbe zu Pink (Mischung aus Rot und Blau). Die Farbanteile können als int-Werte im Bereich 0 bis 255 oder als float-Werte im Bereich 0.0 bis 1.0 angegeben werden.
Das Programm erzeugt folgende Grafik:
Dieses Programm zeichnet noch schnell genug, um als interaktives Programm durchzugehen. Falls das Neuzeichnen des Fensters zu lange dauert, sollte man auf andere Techniken zurückgreifen. In diesem Fall blockiert nämlich die Ereignisbearbeitung in paint die gesamte Ereignisbehandlung des Programms.
Es ist auch möglich, mehrere Elemente zu einem Fenster hinzuzufügen. In diesem Fall muss man aber die Anordnung (das Layout) der Elemente einstellen. Java benutzt Instanzen spezieller Klassen für diese Arbeit, die Layout-Manager.
Im folgenden Beispiel verwenden wir ein BorderLayout. Dieses Layout hat Nord-, Süd-, Ost-, West-Elemente und ein zentrales Element, das den meisten Platz einnimmt. Man braucht nicht alle diese Elemente verwenden. Als Beispiel erzeugen wir das folgende Fenster.
Im Norden befindet sich eine Komponente vom Typ JLabel, die den Text "Farbenspiel" enthält. Im Süden haben wir eine Komponente vom Typ JPanel, die zwei Komponenten vom Typ JButton enthält. Manche Komponenten, wie JPanel dienen nur dazu, andere Komponenten aufzunehmen.
Wir ersetzen lediglich die Klasse TestFrame.
class TestFrame extends JFrame { public TestFrame() { super("Test Frame"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Setze Layout: setLayout(new BorderLayout()); // Oben das Label: JPanel north = new JPanel(); north.add(new JLabel("Farbenspiel")); add("North", north); // Im Zentrum die Farben: add("Center", new MyCanvas()); // Unten die Knöpfe: JPanel south = new JPanel(); south.add(new JButton("Change")); south.add(new JButton("Close")); add("South", south); } }
Wie man sieht, muss man sich nur eine Instanz von BorderLayout beschaffen und als Layout-Manager einrichten (mit setLayout()). Außerdem übergibt man der Methode add(), die übrigens jede Komponente hat, einen String, der die Ausrichtung angibt. Die MyCanvas-Klasse bleibt von all dem natürlich unberührt.
Die Knöpfe tun derzeit noch nichts.
Es gibt auch andere Layout-Manager. Einfach zu handhaben ist GridLayout. Der Konstruktor für GridLayout benötigt eine Zeilen- und eine Spaltenzahl und die Komponenten werden einfach in eine rechteckige Matrix geformt. Ist dabei die Zeilenzahl 0, so bestimmt die Anzahl der übergebenen Komponenten die Zeilenzahl. Hier ist ein Beispiel.
import javax.swing.*; import java.awt.*;
public class Test { public static void main (String args[]) { JFrame F = new JFrame(); F.setTitle("Test"); F.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); F.setPreferredSize(new Dimension(400,100));
// Setze Layout: F.setLayout(new GridLayout(0, 2)); F.add(new JLabel("Text 1: ")); F.add(new JLabel("High")); F.add(new JLabel("Text 2: ")); F.add(new JLabel("Zweite Zeile"));
// Packen: F.pack(); F.setLocationRelativeTo(null);
// Anzeigen: F.setVisible(true); } }
Die Methode pack(), die hier noch aufgerufen wurde, sorgt dafür, dass das Fenster gerade so groß wird, dass man alle enthaltenen Komponenten sehen kann. Allerdings wird setPreferredSize() berücksichtigt, wenn dies größer ist.
Ein etwas schwierigerer Layout-Manager ist GridBagLayout. Er ordnet die Komponenten im Prinzip in Zeilen und Spalten an, jedoch mit unterschiedlichen Maschenweiten. Außerdem kann sich eine Komponente über mehrere Zeilen und Spalten erstrecken. Erfahrungsgemäß braucht man diesen Manager sehr selten.
Es gibt noch den CardPanel, der alle Komponenten übereinander anordnet, wobei jeweils nur eine sichtbar ist.
Schließlich kann man noch die Anordnung der Komponenten in eigener Regie vornehmen. Dazu kann man einfach die Methode doLayout der Komponenten überschreiben und in dieser Methode das Layout der enthaltenen Komponenten selbst vornehmen (mittels setLocation und setSize).
Den Font von Komponenten ändert setFont. Dazu besorgt man sich einen neuen Font und stellt ihn ein. Der Font wird mit seinem Font-Namen, der Fontart (PLAIN, BOLD, ITALIC) und der Font-Größe angegeben. Mit
String[] fonts=Toolkit.getDefaultToolkit().getFontList();
kann man sich eine Liste aller verfügbaren Fonts ausdrucken. Für ein JLabel stellt man den Font folgendermaßen ein, wobei wir hier die doppelte Größe des eingestellten Fonts wählen.
JLabel label=new JLabel("Text 1: "); label.setFont(new Font("Dialog",Font.BOLD,l.getFont().getSize()*2));
Das gleiche funktioniert bei einem Graphics-Objekt, so dass man auch verschiedene Fonts in paint verwenden kann. Voreingestellt ist der Font der Komponente (z.B. des Canvas).
Wir stellen uns nun die Aufgabe einen String genau zentriert auf einem Canvas darzustellen, egal wie groß dieses Canvas ist. Dazu muss man sich eine Klasse FontMetrics besorgen, mit deren Hilfe man die Größe des Strings berechnen kann. Außerdem muss man beachten, dass die Methode drawString von Graphics Strings immer an der Basislinie ausgibt. Deswegen muss man zum linken oberen Eck des Strings noch den Ascent hinzuzählen. Wir ändern einfach die Klasse MyCanvas, um einen String genau in der Mitte auszugeben.
Außerdem verwenden wir die Klasse Graphics2D. Dies ist eine Erweiterung von Graphics. Sie erlaubt es die Ausgabe zu glätten (Anti-Aliasing), was für Fonts und Grafik separat eingestellt werden muss. Außerdem funktioniert die Einstellung der Linienbreite nun über Strokes.
import java.awt.*; import javax.swing.*; class MyCanvas extends Canvas { @Override public void paint (Graphics g1) { Dimension d = getSize(); int w = d.width, h = d.height; Graphics2D g = (Graphics2D) g1; g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setFont(new Font("Dialog", Font.BOLD, 40)); FontMetrics metrics = g.getFontMetrics(); String s = "Ich grüße die Welt!"; // Berechne Stringbreite und -höhe int ws = metrics.stringWidth(s); int hs = metrics.getHeight(); // Berechne Aufpunkt für String int x = (w - ws) / 2; int y = (h - hs) / 2 + metrics.getAscent(); // Zeichne Rechteck g.setColor(Color.gray); g.setStroke(new BasicStroke(4)); g.drawRect((w - ws) / 2 - 10, (h - hs) / 2 - 10, ws + 20, hs + 20); // Zeichne String g.setColor(Color.black); g.drawString(s, x, y); } } class TestFrame extends JFrame { public TestFrame () { super("Test Frame"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); add(new MyCanvas()); } } public class Test { public static void main (String args[]) { JFrame F = new TestFrame(); F.setSize(600, 600); F.setLocationRelativeTo(null); F.setVisible(true); } }