eigenständige Listener-Klasse

Hallo zusammen.

Bin Anfänger in Java und schlage mich dementsprechend mit einem Anfänger-Problem herum.

Bis jetzt verzichtete ich auf Eclipse und schrieb alles in einem einfachen Editor (Textpad oder Wordpad).
Ich finde das zum Lernen der Sprache vorteilhaft, da mir Eclipse zu viele Sachen automatisch macht, welche ich dann noch nicht richtig verstehe.


Im Allgemeinen geht es um die Hierarchie von Klassen und dem Zugriff auf Instanzen von einer Klasse zur Anderen.

In einem Projekt möchte ich der Übersicht halber meine Listener selber, in separaten Klassen schreiben.
Somit benutze ich keine anonymen ad hoc Listener, und die geschriebenen Listener-Klassen sollen keine inneren Klassen der Gui-Klasse sein.

Solange ich meine Listener-Klassen als innere Klasse vom Gui schreibe, funktioniert alles wunderbar.
Dadurch wird aber der Programm-Code lang und unübersichtlich.
Mache ich die Listener-Klassen zu eigenständigen, wirds problematisch.

Ich gehe dabei folgendermassen vor:

Ich habe eine "Starter"-Klasse, welche nur die Main-Methode enthält.
Dort instanziere ich ein Gui-Objekt (JFrame) und mache es sichtbar.
Z.B: Gui fenster = new Gui("blabla");

Meine Gui-Klasse erbt von JFrame.
Im Konstruktor der Gui-Klasse baue ich dann die graphische Oberfläche auf, JPanels, JButtons, JLabels etc.
Dort instanziere ich auch meine Listener-Objekte und adde sie den entsprechenden Elementen zu.

Z.B.:

MeinSchalterListener druck1 = new MeinSchalterListener();
button1.addActionListener(druck1);


Neben dem Konstruktor enthält die Gui-Klasse noch die Getter und Setter-Methoden.

So weit so gut.
Das Gui steht, die Listener sind aktiv.
Wie kann ich jetzt aber in den Performer-Methoden der Listener auf die Instanzen / Methoden der Gui-Klasse zugreifen?
Die Instanz "fenster" ist aus den Listener-Methoden heraus nicht sichtbar.
Ich kann weder auf Gui-Elemente (wenn ich das Kapselung-Prinzip ignoriere), noch auf Getter und Setter der Gui-Klasse zugreifen, was kein Problem ist, solange die Lister-Klassen innere der Gui-Klassen sind.

Allgemein formuliert:
Wie können bei zwei eigenständigen Klassen die Methoden der einen auf die Instanzen der anderen zugreifen und das in beide Richtungen?
Ich konnte das nur realisieren, wenn beide Klassen innere Klassen einer äusseren Klasse waren.

Hoffe, dass das Problem auch ohne Code verständlich formuliert ist...
Bedanke mich für die Antworten
 
du kannst der Listener- Klasse im Konstruktor ein Objekt der GUI Klasse mitgeben, wenn du ihn in der Klasse eh initalisiertst .zb. so:

GUI Klasse:

Java:
...
MeinSchalterListener druck1 = new MeinSchalterListener(this);
...

dann kannst du in der Listenerklasse auf die Getter/Setter der GUI Klasse zugreifen
 
Wie können bei zwei eigenständigen Klassen die Methoden der einen auf die Instanzen der anderen zugreifen und das in beide Richtungen?


Ganz allgemein: Indem man dem erzeugten Objekt eine Referenz auf die bestehende Instanz der anderen Klasse übergibt.

In deinem Fall geht es auch ein bisschen einfacher:
Code:
public void setActionCommand(String actionCommand)
AbstractButton (Java 2 Platform SE v1.4.2))
und mit
Code:
public String getActionCommand()
ActionEvent (Java 2 Platform SE v1.4.2))
 
Wie können bei zwei eigenständigen Klassen die Methoden der einen auf die Instanzen der anderen zugreifen und das in beide Richtungen?
Für die Entwicklung von Applikationen mit GUI sollte man meiner Meinung sich vorher mit den Grundlagen recht sicher sein. Dein Problem ist ein recht allgemein verbreitetes Anfängerproblem. Eine einfache Antwort: Die Objekte dieser Klassen müssen (z.B. in einer Instanzvariable) eine Referenz auf das jeweils andere Objekt besitzten. "Jemand" erzeugt Objekt A und Objekt B und übergibt dann beiden eine Referenz auf das andere Objekt.
Allerdings ist der direkte beidseitige Zugriff oftmals unnötig und erhöht dazu auch noch die Abhängigkeit beider Klassen untereinander. Vor allem bei der GUI Entwicklung verwendet man dazu verschiedene Entwurfsmuster, ein bekanntes ist z.B. das MVC Model View Controller ? Wikipedia Hiermit können die einzelnen Elemente recht lose miteinander gekoppelt und recht unabhängig von einander entwickelt werden.

