Java und Buttons

Hallo!

Ich bin sozusagen ein Umsteiger. Ich programmiere Delphi und möchte mich jetzt etwas in Java einarbeiten.
In Delphi ist es so, dass man Anzeige und funktion nicht wirklich trennt. Das ist, soweit ich gelesen habe, ich Java anders.
In Delphi habe ich z.b. ein Formular mit 5 Knöpfen. Jeder Knopf hat dann einen Click-Event. Der Code für jeden Click steht dann in der gleichen Datei wie der Button selbst.

Wie ist dass denn nun bei Java? Ich habe gelesen, dass es einen ActionEvent gibt. Muss ich jetzt für jeden Button eine neue Klasse anlegen, nur um einen Click zu empfangen?

Wie macht ihr das?

Gruß
Alex

hi Alex

ich bin nicht unbedingt der erfahrene gui-programmierer, daher meine antwort nicht als der weisheit letztem schluss heranziehen…

im java gibt es grundsätzlich das model-view-controller-konzept. demzufolge gibt es eine trennung zwischen der reinen gui (also der klasse, die dir das fenster und die buttons zeichnet), dem model (quasi die datenhaltung, also die klasse, die den zustand des fensters und die abzubildenden daten verwaltet) und dem controller als schnittstelle zwischen model und view.

bedeutet aber nicht, dass man unbedingt so programmieren muss.

im speziellen fall: zu jedem button kann man einen action-listener registrieren. das sind klassen, die das actionlistener-interface implementieren. damit hast du viele verschiedenen möglichkeiten, auf die clicked-events zu reagieren. hier mal die drei offensichtlichsten:

  • der eher unsaubere ansatz entspricht der programmierung unter RAD-Tools: du hast eine klasse, die das gui zeichnet. diese klasse implementiert zusätzlich das actionlistener-interface und registriert sich selber als action-listener in allen buttons. du hast dann eine einzige action-performed-methode, die dann aufgrund der quelle des events rausfinden muss, welcher button gedrückt wurde (ist aber recht leicht). bei recht schlanke guis (also wenige buttons) ist der ansatz nicht mal so schlecht, da man in einer einzigen klasse alles konzentriert hat und einen schnellen überblick über das fenster bekommt.

  • gui-generatoren wie z.b. jbuilder verwenden oft den ansatz, dass pro button eine anonyme klasse erzeugt wird, die das actionlistener-interface implementiert. deren code ist aber direkt in die gui-klasse eingebettet. das entspricht sogar noch etwas mehr der programmierung in rad-tools, da ich recht komfortabel für jeden button einen eigenen code hinterlegen kann und keine abhängigkeiten zwischen den buttons berücksichtigen muss. allerdings ist das meiner meinung nach auch der schnellste weg, unlesbaren spagetti-code zu erzeugen. für kleine guis ev. noch vertretbar, bei umfangreichen programmen auf dauer tödlich.

  • der „saubere“ ansatz (sauber, weil am ehesten an das model-view-controller-konzept angelehnt) ist das erstellen einer eigenen controller-klasse, die dann eben alle notwendigen listener-interfaces implementiert, die für die gui-steuerung notwendig sind. diese controller-klasse wird dann bei allen controls als listener registriert. damit übernimmt diese controller-klasse die gesammte steuerung der gui und ist damit die zentrale schnittstelle zwischen gui und den daten (also dem model). auf den ersten blick ist sowas eher unübersichtlich. vor allem bei kleinen guis habe ich damit mind. 2 klassen. wenn man aber sehr umfangreiche guis hat, ist die saubere trennung von gui und controller auf dauer besser.

natürlich gibt es noch mehrere mischformen. im wesentlichen muss man sich für eine vorgehensweise entscheiden, die einem am besten liegt. objektorientierung ist ja keine religion mit dogmen sondern ein ansatz, der einem dabei helfen soll, umfangreiche sw-projekte möglichst fehlerfrei und mit wenig wartungsaufwand zu bewältigen.

lg
erwin

ps: ich gestehe, dass ich keine ahnung von der programmierung mit swt habe. die obigen aussagen sollten für swing stimmen, unter swt sind sie möglicherweise blödsinn…

