# Action Listener implementieren über Objekt



## kelanol112358 (13. Aug 2012)

Hallo,

ich habe mich (hoffentlich) an das MVC Konzept gehalten. Der Übersichtlichkeit wegen habe ich die Erstellung eines Teils der GUI ausgelagert. Dazu habe ich in einer Methode der view Klasse ein Objekt der besagten Klasse erstellt. 

In der controll Klasse wollte ich nun mit 

```
view.besagteklasse.button2addActionListener(new Action Listener());
```
 im Konstruktor den ActionListener dem Button zuweisen. Dies funktioniert aber nicht, da bei erzeugung des controllers in der Main Klasse der Objektname besagteklasse noch nicht existiert. Dies geschieht erst in einer Methode. 

Wie geht man denn am besten vor, um nach diesem Schema den ActionListener für meinen Button zu erstellen?


----------



## Marcinek (13. Aug 2012)

Dein posting beinhaltet viel zu wenig Code. Man kann gar nicht sagen an was du dich hier hälst. Einen Action listender an ein Button hinzuzufügen ist kein mvc 

Hast du dir Beispiele zu mvc angeschaut??


----------



## kelanol112358 (14. Aug 2012)

Ich glaube, das Programm läuft nun so wie von mir gewünscht. Vielleicht kann sich dieses Beispiel jemand ansehen und mir sagen, ob dies auch ein guter Programmierstil ist bzw. was man besser machen kann. Mir geht es dabei eigentlich nur um den Button 2 und den Code verbunden mit diesen Zeilen:

```
viewer.neuenButtonersellen();
              viewer.neu.addButton2Listener(new Button2Listener());
```


```
class Main{
 public static void main(String[] args){
   Model model = new Model();
   Viewer view = new View(model);
   Controller controller = new Controller(view, model);
   }
}
```


```
class Viewer{
  JButton button1;
  JFrame fenster;
  Model model;
  //...
  Viewer(Model model){
    this.model = model;
    fenster = new Frame();
    button1 = new JButton;
  }
   void addButton1Listener(ActionListener e) {
        button1.addActionListener(e);
   }
  void neuenButtonerstellen(){
    Button neu = new Button();
    fenster.add(neu.panel2);
  }
}
```


```
class Button{
  JPanel panel2;
  JButton button2;
  Button(){
     panel2 = new JPanel();
     panel2.add(button2);
  }
  addButton2Listener(ActionListener e){
     button2.addActionListener(e);
  }
}
```
Die Controller Klasse beinhaltet nun eine innere Klasse für den ActionListener:

```
class Controller{
Viewer viewer;
Model model;
  Controller(Viewer viewer, Model model){
      this.viewer = viewer;
      this.model = model;
      viewer.addButton1Listener(new Button1Listener());
  }
  class Button1Listener{
      public void actionPerformed(ActionEvent ae) {
              viewer.neuenButtonersellen();
              viewer.neu.addButton2Listener(new Button2Listener());
      }
  }
  class Button2Listener{
      public void actionPerformed(ActionEvent ae) {
              //irgendwas
      }
   }
}
```


----------



## kelanol112358 (14. Aug 2012)

Ich habe noch eine Frage, die sich auf obigen Code bezieht. 
In der Methode neuenButtonerstellen der Klasse Viewer soll auch noch gleich der Button einem TabbedPane zugewiesen werden, sodass ich bei mehreren Tabs in jedem Tab einen Button habe.


Die Klasse viewer:

```
class Viewer{
  JTabbedPane tabs;
  JButton button1;
  JFrame fenster;
  Model model;
  //...
  Viewer(Model model){
    this.model = model;
    fenster = new Frame();
    button1 = new JButton;
  }
   void addButton1Listener(ActionListener e) {
        button1.addActionListener(e);
   }
  void neuenButtonerstellen(String name){ //name wird abgefragt vom Nutzer
    Button neu = new Button(name);
    fenster.add(neu.panel2);
    tabs.add(name, fenster)//anstatt "hallo" wird ein string vom nutzer abgefragt
  }
}
```

