GUI Erstellung - MVC Denkweise?

Steusi

Nasenbär
Hallo zusammen,

ich habe noch nicht viel mit Java gemacht, möchte aber gern folgendes einfaches Programm schreiben:

- User bekommt eine Menüleiste mit Buttons
- jeder Button füllt ein Textfeld mit Inhalt aus einer MySQL-Datenbank
- der aktuelle Text kann akzeptiert oder abgelehnt werden, die Auswahl wird zurück in die Datenbank geschrieben.

Leider komme ich schnell an die Grenzen meiner Denkweise, daher möchte ich gern wissen, wie man es richtig macht.

Ich habe eine MAIN-Klasse (mit Startpunkt) und eine GUI-Klasse.

Aus der Datenbank hole ich mir alle Informationen für den Button, wie Name, Icon, Text (für Textfeld), Anzahl der Buttons etc. Damit erstelle ich ja die Button, gehört es nun in die GUI oder in die MAIN?

Ich habe auch schon versucht mir eine eigene Klasse für die Buttons zu schreiben, doch dann kann ich mit einer Methode dieser Button-Klasse ja nicht mehr das Textfeld ändern, da es einfach nicht bekannt ist.

Wie strukturiert man sowas nach dem OOP Gedanken korrekt?
Kann mir jemand ein Beispiel zeigen, gibt es eine gute Quelle wo man es versteht?
 
MVC (Model View Controller)
in die gui (View) machste erstmal den normalen aufbau.
dann schreibs du für die textfelder jewweils eine settext methode in die gui z.B
dann hast du einen "controller" den du sogesehen als Listener machst, also er wartet auf die buttonklicks und reagiert darauf.
dein Model (da liegen die daten) ist einfach nur deine datenbank.
der controller muss daher auf die datenbank zugreifen können.
bei buttonklick kriegt der controller es dann mit.
darauf holt er sich was aus der datenbank und nutzt dann die settext methoden um das textfeld zu füllen.

so würde ich es nach deiner beschriebung jetzt machen.
richtiges mvc wurde uns in der uni aber bischen anders erklärt, da ist die view wirklich nur darstellung, der controller vermitteln zum model, was die view so macht (Daten ändern etc) und die View ist ein "Observer" und das Model "Observable".
durch sowas wie notifyObservers() sagt das model dann hey meine daten haben sich geändert und die gui aktualisiert sich. beispiel hab ich auch noch irgendwo, ich such mal und editier es dann rein

Edit:: ok hab doch keins mehr xd ich bastel dir schnell ein kleines verständliches beispiel zu mvc

Unser Model
Java:
import java.util.Observable;
import java.util.Vector;

//Überwachte daten
public class Model extends Observable{
	
	private Vector<String> namen = new Vector<String>();
	
	public void addName(String name)
	{
		this.namen.add(name);
		
		//hier setzen wir ein flag das sich etwas geändert hat, dann benachrichtigen wir die überwacher.
		//ist ein flag gesetzt, wird deren update methode aufgerufen
		this.setChanged();
		this.notifyObservers();
	}
	
	public void deleteName(String name)
	{
		if(namen.contains(name))
			this.namen.remove(name);
		this.setChanged();
		this.notifyObservers();
	}
	
	//damit sich die view auch irgendwie mit den namen updaten kann
	public Vector<String> getNames()
	{
		return namen;
	}

}

unser controller
Java:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;

//einfacher listener aufbau, leitet sogesehen von view zum model
public class Controller implements ActionListener{
	
	private View view;
	private Model model;
	
	public Controller(View v, Model m)
	{
		this.view=v;
		this.model=m;

		for(JButton b : view.getComponentsForController())
		{
			b.addActionListener(this);
		}
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		
		if(e.getActionCommand().equals("add"))
			model.addName(view.getTextAtField());
		else if(e.getActionCommand().equals("delete"))
			model.deleteName(view.getTextAtField());
		
	}

}

unsere view
Java:
import java.awt.Component;
import java.util.*;
import javax.swing.*;

//Observer implementieren um das Model zu überwachen
public class View extends JFrame implements Observer{

