# Funktion zu einem JButton hinzufügen



## Keviehn (29. Aug 2010)

Hallo Leute,

bin ziemlich neu im Bereich JAVA. Ich habe einige Kenntnisse im EDV-Bereich, aber für JAVA hatte ich bis jetzt kein Interesse. Da ich nun für ein "kleines" Projekt JAVA benötige, werde und will ich mich natürlich mit JAVA beschäftigen.

Mein Ziel ist es nach einiger Zeit einen Bildbearbeitungsprogramm mit JAVA geschrieben zu haben.
Dieses Programm soll einige Funktionen können:

- Grafische Oberfläche
- Bild Laden
- Bild bearbeiten (Schwarz-Weiß-Funktion, Negativ, Bild drehen, Bild spiegeln, Bild invertieren)
- Bild speichern

Mein jetziges Wissen ist sehr beschrenkt und baut auf dieses Forum und einige Bücher auf. 

Ich nutze JDK 6 Update 21 und den JavaEditor (falls ihr ein anderes Programm kennt, was für mein Vorhaben besser geeignet ist, dann nur raus damit.)

Jetzt zu meinen Problem:

Ich habe mit dem JavaEditor einen JFrame erstellt und dahin ein JButton erstellt und möchte diesen JButton die "Bild-Ladefunktion" zuweisen. 


```
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

/**
  *
  * Beschreibung
  *
  * @version 1.0 vom 05.01.2010
  * @author
  */

public class LADEN extends JFrame {
  // Anfang Attribute
  private JButton _LOAD = new JButton();
  // Ende Attribute

  public LADEN(String title) {
    // Frame-Initialisierung
    super(title);
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    int frameWidth = 300;
    int frameHeight = 300;
    setSize(frameWidth, frameHeight);
    Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
    int x = (d.width - getSize().width) / 2;
    int y = (d.height - getSize().height) / 2;
    setLocation(x, y);
    Container cp = getContentPane();
    cp.setLayout(null);
    // Anfang Komponenten

    _LOAD.setBounds(40, 24, 177, 89);
    _LOAD.setText("BILD LADEN");
    _LOAD.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        _LOAD_ActionPerformed(evt);
      }
    });
    _LOAD.setToolTipText("LÄDT EIN BILD");
    cp.add(_LOAD);
    // Ende Komponenten

    setResizable(false);
    setVisible(true);
  }

  // Anfang Methoden
  public void _LOAD_ActionPerformed(ActionEvent evt) {
    // TODO hier Quelltext einfügen
  }

  // Ende Methoden

  public static void main(String[] args) {
    new LADEN("LADEN");
  }
}
```

Später möchte ich natürlich weitere JButton's hinzufügen bezüglich des Speichern und andere Funktionen. Nun die Frage: In welchen Bereich des Quelltextes muss ich dann die Funktion hinzufügen?

Ich hoffe ich habe einigermaßen klar ausgedrückt, wenn nicht einfach fragen. 


Danke und viele Grüße
Keviehn


----------



## Marcinek (29. Aug 2010)

Ich würde einen separaten Handler für ButtonEvents schreiben und diesen an jedem Button festmachen.

In unserem selbstgeschreibenen MVC Framework ist das so, dass jeder Button selbst ein Listener ist und entsprechend an seinen Controller seine Events verschickt.

Gruß,

Marcin


----------



## Marco13 (29. Aug 2010)

Dass ein JButton selbst ein Listener IST erscheint mir (zurückhaltend gesagt) "ungewöhnlich".

Der "geeignetste Platz" ist aber schwer zu benennen. Ganz abstrakt formuliert: Es gibt irgendein Datenmodell, das Funktionen anbietet. Und irgendein GUI-Element, mit dem man diese Funktion auslösen will. Und mit einem Listener (Controller) wird diese Verbindung hergestellt. 

Im Moment gibt es die Methode "_LOAD_ActionPerformed" (die SO sehr den Naming conventions widerspricht, dass es schon fast beeindruckend ist  ). Wenn das Programm erstmal besser "ausstrukturiert" ist, gibt es vielleicht eine Klasse "ImageLoader" (oder so), und die bietet eine Methode zum Laden eines Bildes an. Diese Methode könnte dann z.B. vom ActionListener aufgerufren werden. 

