# Generics



## OlliL (24. Dez 2012)

Hallo,

ich habe folgenden Codeschnipsel:


```
public void handleMyEventClassEvent(EventObject e, MenuHandler handler, MenuEvent s,
			SessionDTO sessionDTO) {

		BasicControllerIf controller;

		switch (menuEvent.getFunction()) {
			case MenuEvent.NEW_CONTRACTPARTNER:
				controller = new EditContractPartnerController();
				controller.addModel(contractPartnerModel);
				break;

			case MenuEvent.NEW_CAPITALSOURCE:
				controller = new EditCapitalSourceController();
				controller.addModel(newCapitalSourceModel);
				break;
			default:
				controller = new BasicController();
		}
		controller.addEvent(handler);
		controller.addSession(sessionDTO);
	}
```

Wie die einzelnen Modelle definiert werden, sei mal dahin gestellt.

BasicControllerIf:

```
public interface BasicControllerIf<M, V> extends ActionListener {
	public void addEvent(MenuHandler e);

	public void addSession(SessionDTO s);

	public void addView(V view);

	public void addModel(M m);

}
```

BasicController:

```
public class BasicController<M, V> implements BasicControllerIf<M, V> {
	protected MenuHandler	event;
	protected SessionDTO	sessionDTO;
	protected M				model;
	protected V				view;

	public void actionPerformed(ActionEvent e) {
	}

	public void addEvent(MenuHandler e) {
		this.event = e;
	}

	public void addSession(SessionDTO s) {
		this.sessionDTO = s;
	}

	public void addView(V view) {
		this.view = view;
	}

	public void addModel(M m) {
		this.model = m;
	}
}
```

EditContractPartnerController:

```
public class EditContractPartnerController extends
		BasicController<ContractPartnerModel, EditContractPartnerView> {

	@Override
	public void actionPerformed(ActionEvent e) {
...
	}
}
```

Nun zu meinem Problem. Ich bekomme mit meinem 1. Code-Ausschnitt Warnings (logischerweise):

BasicControllerIf is a raw type. References to generic type BasicControllerIf<M,V> should be parameterized
Type safety: The method addModel(Object) belongs to the raw type BasicControllerIf. References to generic type BasicControllerIf<M,V> should be parameterized

Wie werde ich die Warnings los? Ich kann bei der Deklaration von "controller" noch nicht die Generics auflösen, da sie ja je nach case andere sind.... (M und V sind quasi case-abhängig).


----------



## tröööt (24. Dez 2012)

1) also der erste code dürfte so nicht gerade sinnvoll sein ... geschweige denn compilen ... denn in deinem switch() gehst du auf "menuEvent" ... was aber nicht in der parameter-liste zufinden ist ... sicher das hier nicht "s" gemeint ist ?
2) er beschwert sich das in zeile 4 nur "BasicControllerIf" steht ... korrekter weise müsste aber sowas hier stehen :

```
BasicControllerIf<ContractPartnerModel, EditContractPartnerView> controller=new EditContractPartnerController();
```
ansonsten bekommst du halt die raw-type warning ...


----------



## OlliL (24. Dez 2012)

1. er compiliert - mit SupressWarnings geht auch alles wunderbar.
ich schrieb ja - Code*schnipsel* - es dient nur zur Veranschaulichung. menuEvent ist im Code definiert. 

2. Das nutzt mir aber nichts. Ich bräuchte für jeden Case eine andere Deklaration von controller. Das geht aber nicht. Wenn ich deine Zeile so oben ausserhalb des Switches packe, bekomme ich im 2. Fall einen Compilation Error, da der Controller im 2. Case eine andere Definition hat.


```
public class EditCapitalSourceController extends
		BasicController<CapitalSourceModel, EditCapitalSourceView> {
```


----------



## tröööt (24. Dez 2012)

naja ... hast das interface "BasicControllerIf<M, V>" ... arbeitest aber nur gegen den RAW-type "BasicControllerIf" ... wesshalb der compiler drauf aufmerksam macht ...
also entweder musst du mit der warning leben und riskierst das irgendwo mal was wegen nem cast-fehler kracht ... oder du baust das ganze so um das du auch vernünftig mit den generics arbeitest ...


----------



## OlliL (24. Dez 2012)

Hallo,

wie könnte ich es denn so umbauen, das ich nicht


```
controller.addEvent(handler);
        controller.addSession(sessionDTO);
```

in jeden case schreiben muss, aber "_vernünftig mit Generics arbeite_"


----------



## Marco13 (24. Dez 2012)

Kann man das (alle Klassen zusammen in EINEN Code-Block) zu einem KSKB zusammenfrickeln?


----------



## tröööt (24. Dez 2012)

ich versuchs dir mal anhand einer ArrayList klar zu machen ...

über den type-parameter gibst du an von welchem typ der inhalt sein soll .. also z.b. ArrayList<String> ...

wenn man jetzt davon erben würde ... z.b. StringArrayList extends ArrayList<String> ... und dann aber gegen List<?> programmieren will ... müsste man trotzdem List<String> list=new StringArrayList(); schreiben

es macht also irgendwie keinen sinn von einer generic-klasse zu erben nur um die type-parameter hinter einem anderen klassennamen zu verstecken ... wenn man am ende eh wieder gegen die super-klasse arbeitet ...

mal davon abgesehen ... von ActionListener erben ? das kann irgendwie nicht richtig sein ... da scheint echt noch einiges anderes in deinem projekt überhaupt nicht zu stimmen ...

um allerdings noch mal auf dein beispiel einzugehen ...
man könnte es (wenn auch sehr dreckig) noch folgt lösen


BasicControllerIf<BasicModel, BasicView> controller;

im default dann

controller=new BasicController<DefaultBasicModel, DefaultBasicView>();

und ansonsten sowas

public class EditCapitalSourceController extends BasicController<CapitalSourceModel extends BasicModel, EditCapitalSourceView extends BasicView>

und dann natürlich die Model und View klassen entsprechende vererbungshirarchie ...

aber wie gesagt : das wäre dann immer noch nicht ganz im sinne von generics ... und würde auch ein extremes wirr warr erzeugen ...

wie du es "sauber" machen könntest kann ich dir leider auch nicht sagen da ich mich dafür zu wenig mit generics auskenne ... aber fakt ist : so wie du es da versuchst ist definitiv nicht der richtige weg


----------



## Spacerat (24. Dez 2012)

Wie sieht's denn aus, wenn du in Zeile 4 BasicControllerIf<?, ?> schreibst und in den cases konkretere Typen definierst? Warnings bekommst du dann zwar auch, aber dieses "@SupressWarnings" muss dann zumindest nicht mehr am Methodenrumpf stehen. Hauptsache dabei ist, dass sich Session und Event dann auch zu dem Raw-Type (<?, ?>) adden lassen.
Was man noch machen könnte wäre die Generics von BasicControllerIf erweitern (bzw. begrenzen )...

```
interface BasicControllerIf<M super Model<M>, V super View<V>> {
}
// oder
interface BasicControllerIf<M extends Model<M>, V extends View<V>> {
}
//oder
interface BasicControllerIf<M super Model, V super View> {
}
// oder
interface BasicControllerIf<M extends Model, V extends View> {
}
```
...dafür müssten dann aber widerum die Interfaces oder Klassen Model und View (wahlweise auch generisch) erstellt werden, aber Zeile 4 könnte dann so aussehen..

```
BasicControllerIf<Model, View> controller;
// bzw.
BasicControllerIf<Model<?>, View<?>> controller;
```


----------



## OlliL (24. Dez 2012)

trööt - wiso ist von ActionListener erben nicht richtig? Mein Controller führt die Action meines Button-Clicks aus.

Das Problem meiner Controller ist, das sie Methoden von dem "Model" aufrufen welche nicht generisch sind, sondern Model-spezifisch. Daher ist es wichtig, dass der Controller beim extend von BasicController das Model über die Generics mitgibt.


```
public class LogonController extends BasicController<LogonModel, LogonView> {

	@Override
	public void actionPerformed(ActionEvent e) {

		sessionDTO = model.doLogin(sessionDTO);
		if (sessionDTO.getUserid() > 0) {
			event.fireEvent(new MenuEvent(MenuEvent.LOGIN_DONE), sessionDTO);
		}
	}
}
```

"doLogin" gibt es ausschliesslich im LogonModel.

Ich habe das jetzt mal wie von Spacerat vorgeschlagen probiert (hoffe ich doch ):


```
public class BasicController<M extends BasicModelIf, V extends BasicViewIf> implements BasicControllerIf<M, V> {
```


```
public interface BasicControllerIf<M extends BasicModelIf, V extends BasicViewIf> extends ActionListener {
```

und dann in meiner Methode:

```
default:
//				@SuppressWarnings("rawtypes")
				BasicControllerIf<BasicModelIf,BasicViewIf> controller;
				BasicModelIf model;
				BasicPanelViewIf view;

				switch (menuEvent.getFunction()) {
					case MenuEvent.SHOW_CAPITALSOURCES:

						controller = new ListCapitalSourceController();
						model = new CapitalSourceModel(sessionDTO);
						CapitalSourceTableModel listCapitalSourceTableModel = new CapitalSourceTableModel(model);
						view = new ListCapitalSourceView(sessionDTO, textHandler, 	listCapitalSourceTableModel, model);
						break;
					case MenuEvent.SHOW_CONTRACTPARTNERS:

						controller = new ListContractPartnerController();
						model = new ContractPartnerModel(sessionDTO);
						ContractPartnerTableModel listContractPartnerTableModel = new ContractPartnerTableModel(model);
						view = new ListContractPartnerView(sessionDTO, textHandler, listContractPartnerTableModel, model);
						break;
[....]
					default:
						controller = new BasicController<MainModel, MainView>();
						model = new BasicModel();
						view = new BasicPanelView(sessionDTO, textHandler);
						menuEvent = new MenuEvent(MenuEvent.START);
				}
				model.addObserver(view);
				view.addController(controller);

				controller.addModel(model);
				controller.addEvent(handler);
				controller.addSession(sessionDTO);
				controller.addView(view);
```