Aber grundsätzlich spricht nichts gegen ananoyme innere Listener, da man oftmals ohnehin nicht im weiteren Codeverlauf auf diese zugreifen muss. Inwiefern der Code dadurch länger und unübersichtlicher wird, kann ich jetzt auf Anhieb nicht nachvollziehen.
 
Bin mir schon bewusst, dass ich Anfänger-Probleme mit OOP habe.
Wird halt von der Schule erwartet, sich in GUIs zu stürzen ohne gut fundierte OOP-Kenntnisse zu haben. (Werden auch keine geliefert)
Die Idee dabei, separate Listener-Klassen zu machen ist, dass ich jede Klasse in einem separaten File speichern kann, und sich dadurch den Code-Umfang pro File reduziert und überschaubarer wird.

Bin noch nicht sicher, ob ich richtig verstanden wurde, oder ob ich ein Brett vor dem Kopf hab...
Habe darum ein kleines Demo-Programm geschrieben:

Ein Gui mit 4 JButtons, die jeweils mit einem Listener verknüpft werden.
1. anonymer ad hoc Listener+Performer
2. anonymer Listener
3. eigener Listener aus innerer Klasse
4. eigener Listener aus separater Klasse

1-3 funktioniert
Bei 4 habe ich keine Referenz auf das Gui, kann JButton nicht umfärben.
Wie kriege ich für Listener 4 eine Referenz auf das Objekt fenster?



Java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;

class Gui extends JFrame implements ActionListener{
	Color schwarz;
	Color weiss;
	Color rot;

	JPanel  container;
	JButton schalter1;
	JButton schalter2;
	JButton schalter3;
	JButton schalter4;

	//Konstruktor
	Gui(String titel){
		super(titel);
		setLayout(null);

		schwarz		= new Color(0, 0, 0);
		weiss		= new Color(255, 255, 255);
		rot			= new Color(255, 0, 0);
		container 	= new JPanel();
		schalter1	= new JButton("Schalter 1");
		schalter2	= new JButton("Schalter 2");
		schalter3	= new JButton("Schalter 3");
		schalter4	= new JButton("Schalter 4");

		container.setLayout(null);
		container.setBackground(weiss);
		container.setBounds(0, 0, 700, 300);
		add(container);

		schalter1.setBounds(50, 50, 100, 50);
		schalter1.setBackground(rot);
		container.add(schalter1);

		schalter2.setBounds(200, 50, 100, 50);
		schalter2.setBackground(rot);
		container.add(schalter2);

		schalter3.setBounds(350, 50, 100, 50);
		schalter3.setBackground(rot);
		container.add(schalter3);

		schalter4.setBounds(500, 50, 100, 50);
		schalter4.setBackground(rot);
		container.add(schalter4);

		//Listener + Performer ad hoc, anonym für Schalter 1
		schalter1.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				System.out.println("Schalter 1 gedrueckt");
				schalter1.setBackground(schwarz);
			}
		});


		//anonymer Listener für Schalter 2
		schalter2.addActionListener(this);


		//Listener aus eigener innerer Klasse für Schalter 3
		schalter3.addActionListener(new Lauscher3());

		//Listener aus eigener separater Klasse für Schalter 4
		schalter4.addActionListener(new Lauscher4());


		setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
	}



	//Action Performer für Schalter 2
	public void actionPerformed(ActionEvent e){
		System.out.println("Schalter 2 gedrueckt");
		schalter2.setBackground(schwarz);
	}



	//eigene innere Klasse mit Action Performer für Schalter 3
	class Lauscher3 implements ActionListener{
		public void actionPerformed(ActionEvent e){
			System.out.println("Schalter 3 gedrueckt");
			schalter3.setBackground(schwarz);
		}
	}
}


//eigene separate Listener Klasse für Schalter 4
class Lauscher4 implements ActionListener{
	public void actionPerformed(ActionEvent e){
		System.out.println("Schalter 4 gedrueckt");

//*** geht nicht, kein Zugriff ****************************
//
//		fenster.schalter4.setBackground(fenster.schwarz);
//
//*********************************************************

	}
}

public class Starter{
	public static void main(String[]args){
		Gui fenster = new Gui("Gui");
		fenster.setBounds(600, 100, 700, 300);
		fenster.setVisible(true);
		fenster.container.setBackground(fenster.weiss);
	}
}
 
[c]class Gui extends JFrame implements ActionListener[/c]

Das-kannste-schon-so-machen,-aber-dann-isses-halt-.png


Erst einmal finde ich es nicht gut, einen "globalen" Listener zu haben. Zum einen braucht dieser Listener wieder unnötigen, länglichen Code, um herauszufinden, welches Knöpfchen denn jetzt gedrückt wurde. Zum andern gibt es nicht nur ActionListener, sondern du endest mit einer Klasse, die ActionListenern, ItemListener, KeyListener und OmaHatIhrGebissVerlegtListener implementiert. Mit anderen Worten: Der Ansatz sieht für kleine Projekte attraktiv aus, "skaliert" aber nicht, wenn es komplizierter wird.