Für ein komplexeres, ausgefeilteres GUI würde man an dieser Stelle wohl "Actions" verwenden (siehe How to Use Actions (The Java™ Tutorials > Creating a GUI With JFC/Swing > Using Other Swing Features) ). Damit kann man sehr elegant eine Aktion (wie z.B. laden) mit mehreren GUI-Componenten (wie z.B. einem Button und einem Menüeintrag) verbinden.


----------



## Landei (29. Aug 2010)

Ich würde auch zu Actions raten. Eine mögliche Grundstruktur sieht so aus:


```
public class MyClass {

   private Action sayHelloAction = new AbstractAction("Say Hello") {
       public void actionPerformed(ActionEvent ae) {
           System.out.println("hello");
       } 
   }

   public MyClass() {
       ...
       JButton sayHelloButton = new JButton(sayHelloAction);  
       somePanel.add(sayHelloButton);
       ...
   }
}
```


----------



## Keviehn (30. Aug 2010)

... so ich habe mich von meinen den JButtons bzw. von den automatisch gennerierten Quelltext verabschiedet und habe ein wenig getüfftelt. Mein jetztiger Code sieht zurzeit so aus:


```
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class Menüleiste {
  public Menüleiste () {
    Frame f = new Frame ("Test der Menüleiste ");
    f.addWindowListener(new WindowAdapter () {
      public void windowClosing (final WindowEvent e) {
        System.exit(0);
      }
    });
    f.setMenuBar(this.getMenubar ());
    f.setSize(300,140);
    f.setVisible(true);
  }
//111111111111111111111111111111   MENÜ LEISTE   1111111111111111111111111111111


  protected MenuBar getMenubar () {                          // Anlegen der Menüleiste
    MenuBar menueLeiste = new MenuBar ();                    // Das Menü anlegen


//22222222222222222222222222   MENÜ LEISTE - DATEI   222222222222222222222222222

    Menu datei = new Menu ("Datei ");                                                 // Menüeintrag anlegen
    
    MenuItem _NEW = new MenuItem ("Neu ");                                            // Menü zum Menüeintrag hinzufügen (NEU)
    datei.add (_NEW);                                                                 // Hinzufügen
    
    MenuItem _LOAD = new MenuItem ("Laden ");                                         // Menü zum Menüeintrag hinzufügen (Laden)
    datei.add (_LOAD);                                                                // Hinzufügen
    
    MenuItem _SAVE = new MenuItem ("Speichern ");                                     // Menü zum Menüeintrag hinzufügen (Speichern)
    datei.add (_SAVE);                                                                // Hinzufügen
    
    MenuItem _CLOSE = new MenuItem ("Beenden ");                                      // Menü zum Menüeintrag hinzufügen (Beenden)
    datei.add (_CLOSE);                                                               // Hinzufügen

    menueLeiste.add(datei);                                                           // Hinzufügen abschliessen

//22222222222222222222222222   MENÜ LEISTE - DATEI   222222222222222222222222222

//3333333333333333333333   MENÜ LEISTE - Transformation   3333333333333333333333



   Menu extra = new Menu ("Transformation ");                                         // Menüeintrag anlegen

   Menu _SPIN = new Menu ("Drehen ");                                                 // Untermenü1 hinzufügen
   Menu _REFLECT = new Menu ("Spiegeln ");                                            // Untermenü2 hinzufügen
   Menu _FILTER = new Menu ("Filter ");                                               // Untermenü2 hinzufügen

////////////////////////////////////   Untermenü1   //////////////////////////////////////////////
   extra.add(_SPIN);                                                                  // !
   _SPIN.add("90° Links ");                                                           // Menü zum Untermenüeintrag hinzufügen (90° nach Links drehen)
   _SPIN.add("90° Rechts ");                                                          // Menü zum Untermenüeintrag hinzufügen (90° nach Rechts drehen)
   
   _SPIN.addSeparator();                                                              // Trennung

   _SPIN.add("180° Links ");                                                          // Menü zum Menüeintrag hinzufügen (180° nach Links drehen)
   _SPIN.add("180° Rechts ");                                                         // Menü zum Menüeintrag hinzufügen (180° nach Rechts drehen)
////////////////////////////////////   Untermenü2   //////////////////////////////////////////////
   extra.add(_REFLECT);                                                               // !
   _REFLECT.add("Vertikal ");                                                         // Menü zum Untermenüeintrag hinzufügen (Vertikal Spiegeln)
   _REFLECT.add("Horizontal ");                                                       // Menü zum Untermenüeintrag hinzufügen (Horizontal Spiegeln)
////////////////////////////////////   Untermenü3   //////////////////////////////////////////////
   extra.add(_FILTER);                                                                // !
   _FILTER.add("Verrauschen ");                                                       // Menü zum Untermenüeintrag hinzufügen (Verrauschen Spiegeln)
   _FILTER.add("Unscharf ");                                                          // Menü zum Untermenüeintrag hinzufügen (Unscharf Spiegeln)
   _FILTER.add("Invertieren ");                                                       // Menü zum Untermenüeintrag hinzufügen (Invertieren Spiegeln)
   _FILTER.add("Schwarz / Weiß ");                                                    // Menü zum Untermenüeintrag hinzufügen (Schwarz/Weiß Spiegeln)
////////////////////////////////////   Untermenü1   //////////////////////////////////////////////
////////////////////////////////////   Untermenü2   //////////////////////////////////////////////
////////////////////////////////////   Untermenü3   //////////////////////////////////////////////

  menueLeiste.add(extra);                                   // Hinzufügen abschliessen
              return menueLeiste;
                     }

//111111111111111111111111111111   MENÜ LEISTE   1111111111111111111111111111111

  public static void main (String[] args) {
    Menüleiste menusample = new Menüleiste ();
  }
}
```

