Von C nach Java, Teil 5: Wie eine grafische Oberfläche entsteht

Seite 2: Dialogbau

Inhaltsverzeichnis

Bei genauerer Betrachtung des Dialogs stellt sich auch die Frage, wie sich das Erzeugen der Kontrollelemente geschickt im Programmcode bewerkstelligen lässt. Eine raffinierte Methode ist es, ein Array für die Elemente mit den wichtigsten, gemeinsamen Parametern anzulegen und in einer Schleife das Array abzuarbeiten. Danach ist dafür Sorge getragen, dass das jeweilige Element in seiner richtigen Position und Größe innerhalb des Dialogs platziert wurde und sichtbar ist. Je nach Typ des Elements sind eventuell noch einige Nacharbeiten notwendig, die entweder direkt in der Schleife oder aber nachher auszuführen sind. Hier nun das Codebeispiel zum Erzeugen der Kontrollelemente:

public void buildGUI() {
...
Object[] ctrlList = {
jlGroupBox1 , 2, 22,178, 86,
jlGroupBox2 , 2,114,595,368,
jlGroupBox3 ,180, 22,110, 86,
jlGroupBox4 ,290, 22,150, 86,
jlImage ,588, 2,128,128,
jbStart ,600,134,100, 19,
jbCancel ,600,154,100, 19,
jbAddFile ,600,174,100, 19,
jbExit ,600,194,100, 19,
jspFileTable, 10,134,580,342,
jlPasswd , 10, 50,100, 24,
jcbMultiThread,86,48, 90, 22,
jtfPasswd , 10, 72,160, 24,
jlBufSize ,300, 50, 80, 24,
jspBufSize ,300, 72, 80, 24,
jrbComp1 ,188, 46, 90, 16,
jrbComp2 ,188, 62, 90, 16,
jrbComp3 ,188, 78, 90, 16,
jrbBufKB ,380, 62, 50, 16,
jrbBufMB ,380, 78, 50, 16,
null
};
...

for (int i=0;ctrlList[i]!=null;i+=5) {
getContentPane().add((JComponent)ctrlList[i]);
((JComponent)ctrlList[i]).setBounds
((Integer)ctrlList[i+1],
(Integer)ctrlList[i+2],(Integer)ctrlList[i+3],
(Integer)ctrlList[i+4]);
if ((JComponent)ctrlList[i] == jlGroupBox1) {
jlGroupBox1.setBorder(BorderFactory.createTitledBorder
("Security settings"));
} else if ((JComponent)ctrlList[i] == jlGroupBox2) {
jlGroupBox2.setBorder(BorderFactory.createTitledBorder
("Select file(s) for encryption"));
} else if ((JComponent)ctrlList[i] == jlGroupBox3) {
jlGroupBox3.setBorder(BorderFactory.createTitledBorder
("Compression"));
} else if ((JComponent)ctrlList[i] == jlGroupBox4) {
jlGroupBox4.setBorder(BorderFactory.createTitledBorder
("Buffer Size"));
} else if ((JComponent)ctrlList[i] instanceof JButton) {
((JButton)ctrlList[i]).setRolloverEnabled(true);
((JButton)ctrlList[i]).addActionListener(this);
} else if ((JComponent)ctrlList[i] instanceof JRadioButton) {
if (Arrays.asList(jrbComp1, jrbComp2,
jrbComp3).contains(ctrlList[i])) {
bg1.add((JRadioButton)ctrlList[i]);
} else {
bg2.add((JRadioButton)ctrlList[i]);
((JRadioButton)ctrlList[i]).addActionListener(this);
}
}
((JComponent)ctrlList[i]).setVisible(true);
}
}

Es wird ein Array vom Typ Object erzeugt, das pro Kontrollelement fünf Variablen enthält. Die erste enthält das Objekt "Kontrollelement", die anderen vier sind numerische Werte und umfassen die Position sowie die Größe des Elements innerhalb des sogenannten Panels (ContentPane). In der für jedes Element durchlaufenen for-Schleife wird nun das Element auf dem ContentPane erstellt und wie vorgegeben platziert. Die Voraussetzung hierfür ist, dass die Elemente, so unterschiedlich sie sind, über einen gemeinsamen Nenner verfügen. Er ist die übergeordnete Klasse JComponent, deren Methode setBounds an alle darunter liegenden Klassen vererbt wird. Eine Typumwandlung nach JComponent des Elements lässt den Compiler den Aufruf der Methode setBounds() akzeptieren:

((JComponent)ctrlList[i]).setBounds((Integer)ctrlList[i+1],
(Integer)ctrlList[i+2],(Integer)ctrlList[i+3],
(Integer)ctrlList[i+4]);

Der Aufruf bedient sich ebenfalls der Typumwandlung:

getContentPane().add((JComponent)ctrlList[i]);

DasContentPane des Dialogs erhält das entsprechende Kontrollelement. Damit das Ganze wie in der Abbildung funktioniert, darf kein Layout-Manager zum Einsatz kommen, der allerdings bei Swing beziehungsweise AWT beim Erzeugen eines Dialogs oder Frame automatisch zugeteilt wird (Default). Vor dem Ausführen der Schleife ist deshalb explizit mit dem Aufruf von:

setLayout(null);

jedes "Layouting" zu verhindern. Ist das eingerichtet, hat man dem Kontrollelement mit setBounds(x,y,x,y) oder setLocation(x,y) und setSize(x,y) die Position und Größe innerhalb des ContentPane mitzuteilen. Wenn das nicht geschieht, sind diese Werte 0 und nichts ist auf dem Bildschirm zu sehen.

Die restlichen Anweisungen in der Schleife haben größtenteils kosmetischen Charakter. Hier werden die Rahmen, die zur Kennzeichnung von Kontrollelementgruppen vorgesehen sind, mit einem Text und einem definierten Aussehen belegt. Hierzu kommen JLabel-Kontrollelemente zum Einsatz. Sie erhalten einen mit Text versehenen Rahmen (Aufruf der statischen Methode der Klasse BorderFactory: BorderFactory.createTitledBorder("Groupbox Name")):

jlGroupBox1.setBorder(BorderFactory.createTitledBorder
("Security settings"));

Jetzt sind noch Anweisungen zu finden, nach deren Aufruf dem Dialog "Leben" eingehaucht wird. Es handelt sich um Buttons und Radiobuttons. Beide Steuerelemente (im Gegensatz zu statischen Labels oder Icons) sind schließlich dazu da, auf Eingaben in irgendeiner Form zu reagieren. Damit das Programm die Eingaben entgegennimmt und verwertet, muss irgendein Mechanismus das regeln. In AWT beziehungsweise Swing übernehmen das Listener. Von denen gibt es eine ganze Reihe, je nachdem welchem Kontrollelement man sie zuordnet.

Listener sind als sogenannte Interfaces implementiert. Diese sind in Java die speziellen Klassen, deren Methoden, die in ihnen definiert sind, von der "aufrufenden" Anwenderklasse heraus zu implementieren sind. Sie ermöglichen den transparenten Austausch von Objekten zwischen verschiedenen Klassen. Hierzu ein Beispiel: Ein JButton benötigt die "Adresse" einer Klasse, die auf den Mausklick reagiert. In dem Fall wird eine spezielle Methode dieser (dem Button selbst unbekannten) Anwenderklasse aufgerufen. Damit sichergestellt ist, dass die Methode in der Anwenderklasse existiert, gibt es eben das spezielle Interface, also eine Klasse, die lediglich die benötigte Methode beschreibt. Der betreffende Code im JButton könnte demnach so aussehen:

public class JButton {
ActionListener al=null;
...

public void addActionListener(ActionListener l) {
this.al=l;
}
...

private void click() {
if (al != null)
al.actionPerformed(new ActionEvent(...));
}
...

}

ActionListener ist das Interface, das in der Anwenderklasse implementiert sein muss. Der betreffende Codeschnipsel in der Anwenderklasse müsste wie folgt aussehen:

public class UserClass extends JFrame implements ActionListener {
...

public void buildGUI() {
JButton jb=new JButton("ok");

...

jb.addActionListener(this);
...

}

public void actionPerformed(ActionEvent e) {
...

}
}

Die Methode addActionListener() überträgt die Adresse der zu implementierenden Klasse zur Klasse des Kontrollelements und ist damit so etwas wie das "Herz" der Schnittstelle.

Wie für ActionListener gibt es für andere Kontrollelemente andere Interfaces, da sich die Events der Kontrollelemente teilweise stark voneinander unterscheiden. Ein Beispiel wäre eine Scrollbar, also die Leiste, die sich etwa am rechten Rand einer Liste befindet und mit der man nach oben und unten scrollen kann. Hier gibt es keinen ActionListener, sondern einen AdjustmentListener, dessen Methoden die Anwenderklasse implementieren kann, um von etwaigen Veränderungen der Position einer Scrollbar informiert zu werden.