Dann sollte man auch nicht ohne Not JFrame erweitern. Das ist in Ordnung, wenn man neue Fenster-Funktionalität (etwa einen blinkenden Rahmen oder einen Titel in Laufschrift) hinzufügt, aber das tust du nicht - du benutzt nur ein Fenster, in dem du es konfigurierst und Inhalte einfügst, aber du erweiterst nicht das Konzept eines Fensters, du schaffst keine neue Art von Fenster - es ist immer noch ein stinknormales JFrame. Zu entscheiden, wann Vererbung und wann Komposition das richtige ist, ist eine Kunst, aber ich kann in diesem Fall fünfzehn Jahre Programmiererfahrung dafür in die Waagschale werfen, dass hier Vererbung fehl am Platze ist.

Wenn du eine separate Klasse GUI (ohne extends JFrame) hast, erzeugst du darin ein neues JFrame, und eventuell auch separate Listener, denen du die Referenz auf das JFrame oder andere Teile mitgeben kannst. Das JFrame und seine Komponenten sollten immer so "dumm" wie möglich sein - sie sollen etwas anzeigen, nichts berechnen.
 
Zuletzt bearbeitet:
Bei 4 habe ich keine Referenz auf das Gui, kann JButton nicht umfärben. Wie kriege ich für Listener 4 eine Referenz auf das Objekt fenster?
Eigentlich gibt es nur zwei (relevante) Möglichkeiten eine Referenz auf ein Objekt zu bekommen. Entweder man übergibt die Referenz direkt im Konstruktor oder zu einem späteren Zeitpunkt per Methode.
Hier mal ein Beispiel mit drei verschiedenen Varianten, in einem "externen" Listener auf den auslösenden Button zu zugreifen. Eine Referenz auf den Button ist übrigens auch im ActionEvent enthalten.
Java:
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.*;

import javax.swing.*;
 
public class Gui extends JFrame implements ActionListener{
    private JButton[] schalter;
    private int anzahlSchalter = 4;
 
    //Konstruktor
    Gui(String titel){
        super(titel);
        
        schalter = new JButton[anzahlSchalter];
        
        JPanel panel = new JPanel(new GridLayout(1, schalter.length, 30, 0));
        this.add(panel);
        panel.setBorder(BorderFactory.createEmptyBorder(80, 20, 80, 20));
        panel.setBackground(Color.WHITE);
        
        for (int i=0; i < schalter.length; i++) {
        	schalter[i] = new JButton("Schalter " + (i+1));
        	schalter[i].setBackground(Color.RED);
        	panel.add(schalter[i]);
        }

        //Dies ist kein anonymer Listener.
        //Hier wird der ActionListener "Gui" zugewiesen
        schalter[0].addActionListener(this);
        
        //externe Klassen als Listener
        schalter[1].addActionListener(new LauscherVariante1());
        schalter[2].addActionListener(new LauscherVariante2(schalter[2]));
        schalter[3].addActionListener(new LauscherVariante3(this));
    }
    
    public JButton getButton() {
    	return schalter[3];
    }
 
    public void actionPerformed(ActionEvent e){
    	System.out.println(e.getActionCommand() +" gedrueckt");
        schalter[0].setBackground(Color.BLACK);
    }
    
    public static void main(String[]args){
        Gui fenster = new Gui("Gui");
        fenster.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        fenster.setBounds(600, 100, 700, 300);
        fenster.setVisible(true);
    }
}
 
class LauscherVariante1 implements ActionListener{
    public void actionPerformed(ActionEvent e){
    	System.out.println(e.getActionCommand() +" gedrueckt");
    	JButton button = (JButton)e.getSource();
    	button.setBackground(Color.BLACK);
    }
}

class LauscherVariante2 implements ActionListener{
	private JButton button;
	
	public LauscherVariante2(JButton button) {
		this.button = button;
	}
	
    public void actionPerformed(ActionEvent e){
    	System.out.println(e.getActionCommand() +" gedrueckt");
    	button.setBackground(Color.BLACK);
    }
}

class LauscherVariante3 implements ActionListener{
	private Gui gui;
	
	public LauscherVariante3(Gui gui) {
		this.gui = gui;
	}
	
    public void actionPerformed(ActionEvent e){
    	System.out.println(e.getActionCommand() +" gedrueckt");
    	gui.getButton().setBackground(Color.BLACK);
    }
}
 
Eigentlich gibt es nur zwei (relevante) Möglichkeiten eine Referenz auf ein Objekt zu bekommen. Entweder man übergibt die Referenz direkt im Konstruktor oder zu einem späteren Zeitpunkt per Methode.

Ich weiß nicht, ob das das jetzt "relevant" ist, aber man kann sich eine Referenz auch von einem DI-Framework (Spring, Guice) injizieren lassen, über das Service Provider Interface Implementierungen suchen oder einen Stream o.ä. deserialisieren. Gibt sicher noch mehr Möglichkeiten...
 

Zurück
Oben