Ein klassiches Frame mit 2 Menüeinträgen und weiteren Untermenüs. Da ich selbst mit den Actions zurzeit noch nicht zurecht kommen, würde ich Euch bitte meinen Code so zu verändern das die Funktion "schließen" funktioniert.

Ich möchte somit sehen, wie der Code auszusehen hat, wenn man auf Datei --> Schließen drückt.

Ich hoffe das ist mit meinen Code möglich.

Ich hoffe mir darauf aus meinen eigenen Code zu lernen und die restlichen Funktionen auch einzubinden.


Danke für Eure Unterstützung und viele Grüße
Keviehn


----------



## w0ddes (30. Aug 2010)

Also habs grad mal aus dem Bauch herraus gemacht. Hab jetzt mal nur den "betroffenen" Abschnitt hier reingenommen:


```
MenuItem _CLOSE = new MenuItem ("Beenden ");  // Menü zum Menüeintrag hinzufügen (Beenden)
    _CLOSE.addActionListener(new java.awt.event.ActionListener(){
		public void actionPerformed(java.awt.event.ActionEvent e) {
			System.exit(0);   //Hier deine Action einfügen! In diesem Fall halt "Beenden"
		}
    });
    datei.add (_CLOSE);
```

P.S: Die Namen wie _CLOSE sind leicht stressig, solltest du vielleicht noch ändern.

Edit: 

Sollten die Einträge wie 
	
	
	
	





```
90° links
```
 etc. nicht auch MenuItems sein?!

Also so: 

```
extra.add(_SPIN);                                                                  // !
   
   MenuItem links90 = new MenuItem("90° Links ");
   _SPIN.add(links90);      // Menü zum Untermenüeintrag hinzufügen (90° nach Links drehen)
   
   MenuItem rechts90 = new MenuItem("90° rechts");
   _SPIN.add(rechts90);
```

Edit 2: Außerdem (auf deine Kommentare bezogen): 
Du fügst nicht dem Menüeintrag das Menu hinzu sondern umgekehrt. 
	
	
	
	





```
//Unterenüeintrag zum Menü hinzufügen
```


----------



## Keviehn (30. Aug 2010)

> Also habs grad mal aus dem Bauch herraus gemacht. Hab jetzt mal nur den "betroffenen" Abschnitt hier reingenommen:



Dein Bauch ist gut.  Funktioniert jetzt und ich kenn jetzt den Aufbau der Actions. Danke!



> Die Namen wie _CLOSE sind leicht stressig, solltest du vielleicht noch ändern.



Sowas lernte ich (glaube ich) bei Youtube, aber ich werde sie noch ändern. 