Nun bekomme ich aber


```
Type mismatch: cannot convert from ListCapitalSourceController to BasicControllerIf<BasicModelIf,BasicViewIf>
Type mismatch: cannot convert from ListContractPartnerController to BasicControllerIf<BasicModelIf,BasicViewIf>
```

Ich kann aber halt ListCapitalSourceController nicht auch als Generic definieren, da dann die Model-spezifischen Funktionen in ListCapitalSourceController nicht mehr referenzierbar sind.

Komme irgendwie nicht weiter


----------



## Spacerat (25. Dez 2012)

Hab' mir fast gedacht, dass da noch was fehlt... Die Wildcards bei der Definition von BasicControllerIf.
Naja... das hier klappt zumindest ganz ohne Warnungen. Hoffe du kannst ein paar Klassennamen für deine Zwecke ändern und gut.

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

interface Model {
}

interface View {
}

interface Controller<M extends Model, V extends View> extends ActionListener {
	void addModel(M model);
	void addView(V view);
	void addEvent(Event e);
	void addSession(Session s);
}

class Model1 implements Model {
}

class Model2 implements Model {
}

class View1 implements View {
}

class View2 implements View {
}

class Controller1 implements Controller<Model1, View1> {
	@Override
	public void actionPerformed(ActionEvent e) {
	}

	@Override
	public void addModel(Model1 model) {
	}

	@Override
	public void addView(View1 view) {
	}

	@Override
	public void addEvent(Event e) {
	}

	@Override
	public void addSession(Session s) {
	}
	
}

class Controller2 implements Controller<Model2, View2> {
	@Override
	public void actionPerformed(ActionEvent e) {
	}

	@Override
	public void addModel(Model2 model) {
	}

	@Override
	public void addView(View2 view) {
	}

	@Override
	public void addEvent(Event e) {
	}

	@Override
	public void addSession(Session s) {
	}
	
}

class Session {
	
}

class Event {
	
}