	//Unser Model, wir erstellen es einfach, normal wäre ein verweis auf was vorhandenes (z.B. Datenbank)
	private Model model = new Model();
	
	private JTextField text;
	private JButton add,delete;
	private JScrollPane scroll;
	private JTextArea data;
	private JLabel label;
	
	public View()
	{
		super("MVC Test");
		init();
		
		//Wichtig, damit wir model überwachen können
		model.addObserver(this);
		
		this.setVisible(true);
		
		Controller c = new Controller(this,model);
	}
	
	//gui aufbau
	public void init()
	{
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLayout(null);
		setBounds(200,200,190,300);
		
		text=new JTextField();
		text.setBounds(10,10,150,30);
		getContentPane().add(text);
		
		add = new JButton("Add");
		add.setActionCommand("add");
		add.setBounds(10,50,70,30);
		getContentPane().add(add);
		
		delete=new JButton("Delete");
		delete.setActionCommand("delete");
		delete.setBounds(90,50,70,30);
		getContentPane().add(delete);
		
		label = new JLabel("<html><u><b>Data:</u></b></html>");
		label.setBounds(75,80,50,30);
		getContentPane().add(label);
		
		data=new JTextArea();
		data.setEditable(false);
		
		scroll = new JScrollPane(data);
		scroll.setBounds(10,110,155,120);
		
		getContentPane().add(scroll);	
	}
	
	//gebe componenten zurück, die den controller als listener brauchen.
	//alternativ auch eine funktion in controller, die einen componenten bekommt,
	//worauf der controller sich danach selbst als listener einträgt
	public JButton[] getComponentsForController()
	{
		return new JButton[] {this.add,this.delete};
	}
	
	//damit der controller an die daten aus dem textfeld kommen kann
	public String getTextAtField()
	{
		return text.getText();
	}
	
	
	//unsere methode aus dem interface oberserver, sie wird ausgeführt wenn ein "obervable" notify aufruft
	@Override
	public void update(Observable o, Object arg) {
		// TODO Auto-generated method stub
		
		//daten leeren
		this.data.setText("");
		//daten neu einlesen
		for(String s : model.getNames())
		{
			this.data.append(s+"\n");
		}
		
	}
	
	public static void main(String[] args)
	{
		View v = new View();
	}

}

Ziel dabei ist es, die View "austauschbar" zu machen. d.h. alles muss möglichst "unabhängig sein", hab ich jetz glaub ich auch ncht zu 100%, aber am ende, wenn alles perfekt ist, soll es möglich sein, an die stelle der View einfach irgendeine andere View tun zu können, also "austauschbare" komponenten möglich zu machen.
deswegen sollte z.B. der Controller auch auf keinsten fall irgendwie auf das textfeld oder ähnliches selber zugreifen, sondern die gui sollte methoden bieten, die den inhalt des textfeldes wiedergeben.

selbiges mit den Button, wollte eigentlich über nen Component[] iterieren aber da ging das ganze mit dem actionlistener irgendwie nicht, deswegen einfach jetzt mit jbuttons gemacht

wie das ganze jetzt aber mit datenbanküberwachung aussieht, weiß ich nicht genau.
könntes den controller als "reinen vermittler nehmen".
Habe nicht so ganz verstanden was du erreichen willst, bzw wie genau der ablauf da nun ist, mit button klicks und daraus wird irgendwas neues mit icon etc erstellt ... etwas Code wäre gut :)
so wie ichs verstanden habe:

button wird geklickt, controller sendet irgendeine abfrage, kriegt was zurück, gibt das der gui.
was die gui nun damit macht ist ihre sache (also sollte das erstellen klar da rein, denn, gui soll austauschbar sein, und eine andere gui könnte mit den gleichen daten [icon bliblablub] etwas ganz anderes erstellen :p)



Edit2:: Kritik von anderen dazu ist auch erwünscht :D
 
Zuletzt bearbeitet:
Vielen Dank für das Beispiel, wenn man es so ließt wirkt es sehr schön. Aber für die Umsetzung muss ich mich noch einmal tiefer einarbeiten.