Ich melde mich sobald ich weitergekommen bin. :applaus:


----------



## Marco13 (30. Aug 2010)

Ja, sowas wie

```
MenuItem _NEW = new MenuItem ("Neu ");                                            // Menü zum Menüeintrag hinzufügen (NEU)
    datei.add (_NEW);                                                                 // Hinzufügen
```
braucht man auch nicht zu kommentieren.

Aber mal was ganz anderes: An welchem Punkt bist du von Swing (JFrame, JButton, JMenu(!)) auf AWT (Frame, Button, Menu(!)) umgestiegen?


----------



## Keviehn (30. Aug 2010)

Da ich mich erst seit geraumer Zeit mit JAVA beschäftige Kommentier ich eigentlich jede Funktion die ich einbaue. (Dient für mich als "Sicherheit").

Ich bin auf AWT umgestiegen, da es für mich den anscheind hat es am "leichtesten" zu erlernen.


----------



## Landei (30. Aug 2010)

AWT ist tot, es ist häßlich, umständlich, wird nicht weiterentwickelt, und seine Trümmer sind gerade noch gut genug, um als Swing-Fundament Verwendung zu finden (das allerdings selbst mal wieder eine Generalüberholung nötig hätte).


----------



## AmunRa (30. Aug 2010)

AWT ist zwar tot, ist aber um einige Grundlagen (ala. DoubleBuffering) zu lernen noch immer nützlich.
Ein Anfänger sollte ruhig einmal ein bisschen mit AWT spielen, da er wahrscheinlich mit swing sonst nie mit einem WindowListener in kontakt kommt(DISPOSE_ON_CLOSE),


----------



## w0ddes (30. Aug 2010)

Was ich dir zu Swing wirklich sehr empfhelen kann sind die Java Tutorials zu Swing (Solange du einigermaßen gut Englisch verstehst). Ich habe erst vor einem Jahr mit Java bzw mit "richtigem" Programmieren überhaupt begonnen und die Tutorials haben mir sehr geholfen.


----------



## Keviehn (30. Aug 2010)

Ich bin langsam echt an zweifeln ob ich überhaupt mit AWT an mein gewünschtes Ziel komme.
Swing bietet (soweit ich weiß) ja den JFileChooser mit dem es sicherlich einfacher ist, ein Bild zu laden und zu speichern, scheint übersichtlicher zu sein, leicht erweiterbar etc.

Im Endeffekt soll es am Ende so aussehen:






Im Menü soll es die Menüpunkte: 


Neu - (Soll kein Bild anzeigen bzw. einen weißen Hintergrund)
Laden - (Auswahl einer Bilddatei und anzeigen im selben "Panel")
Speichern - (Das Bild speichern)

... dazu müssen noch einige Funktionen eingebunden werden, ob es in einen Menü ist oder mittels Buttons ist eigentlich egal, ich muss sie nur nutzen können. 

Anscheinend ein kleiner und harter Brocken für einen Anfänger. 

Somit denke ich werde ich wohl doch "wieder" auf Swing umsteigen. Gibt ja tatsächlich gute Tuts. dafür und die meisten Bücher scheinen auch darauf aufzubauen.


----------



## AmunRa (31. Aug 2010)

Naja du könntest dir den FileChooser selbst in AWT schreiben 

Natürlich geht alles einfacher mit Swing als mit AWT und wenn du wirklich ein Programm schreiben möchtest, dass du nacher auch veröffentlichen willst solltest du zumindest Swing verwenden. Aber
du lernst eine ganze menge wenn du versuchst dein Programm in AWT zu schreiben. (Wie man ordentlich mit einen Tree umgeht, doublebuffering, ...)


----------



## w0ddes (31. Aug 2010)

Da er wohl überhaupt Java-Anfänger ist, wäre das ganze selbst zu schreiben wohl etwas zu viel für ihn  Das kann er dann ja in Angriff nehmen, wenn er mal mehr Erfahrung mit Java und Programmieren hat


----------



## AmunRa (31. Aug 2010)

Könnte sein, 

ich glaub, dass das vom Individuum abhängt ob er es als anfännger schafft oder nicht (ich hab nicht gesagt dass es einfach ist so etwas selbst zu schreiben). Aber ween er sich anstrengt und es ihm Spass macht sich da durch zu beißen dann kann er dass schon versuchen.