Durch Klicken auf den Button1 erstelle ich einen neuen Tab. Wenn ich jetzt wieder einen neuen Tab erstelle, dann wird das Objekt neu ja wieder überschrieben, ich komme also an meinen String nicht mehr heran, den ich übergeben habe. 
Als Lösung würde mir natürlich eine ArrayList einfallen, die ich aber für sehr umständlich halte. Ich müsste eine ArrayList erstellen und die Objekte neu dann hinzufügen. Allerdings muss ich doch dann immer erst herausfinden, in welchem Tab sich der Nutzer gerade befindet, um den String zu bekommen.


----------



## Marcinek (14. Aug 2012)

Hallo,

ich möchte hier nicht zu sehr auf Details eingehen, daher hier nur die wichtigsten Sachen, die mir aufgefallen sind:

1. Wieso erstellt der Controller nicht die View und das Modell?

2. Diese "view.new" geschichte ist falsch. Hier würde man eher die neue Komponente einfach als Rückgabewert der Methode definieren. Oder gleich den ActionListener übergeben.

3. "ist das guter programierstil". Muss man eindeutig mit "NEIN" beantworten. Was passiert mit deinem Code, wenn du mehrere Views, mehrere Modells und mehrere Controler hast => Chaos. Man würde eher versuchen das ganze generischer zu gestalten. Korrekterweise würde ein Controller einen Use-Case abbilden, wie "Benutzer Login". Sein Modell sind die Benutzer und die View ein Dialog mit zwei Feldern und einem Button. 

Wenn ich eine Authentifizierung machen möchte würde ich diesen Controler befragen. Der macht dann immer das gleiche: Neue GUI, Modell holen und anzeigen. Das verhalten lässt sich einfach abstahieren, so dass du eine Klasse "Controller" hast und der rest erbt davon und sagt nur welches Model und welche Gui geladen werden soll.

P.S: Dein Fehler in deinem weiteren Posting ergiibt sich durch das falsche Design, was ich in Punkt 2 angesprochen habe.


----------



## Michael... (14. Aug 2012)

Der Programmierstil wurde ja schon kommentiert.
Wozu die Klasse Button? Die ist doch überflüssig.


----------



## kelanol112358 (14. Aug 2012)

@ marcinek

1. Ich habe in den google Suchergebnissen recht weit oben folgendes Beispiel gefunden, an das ich mich gehalten habe:
Java: Model-View-Controller (MVC) Structure
Hier wird auch alles in der Main Klasse erstellt und dann übergeben.

2. Könntest du hier vielleicht genauer darauf eingehen, was du das meinst? Wenn durch den Controller irgendeine Aktion auslösen möchte, dann muss ich doch über viewer.neu viewer.irgendetwas machen.

Hast du denn ein Programm, in der das MVC nach deinen Vorstellungen umgesetzt wurde?

@michael

Mir wurde in einem anderen Thread hier empfohlen, für einen neuen Tab im TabbedPane eine neue Klasse zu erstellen. Der Tab soll nämlich mehr bekommen als nur einen Button.


----------



## Michael... (14. Aug 2012)

Hab in dem obigen Code das JTabbedPane nicht gesehen. Dann sollte die Klasse besser einen anderen Namen bekommen, z.B. TabPanelNameXY. Button ist grundsätzlich ungünstig, da Java Programmier dann meist von der Klasse java.awt.Button ausgehen.
Was mir auch nicht einleuchtet ist was Du mit 
	
	
	
	





```
tabs(name, fenster)
```
 vor hast. fenster ist ein JFrame - abgesehen davon, dass ich bezweile, dass der Code funktioniert - macht es keinen Sinne einen JFrame in ein JTabbedPane zu stecken - eher umgekehrt.