Ich habe bei mir ein Problem. Ich erstelle X Buttons (die Anzahl bestimmt die Datenbank, ist mir vorher also nicht bekannt), diese heißen bei mir DSButton, was JButtons sind mit ein paar zusätzlichen Attributen.

Nun wollte ich diese in der GUI anzeigen lassen:
Java:
public void createMenu(DSButton[] button){
		for (int i = 0; i < button.length; i++) {
			button[i].getJButton().addActionListener(new ActionListener() {
				
				@Override
				public void actionPerformed(ActionEvent e) {
					try {
						// Textfeld füllen
						textArea.setText(button[i].getRuleContent());
						// Regel-ID setzen
						ruleID = button[i].getRuleID();
						// TODO: Text Label aktualisieren
						ruleLabel.setText("Aktuell aktive Regel: " + Integer.toString(ruleID));
						ruleLabel.repaint();
					} catch (Exception e1) {
						// TODO: handle exception
						e1.printStackTrace();
					}
				}
			});
			topLeft.add(button[i].getJButton());
		}
	}

Leider meckert der Compiler an folgender Zeile:
Java:
textArea.setText(button[i].getRuleContent());
mit:
Cannot refer to a non-final variable button inside an inner class defined in a different method

setze ich button final kommt das Gleiche bei meinem Laufindex i, welchen ich schlecht final setzen kann.

Deshalb habe ich mir, sicher etwas unsauber, folgendes überlegt:
Java:
	public void createMenu(final DSButton[] button){
		for (int i = 0; i < button.length; i++) {
			button[i].getJButton().addActionListener(new DSHandler(button[i]));
			topLeft.add(button[i].getJButton());
		}
	}


	private class DSHandler implements ActionListener {

		DSButton intern;
		
		public DSHandler(DSButton tmp){
			this.intern = tmp;
		}
		
		@Override
		public void actionPerformed(ActionEvent e) {
			try {
				// Textfeld füllen
				textArea.setText(intern.getRuleContent());
				// Regel-ID setzen
				ruleID = intern.getRuleID();
				// TODO: Text Label aktualisieren
				ruleLabel.setText("Aktuell aktive Regel: " + Integer.toString(ruleID));
				ruleLabel.repaint();
			} catch (Exception e1) {
				// TODO: handle exception
				e1.printStackTrace();
			}
		}
		
	}
Der Compiler meckert nicht, aber beim Aufruf wird die Methode actionPerformed nicht mehr ausgeführt, wie zwinge ich Java dazu?

Ich hoffe jemand versteht mein Problem und hat eine Idee :)
 
für mich sieht da nix wirklich falsch aus.
aber ich kann mit der final exception da auch nichts anfangen, könntes du den kompletten code posten oder ist das nicht möglich?
dann könnte ich mir das "im ganzen" angucken ^^

btw: dein DSButton, hat der nen JButton in sich oder erbt der von JButton?
 
Zuletzt bearbeitet:
Du kannst i zwar nicht final machen, aber:

Java:
for (int i = 0; i < button.length; i++) {
    final int curCounter = i;

    //...
}
 
Natürlich kann ich dir den Code zeigen :)

Meine Main:
Java:
public class Main {
	
	// Benutzer ID in Datenbank
	private static int userID = 0;
	
	public static void main(String[] args) {
		
		// Usernamen aus Systemnamen ermitteln
		// String username = System.getProperty("user.name");		
		// TODO: Test-Zweck
		String username = "Hans.Werner";
		
		// Benutzer-ID ermitteln
		userID = SQLHandler.getUserID(username);
		
		// Zutreffende Regeln ermitteln
		int cnt_rules = SQLHandler.countRules(userID);
		
		// DSButtons sind spezielle JButtons
		DSButton rule[] = new DSButton[cnt_rules];
		rule = SQLHandler.getDSButtons(userID);

		// GUI erstellen
		GUI window = new GUI(userID);
		
		// Buttons erstellen
		window.createMenu(rule);
		
		// GUI anzeigen
		window.showFrame();	
		
	}
}

