Advanced
Java
Services
|
Canvas Image MediaTracker |
|
Wie schon im vorigen Abschnitt gesehen, dient ein Canvas dient zum Entwicklen eigener Komponenten.
Die AWT Standardklassen Button
oder Label können keine Bilder darstellen. Daher zählt ein Canvas, der Bilder im
Format .jpg oder .gif darstellen kann, zu den beliebtesten Beispielen. Durch die Swingklassen JLabel
und JButton hat natürlich die Klasse Canvas in dieser Richtung an Bedeutung verloren.
Es ist trotzdem eine gute Übung, einen solchen Canvas für Bilder zu entwickeln. Um
Canvas sinnvoll einzusetzen, muß man eine Unterklasse von Canvas anlegen und
mindestens die Methode public void paint(Graphics g) überschreiben. paint() hat in
Canvas folgende Implementierung.
public void paint(Graphics g)
{
g.setColor(getBackground());
g.fillRect(0, 0, width, height);
}
Im einfachsten Fall legen wir im Datenteil eine Referenz für ein Imageobjekt an, das dann
über den Konstruktor initialisiert wird. Mit der Methode drawImage() aus der Klasse Graphics
wird das Bild dann in paint() dargestellt. Da die Methode nicht prüft, ob das übergebene
Objekt nicht null ist, müssen wir das tun.
import java.awt.*;
public class SimpleImageCanvas extends Canvas
{
private Image img ;
public SimpleImageCanvas(Image im)
{
img = im ;
}
public void paint(Graphics g)
{
if (img!=null)
g.drawImage(img, 0, 0, this) ;
}
} // end class
Ein einfaches Demoprogramm, das unsere Klasse testet, kann so aussehen.
import java.awt.*;
public class SimpleImageCanvasDemo extends Frame
{
public SimpleImageCanvasDemo()
{
super("SimpleImageCanvasDemo");
Image im = Toolkit.getDefaultToolkit().getImage("mandrill.jpg");
SimpleImageCanvas sic = new SimpleImageCanvas(im);
add(sic);
setSize(400,300);
setLocation(60,100);
setVisible(true);
}
public static void main(String[] args)
{
SimpleImageCanvasDemo sc = new SimpleImageCanvasDemo();
} // end main
} // end class
Das Programm zeigt auch, wie man mit Hilfe der Klasse Toolkit aus einer Bilddatei ein Bildobjekt
vom Typ Image erhält. Dies ist für einen Benutzer unserer Klasse aber umständlich.
Deshalb ergänzen wir unsere Klasse um einen Konstruktor, der diese Arbeit erledigt.
import java.awt.*;
public class SimpleImageCanvas extends Canvas
{
private Image img ;
public SimpleImageCanvas(Image im)
{
img = im ;
}
public SimpleImageCanvas(String fileName)
{
if(fileName!=null)
img = Toolkit.getDefaultToolkit().getImage(fileName);
}
public void paint(Graphics g)
{
if (img!=null)
g.drawImage(img, 0, 0, this) ;
}
} // end class
Damit vereinfacht sich unser Testprogramm
import java.awt.*;
public class SimpleImageCanvasDemo extends Frame
{
public SimpleImageCanvasDemo()
{
super("SimpleImageCanvasDemo");
SimpleImageCanvas sic = new SimpleImageCanvas("mandrill.jpg");
add(sic);
setSize(400,300);
setLocation(60,100);
setVisible(true);
}
public static void main(String[] args)
{
SimpleImageCanvasDemo sc = new SimpleImageCanvasDemo();
} // end main
} // end class
Als nächstes möchten wir unser Bild zentriert anzeigen. Dazu müssen wir die
Größe des Bildes wissen. Zwar gibt es in der Klasse Image die Methoden getHeight()
und getWidth(), da aber das Laden eines Bildes vom externen Speicher inn den Hauptspeicher ein
längerdauernder Vorgang ist, liefern diese Methoden zunächst -1 . Wir können sie erst
benützen, wenn das Bild vollständig geladen ist. Mit der Klasse MediaTracker haben wir
die Möglichkeit, abzuwarten und auf das Bild erst dann zuzugreifen, wenn es vollständig geladen
ist. Dafür schreiben wir uns die Methode trackImage(). Sie erhält ein Imageobjekt, und
erzeugt ein Mediatrackerobjekt. Mit addImage() wird das Bild an den MediaTracker
übergeben, waitForAll() wartet, bis das Bild vollständig geladen ist, danach wird die
Bildgröße ermittelt. Als Parameter in getWidth() und getHeight() ist vom Typ
ImageObserver. Dieses Interface wird von der Klasse Component implementiert. Hier wird die
Komponente angegeben, die das Bild darstellt. Um die Bildgröße aufnehmen zu können,
haben wir den Datenteil der Klasse entsprechend ausgebaut. Die neue Methode kommt in den Konstruktoren
zur Anwendung. Außerdem überprüfen die Konstruktoren nun, ob "vernünftige"
Parameter übergeben wurden.
import java.awt.*;
public class SimpleImageCanvas extends Canvas
{
private Image img ;
private int imWidth, imHeight;
public SimpleImageCanvas(Image im)
{
if (im!=null)
{
img = im ;
trackImage(img);
}
}
public SimpleImageCanvas(String fileName)
{
if(fileName!=null)
img = Toolkit.getDefaultToolkit().getImage(fileName);
if (img!=null)
trackImage(img);
}
private void trackImage(Image im)
{
MediaTracker mt = new MediaTracker(this);
mt.addImage(img,0);
try
{
mt.waitForAll();
imWidth = img.getWidth(this) ;
imHeight = img.getHeight(this) ;
}
catch(InterruptedException ex)
{
}
}
public void paint(Graphics g)
{
int x, y ;
Dimension dim = getSize() ;
if(img!=null) // Bild zentrieren, falls möglich
{
x = (dim.width-imWidth)/2 ;
y = (dim.height-imHeight)/2 ;
if (x<0) x=0 ;
if (y<0) y=0;
g.drawImage(img,x,y,this);
}
}
} // end class
In paint() wird nun versucht, das Bild zu zentrieren. Falls die Bildbreite kleiner ist als die
Breite der Komponente wird in dieser Richtung zentriert, falls nicht wird linksbündig dargestellt.
Analog für die vertikale Richtung.
Es wäre schön, wenn wir nachträglich noch ein anderes Bild anzeigen könnten, oder auch
ein Bild löschen könnten, ohne ein neues anzuzeigen. Die entsprechenden Methoden sind schnell
geschrieben:
// Bild nachträglich einfügen
public void setImage(Image im)
{
if (im!=null)
{
img = im;
trackImage(img);
repaint();
}
}
public void setImage(String file)
{
if (file!=null)
{
img = Toolkit.getDefaultToolkit().getImage(file);
if (img!=null)
{
trackImage(img);
repaint();
}
}
}
public void deleteImage()
{
if (img!=null)
{
img=null;
repaint();
}
}
public Image getImage()
{
return img;
}
Alle set-Methoden verändern nur etwas, wenn nicht null übergeben wird.
setImage(String f) überprüft zudem, ob getImage() wirklich etwas liefert für
den Fall, daß einDateiname übergeben wird, der keine Bilddatei darstellt. deleteImage()
prüft, ob img nicht schon null ist, in diesem Fall ist ja nichts zu tun. getImage()
schließlich ist mehr der Vollständigkeit halber geschrieben worden, um ein sauberes
objektorientiertes Design zu haben.
Mehr Überlegung fordert der Wunsch, unter dem Bild den Bildtitel zentriert auszugeben. Wir
ergänzen im Datenteil der Klasse private String fileName. Der passende Konstruktor kann dieses
Datum setzen. Außerdem muß darauf geachtet werden, daß in den anderen Methoden die neue
Variable richtig gesetzt wird.
Die Pixelgröße eines Strings kann man mit einem Objekt der Klasse FontMetrics in drei
Zeilen ermitteln.
FontMetrics fm = g.getFontMetrics();
int h = fm.getHeight() ; // Pixelhöhe der Schrift
int w = fm.stringWidth(fileName) ; // Pixelbreite des Strings
Ein ähnlicher Algorithmus wie bei der Bildzentrierung zentriert den Dateinamen mit 5 Pixel Abstand
unter dem Bild, falls die Breite des Dateinamens kleiner als die Bildbreite ist.
import java.awt.*;
public class SimpleImageCanvas extends Canvas
{
Image img ;
String fileName;
int imWidth, imHeight;
public SimpleImageCanvas(Image im)
{
if (im!=null)
{
img = im ;
trackImage(img);
}
}
public SimpleImageCanvas(String file)
{
if (file!=null)
{
fileName = file;
img = Toolkit.getDefaultToolkit().getImage(file);
if (img!=null)
trackImage(img);
}
}
private void trackImage(Image im)
{
MediaTracker mt = new MediaTracker(this);
mt.addImage(img,0);
try
{
mt.waitForAll();
imWidth = img.getWidth(this) ;
imHeight = img.getHeight(this) ;
}
catch(InterruptedException ex)
{
}
}
// Bild nachträglich einfügen
public void setImage(Image im)
{
if (im!=null)
{
img = im;
fileName = null;
trackImage(img);
repaint();
}
}
public void setImage(String file)
{
if (file!=null)
{
fileName = file;
img = Toolkit.getDefaultToolkit().getImage(file);
if (img!=null)
{
trackImage(img);
repaint();
}
}
}
public void deleteImage()
{
if (img!=null)
{
img=null;
fileName=null;
repaint();
}
}
public Image getImage()
{
return img;
}
public void paint(Graphics g)
{
int x, y ;
Dimension dim = getSize() ;
if(img!=null)
{
// Bild zentrieren, falls möglich
x = (dim.width-imWidth)/2 ;
y = (dim.height-imHeight)/2 ;
if (x<0) x=0 ;
if (y<0) y=0;
g.drawImage(img,x,y,this);
if(fileName!=null)
{
FontMetrics fm = g.getFontMetrics();
int h = fm.getHeight() ; // Pixelhöhe der Schrift
int w = fm.stringWidth(fileName) ; // Pixelbreite des Strings
int offset = (imWidth-w)/2 ;
if (offset>=0)
g.drawString(fileName, x+offset, y+imHeight+h+5);
else // Schrift ist breiter als Bild
{
offset = (dim.width-w)/2 ;
if (offset<0) offset=0 ; // Schrift ist breiter als Canvas
g.drawString(fileName, offset, y+imHeight+h+5);
}
}
}
}
public Dimension getPreferredSize()
{
if ( imWidth>0 && imHeight>0)
return new Dimension(imWidth, imHeight) ;
return super.getPreferredSize() ;
}
public Dimension getMinimumSize()
{
if ( imWidth>0 && imHeight>0)
return new Dimension(imWidth, imHeight) ;
return super.getMinimumSize() ;
}
} // end class
Zudem haben wir noch die Methoden getPreferredSize() und getMinimumSize() überschrieben.
Die Methode getPreferredSize() wird von LayoutManagern benützt, um die Größe einer
Komponente zu ermitteln. Je nach Layout verwendet der Manager beide Maße (FlowLayout), eines
der beiden Maße (BorderLayout) oder keines (BorderLayout, GridLayout).