Auch die Nummerierung in den Variablen und Methodennamen ist unschön. Namen sollten sprechend sein, z.B. saveButton, addSaveListener(...) ...
Weiter ist der direkte Zugriff auf Instanzvariablen unschön (z.B. 
	
	
	
	





```
neu.panel2
```
) hiermit verbaut man sich einiges an Flexibilität und loser Kopplung, die man doch eigentlich mit dem MVC erreichen will.
Schaut Dir doch nochmal das in dem von Dir verlinkten Beispiel an, da ist das ganze eigentlich recht schön gelöst - abgesehen von den Unterstrichen in den Variablennamen 
	
	
	
	





```
m_model
```
...


----------



## kelanol112358 (14. Aug 2012)

@Michael
Mein Programm ist etwas länger und ich möchte es daher nicht komplett im Forum posten. Wenn ich mir dann konkrete Beispiele für mein Problem ausdenke, dann achte ich nicht unbedingt auf Namen usw. Die von dir angesprochenen Stilfehler sind mir aber bekannt und daran halte ich mich auch in jedem meiner Programme.


----------



## kelanol112358 (14. Aug 2012)

Wenn ich über eine neue Klasse ein Panel erstelle, in der ich dann dem Panel einen Button zuweise, dann muss ich mir doch das Panel über den Objektnamen holen. Eine andere Möglichkeit fällt mir nicht ein:
Zum Hinzufügen dieses Panels in einem neuen Tab mache ich dann folgendes:


```
tabbedpane.add(String,objektname.panel)//Pseudecode
```

Welche andere Möglichkeit gibt es. um das objektname.panel zu verhindern?

Ich habe es bereits geschafft, den Button2 aus der seperaten Klasse mit einem ActionListener auszustatten. Dies habe ich so gemacht, wie in dem Code in meinem Post. 

Im Allgemeinen habe ich mich überhaupt in meinem Code streng an das Beispiel gehalten. Den ActionListener für Button 1 habe ich genau gleich implementiert. Für Button2 musste ich eben eine zusätzliche "Wegweiser" angeben:

```
viewer.neu.addButton2Listener(new Button2Listener());
```
Mir stellt sich auch die Frage, wo ich denn von dem Beispiel abgewichen bin.

Das Hauptproblem, dass ich derzeit noch habe ist mit den Panels, die ich in meiner Klasse Button erstellt habe. Diese möchte ich nämlich während der Laufzeit des Programmes noch ändern. Das bedeutet:
Ich habe nach z.B fünfmaligen Drücken auf Button1 fünf Tabs. Jedes einzelne Panel eines Tabs wurde über eine seperate Klasse erstellt. In dem Panel eines Tabs befindet sich wieder ein Button, mit dem man das Panel individuell anpassen kann. 
Wenn ich allerdings einen neuen Tab durch Klicken auf Button1 erstelle, dann wird mein Objekt neu ja überschrieben, ist also nicht mehr zugänglich. 
Die Idee von mir war eine ArrayList, in der ich die einzelnen Panels speichere. Das kann dann aber umständlich werden, wenn ich die Panels verändern möchte (suchen nach dem entsprechenden Panel in der ArrayLst usw.)

Kann mir vielleicht jemand beschreiben, wie man das anders lösen kann?


----------



## Michael... (14. Aug 2012)

kelanol112358 hat gesagt.:


> Im Allgemeinen habe ich mich überhaupt in meinem Code streng an das Beispiel gehalten. Den ActionListener für Button 1 habe ich genau gleich implementiert. Für Button2 musste ich eben eine zusätzliche "Wegweiser" angeben:
> 
> ```
> viewer.neu.addButton2Listener(new Button2Listener());
> ...


In dem Beispiel findest Du z.B. nirgends soetwas wie viewer.*neu.*addButton2Listener(...), nur viewer.addButton2Listener(...)

Allgemein verstehe ich recht wenig von dem was Du da schreibst. Was ist für Dich ein "Objektname", der Name einer Variablen?

Evlt. beschreibst Du mal was Du vorhast umzusetzen und nicht wie Du es vorhast zu lösen. Wenn ich Deine Lösungansätze nur halbwegs richtig verstehe, hört sich das alles recht fragwürdig und nach fehlendem Grundlagenwissen an.
Wie lange beschäftigst Du Dich den mit Java? Evtl. ist die GUI Programmierung noch etwas zu komplex.


----------



## kelanol112358 (14. Aug 2012)

```
viewer.neu.addButton2Listener(new Button2Listener());
```
 Darum dreht sich doch genau meine Frage, wie ich den ActionListener für Button 2 korrekt implementiere. Dies ist so nicht in dem Beispiel vorhanden. In dem Beispiel wird aber auch über keine neue Klasse ein Panel/Button hinzugefügt.

Von einer Klasse kann man doch ein Objekt erstellen. Dies hat dann einen Namen. Also Objektname?



> Wie lange beschäftigst Du Dich den mit Java? Evtl. ist die GUI Programmierung noch etwas zu komplex.


Nun ja, ich hatte 1 Semester Einführung in die Programmierung und 1 Semester Algorithmen und Datenstrukturen an der Uni. 


> Evlt. beschreibst Du mal was Du vorhast umzusetzen und nicht wie Du es vorhast zu lösen. Wenn ich Deine Lösungansätze nur halbwegs richtig verstehe, hört sich das alles recht fragwürdig und nach fehlendem Grundlagenwissen an.


Ich möchte ein Fenster erstellen, auf dem sich ein Button befindet, der wenn gedrückt wird einen Tab öffnet. In jedem dieser Tabs sollen dann wieder zwei neue Tabs angezeigt werden. In einem dieser zwei Tabs soll dann ein Button angezeigt werden (besagter Button2). Dieser Button soll dann zur Laufzeit des Programmes Änderungen an dem Inhalt des Tabs machen, in dem er sich befindet. 

Beispiel:
Nutzer klickt auf einen übergeordneten Tab. Öffnet dann in diesem Tab den einen Tab, in dem sich Button2 befindet und klickt auf den Button. Dann soll sich die Farbe zum Beispiel in Rot ändern oder eine Farbe vom Nutzer abfragen, die das Panel dann annimmt. 

Ich habe bis jetzt das Fenster sowie den Button in der Klasse Viewer erstellt. Die zwei Tabs in dem Tab erstelle ich in einer neuen Klasse.


----------



## kelanol112358 (15. Aug 2012)

Hier noch einmal mein Code:


```
public class Haupt {
    public static void main(String[] args) {
        Model model = new Model();
        Viewer viewer = new Viewer(model);
        Controller controller = new Controller(model, viewer);
    }
}
```


```
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

class Controller {
Model model;
Viewer viewer;
    Controller(Model model, Viewer viewer) {
        this.model = model;
        this.viewer = viewer;
        
        viewer.addButton1ActionListener(new Button1Listener());
    }
    
    class Button1Listener implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent ae) {
            viewer.taberstellen();
            viewer.neu.addButton2ActionListener(new Button2Listener());
        }   
    }
    class Button2Listener implements ActionListener{
        
        @Override
        public void actionPerformed(ActionEvent ae) {
            viewer.neu.setText();
        }
    }
}
```


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

class Viewer extends JFrame {
    Model model;
    JButton button1;
    JTabbedPane tab;
    Tabinhalt neu;
    Viewer(Model model){
        this.model = model;
        this.setLayout(new FlowLayout());
        this.setTitle("Hauptfenster");
        this.setSize(1500,750);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
        
        button1 = new JButton("Button1");
        
        this.add(button1);
        
        tab = new JTabbedPane();
        
        this.add(tab);
    }
    void addButton1ActionListener(ActionListener e){
        button1.addActionListener(e);
    }
    void taberstellen(){
        neu = new Tabinhalt();
        tab.add("    neuer Tab    ", neu.panel);
    }
}
```