DSButton:
Java:
import javax.swing.ImageIcon;
import javax.swing.JButton;

public class DSButton {
	// header = linkname
	private String header;
	// ruleID in Database
	private int ruleID;
	// content from rule in HTML syntax
	private String ruleContent;
	// time in days for rule check
	private long repeat;
	// Timestamp (last check)
	private long lastTimestamp;
	// rule accepted?
	private Boolean accept;
	// time period expired -> rule must display
	private Boolean showLink;
	// icon for Link
	private ImageIcon icon;
	
	public DSButton(int rule_id, String header, String ruleContent, Integer repeat, long timestamp, Boolean accept){
		this.ruleID = rule_id;
		this.header = header;
		this.ruleContent = ruleContent;
		// repeat from days to seconds
		this.repeat = repeat*24*3600;
		this.lastTimestamp = timestamp;
		this.accept = accept;
		
		// current timestamp
		long timestamp2 = System.currentTimeMillis() / 1000;
		
		// the rule must be displayed?
		if(timestamp2 - this.lastTimestamp > this.repeat){
			this.showLink = true;
			// the rule was accepted?
			if(this.accept){
				icon = new ImageIcon("img/true.png");
			} else {
				icon = new ImageIcon("img/false.png");
			}
		} else {
			this.showLink = false;
		}
	}
		
	public JButton getJButton(){
		return new JButton(this.header, this.icon);
	}	
	
	public int getRuleID(){
		return this.ruleID;
	}
	
	public String getRuleContent(){
		return this.ruleContent;
	}
	
	public boolean getEnabled(){
		return this.showLink;
	}
}

SQLHandler.getDSButtons(userID); sieht wie folgt aus:
Java:
	public static DSButton[] getDSButtons(int userid){
		String sql_rule_qry = "SELECT r.ID,r.name,r.content,r.repeat,uar.timestamp,uar.accept FROM rule as r " +
				  "INNER JOIN group_rule as gr " +
				  "ON r.ID = gr.role_id " +
				  "INNER JOIN group_member as gm " +
				  "ON gr.group_id = gm.group_id " +
				  "INNER JOIN user as u " +
				  "ON u.ID = gm.user_id	" +
				  "LEFT JOIN user_accept_rule as uar " +
				  "ON uar.user_id = u.ID AND uar.rule_id = r.ID " +
				  "WHERE u.ID = " + userid + " " + 
				  "ORDER BY uar.timestamp";
		
		// TODO: Test-Zweck
		System.out.println(sql_rule_qry);

		try {
			// Query ausführen
			ResultSet sql_rule = dbCon.getConnection().createStatement().executeQuery(sql_rule_qry);
			
			// Regelanzahl ermitteln
			int cnt_rules = countRules(userid);
			// DSButton erstellen
			DSButton rule[] = new DSButton[cnt_rules];
			
			int i = 0;
			while (sql_rule.next()) {
				rule[i++] = new DSButton(sql_rule.getInt(1), 
						  			     sql_rule.getString(2), 
						  			     sql_rule.getString(3),
										 sql_rule.getInt(4), 
										 sql_rule.getLong(5), 
										 sql_rule.getBoolean(6));
			}
			return rule;
			
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}

Und das CreateMenu habe ich ja bereits gepostet.
Ich weiß, dass dein beschriebenes Konzept noch nicht integriert ist :( Wollte mich langsam vortasten.

Eine Frage nebenbei, die GUI ist starr und wird nicht immer wieder neu "gezeichnet" oder? Sprich, werden Attribute bei Objekten geändert müssen diese neu "gezeichnet" werden.


// EDIT: Mir wird gerade mein Fehler bewusst. Wenn ich meinem Button die addActionListener übergebe, wird dies nicht gespeichert in meinem DSButton. Bekomme ich es über Vererbung hin?
 
Zuletzt bearbeitet:
das problem ist einfach das du da gaube ich immer mit getjbutton nen neuen machst
mach mal oben in den klassenvariablen
Java:
private JButton button;
//später wenn du icon und header hast 
button = new JButton(...);
//in der get methode dann
return this.button;
 
Zurück