WOW, Vielen Dank! Das waren genau die Antworten, die ich gesucht habe. Nur nochmal zum Verständnis: Wenn ich den „Königsweg“ gehe und eine Controller-Klasse einsetze, dann habe ich ja eine ActionPerformed-Methode zu implementieren, in der ich entscheide, was ich tue. Soweit ich es gesehen habe, gibt es ein Property, mit dem ich den Namen des Senders bekomme. Das heißt dann aber auch, dass ich für jeden Button etc. prüfen muss „if(Sender = „Button1“)… if(Sender = „Button2“)…“.
Ich weiß nicht, aber bei Stringvergleichen sträuben sich mir immer die Haare. Wenn ich jetzt den Namen umändere und nicht daran denke, den Controller anzupassen, dann hat der Knopf keine Funktion mehr.
Gibt es da ein „Rezept“ wie man seine Actions benennt? Ich würde jetzt den Namen des buttons bzw. des Steuerlementes reinschreiben. Also z.B. btnOK, btnAbbruch etc.

Gruß
Alex

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

hi ales

WOW, Vielen Dank! Das waren genau die Antworten, die ich
gesucht habe. Nur nochmal zum Verständnis: Wenn ich den
„Königsweg“ gehe und eine Controller-Klasse einsetze, dann
habe ich ja eine ActionPerformed-Methode zu implementieren, in
der ich entscheide, was ich tue. Soweit ich es gesehen habe,
gibt es ein Property, mit dem ich den Namen des Senders
bekomme. Das heißt dann aber auch, dass ich für jeden Button
etc. prüfen muss „if(Sender = „Button1“)… if(Sender =
„Button2“)…“.
Ich weiß nicht, aber bei Stringvergleichen sträuben sich mir
immer die Haare. Wenn ich jetzt den Namen umändere und nicht
daran denke, den Controller anzupassen, dann hat der Knopf
keine Funktion mehr.

also, stringvergleiche sind immer ungut. sind auch nicht notwendig. jeder event hat eine getSource-Methode, die jenes object zurückgibt, das den event erzeugt hat. du musst also die object-reference von deinem button mit der source des events vergleichen.

also z.b.:
jbOK = new JButton(…);

actionPerformed(ActionEvent e) {
if (e.getSource() == jbOK) {

}
}

du hast damit lediglich das problem, dass dein controller ja eine eigene klasse ist, der nun alle buttons der gui-klasse kennen muss. „sauber“ programmiert müssten die buttons in privaten feldern stehen. in diesem fall wird man eher auf package oder protected umsteigen müssen.

Gibt es da ein „Rezept“ wie man seine Actions benennt? Ich
würde jetzt den Namen des buttons bzw. des Steuerlementes
reinschreiben. Also z.B. btnOK, btnAbbruch etc.

es gibt sicher best practices über das benennen von gui-elementen, die in praktisch allen anwendungen vorkommen. ich bin aber kein gui-programmiere und kenne die nicht auswendig. müsste selber erst googeln - das kannst du auch :wink:

grundsätzlich würde ich aber das benennen von variablen, methoden, actions etc. nicht zur wissenschaft machen. der grund, warum man statt den variablenname „a“, „b“ und „c“ die namen „name“, „vorname“, „titel“ verwendet, ist doch der, dass man später noch den eigenen code lesen kann (wobei später auch schon eine woche später sein kann). der bytecode, den der compiler liefert, ist gleich gross und schnell, egal was man nimmt. das selbe gilt gilt für methoden, klassen etc.

der name ist auf jeden fall ok, wenn:

  • er gängigen namensschemata folgt (gross-kleinschreibung)
  • auf den ersten blick offensichtlich ist, was damit gemeint ist
  • alle variablen/methoden/klassen mit ähnlicher funktion ähnliche namen haben
  • alle variablen/methoden mit der selben funktion in unterschiedlichen klassen den selben namen haben

auf diese weise wirst du auch bei umfangreicheren sw-projekten deinen code im griff haben - auch nach ein paar wochen noch.

lg
erwin

Ich weiß nicht, aber bei Stringvergleichen sträuben sich mir
immer die Haare. Wenn ich jetzt den Namen umändere und nicht
daran denke, den Controller anzupassen, dann hat der Knopf
keine Funktion mehr.

Also, Controllerklasse ist der saubere Weg. Und zur Identifikation der Buttons nutzt man:

but.setActionCommand("actOeffnen");//beliebiger String

und zum abfragen:

public void actionPerformed(ActionEvent e){
 if (e.getActionCommand().equals("actOeffnen")){
 //mach was
 } else if ...

}

Micha

Und genau hier sehe ich das Problem:
if (e.getActionCommand().equals(„actOeffnen“)){

Was ist, wenn ich mich verschrieben habe? Dann hat mein button keine Action mehr. Oder was ist, wenn ich das ActionCommand ändere, aber vergesse es im Controller zu ändern.

Die andere diskutierte Möglichkeit alle Buttons zu übergeben bringt sehr viel Code mit sich.

Dennoch Danke, jetzt habe ich 2 verschiedene Wege und kann beide mal testen.

Gruß
Alex

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hi,

Und genau hier sehe ich das Problem:
if (e.getActionCommand().equals(„actOeffnen“)){

Was ist, wenn ich mich verschrieben habe? Dann hat mein button
keine Action mehr. Oder was ist, wenn ich das ActionCommand
ändere, aber vergesse es im Controller zu ändern.

Erstens werden für sowas Konstanten verwendet, dann gibt es kein verschreiben.
Zweitens wird pro mögliche Handlung (entspricht der Action) genau ein ActionCommand angelegt, das man dann in PopupMenues, Buttons und sonstwo verwendet. Wenn Du das ActionCommand änderst wird der Controller sich automatisch anpassen, weil es nur eine Definition dieses ActionCommands gibt.

Gruss,
Herb

Warum keine Actions?
Hi Alex,

warum sollte der Königsweg eine einzige Controller-Klasse sein?
Mein „Königsweg“ ist die Kapselung der Aktionen (deswegen heißen die denn bestimmt auch so) in Actions, d.h. in eigene Klassen, die das Interface Action implementieren.

Somit habe ich dann verschiedenste Actions in meiner Gui, z.B. NewAction, EditAction und DeleteAction. Ich statte meine Actions sogar noch mit eigenen Methoden für die Rückgabe von Icon-Eigenschaften und anderen nützlichen Sachen aus.

Letzten Endes kann ich nun eine Action in einem Menü, Popupmenü oder auch in der Toolbar verwenden. Jedes Element, welches die Action verwendet, zieht sich dabei das richtige Icon aus der Action und passt sich somit selbst entsprechend an die Oberfläche an. In meinem Fall mache ich das Ganze eigentlich über Factory-Mechanismen (siehe Design-Patterns).

Die Kapselung in einer eigenen Klasse bringt dabei den Vorteil, das sich die Funktionalität schnell finden und erweitern lässt. Du brauchst also keine immer größer werdenden if/then/else in deinem einzigen Controller, wenn neue Actions hinzukommen. Dies entspricht außerdem nicht so ganz dem objektorientierten Design.

Alles kann - nichts muss :smile:
Ciao, Bill

Ein interessanter Ansatz.

Ich hätte jetzt eine ControllerKlasse pro Fenster erstellt. Vielleicht kombiniere ich beide Vorschläge.
Dass ich eine Action mehrmals in einem Fenster brauche kann ich mir gut vorstellen. Dass ich aber eine Action mehrmals im gesamten Programm brauche, ist wohl eher unwahrscheinlich.

Mein Ansatz:
Pro Fenster/Frame eine Controller-Klasse. Dann „sprechende“ Actions vergeben und diese in den Steuerelementen verwenden. Wird eine Action dann mehrfach benötigt, ist das kein Problem.

Deine Idee mit den „automatischen“ Icons müßtest du mir mal in einem Beispiel zeigen.

Gruß
Alex

Hallo Alex,

Deine Idee mit den „automatischen“ Icons müßtest du mir mal in
einem Beispiel zeigen.

da hatte ich es wohl ein bisschen zu allgemein gehalten :smile:
Ich nutze für das Erzeugen der Actions ein ResourceBundle (RB), in dem ich neben dem Anzeigetext auch ein kleines und großes Toolbar-Icon sowie ein Menü-Icon definiere.

Die Action selbst liefert im Prinzip den Präfix des Schlüssels, der im RB allen Eigenschaften vorangestellt ist.

public class MySpecialAction extends AbstractSpecialAction
{
 public String getResourceKey()
 {
 return "MySpecialAction";
 }
}

Im RB finden sich dann z.B. die Einträge

MySpecialAction.name = Spezial-Aktion
MySpecialAction.icon.toolbar.small = /images/small.png
MySpecialAction.icon.toolbar.large = /images/large.png
MySpecialAction.icon.menu = /images/small.png

Meiner ActionFactory übergebe ich immer nur den Klassenname zum Konstruieren einer neuen Action. Dies erfolgt über Methoden, z.B.

JMenuItem item = new JMenuItem(ActionFactory.createMenuAction(MySpecialAction.class)); // es wird eine Action erzeugt und zurückgegeben

Im Fall einer Toolbar-Action nutze ich ne andere Methode.

Im Nachhinein kann man so einfach mal eine Action, die bisher nur im Menü verwendet wurde, in die Toolbar einfügen.

Ciao, Bill

Hallo.

Zweitens wird pro mögliche Handlung (entspricht der Action)
genau ein ActionCommand angelegt, das man dann in PopupMenues,
Buttons und sonstwo verwendet. Wenn Du das ActionCommand
änderst wird der Controller sich automatisch anpassen, weil es
nur eine Definition dieses ActionCommands gibt.

Konstruiertes Beispiel:
in meiner Gui-Klasse habe ich einen JButton myButton, setze das ActionCommand mit myButton.setActionCommand(„myAction“).
In der Controller-Klasse prüfe ich if (((JButton)e.getSource()).getActionCommand.equals(„myAction“)).
Wenn ich nun im nachhinein das ActionCommand ändere (durch erneuten Aufruf von myButton.setActionCommand(„newAction“)), wieso sollte sich dann der Controller „anpassen“?

Gruss.

Hi,

wieso sollte sich dann der Controller „anpassen“?

weil der Controller für jedes ActionCommand eine ActionMethode implementieren „muss“.

Von selbst schreibt sich der Code natürlich nicht…

Gruss,

Herb

wieso sollte sich dann der Controller „anpassen“?

weil der Controller für jedes ActionCommand eine ActionMethode
implementieren „muss“.

Aehm, ich glaube, das habe ich noch nicht verstanden. Ich registriere für jeden Button den gleichen ActionListener in dem ich dann den ActionCommand abfrage:

public void actionPerformed(ActionEvent e) {
 if (((JButton)(e.getSource())).getActionCommand()=="myAction") {
 //rufe neue Methode oder was auch immer
 }
}

Wenn ich jetzt den ActionCommand ändere, dann gibt es ja immernoch einen Listener, nur tut dieser nichts mehr…
Ich glaube darum ging es ursprünglich.

Hi,

das hast Du falsch verstanden.

Zuerst definierst Du ein Interface mit den ActionCommands:

public Interface IActionCommands
{
 public final static String ACTION\_EXIT="action\_exit";
 public final static String ACTION\_OPEN\_FILE="action\_openfile";
...
}

Die Action selbst erzeugst Du so:

public static Action A\_EXIT=new MyAction(IActionCommands.ACTION\_EXIT);

wobei die Klasse MyAction zum Beispiel so aussehen kann:

public static class MyAction extends AbstractAction
{
 private static IconManager iconManager=IconManager.getSingleInstance();

/\*\*
 \* creates the action by an action name
 \* @param actionName the name of the action is equal with the actionCommand
 \*/
 public MyAction(String actionName)
 {
 super(actionName, iconManager.getIcon(actionName));
 }

 /\*\*
 \* does nothing
 \* @param e an action event
 \*/
 public void actionPerformed(ActionEvent e) {}
}

so, und wenn die actionPerformed Methode aufgerufen wird, dann folgendes:

public void actionPerformed(ActionEvent e)
{
 if(e.getActionCommand().equalsIgnoreCase(IActionCommands.TA\_EXIT))
 exit();
}

Ob die Quelle des ActionEvent ein Button ist interessiert also nicht, es zählt nur das ActionEvent selbst.

Wenn ich jetzt den ActionCommand ändere, dann gibt es ja
immernoch einen Listener, nur tut dieser nichts mehr…

Klingt logisch. Und wenn Du irgendwo im Coding ein System.exit(0) vergräbst, dann beendet sich das Programm…
Der Mummenschanz mit dem IActionCommands Interface soll verhindern, dass man sich bei den ActionCommands nicht vertippt.
Wenn Du allerdings absichtlich das ActionCommand änderst, dann bist Du auch dafür verantwortlich, dass es in der actionPerformed Methode abgefragt wird.

Gruss,

Herb