```
class Tabinhalt {
JPanel panel;
JButton button2;
JLabel label;
    Tabinhalt(){
        panel = new JPanel();
        
        button2 = new JButton("Button2");
        
        panel.add(button2);
        
        label = new JLabel();
        panel.add(label);
    }
    void addButton2ActionListener(ActionListener ae){
        button2.addActionListener(ae);
    }
    void setText(){
        label.setText("peter");
    }
}
```

Also: Der ActionListener für Button2 wurde ja bemängelt. Wie macht man es aber besser?

Wenn man wie in diesem Beispiel einen neuen Tab erstellt, und den Button2 drückt, dann wird Peter in das Panel geschrieben. Wenn man aber beispielsweise 3 Tabs erstellt, und in dem zuerst erstellten Tab den Button2 klickt, dann werden nur Änderungen im letzten tab ausgeführt. Das ist ja auch logisch, da das Objekt neu überschrieben wird, also nicht mehr zugänglich.


----------



## Michael... (16. Aug 2012)

Dein Problem ist, dass die View keine Referenz auf den Controller/Listener hält, sonst könnte man beim Erstellen der Subkomponenten, an diesen den direkt Controller/Listener registieren.

Zumindest bei Deinem Bsp. stellt sich aber die Frage inwieweit alles über einen zentralen Controller laufen muss. Wenn die Aktionen innerhalb der Subkomponenten nur die Subkomponente selbst betreffen, könnten diese ja über einen eigenen Controller bedient werden.
Hier mal ein Bsp ohne Model, klar könnte man den Code noch optimieren, hab aber versucht mich nicht zu weit von dem von Dir als Vorlage genutzten Demo Code zu entfernen.

```
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;


public class MVCTab {
	public static void main(String[] s) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setBounds(0, 0, 300, 300);
		frame.setLocationRelativeTo(null);
		
		MainView view = new MainView();
		new MainController(view);
		
		frame.add(view);
		frame.setVisible(true);
	}
}

interface ViewListener {
	public void notifyAboutAction(String command);
}

class MainController implements ViewListener {
	private MainView view;
	
	public MainController(MainView view) {
		this.view = view;
		this.view.addViewListener(this);
	}
	
	public void notifyAboutAction(String command) {
		view.insertNewTab();
	}
}

class MainView extends JPanel {
	private JTabbedPane tabPane;
	private int tabIndex = 1;
	
	private List<ViewListener> controllers;
	
	public MainView() {
		controllers = new ArrayList<ViewListener>();
		
		this.setLayout(new BorderLayout());
		tabPane = new JTabbedPane();
		this.add(tabPane, BorderLayout.CENTER);
		JButton genTabButton = new JButton("Create new Tab");
		this.add(genTabButton, BorderLayout.SOUTH);
		
		genTabButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				for (ViewListener listener : controllers)
					listener.notifyAboutAction(null);
			}
		});
	}
	
	public void insertNewTab() {
		TabComponent comp = new TabComponent();
		new TabController(comp);
		tabPane.addTab("Tab " + tabIndex++, comp);
		tabPane.setSelectedIndex(tabPane.getTabCount()-1);
	}

	public void addViewListener(ViewListener listener) {
		controllers.add(listener);
	}
	
	class TabController implements ViewListener {
		private TabComponent view;
		
		public TabController(TabComponent view) {
			this.view = view;
			this.view.addViewListener(this);
		}
		
		public void notifyAboutAction(String command) {
			view.changeSomething(command);
		}
	}
	
	class TabComponent extends JPanel {
		private JLabel label;
		
		private List<ViewListener> controllers;
		
		public TabComponent() {
			controllers = new ArrayList<ViewListener>();
			
			this.setLayout(new BorderLayout());
			this.setOpaque(true);
			label = new JLabel("initial", JLabel.CENTER);
			this.add(label, BorderLayout.CENTER);
			JPanel panel = new JPanel(new GridLayout(1,2));
			this.add(panel, BorderLayout.NORTH);
			
			final String[] dummy =  new String[]{"RED", "GREEN", "BLUE", "ORANGE", "CYAN", "YELLOW"};
			
			JButton button = new JButton("Change Text");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent evt) {
					for (ViewListener listener : controllers)
						listener.notifyAboutAction(dummy[(int)(Math.random()*dummy.length)]);
				}
			});
			panel.add(button);
			button = new JButton("Change Color");
			button.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent evt) {
					for (ViewListener listener : controllers)
						listener.notifyAboutAction("Color:" + dummy[(int)(Math.random()*dummy.length)]);
				}
			});
			panel.add(button);
		}
		
		public void changeSomething(String command) {
			if (command.startsWith("Color:")) {
				Color color = Color.WHITE;
				try {	
					color = (Color)Color.class.getDeclaredField(command.split(":")[1]).get(color);
					this.setBackground(color);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			else
				label.setText(command);
		}

		public void addViewListener(ViewListener listener) {
			controllers.add(listener);
		}
	}
}
```