Der TO muss sich selbst überlegen wie er es machen kann und will. Hängt ja auch ab was er wirklich vor hat


----------



## Keviehn (31. Aug 2010)

Ich habe mich heute mal mit SWING beschäftigt und man versteht es eigentlich leichter als anderes. Ich finde die Tuts. für SWING auch angenehmer als AWT. 

Werde das Projekt für mich persönlich auf Eis legen. Ich werde und muss um JAVA zu verstehen aufjedenfall bei Kapitel 1 anfangen und nicht probieren aus anderen Quellcodes zu lernen. Kann nur schief gehen.

Sicherlich die unverschämteste Frage die ihr hier bekommt, aber gibt es die Möglichkeit das einer von euch mir so ein Programm schreibt bzw. mit den Quellqode gibt. . . wobei jetzt nicht auf die Sauberkeit geachtet werden müsste.

Falls kein Interesse besteht, brauch auch nicht weitergelesen werden:

Hier nochmal eine kleine Übersicht was ich eigentlich möchte:


Ich benötige eine JAVA - Applikation die ein Bild laden und anzeigen kann. Ob das Bild mittel JFilechooser oder im Quelltext angegeben wird wäre mir relativ egal.


Das geladene Bild muss manipuliert werden. (Schwarz-Weiß-Funktion, Negativ, Bild drehen, Bild spiegeln, Bild invertieren)


Das geänderte Bild muss gespeichert werden.

Würde mich um eine Antwort freuen.


Grüße
Keviehn


----------



## AmunRa (31. Aug 2010)

Bis wann sollte denn ds ganze fertig sein?


----------



## Keviehn (31. Aug 2010)

Da habe ich keine wirklichen Ansprüche. Wenn es die Zeit und Lust erlaubt wird es sicherlich irgendwann fertiggestellt. 


Grüße
Keviehn


----------



## Landei (31. Aug 2010)

Hier mal ein mögliches Grundgerüst (mit Bild laden und Spiegeln an der X-Achse):


```
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;

public class ImageEditor {

    private BufferedImage image;
    private JPanel panel = new JPanel() {

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            if (image != null) {
                int w = Math.min(getWidth(), getHeight() * image.getWidth() / image.getHeight());
                int h = Math.min(getHeight(), getWidth() * image.getHeight() / image.getWidth());
                g.drawImage(image, 0, 0, w, h, null);
            }
        }
    };

    private Action loadAction = new AbstractAction("Load") {
        public void actionPerformed(ActionEvent ae) {
            JFileChooser fc = new JFileChooser(".");
            fc.setFileFilter(new FileFilter() {
                public boolean accept(File f) {
                    String name = f.getName().toLowerCase();
                    return name.endsWith(".png") || name.endsWith(".gif") ||
                           name.endsWith(".jpg") || name.endsWith(".jpeg") || f.isDirectory();
                }
                public String getDescription() {
                    return "Images (*.png, *.gif, *.jpg, *.jpeg)";
                }
            });
            if(fc.showOpenDialog(panel) == JFileChooser.APPROVE_OPTION) try {
                image = ImageIO.read(fc.getSelectedFile());
                panel.invalidate();
                panel.repaint();
            } catch(IOException ex) {
                ex.printStackTrace();
            }
        }
    };

    private Action flipXAction = new AbstractAction("Flip X") {
        public void actionPerformed(ActionEvent e) {
            if (image != null) {
                BufferedImage bi = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
                Graphics g = bi.getGraphics();
                g.drawImage(image, image.getWidth(), 0, -image.getWidth(), image.getHeight(), null);
                g.dispose();
                image = bi;
                panel.invalidate();
                panel.repaint();
            }
        }
    };

    public ImageEditor() {
        JFrame frame = new JFrame("ImageEditor");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setBounds(50, 50, 800, 600);
        frame.getContentPane().add(panel);
        Box box = Box.createVerticalBox();
        box.add(new JButton(loadAction));
        box.add(new JButton(flipXAction));
        frame.getContentPane().add(box, BorderLayout.EAST);
        frame.setVisible(true);
    }

    public static void main(String... args) {
        new ImageEditor();
    }
}
```