class MVC {
	void doWork(Session s, Event e) {
		Controller<? extends Model, ? extends View> controller;

		if(e == null) {
			controller = new Controller1();
			((Controller1) controller).addModel(new Model1());
			((Controller1) controller).addView(new View1());
		} else {
			controller = new Controller2();
			((Controller2) controller).addModel(new Model2());
			((Controller2) controller).addView(new View2());
		}

		controller.addEvent(e);
		controller.addSession(s);
	}
}
```


----------



## OlliL (25. Dez 2012)

Jo, das geht so... aber 

Model ist bei mir auch "generisch" ausserhalb deklariert:


```
BasicModelIf model;
```

Das geht dann nur mit einem weiteren Cast:


```
((EditContractPartnerController)controller).addModel((ContractPartnerModel)model);
```

Auf deine MVC "umgestrickt":


```
class MVC {
    void doWork(Session s, Event e) {
        Controller<? extends Model, ? extends View> controller;
        Model model;
        if(e == null) {
            controller = new Controller1();
            model = new Model1();
            ((Controller1) controller).addModel((Model1)model);
            ((Controller1) controller).addView(new View1());
        } else {
            controller = new Controller2();
            model = new Model2();
            ((Controller2) controller).addModel((Model2)model);
            ((Controller2) controller).addView(new View2());
        }
 
        controller.addEvent(e);
        controller.addSession(s);
    }
}
```

Nun wollte ich mir aber eigentlich die ganzen .addModel und .addView Aufrufe in meinen drölftausen Cases sparen und einmal am Ende machen. Deswegen kam ich überhaupt erst auf den Generics-Trichter. Geht nicht? 

Ich meine - ist mir grundsätzlich schon klar. Ich sage meinem Controller mit den darin aufgelösten Generics, das es sich um "Model1" handelt, komme aber beim Aufruf von addModel mit irgendwas an, was "Model" implementiert an - das könnte ja auch "Model2" sein - schon klar das der Compiler da "njet" sagt, da er zwingend "Model1" erwartet....


Mit .addModel nach meinem switch() statt in den Cases bekomme ich


```
The method addModel(capture#25-of ? extends BasicModelIf) in the type BasicControllerIf<capture#25-of ? extends BasicModelIf,capture#26-of ? extends BasicViewIf> is not applicable for the arguments (BasicModelIf)
```


----------



## Spacerat (25. Dez 2012)

Dann vllt. so:

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

interface Model<M extends Model<M>> {
}

interface View {
}

interface Controller<M extends Model<M>, V extends View> extends ActionListener {
	void addModel(Model<?> model);
	void addView(View view);
	void addEvent(Event e);
	void addSession(Session s);
	V getView();
	M getModel();
}

class Model1 implements Model<Model1> {
}

class Model2 implements Model<Model2> {
}

class View1 implements View {
}

class View2 implements View {
}

class Controller1 implements Controller<Model1, View1> {
	@Override
	public void actionPerformed(ActionEvent e) {
	}

	@Override
	public void addView(View view) {
	}

	@Override
	public void addEvent(Event e) {
	}

	@Override
	public void addSession(Session s) {
	}

	@Override
	public void addModel(Model<?> model) {
	}

	@Override
	public View1 getView() {
		return null;
	}

	@Override
	public Model1 getModel() {
		return null;
	}
}

class Controller2 implements Controller<Model2, View2> {
	@Override
	public void actionPerformed(ActionEvent e) {
	}

	@Override
	public void addView(View view) {
	}

	@Override
	public void addEvent(Event e) {
	}

	@Override
	public void addSession(Session s) {
	}

	@Override
	public void addModel(Model<?> model) {
	}

	@Override
	public View2 getView() {
		return null;
	}

	@Override
	public Model2 getModel() {
		return null;
	}
}

class Session {
	
}

class Event {
	
}

class MVC {
	void doWork(Session s, Event e) {
		Controller<? extends Model<?>, ? extends View> controller;
		Model<?> model;
		View view;

		if(e == null) {
			controller = new Controller1();
			view = new View1();
			model = new Model1();
		} else {
			controller = new Controller2();
			view = new View2();
			model = new Model2();
		}

		controller.addModel(model);
		controller.addView(view);
		controller.addEvent(e);
		controller.addSession(s);
	}
}
```
...hat natürlich den Nachteil, dass du nun in den konkreten Controllern eine instanceof abfrage machen musst, ob der richtige Modeltyp übergeben wurde. Dann machen Generics aber eigentlich keinen Sinn mehr.


----------



## OlliL (25. Dez 2012)

Mh - wie würde man es denn sonst machen? Ich hatte vorher keine Generics oder Interfaces und hatte halt Controller, Model und View in jedem Case einzeln definiert, die Controller Methoden aufgerufen usw... das kam mir aber doch ziemlich redundant vor.


----------



## Marco13 (25. Dez 2012)

Kannst du nochmal ein KSKB wie die von Spacerat posten, wo man genau sieht, wo's hakt?


----------



## tröööt (25. Dez 2012)

naja ... es ist schon richtig das generics helfen sollen code-redundanzen zu vermeiden ... aber wenn halt je type auch was anderes gemacht wird ... und nichts anderes ist es ja wenn man für jeden type ne eigene klasse implementiert die vom abstracten super erbt ... muss man doch wieder für alles jeweils eigenen code schreiben ... und so macht man sich am ende nur mehr aufwand ...
natürlich hält man sich mit generics und interfaces die option offen einfach code auszutauschen ... was man so natürlich nicht einfach kann wenn man fest für spezielle types programmiert ...

mit code-dynamik zu arbeiten ist halt etwas komplex ... gerade wenn man interfaces mit generics mischt und dann noch abstracte super einbaut ... ist eigentlich alles dabei ...

das dann hier das eine oder andere kracht ist klar ...

den ersten schritt hast du ja schon mal getan : im endeffekt sollen alle klassen ActionListener sein ... (wobei ich das irgendwie immer noch recht seltsam finde ... aber ok .. wird seinen grund haben)

dann gehts einen schritt weiter nach unten : alle ActionListener sollen ein generisches interface BasicController<M, V> extends ActionListener sein ... (BasicControllerIf für interface ist nicht gerade java-stil ... genau so wenig wie IBasicController ... einfach nur BasicController als name fürs interface ... und die implementierung dann lieber "DefaultBasicController" oder "BasicControllerImpl" .. in deinem fall auch "AbstractBasicController")
das sub-interface definiert noch folgende methoden

public void addEvent(MenuHandler e);
public void addSession(SessionDTO s);
public void addView(V view);
public void addModel(M m);

dann willst du aus dem sub-interface ein abstact-super machen ... ich versteh es zwar nicht warum ... aber korrekt wäre es dann so :


```
public abstact class AbstractBasicController<M, V> implements BasicController<M, V> {
    protected MenuHandler   event;
    protected SessionDTO    sessionDTO;
    protected M             model;
    protected V             view;
 
    public abstact void actionPerformed(ActionEvent e) {
    }
 
    public void addEvent(MenuHandler e) {
        this.event = e;
    }
 
    public void addSession(SessionDTO s) {
        this.sessionDTO = s;
    }
 
    public void addView(V view) {
        this.view = view;
    }
 
    public void addModel(M m) {
        this.model = m;
    }
}
```

eine konkrete sub-class müsste dann von AbstractBasicController<M, V> erben und mindestens actionPerformed implementieren ... da diese ja abstract ist ...

wenn du jetzt auch noch type-safe mit switch arbeiten willst ... dann bräuchtest du noch super-klassen der beiden type-parameter ... also z.b. AbstractBasicModel und AbstractBasicView ... und dann in der deklaration :

BasicController<? extends AbstractBasicModel, ? extends AbstractBasicView> controller;

wenn das ganze so durch den compiler geht ... (ist jetzt alles nur ausm kopf) ... sollte es zumindest "type-safe" sein und keine warning mehr erzeugen ...
wie wirklich sinnvoll das nun ist ... das ist ne andere frage


----------



## OlliL (25. Dez 2012)

Hier nochmal ein KSKB von meinem Ausgangsfall:


```
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JButton;

public class Test {

    //==========================================
	// interfaces Model, View, Controller
	public interface BasicModelIf {
		public void addObserver(Observer observer);

		public void notifyUpdate();

	}

	public interface BasicViewIf extends Observer {
		public void addController(ActionListener controller);

	}

	public interface BasicControllerIf<M extends BasicModelIf, V extends BasicViewIf> extends
			ActionListener {
		public void addEvent(String e);

		public void addSession(String s);

		public void addView(V view);

		public void addModel(M m);

	}

	
    //==========================================
	// super classes Model, View, Controller
	public class BasicModel extends Observable implements BasicModelIf {

		public void notifyUpdate() {
			setChanged();
			notifyObservers("blah blah");
		};

	}

	public class BasicView implements BasicViewIf {
		protected ActionListener	modelController;
		protected JButton			saveButton	= new JButton("Speichern");

		public void update(Observable obs, Object obj) {
		}

		public void addController(ActionListener controller) {
			this.modelController = controller;
			saveButton.addActionListener(controller);
		}

	}

	public class BasicController<M extends BasicModelIf, V extends BasicViewIf> implements
			BasicControllerIf<M, V> {
		protected String	event;
		protected String	sessionDTO;
		protected M			model;
		protected V			view;

		public void actionPerformed(ActionEvent e) {
		}

		public void addEvent(String e) {
			this.event = e;
		}

		public void addSession(String s) {
			this.sessionDTO = s;
		}

		public void addView(V view) {
			this.view = view;
		}

		public void addModel(M m) {
			this.model = m;
		}
	}
	
	
    //==========================================
	// Implementation: Logon
	public class LogonModel extends BasicModel implements BasicModelIf {
		public String doLogin(String sess) {
			return sess;
		}

	}
	public class LogonView extends BasicView implements BasicViewIf {
		
	}

	public class LogonController extends BasicController<LogonModel, LogonView> {

		@Override
		public void actionPerformed(ActionEvent e) {

			sessionDTO = model.doLogin(sessionDTO);
			System.out.println(sessionDTO);
		}
	}
	
	
    //==========================================
	// Implementation: Hugo
	public class HugoModel extends BasicModel implements BasicModelIf {
		public String doHugo(String sess) {
			return sess;
		}

	}
	public class HugoView extends BasicView implements BasicViewIf {
		
	}

	public class HugoController extends BasicController<HugoModel, HugoView> {

		@Override
		public void actionPerformed(ActionEvent e) {

			sessionDTO = model.doHugo(sessionDTO);
			System.out.println(sessionDTO);
		}
	}

	
	class MVC {
	    void doWork(String s,  String e) {
	        BasicControllerIf controller;
	        BasicModelIf model;
	        BasicViewIf view;
	 
	        if(e == null) {
	            controller = new LogonController();
	            view = new LogonView();
	            model = new LogonModel();
	        } else {
	            controller = new HugoController();
	            view = new HugoView();
	            model = new HugoModel();
	        }
			model.addObserver(view);
			view.addController(controller);
	 
	        controller.addModel(model);
	        controller.addView(view);
	        controller.addEvent(e);
	        controller.addSession(s);
	    }
	}	

}
```


----------



## OlliL (25. Dez 2012)

tröööt hat gesagt.:


> wenn du jetzt auch noch type-safe mit switch arbeiten willst ... dann bräuchtest du noch super-klassen der beiden type-parameter ... also z.b. AbstractBasicModel und AbstractBasicView ... und dann in der deklaration :
> 
> BasicController<? extends AbstractBasicModel, ? extends AbstractBasicView> controller;



Aber habe ich nicht genau das? (siehe mein KSKB) Wenn ich Controller so deklariere in meiner Switch-Methode bekomme ich einen Compile Error


The method addModel(capture#7-of ? extends Test.BasicModel) in the type Test.BasicControllerIf<capture#7-of ? extends Test.BasicModel,capture#8-of ? extends Test.BasicView> is not applicable for the arguments (Test.BasicModelIf)

The method addView(capture#10-of ? extends Test.BasicView) in the type Test.BasicControllerIf<capture#9-of ? extends Test.BasicModel,capture#10-of ? extends Test.BasicView> is not applicable for the arguments (Test.BasicViewIf)


----------



## Spacerat (25. Dez 2012)

Hmm... solange die verschiedenen Models sich nur in einer Aktion unterscheiden ist's doch klar... Man definiert diese Aktion einmalig im BasicModelIf und nennt sie "doWork()" oder ähnliches. Sollen es mehrere Aktionen werden, verwendet man z.B. Techniken wie z.B. Runnables bei Threads oder "invokeLater()". Hab' leider kein Plan wie man diese Technik nennt, zumindest geht's darum, die Implementation einer Methode dynamisch zu machen und dazu braucht man weder "switch" noch Generics.
[OT]Mal 'ne Kleinigkeit zu deinem KSKB...
1. Session, Event, Model und View sind keine Collections sondern Member (okay, Collections wären zwar auch Member, aber ich denke mal, dass klar ist, was ich meine). Eine Methode auf diese Elemente "add" zu nennen könnte irreführend sein.
2. Öffentliche oder "protegierte" Member einer Klasse sollten "final" sein, damit sie von erweiternden Klassen nicht einfach so geändert werden können. Veränderbare Member sollten "private" sein und über Setter verändert werden. Der Grund: Nur so kann definierende Klasse ihre Member konsistent halten.
3. Die "implements SonstwasIf" in den finalen Klassen Model, Controller und View sind redundant.
4. "Observable" erweitert man nicht, sondern definiert es als privaten finalen Member. "add"- und "removeObserver" implementiert man dann in der Klasse, die eigentlich Observalbe erweitert und delegiert in dieser zu dem privaten Observer. Grund: "notifyObservers()" ist damit nur durch die definierende Klasse erreichbar. Damit wird verhindert, dass andere Klassen registrierte Observer mit Aufrufen fluten können.[/OT]


----------



## OlliL (25. Dez 2012)

Die Modelle sind die "Backends" meiner Views. Im KSKB sind sie wirklich nur auf ein absolutes Minimum abgespeckt. In "Wirklichkeit" enthalten sie Eigenschaften der in der View dargestellten Entity, Methoden zur Arbeit mit dieser Entity und entsprechende Getter und Setter. Die Interaktion des Controllers mit dem Model beschränkt sich auf "speichern", "löschen", "suchen", "bearbeiten" usw... Diese sind aber je Model unterschiedlich. doLogon() und doHugo() waren jetzt nur sehr stark vereinfacht.

Deine Offtopic-Hinweise arbeite ich mal durch.


----------



## tröööt (25. Dez 2012)

ich frag mich eigentlich immer noch : warum unbedingt generics wenn gegen ein interface gearbeitet wird ?
klar ... wenn man im interface nur "addModel(Object)" stehen hat muss man casten ... und wenn dann die klassen auch noch unterschiedlich sind machen generics schon eher sinn ... aber warum dann nicht einfach "Model" und "View" ebenfalls als Interface machen ... in diesen halt "doIt()" definieren ... und in den einzelnen klassen implementieren ... so arbeitet man nur gegen interfaces und generics fallen komplett raus ...

ich kenn mich halt mit generics nur grundlegend aus und weis das es eine bequeme möglichkeit für dynamisches casting ist ... aber das wars dann auch ...

außerdem arbeite ich stark modular ... wesshalb bei mir grundsätzlich alles nur gegen interfaces und allerhöchstens abstract-super geschrieben ist ...


----------



## OlliL (25. Dez 2012)

@Trööt

du würdest dann quasi eine doIt(int was) methode in jedem Model machen und in der Implementation von doIt(int was) im Model dann über "was" entscheiden, was eigentlich genau zu tun ist? Quasi eine Art Entry-Point ins Model was bei jedem Model gleich ist, und über die jeweiligen kontextspezifischen übergebenen Konstanten dann im Model entscheiden was der Controller eigentlich vom Model will?

Sowas in der Art habe ich schon für die Eigenschaften des Models - habe eine getProperty(property) und setProperty(property, wert).

@Spacerat
Erstmal vielen Dank für deine "Off-Topic" Kommentar! Solche Hinweise suche ich immer!

1. so habe ich das  noch gar nicht gesehen  Habe mal in set... refactored.
2. mh - meinst du z.B. die Event, Session, Model, View Eigenschaften in meiner Super-Class "BasicController"? Also entweder "final protected" oder "private" + getter/setter? Macht vollkommen Sinn... müsste ich dann noch Getter implementieren - mache ich.
3. Ja - das ist mir teilweise auch schon aufgefallen - hatte aber in der Tat noch bei einigen die schon extenden nochmal extra implements drinn... ist nun auch draussen.
4. Auch das macht schon wieder Sinn  Aber....:


```
public class BasicModel implements BasicModelIf {
	final Observable observable = new Observable();
	
	public void addObserver(Observer observer) {
		observable.addObserver(observer);
	}
	
	public void notifyUpdate() {
		notifyObservers(new ObserverMessage(ObserverMessage.OBSERVER_UPDATE));
	};
	
	public void notifyObservers(ObserverMessage observerMessage) {
//		observable.setChanged();
		observable.notifyObservers(observerMessage);
	};


}
```

setChanged() ist protected synchronized und kann nicht aufgerufen werden. Bräuchte ich aber


----------



## Spacerat (25. Dez 2012)

Ich meinte es eigentlich so, dass die Observer ausschliesslich durch jene Klasse informiert werden können, in welcher die Änderungen stattfinden.

```
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;


public class ObserverPatternDemo {
	private final ObservableDispatcher dispatcher = new ObservableDispatcher();
	private final List<Object> someThings = new ArrayList<>();

	public void addObserver(Observer obs) {
		dispatcher.addObserver(obs);
	}

	public void removeObserver(Observer obs) {
		dispatcher.deleteObserver(obs);
	}

	public void addSomething(Object obj) {
		if(someThings.add(obj)) {
			dispatcher.notifyChanged(obj);
		}
	}

	private static final class ObservableDispatcher extends Observable {
		private void notifyChanged(Object arg) {
			setChanged();
			notifyObservers(arg);
		}
	}
}
```
Kann allerdings sein, dass deine Klasse im Prinzip dieser Dispatcher werden soll, allerdings sollte die Methoden zum Weiterreichen der Messages nicht von überall her erreichbar sein (protected bzw. package private).
@tröööt: Kannst dich noch an die Zeiten vor Generics und den Krampf z.B. mit Collections erinnern? Generics sind eine Art "Programmierhilfe". Sie reduzieren notwendige Casts, verhindern das Anfügen invalider Objekte in eine Collection und sind anschliessend im Bytecode nicht mehr vorhanden. Wenn man Bytecode, welcher einst Generics verwendet hat, dekompiliert, bekommt man Quellcode, der einen in die Pre-Generics-Zeit versetzt.


----------



## OlliL (25. Dez 2012)

Klar... private Klasse die Observable extended... da haette ich auch draufkommen können 


```
public class BasicModel implements BasicModelIf {
	private final ObservableDispatcher	dispatcher	= new ObservableDispatcher();

	private static final class ObservableDispatcher extends Observable {
		private void notifyChanged(ObserverMessage observerMessage) {
			setChanged();
			notifyObservers(observerMessage);
		}
	}

	public void addObserver(Observer observer) {
		dispatcher.addObserver(observer);
	}

	public void notifyUpdate() {
		notifyMyObservers(new ObserverMessage(ObserverMessage.OBSERVER_UPDATE));
	}

	public void notifyMyObservers(ObserverMessage observerMessage) {
		dispatcher.notifyChanged(observerMessage);
	}

}
```

Damit habe ich sogar noch etwas mehr sichergestellt - das meine Models alle nur ObserverMessage Nachrichten durch die Gegend schicken und nicht irgendwas anderes - stelle ich zwar im Observer (die View) auch nochmal mit nem instanceof Check sicher, aber koennte ich nun wohl auch ausbauen.


----------



## Spacerat (25. Dez 2012)

OlliL hat gesagt.:


> Damit habe ich sogar noch etwas mehr sichergestellt - das meine Models alle nur ObserverMessage Nachrichten durch die Gegend schicken und nicht irgendwas anderes - stelle ich zwar im Observer (die View) auch nochmal mit nem instanceof Check sicher, aber koennte ich nun wohl auch ausbauen.


Du würdest noch auch noch sicherstellen, dass ausschliesslich Models ObserverMessage Nachrichten durch die Gegend schicken, wenn du "notifyUpdate()" endlich "protected" machst (der eigentliche Sinn der Übung ). So wie es jetzt ist, kann jeder Hans und Franz zu jeder Zeit ObserverMessage Nachrichten durch die Gegend schicken, selbst wenn es nichts zu updaten gibt. Kurzum: Nur das Observable sollte "Observer#notifyObservers()" erreichen können.

Bin mir im übrigen durchaus im Klaren darüber, dass dieses eigentlich durch "setChanged()" selber verhindert werden konnte, aber warum dieses? Das Verfahren ist doch dasselbe geblieben. Vorher setzte das Observable nur das "changed"-Flag, während man von überall her fröhlich, selbst bei nicht gesetztem Flag, "notifyObservers()" aufrufen konnte. Nun aber kann nur das Observable selbst seine Observer über Änderungen informieren und zwar genau nur dann, wenn's nötig ist.


----------



## OlliL (25. Dez 2012)

protected geht aktuell nicht...
Ist vielleicht etwas "broken by design", aber aktuell fordere ich einmal am Ende der View-Erzeugung aus der View herraus ein Update der selbigen an. Ich würde das gerne anders lösen, aber ich habe folgendes Konstrukt:

- Ich habe eine View für die Neuanlage einer Entity und für das editieren von Entities.
- Ob die View im Modus "Neuanlage" oder "Editieren" läuft, stellt sie fest indem sie das Model der View anfragt (isEditMode()).
- Im Falle "Edit" wurde das Model vorher mit einem gefüllten Data-Transfer-Objekt instanziiert und hat dieses im Constructor in sich überführt.
- Im Falle "New" wurde das Model ohne DTO instanziiert (Model hat 2 Konstruktoren). In diesem Fall befüllt sich das Model über einen EJB Aufruf selbst mit Vorschlagswerten.

Der nun entscheidene Punkt:
- Das DTO für den Edit-Mode wie auch die Vorschlagswerte im New-Mode enthalten eine Collection deren Item-Anzahl im Vorfeld nicht bekannt ist da sie von verschiedenen Faktoren abhängt (Business-Logik).
- Die View fragt nun das Model an, wieviel Collection-Items vorliegen und stellt in einer Schleife entsprechende Textfelder und Labels in Listen zur Verfügung.
- Am Ende wird dann ein Update der View ausgeführt.
- In dem Update werden alle erzeugten Textitems aktualisiert

- Würde das Update nun schon im Constructor des Models (Edit Mode) oder im New-Mode nach ermitteln der Vorschlagswerte ausgeführt werden, hat die View zu diesem Zeitpkt. noch nicht die entsprechenden Labels und Textfelder erzeugt, das update() der View kann also noch nichts updaten.

Wie könnte man es denn besser lösen? Eine "einfache" Lösung wäre für diese einzelne View in dem Model eine public Methode einzubauen welche dann das protected notifyUpdate aufruft - aber vielleicht gehts ja auch ganz anders und besser.


----------



## Spacerat (25. Dez 2012)

Woher hast du denn einen derartigen MVC-Ansatz? Da sind doch Logik, Daten und Anzeige kaum voneinander getrennt. Okay, zugegeben, die Definition von MVC ist in dieser Hinsicht ein wenig viel schwammig.
Aber:
Ein Model bekommt Daten aus einer Datenbank oder per Userinteraktion über den Controller (Eingabe).
Ein Controller empfängt Events von der GUI (View) und vermittelt eventuelle Daten an das entsprechende Model (Verarbeitung).
Eine View schliesslich soll nur etwas anzeigen (Ausgabe).
Alles in allem hat man so einen MVC-Ansatz, der kompromisslos dem EVA-Prinzip entspricht. Das bedeutet, deine Views benötigen unabhängig von ihrem Mode (welcher ohnehin in den Controller bzw. ins Model gehört) zur Ausgabe stets aktuelle Parameter. Diese bekommen sie entweder direkt vom Model oder vom Controller, der sie vorher selbst vom Model bekommt. Deine Views sollten auch keine Updates selbständig machen, ist nämlich 'ne Controller- bzw. Verarbeitungs-Funktion. Darüber hinaus sollte auch nur der Controller irgendwelche Events empfangen, denn nur der Controller sollte "Logik" beinhalten.


----------



## OlliL (25. Dez 2012)

Meine View fragt das Model vor dem Aufbau ob das Model im Edit oder New Modus aufbereitet wurde.
Sollte der View das besser von aussen gesagt werden? Also z.B. durch einen Aufruf einer Controller-Funktion?

1. ich instanziere Model im Edit oder New Mode
2. ich rufe eine Controller Funktion "setEditMode" auf, in der View wird ein boolean gesetzt mit dem ich das dann entscheide
3. ich rufe view.getPanel auf um das durch die View aufbereitete Panel zu bekommen
4. ich stelle das Panel dar.

Punkt 2 und 3 mache ich nun durch eine Abfrage in der View beim Model. Die View bekommt es also nicht gesagt, sondern fragt das Model. Ist der Ansatz 1-4 besser?

"_Deine Views sollten auch keine Updates selbständig machen, ist nämlich 'ne Controller- bzw. Verarbeitungs-Funktion._"

Ja.. so hatte ich das mal am Anfang, aber das empfand ich als umständlich wenn ich im Controller vor der Ausführung der Button-Action erstmal die komplette View abklapper und die Daten ins Model pumpe. Da benötige ich dann auch wieder eine ganze Reihe an Methoden in den Views um die Werte der View aus dem Controller heraus abzufragen und dann in das Model zu pumpen.
Bei Views mit JTables wird dann sowieso direkt in die Modelle geschrieben.

Was ich gemacht habe sind eigene Implementierungen von Componenten wie JTextField, JCheckBox usw. Diese haben einen FocusListener und ActionListener welcher dafür sorgt, das sie Ihre Werte automatisch ins Model schreiben. Über einen Constructor bekommen diese Implementierungen ihr Model mit und Ihre "Property". Das Model hat eine generische setProperty und getProperty Funktion welche immer aufgerufen wird mit der jeweiligen Property und dem Wert des Feldes.

z.B.

```
public class MTextField extends JTextField {

	private static final long	serialVersionUID	= 432602729470291786L;
	private PropertyDTO			property;
	private BasicModelIf		basicModelIf;
	private Border				border;
	private int					row;

	public MTextField(int arg0, PropertyDTO property, int row, BasicModelIf basicModelIf,
			final JButton button) {
		super(arg0);
		this.property = property;
		this.basicModelIf = basicModelIf;
		this.border = getBorder();
		this.row = row;
		this.addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				publishChange();
			}
		});
		this.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) {
				publishChange();
				if (button != null) {
					button.doClick();
				}
			}
		});
		setMaximumSize(new Dimension(getPreferredSize().width, getPreferredSize().height));

	}

	private void publishChange() {
		String text = getText();

		/*
		 * if the textField is empty and the model is not empty OR if the
		 * textField is not empty and the model is empty or different
		 */
		if ((text.isEmpty() && basicModelIf.getProperty(property, row) != null && !basicModelIf
				.getProperty(property, row).equals(text))
				|| (!text.isEmpty() && (basicModelIf.getProperty(property, row) == null || !basicModelIf
						.getProperty(property, row).equals(text)))) {
			setBorder(border);
			basicModelIf.setProperty(property, row, text);
		}
	}

}
```

In der View dann:

```
commentField = new MTextField(20, new PropertyDTO(PropertyConstants.PROPERTY_COMMENT), row, model, saveButton);
```

Und im Model:

```
public Object getProperty(PropertyDTO property, int row) {
		switch (property.getPropertyId()) {
			case PropertyConstants.PROPERTY_COMMENT:
				return getComment(row);
[....]
	}
	public void setProperty(PropertyDTO property, int row, Object obj) {
		switch (property.getPropertyId()) {
			case PropertyConstants.PROPERTY_COMMENT:
				setComment(row, (String) obj);
				break;
[....]
	}
```

ich fand das eigentlich ganz schlau :toll:


----------



## Spacerat (25. Dez 2012)

Eigentlich genügt es, wenn das Model weis in welchem Mode es ist. Bei einem Updateevent müssen nur noch die Daten an die View übermittelt werden und die View entscheidet anhand dieser Daten, wie sie auszugeben sind, die View sollte da nicht mehr extra irgendwo Nachfragen müssen und am allerwenigsten irgendwelche anderen Klassen über eine Zustandsänderung informieren. Die Klassen, die diese Information erhalten sollen, sollten es eigentlich schon vorher wissen, wie sonst könnten sie korrekte Datenpakete übermitteln?
Ansonsten werfen Views oder Models Events. Diese Events sammelt man im Controller und der Controller holt sich dann die Daten aus der Eventquelle (halt Model oder View). Deswegen ist's auch relativ egal, ob das Model die komplementäre View selbst updatet oder ob das der Controller macht.


----------



## OlliL (25. Dez 2012)

Hum... die View wird aber mitunter anders aufgebaut je nach Modus... im Edit-Modus gibts eine andere Überschrift in der PanelBorder. Diese würde man dann also im update() der View setzen?

Was mache ich mit meiner speziellen View welche Abhängig der Items in der Collection in meinem Model eine bestimmte Anzahl an Textfelder zur Verfügung stellen muss? Aktuell frage ich beim View-Aufbau wie gesagt die Anzahl ab, erstelle Textfelder und packe die dann in mein GroupLayout des Panels. Wenn ich das jetzt in der Update Routine machen müsste... das ist doch noch schräger?
Eine andere Möglichkeit wäre es, die Anzahl von aussen der View mitzugeben beim erstellen.

Über das Paradigma die View darf nicht auf das Modell zugreifen existieren ja verschiedene Ansichten. Ich sehe das eigentlich nicht so. Welchen Vorteil bringt mir das? Ich werde meine View nicht auf ein anderes Modell pflanzen - wohl aber vielleicht eine weitere/andere View auf das existierende Modell (bzw. habe ich das - eine New/Edit-View und eine Display-View bei gleichem Modell). Somit ist es klar, dass das Modell nichts von der View weiss - aber warum darf die View nichts vom Modell wissen?

Und - wo ist der Vorteil wenn der Controller alle Eingabefelder der View ausliest und diese ins Model überführt, und dann die Aktion im Modell ausführt gegenüber der direkten Aktualisierung des Models durch die View? Ich wills nur einfach verstehen.... Ich hatte vorher in meinem Controller

model.setUsername(view.getUsername());
model.setPassword(view.getPassword());
model.doLogin();

Bei mehren Eigenschaften die in der View dargestellt werden fand ich das schnell "bloated" und überlasse das nun dem Automatismus der Items.

In einer Web-Umgebung ohne AJAX würde ich das natürlich im Controller machen bzw. habe dies auch in Spring/JSP immer so gemacht ist ja auch logisch.. der Benutzer gibt die Daten in der View ein, "schickt" sie ab und dann gehts serverseitig mit dem Controller weiter.
Aber in Swing...? Nur um dem MVC Pattern zu folgen? Auch ohne Vorteile aber mit mehr Code?
Was würdest du z.B. machen wenn der Anwender fordert Felder "online" direkt nach Eingabe und verlassen des Feldes zu validieren?

Bzgl deinem Edit:
Oder würdest du bei einer Feldänderung in der View immer ein Event werfen, den im Controller abarbeiten und den Wert dann im Model eintragen?


----------



## Spacerat (25. Dez 2012)

OlliL hat gesagt.:


> Bzgl deinem Edit:
> Oder würdest du bei einer Feldänderung in der View immer ein Event werfen, den im Controller abarbeiten und den Wert dann im Model eintragen?


Ganz genau (obwohl... nicht bei jeder Änderung eines einzelnen Feldes, sondern erst bei einer entsprechenden Aktion wie z.B. "Senden" oder "Speichern").
Der Vorteil der Ganzen Geschichte ist halt der Sinn von MVC bzw. vom EVA-Prinzip - Eine klare Trennung von Eingabe, Verarbeitung und Ausgabe.
Von daher sollte es eigentlich so sein, dass nur der Controller Kenntnis von Model und View hat und Model und View nur Kenntnis vom Controller. Das Model kann aber wie gesagt, auch die Ausgabe der Daten direkt an die View geben, da eigentlich nur das Model für die Änderung der Daten verantwortlich ist und somit als einziges ein Update der View veranlassen kann. Daten, die über eine Eingabemaske (View) hinzugefügt werden, gelangen über den Controller in das Model. Da das Model den Controller (statt evtl. einer Datenbank) als Quelle erkennt, weis es, dass die View nicht aktualisiert werden muss, weil nur sie als einziger Ursprung in frage kommt. Ebenso, wenn im Model eine Datenabfrage (Userinteraktion View->Controller) eingeht, kann das Model den Controller oder aber auch die View direkt zu einer Anzeigeaktualisierung veranlassen.
Hat eine View mehrere Felder und Modes, so kann man das über eine Propertymap lösen. In einer solchen Map steht dann evtl. ein Key "mode" mit den EnumValues "DISPLAY", "EDIT" oder "NEW" und ein Key "data" mit einer Liste oder einer weiteren Map (Keys "password" und "user", Values irgendwelche Strings) welche die anzuzeigenden Daten (evtl. auch Leerstrings) enthält. In der View gibt man entsprechend des Modes die Überschrift aus, gibt in einer For-Each-Schleife den Inhalt der Liste (bzw. Map) aus und macht die Eingabefelder entsprechend editierbar oder halt nicht.
Eine View sähe demnach etwa so aus:

```
class View {
  void setData(Map<String, Object> data) {
    // Daten und Anzeige aktualisieren
    repaint();
  }

  Map<String, Object> getData() {
    // Reproduzierung der Map aus den aktuellen Daten
    return data;
  }
```
entsprechend ein Controller...

```
class Controller {
  private final View view;
  private final Model model;

  Controller(Model model, View view) {
    this.view = view;
    this.model = model;
    model.addObserver(this); // evtl. auch als anonyme Klasse
    view.addEventListener(this); // evtl. auch als anonyme Klasse
  }
}
```
Der Controller ruft bei Events einer View dessen Daten mit getData() ab und leitet sie an das Model weiter.
Wenn sich nun die Daten für die View durch Userinteraktionen wie z.B. das Anfordern eines Datensatzes ändern, müsste das Model dem Observer nun die Daten für die View übermitteln, damit der Controller diese an die View weiterleiten kann. Dass aber ist mehr oder weniger überflüssig, weil normalerweise nichts anderes als das geschieht. Deswegen kann das Model die Daten auch direkt an die View übermitteln. Wichtig ist, dass "View#setData()" keine weiteren Events wirft (->Endlosrekursion).


----------



## OlliL (25. Dez 2012)

OK, soweit klar - ich denke mal drüber nach, ob ich nochmal ein Refectoring betreibe (meine ich ernst). Ich fand das autom. aktualisieren des Models durch die View-Items eigentlich wahnsinnig sexy 

Wie würdest du es bei einer JTable machen? Aktuell habe ich es so gelöst, das die getValueAt und setValueAt des AbstractTableModel direkt in das Model der View schreiben. Das Model der View ist immer ein Multi-Record-Model. Also es besteht aus einem private Record aller Eigenschaften und eine Liste aus Records. Die public Getter und Setter führen immer einen Index mit welcher angibt welcher Record gerade gelesen/gesetzt werden soll.

Es gibt dann entweder single-Row Views mit einzelnen JComponents und nur einem einzigen Record in der Liste (Edit, New) oder multi-Row Views mit einer JTable (Show) pro Zeile ein Record.
Das würde nach diesem Ansatz bedeuten, man hätte ein Model der View, ein Model der Tabelle und das abgeleitete AbstractTableModel - also 3 Modelle?


----------



## Spacerat (26. Dez 2012)

Versteh' ich das grad' falsch oder übergibst du der View konkrete Swing-Components? Hoffe mal das erste, ansonsten komplett falscher Ansatz . Ich denk' ich frickel da mal ein KSKB zusammen. Ein Record ist ein Datensatz einer Datenbank, hat also ein bis n Felder?


----------



## OlliL (26. Dez 2012)

Ich habe mich wahrscheinlich schlecht ausgedrückt 

Ich habe ein Model welches einen privaten Record beinhaltet und eine Liste von diesen privaten Records:


```
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

public class Model {
	private ArrayList<Record>	records	= new ArrayList<Record>();
	private DateFormat						formatter;

	private class Record {
		private String			validFrom;
		private String			validTil;

		public String getValidFrom() {
			return validFrom;
		}

		public void setValidFrom(String validFrom) {
			this.validFrom = validFrom;
		}

		public String getValidTil() {
			return validTil;
		}

		public void setValidTil(String validTil) {
			this.validTil = validTil;
		}
	}

	public Record getRecord(int row) {
		return records.get(row);
	}

	public void newRecord() {
		records.add(new Record());
	}

	public int getRecordSize() {
		return records.size();
	}

	public String getValidFrom(int row) {
		return getRecord(row).getValidFrom();
	}

	public void setValidFrom(int row, String validFrom) {
		getRecord(row).setValidFrom(validFrom);
	}

	public String getValidTil(int row) {
		return getRecord(row).getValidTil();
	}

	public void setValidTil(int row, String validTil) {
		getRecord(row).setValidTil(validTil);
	}

	public void initNewMode() {
		int row = getRecordSize();
		final Calendar cal = new GregorianCalendar(2999, 11, 31, 0, 0, 0);

		newRecord();
		setValidFrom(row, formatter.format(new Date()));
		setValidTil(row, formatter.format(cal.getTime()));
	}

	public Object getProperty(PropertyDTO property, int row) {
		switch (property.getPropertyId()) {
			case PropertyConstants.PROPERTY_VALIDFROM:
				return getValidFrom(row);
			case PropertyConstants.PROPERTY_VALIDTIL:
				return getValidTil(row);
			default:
				return new Object();
		}
	}

	public void setProperty(PropertyDTO property, int row, Object obj) {
		switch (property.getPropertyId()) {
			case PropertyConstants.PROPERTY_VALIDFROM:
				setValidFrom(row, (String) obj);
				break;
			case PropertyConstants.PROPERTY_VALIDTIL:
				setValidTil(row, (String) obj);
				break;
		}
	}
}
```

Meine View selber bekommt Ihre darzustellenden Daten ausschliesslich im Observer-update()

```
@Override
	public void update(Observable obs, Object obj) {
		if (obj != null && obj instanceof ObserverMessage) {
			ObserverMessage observerMessage = (ObserverMessage) obj;
			switch (observerMessage.getMessage()) {
				case ObserverMessage.OBSERVER_ERROR:
					ViewError viewError = observerMessage.getViewError();
					switch (viewError.getPropertyId()) {
... unwichtig hier ...
					}
					break;
				case ObserverMessage.OBSERVER_UPDATE:
					validFromField.setText((String) model.getProperty(new PropertyDTO(
							PropertyConstants.PROPERTY_VALIDFROM), row));
					validTilField.setText((String) model.getProperty(new PropertyDTO(
							PropertyConstants.PROPERTY_VALIDTIL), row));
					break;
			}
		}
	}
```

und Aktuell kommen die Daten via setProperty ins Model wie weiter oben beschrieben. Die Swing Komponenten werden in der View definiert. Eine New/Edit View arbeitet immer mit row = 0. Bei einer JTable View arbeitet getValueAt und setValueAt mit dem Row-Index der JTable.


```
public void setValueAt(Object value, int row, int column) {
		switch (column) {
			case COMMENT_INDEX:
				model.setComment(row, (String) value);
				break;
...
```

Zum ablegen der Daten in der DB existiert dann eine save() bzw. delete() Methode in dem Model welche vom Controller beim Click auf den jeweiligen Button ausgeführt wird.
Diese Methode überführt dann die Daten des Models mittels einer privaten exportDTO() Funktion in ein DTO welches Basis-Validierungen in seinen Settern durchführt. Werden dabei Fehler festgestellt, wird eine Exception ausgelöst und die Methode im Model meldet den Fehler an meine View (Observer) welche dann eine Message-Box anzeigt und das fehlerhafte Feld rot umrandet.
Wird kein Fehler beim erzeugen des DTO festgestellt, wird ein EJB aufgerufen und diesem das DTO übergeben. Das EJB wiederum erzeug daraus eine Entity, führt fachliche Prüfungen der Inhalte durch, wirft ggf. wieder eine Exception oder legt das Objekt in die DB ab (Hibernate)....

Der Weg aus der DB in die Show-View ist quasi genau andersrum. Das Model wird instanziert. Die View sagt dem Model "ich muss Daten darstellen". Das Model ruft nun das get()-EJB auf. Es liefert eine Liste von DTO zurück. Das Model lädt die DTO mittels einer privaten importDTO() Funktion in sich rein, erzeugt pro DTO einen Record usw... Bei JTable basierten Views werden beim erzeugen der Tabelle in der View autom. die aktuellen Daten über das AbstractTableModel abgefragt und somit angezeigt.


----------



## Spacerat (26. Dez 2012)

Eben dieses "andersrum" führt zum Durcheinander im MVC-Pattern. Die Anzeige von Fehlern ist evtl. Aufgabe einer anderen View, es sei denn, deine View ist ein einziges mehr oder weniger kompliziertes JPanel, welches im Prinzip die komplette Anwendung ausmacht. Okay, ich werd' mal versuchen an diesem Beispiel anzusetzen, damit's verständlich wird.
[OT]Apropos verständlich... Wieso hat das Model eine Methode "public Record getRecord(int row)", während diese ohnehin nur vom Model selbst aufgerufen werden kann, weil die Klasse Record private ist?[/OT]


----------



## OlliL (26. Dez 2012)

Muss private sein, ja.
Ich habe quasi ein JFrame mit einem JPanel welches je nach Menue-Selektion "ausgetauscht" wird (Ein Menue-Eintrag löst ein Event aus welches sich dann um die entsprechenden JPanel-Tauschaktion kümmert. Aus diesem Eventhandler stammt by the way das ursprüngliche Problem dieses Threads 

Die jeweiligen Views sind immer eines dieser austauschbaren JPanel. Daher kümmern diese sich auch um die Fehleranzeige, sprung ins fehlerhafte Feld und Umrandung des Feldes

Ein simpler Fehler-Handler aus meiner Logon-View:


```
public void update(Observable obs, Object obj) {
		if (obj != null && obj instanceof ObserverMessage) {
			ObserverMessage observerMessage = (ObserverMessage) obj;
			switch (observerMessage.getMessage()) {
				case ObserverMessage.OBSERVER_ERROR:
					ViewError viewError = observerMessage.getViewError();
					switch (viewError.getPropertyId()) {
						case PropertyConstants.PROPERTY_USERNAME:
							nameField.requestFocusInWindow();
							nameField.setBorder(new LineBorder(Color.red));
							break;
						case PropertyConstants.PROPERTY_PASSWORD:
							passField.requestFocusInWindow();
							passField.setBorder(new LineBorder(Color.red));
							break;
					}

					JOptionPane.showMessageDialog(null,
							textHandler.getErrorById(sessionDTO, viewError.getErrId()),
							textHandler.getTextById(sessionDTO, TextConstants.TXT_ATTENTION),
							JOptionPane.ERROR_MESSAGE);
			}
		}
	}
```

Das doLogin() aus meinem Model was über den Controller angestoßen wird


```
public SessionDTO doLogin(SessionDTO sess) {
		ObserverMessage observerMessage = null;
		SessionDTO sessionDTO = sess;

		final UserDTO userDTO = new UserDTO();
		try {
			userDTO.setName(getUsername());
			userDTO.setPassword(getPassword());

			LoginBeanIf loginBean =  GetEJBObject.getLoginBean();

			sessionDTO = loginBean.doLogin(userDTO);
		} catch (InvalidDataException ide) {
			observerMessage = new ObserverMessage(ObserverMessage.OBSERVER_ERROR, new ViewError(
					ide.getErrId(), ide.getPropertyId()));
			sessionDTO = sess;
		}

		notifyMyObservers(observerMessage);

		return sessionDTO;
	}
```

Das Logon-Model ist kein multi-row-Model dafür ist es einfach zu speziell.


----------



## Spacerat (26. Dez 2012)

OlliL hat gesagt.:


> Muss private sein, ja.


Aber dass dann entsprechende Getter auf Records ebenso private sein dürfen ist dir klar? Keine andere Klasse ausser Model könnte

```
Record r = myModel.getRecord(index);
```
aufrufen, ganz einfach nur deswegen, weil die Klasse Record dort nicht bekannt sein kann. Der einzige Sinn, diese Methode public zu machen, wäre

```
synchronized(myModel.getRecord(index)) {
  // sonstwas ohne Record
}
```
aber benötigt man das? Da würde doch dann Object als Rückgabetyp genügen und innerhalb von Model könnte man direkt auf die Recordliste zugreifen.


----------



## OlliL (26. Dez 2012)

Hm? Es muss ja auch niemand getRecord() aufrufen ausser Model? Deswegen habe ich dir doch zugestimmt das getRecord() private sein muss? Nach aussen hin habe ich getter/setter für die Eigenschaften des Records bzw. getProperty/setProperty. Oder habe ich dich jetzt falsch verstanden?


```
private Record getRecord(int row) {
		return records.get(row);
	}

	public String getBankCode(int row) {
		return getRecord(row).getBankCode();
	}

	public void setBankCode(int row, String bankCode) {
		getRecord(row).setBankCode(bankCode);
	}

	public String getComment(int row) {
		return getRecord(row).getComment();
	}

	public void setComment(int row, String comment) {
		getRecord(row).setComment(comment);
	}

	public Object getProperty(PropertyDTO property, int row) {
		switch (property.getPropertyId()) {
			case PropertyConstants.PROPERTY_BANKCODE:
				return getBankCode(row);
			case PropertyConstants.PROPERTY_COMMENT:
				return getComment(row);
		}
	}

	public void setProperty(PropertyDTO property, int row, Object obj) {
		switch (property.getPropertyId()) {
			case PropertyConstants.PROPERTY_BANKCODE:
				setBankCode(row, (String) obj);
				break;
			case PropertyConstants.PROPERTY_COMMENT:
				setComment(row, (String) obj);
				break;
		}
	}
```


----------



## OlliL (26. Dez 2012)

So... ich habe nochmal ein wenig gegrübelt. Wie wäre folgender Ansatz:

- Model wird immer "leer" instanziiert.
- View wird "leer" instanziiert.
- Controller wird instanziiert.
- Dem Controller werden View und Model bekannt gemacht
- Eine Methode im Controller wird aufgerufen welche dem Model sagt es soll sich
    a) als "New" Model mit Vorschlagswerten initiieren welche sich das Model selbst aus der DB besorgt
    b) als "Edit" Model. Die zu editierenden Daten werden als DTO durch den Controller an das Model
        übergeben (das Model besorgt sich die Daten also nicht selber, sondern erhält die Daten vom Controller).
- Die Methode welche der Controller im Model aufgerufen hat macht die Änderung der View bekannt
- In der "Änderungsnotiz" an das Model sind alle Daten als HashMap enthalten
- Die View arbeitet die HashMap durch und aktualisiert all Ihre Items
- Ob es ein "New" oder "Edit" Model ist, ist auch in der "Änderungsnotiz" enthalten.
- Beim druck auf "Save" in der View holt sich der Controller die "gleiche" HashMap von der View ab, und sendet diese an das Model
- das Model aktualisiert sich nun auf Basis dieser HashMap, es wird keine erneute Änderungsnotiz an die View gesendet
- nun wird die eigentliche "save" Aktion des Models ausgeführt.

Somit hat die View nun keinen blassen Schimmer vont dem Model mehr.

Wenn das soweit "OK" ist, bleiben aber fuer mich noch folgende Dinge offen:

- was mache ich mit meiner "Spezial-View" welche abhängig von der Anzahl der Datensätze im Model die gleiche Anzahl Textfelder bereitstellen muss. Ich kann diese ja nicht im update() erzeugen und ins Layout einhängen. Wenn ich der View vor Ihrem Aufbau sage wieviel Items sie erstellen muss, kann ich sie nicht  vor dem Model initiieren, was ich aber muesste, da sonst die Änderungsnotiz vom Model bei der View zu nichts führt - es sind noch keine Items da. (Chicken Egg Problem). Das war der einzige Grund warum bei mir die Views die Änderungsnotiz "aktiv" beim Model auslösen... Model wird initiiert, View wird  aufgebaut mit "Model-Abfrage" (wieviel Records hat das Model) und danach wird aktiv notify() Ausgelöst beim Model.

- JTable.... ein extra JTable-Model neben dem View-Model? Also 2 "Models"? Macht es Sinn, dieses JTable-Model als private Model in meine AbstractTable-Klasse zu verfrachten? Eigentlich gefällt mir das nicht so recht - der JTable Record ist doch im Grunde das gleiche wie den Record den ich zur Abbildung der Daten im View-Model brauche.

- HashMap zum Datentransport - irgendwie kann ich mich damit nicht wirklich anfreunden. Das ist eine Abstraktionsebene die ich als unsauber erachte. Ich habe ein schönes Model mit Getter, Setter, die richtigen Typen für die Eigenschaften usw. Und nun packe ich das alles in irgendeine generische HashMap ohne Typensicherheit und schiebe die mittels notify() zur View? Diese parsed die dann und macht was damit? Die Abhängigkeit der View zum Model ist genauso da wie vorher. Ändere ich irgendwas am Model was zu einer Änderung der HashMap führt  muss ich genauso die View anpacken. Es gibt keine feste Abhängigkeit im Quellcode mehr, aber semantisch ist das ganze nach wie vor voneinander abhängig. Eigentlich müsste man das Model der View im notify() "rüberschicken" und nicht irgend ne HashMap die man sich zusammenbastelt zum Datentransport.... weiss nicht....


----------



## OlliL (26. Dez 2012)

Lösung für meine Spezialview könnte so aussehen... ich implementiere in der View eine klassenweite Variable "anzahl Zeilen". 
Nachdem das Model initialisiert wurde, sendet es via notify die Anzahl seiner Records an die View. Die view setzt dann diese Variable auf den gesendeten Wert. Beim Aufbau der View wird diese Anzahl dann beruecksichtigt und entsprechend viele JLabels und JTextFields erzeugt + ins Layout eingebunden.

Danach wird erst die eigentliche View aufgebaut. Eine Veränderung der Anzahl der Records zur Laufzeit wird zwar so nicht unterstützt, aber das kann auch nicht vorkommen.

Generell ist es aber ein Problem, das meine View erst am Ende aufgebaut wird. Zum Zeitpunkt der Model-Befüllung wird die View noch gar nicht angezeigt und ist noch gar nicht erzeugt.


----------



## Spacerat (26. Dez 2012)

1. Zu dem private der Methode... ja, da hab' ich letztendlich etwas falsch verstanden.

2. Du must keine x verschiedenen Views für ein Model haben. Das Model übergibt an die View ein DataObject, welches alle relevanten Daten incl. Statusmeldung und Mode enthält. Mode erweiterst du schlicht um DISPLAY (damit hättest du ADD, UPDATE und DISPLAY bzw. NEW, EDIT und GET für einzelne Records). Was noch fehlt, wäre LIST, womit die View das komplette RecordSet auflistet. Da bin ich grad' nicht so sicher, ob sich das mit der selben View bewerkstelligen lässt, denke aber mal schon.


----------



## OlliL (26. Dez 2012)

Bin nun gerade dabei das nochmal umzustrukturieren. Erstmal den Weg Model -> View. Habe jetzt in meinen Settern des Models notifies eingebaut in der Art:


```
public void setAmount(int row, String amount) {
		getRecord(row).setAmount(amount);
		notifyMyObservers(new ObserverMessage(ObserverMessage.OBSERVER_UPDATE, new PropertyDTO(
				PropertyConstants.PROPERTY_AMOUNT, amount, row)));
	}
```

und in der View dann im update()


```
case ObserverMessage.OBSERVER_UPDATE:
					
					if (observerMessage.getPropertyDTO() != null) {

						Object value = observerMessage.getPropertyDTO().getPropertyValue();
						int row = observerMessage.getPropertyDTO().getRow();
						
						switch (observerMessage.getPropertyDTO().getPropertyId()) {
							case PropertyConstants.PROPERTY_NUMBER_OF_ROWS:
								initPanel((int)value);
								break;
							case PropertyConstants.PROPERTY_YEAR:
								yearField.setText((String) value);
								break;
							case PropertyConstants.PROPERTY_MONTH:
								monthField.setSelectedItem((ComboBoxItem) value);
								break;
							case PropertyConstants.PROPERTY_AMOUNT:
								amountFields.get(row).setText((String) value);
								break;
							case PropertyConstants.PROPERTY_COMMENT:
								amountLabels.get(row).setText((String) value);
								break;
						}
					}
```

Die Initialisierung des Models nehme ich dann aus dem Controller heraus vor (ob New oder Edit).

Das Model ermittelt dann je nach Modus die Anzahl der Datensätze, sendet diese an die View damit sie die Items vorbereiten kann. Danach werden die Daten importiert - die Setter senden dann ne ganze Reihe notifies an die View. Und danach wird dann die View noch in den Edit oder New Modus gesetzt.

Aus dem Model:


```
public void initNewMode() {
		MonthlySettlementBeanIf monthlySettlementBean = GetEJBObject.getMonthlySettlementBean();

		Date lastDate = monthlySettlementBean.getLastMonthlySettlement(sessionDTO);
		Calendar cal = Calendar.getInstance();

		if (lastDate != null) {
			cal.setTime(lastDate);
			cal.add(Calendar.MONTH, 1);
		} else {
			cal.setTime(new Date());
		}

		MonthlySettlementDTO monthlySettlementDTO = monthlySettlementBean
				.getMonthlySettlementProposal(sessionDTO, cal.get(Calendar.MONTH) + 1,
						cal.get(Calendar.YEAR));

		notifyMyObservers(new ObserverMessage(ObserverMessage.OBSERVER_UPDATE, new PropertyDTO(
				PropertyConstants.PROPERTY_NUMBER_OF_ROWS, monthlySettlementDTO.getCount())));

		importDTO(monthlySettlementDTO);

		notifyMyObservers(new ObserverMessage(ObserverMessage.OBSERVER_UPDATE, new PropertyDTO(
				PropertyConstants.PROPERTY_PANEL_MODE, PanelMode.NEW)));

	}
```


----------



## Spacerat (26. Dez 2012)

So, ich hab' mal was dahin gepfuscht, im wahrsten Sinne des Wortes, also beschwer' dich nicht über die Funktion oder das Design. Zumindest ist das Verhalten der View hoffentlich ausreichend dargestellt. Diese wirft nur noch Action- bzw. ViewEvents an den Controller und baut ihren Inhalt anhand des ihr übermittelten Datenobjects auf. Dieses Datenobject bekommt die View vom Controller. Beim Model ist's genau so. Es bekommt die Daten nebst Action vom Controller, sobald in der View eine Aktion ausgelöst wird.
Damit ist der Aktionsfluss View->Controller->Model und der Datenfluss Model->Controller->View vorgegeben.
Leider ist das ganze ein wenig aufwendiger als ein KSKB geworden. Das liegt aber nicht an MVC, sondern daran, dass ich dazu extra ein Datenmodell implementiert habe. Ich hab's deswegen mal als Archiv angehängt.


----------



## OlliL (27. Dez 2012)

Danke für den Code 

Ich habe es inzwischen so ähnlich (zummindest vom Konzept her) implementiert.
Die Items meiner View werden nach wie vor sofort mit dem Model (nach FocusLost) synchronisiert, aber sie sind nun alle Observable, und mein Controller ist der Observer. Beim instanzieren der MVC Objekte bekommt meine View nur ein "addObserver(controller)" Aufruf und fügt diesen Controller intern dann all Ihren Items hinzu.
Noch ist meine View jedoch der Observer vom Model, aber das werde ich auch noch auf den Controller umstellen - das ist ja schnell getan.

Ich habe mich gegen ein DataObject entschieden, und sende nun aus jeder einzelnen Set-Funktion im Model den jeweils neuen gesetzten Wert an meine Observer. Die View nimmt ein Update der Items nur vor, wenn sich der Wert aus dem Model von Ihrem aktuellen Wert unterscheidet (verhindert endless-loop falls das alleinige Update schon wieder eine Model-Versorgung antriggert).

Daneben gibt es noch 3 unabhängige Observer-Messages welche das Model senden kann. "Fehler" um Eingabefehler dem User darzustellen, "Number of Rows" um der View zu sagen wieviel Rows sie benötigt um alles was dann danach kommt darzustellen und "Panel Mode" als Enum damit das JPanel weiss ob es nun eine "New..." "Edit..." oder "Anzeige..." Einstellung braucht.

Die View stellt sicher, das reguläre Observable Messages nur verarbeitet werden, wenn es weiss wieviel Number of Rows es hat und was sein Modus ist.

Die einzelnen View Komponenten sind z.B. von JTextField abgeleitete Items welche bei der Instanzierung ihre Property mitbekommen so das sie wissen was sie an ihren Observer (dem Controller) für eine Eigenschaft senden müssen wenn sie sich aendern.


```
public class MTextField extends JTextField {

	private static final long			serialVersionUID	= 432602729470291786L;
	private PropertyDTO					property;
	private Border						border;
	private int							row;
	private final ObservableDispatcher	dispatcher			= new ObservableDispatcher();

	private String						oldValue;

	private static final class ObservableDispatcher extends Observable {
		private void notifyChanged(ObserverMessage observerMessage) {
			setChanged();
			notifyObservers(observerMessage);
		}
	}

	public void addObserver(Observer observer) {
		dispatcher.addObserver(observer);
	}

	public MTextField(int arg0, PropertyDTO property, int row, final JButton button) {
		super(arg0);
		this.property = property;
		this.border = getBorder();
		this.row = row;
		this.addFocusListener(new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e) {
				publishChange();
			}
		});
		this.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) {
				publishChange();
				if (button != null) {
					button.doClick();
				}
			}
		});
		setMaximumSize(new Dimension(getPreferredSize().width, getPreferredSize().height));

	}

	private synchronized void publishChange() {
		String text = getText();

		/*
		 * if the textField is empty and the model is not empty OR if the
		 * textField is not empty and the model is empty or different
		 */
		if ((text.isEmpty() && oldValue != null && !oldValue.equals(text))
				|| (!text.isEmpty() && (oldValue == null || !oldValue.equals(text)))) {
			setBorder(border);
			dispatcher.notifyChanged(new ObserverMessage(ObserverMessage.OBSERVER_UPDATE,
					new PropertyDTO(property.getPropertyId(), text, row)));
			oldValue = text;
		}
	}

}
```

Der update() part meine View


```
@SuppressWarnings("unchecked")
	@Override
	public void update(Observable obs, Object obj) {
		if (obj != null && obj instanceof ObserverMessage) {
			ObserverMessage observerMessage = (ObserverMessage) obj;
			switch (observerMessage.getMessage()) {
				case ObserverMessage.OBSERVER_ERROR:
					ViewError viewError = observerMessage.getViewError();
					switch (viewError.getPropertyId()) {
						case PropertyConstants.PROPERTY_MONTH:
							errorField(monthField);
							break;
						case PropertyConstants.PROPERTY_YEAR:
							errorField(yearField);
							break;
						case PropertyConstants.PROPERTY_AMOUNT:
							errorField(amountFields.get((int) viewError.getRowId()));
							break;
					}
					switch (viewError.getPropertyId()) {
						case PropertyConstants.PROPERTY_AMOUNT:
							JOptionPane.showMessageDialog(null, textHandler.getErrorById(
									sessionDTO, viewError.getErrId(),
									amountFields.get((int) viewError.getRowId()).getText()),
									textHandler
											.getTextById(sessionDTO, TextConstants.TXT_ATTENTION),
									JOptionPane.ERROR_MESSAGE);
							break;
						default:
							JOptionPane.showMessageDialog(null, textHandler.getErrorById(
									sessionDTO, viewError.getErrId()), textHandler.getTextById(
									sessionDTO, TextConstants.TXT_ATTENTION),
									JOptionPane.ERROR_MESSAGE);
					}
				case ObserverMessage.OBSERVER_UPDATE:

					if (observerMessage.getPropertyDTO() != null) {

						Object value = observerMessage.getPropertyDTO().getPropertyValue();
						int row = observerMessage.getPropertyDTO().getRow();
						int propertyId = observerMessage.getPropertyDTO().getPropertyId();

						if (rows > 0 || propertyId == PropertyConstants.PROPERTY_NUMBER_OF_ROWS) {

							switch (observerMessage.getPropertyDTO().getPropertyId()) {
								case PropertyConstants.PROPERTY_COMBO_LIST_MONTH:
									monthComboItems = (Vector<ComboBoxItem>) value;
									break;
								case PropertyConstants.PROPERTY_COMBO_LIST_YEAR:
									yearComboItems = (Vector<ComboBoxItem>) value;
									break;
								case PropertyConstants.PROPERTY_PANEL_MODE:
									switch ((PanelMode) value) {
										case NEW:
											System.out.println("NEW not supported for "
													+ this.getClass().getName());
											break;
										case EDIT:
											System.out.println("EDIT not supported for "
													+ this.getClass().getName());
											break;
										case LIST:
											setPanelBorder(this.returnPanel,
													textHandler.getTextById(sessionDTO,
															TextConstants.TXT_MONTHLY_BALANCES));
											break;
									}
									break;
								case PropertyConstants.PROPERTY_NUMBER_OF_ROWS:
									rows = (int) value;
									initPanel();
									break;
								case PropertyConstants.PROPERTY_YEAR_COMBO:
									yearField.setSelectedItem((ComboBoxItem) value);
									break;
								case PropertyConstants.PROPERTY_MONTH:
									monthField.setSelectedItem((ComboBoxItem) value);
									break;
								case PropertyConstants.PROPERTY_AMOUNT:
									amountFields.get(row).setText((String) value);
									break;
								case PropertyConstants.PROPERTY_COMMENT:
									amountLabels.get(row).setText((String) value);
									break;
							}
						}
					}
					break;
			}
		}
	}
```


----------