----------



## Harry Kane (16. Aug 2012)

@TE Zur Zeit verwendest Du nicht-private Felder, um einem Objekt den Zugriff auf die Eigenschaften eines anderen Objektes zu geben. Erstens ist das grundsätzlich schlecht, weil auf diese Weise keine Zugriffskontrolle möglich ist (du könntest mit dem Objekt wer weiss was anstellen) und zweitens sehe ich nicht warum es so einen Zugriff überhaupt geben muss.
Vergleiche mal dieses Schnipsel (von dir)

```
class Controller{
	Viewer viewer;
    class Button1Listener implements ActionListener{
 
        @Override
        public void actionPerformed(ActionEvent ae) {
            viewer.taberstellen();
            viewer.neu.addButton2ActionListener(new Button2Listener());
        }   
    }
}
class Viewer extends JFrame {
    Model model;
    JButton button1;
    JTabbedPane tab;
    Tabinhalt neu;
    Viewer(Model model){
        button1 = new JButton("Button1");
        this.add(button1);
        tab = new JTabbedPane();
        this.add(tab);
    }
    void addButton1ActionListener(ActionListener e){
        button1.addActionListener(e);
    }
    void taberstellen(){
        neu = new Tabinhalt();
        tab.add("    neuer Tab    ", neu.panel);
    }
}
class Tabinhalt {
JPanel panel;
JButton button2;
JLabel label;
    Tabinhalt(){
        panel = new JPanel();
        
        button2 = new JButton("Button2");
        button2.addActionListener(ae);
        
        panel.add(button2);
        
        label = new JLabel();
        panel.add(label);
    }
    void setText(){
        label.setText("peter");
    }
}
```
mit diesem hier:

```
class Controller{
	Viewer viewer;
    class Button1Listener implements ActionListener{
 
        @Override
        public void actionPerformed(ActionEvent ae) {
            viewer.taberstellen();
        }   
    }
}
class Viewer extends JFrame {
    Model model;
    JButton button1;
    JTabbedPane tab;
    Tabinhalt neu;
    Viewer(Model model){
        button1 = new JButton("Button1");
        this.add(button1);
        tab = new JTabbedPane();
        this.add(tab);
    }
    void addButton1ActionListener(ActionListener e){
        button1.addActionListener(e);
    }
    void taberstellen(){
        JPanel panel = new JPanel();
        
        JButton button2 = new JButton("Button2");
        button2.addActionListener(new Button2Listener());        
        panel.add(button2);
        
        label = new JLabel();
        panel.add(label);
        tab.add("    neuer Tab    ", panel);
    }
}
```
Warum musst du in der Controller-Klasse Zugriff auf das neu erstellte JPanel des Viewers überhaupt Zugriff haben?
Wenn Du in den ActionListeners Zugriff auf bestimmte Objekte benötigst, übergebe sie einfach im Konstruktor (denn auch Listener dürfen Konstruktoren haben!)


----------