----------



## Marcinek (1. Sep 2010)

Ich weiß nicht, ob der TO tatsächlich die Funktinalität braucht oder das nur nutzen möchte um sich mit Java zu beschäftigen...

Wenn man die Funktionalität braucht, dann kann man bestehende Programme besser nutzen.

Wenn man sich mit Java vertraut machen möchte, dann ist das die denkbar schlechteste Methode.

Außerdem bedarf es mehr als 1 Tag um die Prinzipien nicht nur von Java sondern auch noch von Swing zu erlerenen.  - @TO: Wenn du nach dem ersten (kleinen) Problem "aufhörst", dann wird das nix.

--------

@ Topic:

Das o.g. Beispiel eigent sich in meinen Augen nicht so gut um einen zu Zeigen, wie er das machen soll,d a hier etliche "sonderformen" genutzt worden sind ;D

Um auf mehrere Buttons reagieren zu können, dann macht man das genau wie im WEB auch:

1. Button / Menüpunkt bennen: 
	
	
	
	





```
.setName("Close");
```

2. Einen zentralen ActionListener Proggen:


```
public MyGlobalActionListener extends ActionListener {

public void actionPerformed ( Action a ) {
    if ( a.getSource().getName().equals ( "Close")) {
     // CLOSE
    
    } else if ( a.getSource().getName().equals ( "Andere Aktion") ) 
      // Andere Aktion
    }


}
}
```

3. Man muss nun seinen ActionListener an den Button befestigen. 
	
	
	
	





```
addActionListener ( new MyGlobalActionListener() );
```

Man kann nach beliben referenzen und so an den Actionlistener weiter leiten und die einzelenen Funktionen auslagern in andere Klassen etc...

Gruß,


----------



## Landei (1. Sep 2010)

Ich persönliche finde einen einzelnen "MonsterListener" mit seitenlanger if-else-Kaskade jedenfalls unschön und unflexibel. Einzelne Actions können dagegen wiederverwendet werden (z.B. in einer ToolBar oder im Menü), wie auch sehr schön in der Insel dargelegt.


----------



## Marcinek (1. Sep 2010)

Das ist ja nur ein Anfang.

In einem weiteren Schritt würde ich einen Namen zu einer Klasse in einer HashMap zuordnen.

Dann könnte man abhängig von einem Button eine bestimmte Klasse laden und eine Methode darin ausführen.

Damit lassen sich schön Case Blöcke vermeiden, ohne, dass man inline Klassen und Methoden verwendet.


----------



## AmunRa (1. Sep 2010)

Wobei bitte bei einem ActionListener sollte man dann statt dem namen auf den actionCommand gehen 

also 

if (a.getActionCommand().equals("Close"){


----------



## Landei (1. Sep 2010)

Je länger ich darüber nachdenke, um so klarer wird mir, dass dein Vorschlag ein echtes Antipattern ist:

*MonsterListener-Antipattern*

Erkennungsmerkmal: Der ActionListener-Code für alle Buttons, Menüs u.s.w wird in einem einzigen ActionListener zusammengefasst, der in einer if-else-Kaskade die richtige Komponente über den Namen identifiziert und den zugehörigen Code ausführt.

Kritikpunkte:
- Verletzung des Separation of Concerns-Prinzips
- Der Ansatz skaliert nicht. Bei einer Anwendung mit vielen Komponenten (etwa ein Taschenrechner mit vielen Funktionen als MenuItems) müssen viele (eigentlich unnötige) Tests durchgeführt werden. Die zentrale Methode wird total unübersichtlich.
- Schlecht mehrfach verwendbar: Während Eigenschaften wie "enabled" in einer Action zentral geändert werden können, muss hier der Status jeder Komponente einzeln gesetzt werden. 
- Anfällig für Tippfehler: Die Identifikation erfolgt über Strings. Vertippt man sich, führt das nicht zu Compiletime-Fehlern, sondern Problemen zur Laufzeit
- Probleme beim Refactoring: Wird eine Komponente gelöscht, kann ungenutzer Code im MonsterListener zurückbleiben, ohne dass man es merkt. Wird die Funktionalität zweier Fenster zusammengeführt, können sich Komponenten gleichen Namens ungewollt überdecken (d.h. der falsche Code wird aufgerufen)

Es gibt keinen Grund, sich diesen Besen auf den Buckel zu binden. Eine Lösung, die für das "Web" irgendwie funktioniert, muss noch lange nicht die beste für Desktop-Entwicklung sein. Und "macht man so" ist keine gute Begründung. Wie ich schon ganz zu Anfang geschrieben habe, ist meine Lösung nur eine von mehreren Realisierungsmöglichkeiten, aber sie hat einige Vorteile: Jede Aktion ist eine abgeschlossene, separate und wiederverwendbare Einheit, die Lösung basiert auf zu Compilezeit überprüfbaren Variablen und nicht auf (aus Compilersicht nichtssagenden) Strings und es gibt keine Probleme beim Refactoring. Deine Lösung fällt hingegen aus meiner Sicht auf das Niveau vor Einführung der strukturierten (geschweige denn objektorientierten) Programmierung zurück.


----------



## Tomate_Salat (1. Sep 2010)

Marcinek hat gesagt.:


> Dann könnte man abhängig von einem Button eine bestimmte Klasse laden und eine Methode darin ausführen.
> 
> Damit lassen sich schön Case Blöcke vermeiden, ohne, dass man inline Klassen und Methoden verwendet.



Dann hast du es nur verlagert. Schließe mich Landei an, das bringt dir keinen Vorteil und Wartungsfreundlich ists auch nicht. Definierst du die Aktionen auch im Globalen listener? Für kleinere Projekte "ok". Wenn ich da an unseres im Geschäft denke, die Klasse wäre mehr als 100.000 Zeilen Code lang! Du kommst mit den Idendifizerungen ja garnicht mehr nach.


----------



## Marcinek (1. Sep 2010)

Hi 

Hast du dir das Antipatern selbst überlegt, oder gibt es eine Quelle dafür?

Leider kann ich einige Punkte nicht anchvollziehen:



> Verletzung des Separation of Concerns-Prinzips



Meiner Ansicht nach: Nein - Ich habe einen oder mehrere Dispatcher ( Listener ), die folgende Eigenschaften erfüllen: 
 - Skallierbar: Ich kann soviele Funktionen dort unterbringen, wie ich möchte, denn ich muss meiner Map nur sagen, welcher Command von welcher Klasse gehandelt wird. Der Handler sollte so gewählt werden, so dass er keinen orthogonalen Code enthält.
 - Kaskadierbar: Ich kann leicht das Grundgerüst anpassen, so dass ich pro ActionCommand mehrere Aktionen ausführe, indem ich die Hashmap als OpenHash implementiere
 - Polymorphy.. Ich kann semtliche Features nutzen, wie Vererbung, überlagerung...

Im Prinzip genau, wie deine Methode, jedoch gibt es vorher einen oder mehrere Dispatcher.



> Der Ansatz skaliert nicht. Bei einer Anwendung mit vielen Komponenten (etwa ein Taschenrechner mit vielen Funktionen als MenuItems) müssen viele (eigentlich unnötige) Tests durchgeführt werden. Die zentrale Methode wird total unübersichtlich.



Skalierbarkeit siehe oben. Die zentrale Methode sieht aus wie Folgt:


```
actionPerformed () {
  if(Map.contains (ActionCommnad) {
     Map.get(ActionCommand).execute(Context, Source)
} else {
  // Hier könnte die Anfrage eine Exception auslösen, oder hinweis, oder einen anderen (kaskadierenden) Dispatcher für die Action suchen.
}

}
```

Sieht in meinen Augen nicht so komplex aus.



> Schlecht mehrfach verwendbar: Während Eigenschaften wie "enabled" in einer Action zentral geändert werden können, muss hier der Status jeder Komponente einzeln gesetzt werden.



Eine Action, wie copy, paste, delete, close, kann man einmal implementieren und dann dem Dispatcher unter Applikationsweiten einheitlichen Namen hinterlegen. 



> - Anfällig für Tippfehler: Die Identifikation erfolgt über Strings. Vertippt man sich, führt das nicht zu Compiletime-Fehlern, sondern Problemen zur Laufzeit



Dies kann man meiner Einsicht nach nicht wirklich als Argumentationsgrund nehmen. Normalerweise würde man mit Konstanten arbeiten und das minimiert diesen Fehler auf quasi null.



> Probleme beim Refactoring: Wird eine Komponente gelöscht, kann ungenutzer Code im MonsterListener zurückbleiben, ohne dass man es merkt. Wird die Funktionalität zweier Fenster zusammengeführt, können sich Komponenten gleichen Namens ungewollt überdecken (d.h. der falsche Code wird aufgerufen)



Es gibt in der Tat keine Bindung zwischen Ausführender Klasse und dem ActionCommand (String). Nichtverwendeter Code bleibt stehen. 

Das Problem der Zusammenführung kann leicht gelöst werden, indem geeignete Prefixes für die ActionCommands genutzt werden, die dann eine Prefixfreiheit für alle anderen UIs garantieren.

Fazit: Ich finde diesen Ansatz nicht schlecht und er kann bestimmt in der einen oder anderen Anwenung relevant sein. 
Nämlich dann, wenn ich zu einer UI genau einen Controller habe und der Controller für mich alle Events der UI handelt.

Weiterhin bietet es eine Basis dafür um ellenlange if-else Konstruktionen auch in anderen Anwendungen, als dem Eventhandeling, abhilfe zu schaffen.

In unseren Programmen haben wir Controller, die für genau eine UI zuständig sind. Der Controller wacht über den kompletten Lebenszyklus einer UI und führt lediglich Operationen zur visualisierung seines Models aus. Obwohl mittlerweilse der Contoller auf Typen von Aktionen, wie Listen, Buttons, Trees, separat handeln kann, bleibt bei mehreren Aktionen eines Typs noch eine if/else verzweigung. 

~~~

Ich möchte noch dazu sagen, dass ich nicht willentlich gesagt habe "macht man so". Sollte dies irgentwoe so verstanden worden sein, so möchte ich micht korrekt fassen: "Könnte man so machen". 
Ich würde mir niemals ermessen zu sagen "so und nicht anders"  Bei der Beurteilung des, wie man es machen sollte, spielen sehr viele Faktoren eine Rolle und ich denke, dass sich das ab hier jeder selbst denken kann ^^

Zu guter letztz:



> Deine Lösung fällt hingegen aus meiner Sicht auf das Niveau vor Einführung der strukturierten (geschweige denn objektorientierten) Programmierung zurück.



Wie ich oben gezeigt habe, ist dies nicht der Fall. Lediglich die Stringzuweisung ist in dem Sinne lose gekoppelt und kann nicht ohne weiteres von dem Compiler geprüft werden.

Gruß,

Marcin

P.S.: Ich hoffe, dass du dich auch auf meinen Beitrag zur Ergänzung der von mir genannten if else Konstruktion gestützt hast =)


----------



## Marco13 (1. Sep 2010)

Ich wurde selbst schon mit dem MonsterListener-Antipattern konfrontiert. Und deswegen propagiere ich im allgemeinen entweder anonyme Listener (z.B. für ChangeListener oder einen kleinen MouseListener, ggf. in kleinen Beispielen auch mal für einen ActionListener), und verweise darauf, dass bei "richtigen" Anwendungen für Aktionen [c]Action[/c]s (sic) das Mittel der Wahl sind. Spätestens wenn die gleiche Action an mehreren Components hängen soll hat das unschätzbare Vorteile, und wenn dann noch Mnemonics und Tooltips dazukommen ist alles andere nur unübersichtliches Gefrickel. 

Die Punkte, die Landei aufgezählt hat stimmen meines Erachtens. Es gibt sicher Grenzfälle. In manchen Fällen _könnte_ es OK sein, den ActionListener nur als Dispatcher zu verwenden, und dort wirklich nur einzelne Methodenaufrufe reinzuschreiben (was du wohl mit "orthogonalem Code" meintest)

```
if      (command.equals(COM_A)) execute(new CommandA());
else if (command.equals(COM_B)) execute(new CommandB());
else if (command.equals(COM_C)) execute(new CommandC());
else if (command.equals(COM_D)) execute(new CommandD());
```
aber ich glaube, auch da gibt es meistens elegantere und flexiblere Lösungen. Dazu zählt aber nicht die angesprochene Map, die IMHO nach einem Workaround aussieht.


----------

