Die Model/View/Controller-Architektur (von Artin Avanes) =>Was ist MVC? => Für das Design dieser Aufgabe gibt es eine Standartlösung, das sogenannte Model-View-Controller-Pattern,das in Swing integriert ist! 1. Der grundlegende Aufbau bzw. die Struktur In einem MVC-UI gibt es wie der Name schon eindeutet drei Kommunikationsobjekte : a) the model => die zugrundeliegende logische Repräsentation b) the view => die visuelle Repräsentation c) the controller => ist für den Input, also für die Eingaben des Users zuständig Die Aufgaben dieser Objekte kann man mit folgendem Schema veranschaulichen : Controller -------> Model -------> View -Der Controller interpretiert z.B. einen Mausklick oder Tastatureingaben von dem User als einen Befehl und sendet diese zum Model und/oder zum View (oft auch Viewport genannt), um die Veränderungen zu bewirken. Der Controller ist also dafür verantwortlich wie die Applikation darauf antwortet, wenn der User auf einen Mausbutton klickt etc.. -Das Model ist für die Verarbeitung von einem oder mehreren Datenelementen zuständig, reagiert auf die Fragen nach seinem Zustand bzw. auf die Aufforderung der Änderung des Zustandes! -Das View-Objekt stellt ein "rechteckiges Feld" auf dem Display dar und ist für die Präsentation der Daten für den User durch eine Kombination von Graphiken und Text verantwortlich. Dieses 2.Schema soll die engen Beziehungen und den ständigen Kontakt verdeutlichen : 2.Schema Erklärung : Model pointet auf View,d.h. es wird ihm ermöglicht,die wichtigen Veränderungen an View zu senden. Jedoch braucht das Model nicht zu wissen, wie View aussieht. Im Gegensatz dazu muss View sehr genau wissen, wei Model aussieht, denn es muss ihm erlaubt sein, die Methoden von Model zu benutzen. View pointet auf Controller, doch sollte man nicht dessen Methoden außerhalb aufrufen, da man eventuell irgendwann einen Controller durch einen anderen austauschen möchte. 1. Mehrere Views können einem und denselbem Model zugrundeliegen! 2. Aufgrund der Tatsache, dass Models nichts über die Präsentation wissen müssen, kann man Views modifizieren oder neue kreieren ohne das Model zu tangieren! 2. GUI-EventHandling (GUI-Design mit Swing) Im folgenden wird daraufeingegangen, wie man Events in einer GUI handhabt. Dabei wird zunächst auf ein sehr einfaches Bespiel in AWT-Style eingegangen, die Probleme und Alternativen dieses Beispiels erläutert und schließlich ein zweites, etwas komplexeres Beispiel, was MVC verwendet, um die GUI zu "designen" vorgestellt. Das 1.Beispiel : import java.awt.*; import java.awt.event.*; import javax.swing.* // Subclass JPanel to place widgets in a panel class SimplePanel extends JPanel { // Declare the two components JTextField textField; JButton button; // Add a constructor for our JPanel // This is where most of the work will be done public SimplePanel() { // Create a JButton button = new JButton("Clear Text"); // Add the JButton to the JPanel add(button); // Create a JTextField with 10 visible columns textField = new JTextField(10); // Add the JTextField to the JPanel add(textField); // Add a listener to the JButton // that clears the JTextField button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textField.setText(""); } }); } } // Next, create a simple framework // for displaying our panel // This framework may be used for displaying other // panels with minor modifications // Subclass JFrame so you can display a window public class SimplePanelTest extends JFrame { // Set up constants for width and height of frame static final int WIDTH = 300 static final int HEIGHT = 100; // Add a constructor for our frame. SimplePanelTest(String title) { // Set the title of the frame super(title); // Instantiate and add the SimplePanel to //the frame SimplePanel simplePanel = new SimplePanel(); Container c = getContentPane(); c.add(simplePanel, BorderLayout.CENTER); } // Create main method to execute the application public static void main(String args[]) { // instantiate a SimplePanelTest object // so you can display it JFrame frame = new SimplePanelTest("SimplePanel Example"); // Create a WindowAdapter so the application // is exited when the window is closed. frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // Set the size of the frame and show it frame.setSize(WIDTH, HEIGHT); frame.setVisible(true); } } Solch ein GUI-Framework wird für einfache Applikationen funktionieren, doch bei komplexeren gibt es einige Unzulänglichkeiten! Nehmen wir an, dass wir ein 3. "widget", ein textField einfügen wollen, dass den Durchschnitt der eingegebenen Zahlen ermittelt und das Resultat anzeigt. Es gibt 3.Wege dieses zu verwirklichen : alle drei jedoch haben Schwächen. 1.Möglichkeit : Man kann einen Adapter (=>was ein Adapter genau macht dazu später) einfügen, welcher den Userinput von dem textField-Objekt mit dem avgField-Objekt verbindet. In dessen Listener wir man die Methode brauchen, die den Durchschnitt ermittelt. AWT-Event-Model garantiert jedoch nicht, dass das so auch funktionieren wird. Das textField-Objekt wird 2 ActionListener haben. Nun kann es vorkommen, dass avgField das Event vor textField verarbeitet, so dass die jüngsten eingegebenen Zahlen nicht in die average-Methode mitaufgenommen werden. =>Dieses Problem kann durch "ordered multicasting of events" gelöscht werden, doch bleibt ein Problem : In der MVC-Sprache ist nun textList sowohl ein View als auch ein Model! 2.Möglichkeit : Um zu verhindern, dass einige Zahlen ignoriert werden, kann man einen Adapter zu textList hinzufügen. Wenn dessen Inhalt sich ändert, dann wird textList avgField "refreshen", d.h. ihn ebenfalls auf den neuesten Stand bringen. Dieses eliminiert jedoch nicht das Problem, dass textList in den oben genannten 2 "Rollen" auftritt. Außerdem beginnt man die Spur zu verlieren, wo Events erscheinen => unübersichtlicher Code 3.Möglichkeit : Man kann den Code, der für das Update von avgField zuständig ist, zu dem Adapter hinzufügen, der textList "updatet". Aber wie auch bei der 2.Möglichkeit bindet man das eine Objekt an dem anderen => Binden bzw. Fesseln Die Lösung für dieses Problem ist die MVC-Architektur. Die 1.Variante (schlecht): => Beide View-Objekte,also avgField und textList implementieren das ChangeListener-Interface. => textField (Controller) benutzt ActionListener, um Daten im list-Objekt zu ändern. => list erhält einen ChangeListener und benachrichtigt diesen immer dann, wenn Daten sich verändert haben (Aufruf der stateChanged()-Methode)! Zur Verdeutlichung wiederum ein Schema : PreMVCmodel Das Problem mit dieser Architektur ist, dass die stateChanged()-Methode nicht relevaten Daten beinhaltet (nämlich nur das ChangeEvent). Dem View-Objekt jedoch, sollte es möglich sein, die Model-Daten zu erhalten. Ideal wäre es, wenn man einige Modeldaten der stateChanged()-Methode übergeben : In diesem Fall könnte man einfach die komplete, auf den neuesten Stand gebrachte Liste übergeben. Dieses erlaubt jedoch Swing nicht! Deshalb die 2.Variante : Man verwendet wiederum Adapters, um dieses Problem zu lösen. Die Adapter sind Klassen, die das Interface ChangeListener implementieren. Der grundlegende Unterschied zum vorangegangenen "Szenario" ist, dass diese Adapter nun Typ und Methodeninformation über das Model besitzen. Auch hierzu ein letztes Schema : FirstMVCmodel 3. Klassen sind in der Implementierung von Bedeutung : a) ListView => einer der beiden Views b) IntVectorModel => ein Vector als Model : enthält die Zahlen c) FirstMVC => enthält den Frame, die Adapters und die Hauptmethoden, also kurz : alles Wichtige ListView wird als View für das Model IntVectorModel agieren. Sie beinhaltet eine changed()-Methode, die der entsprechende Adapter aufrufen wird. import javax.swing.*; public class ListView extends JTextArea { public ListView(int n) { super("", n, 10); setEditable(false); } /* This is NOT tied to a particular model's event. * An adaptor is used to isolate the model's type * from the view. * * Method called by adapter * resets JTextArea and copies the data model * Vector back in */ public void changed (Vector v) { setText(""); Enumeration e = v.elements(); while (e.hasMoreElements()) { Integer i = (Integer)e.nextElement(); append (i.toString() + "\n"); } } } IntVectorModel beinhaltet die Liste von Zahlen und benachrichtigt das ChangeListener-Objekt über EventListenerList! import javax.swing.*; import javax.swing.event.*; public class IntVectorModel { protected Vector data = new Vector(); //An Object that holds a list of EventListeners protected EventListenerList changeListeners = new EventListenerList(); public IntVectorModel() { } public void addElement(int i) { data.addElement(new Integer(i)); fireChange(); } public Vector getData() { return data; } // Listener notification support public void addChangeListener(ChangeListener x) { changeListeners.add (ChangeListener.class, x); // bring it up to date with current state x.stateChanged(new ChangeEvent(this)); } //Removes the listener as a listener of the specified type. public void removeChangeListener(ChangeListener x) { changeListeners.remove (ChangeListener.class, x); } protected void fireChange() { // Create the event:ChangeEvent is used to notify // interested parties that state has changed in the event source ChangeEvent c = new ChangeEvent(this); // Get the listener list :Passes back the event listener // list as an array of ListenerType-listener pairs Object[] listeners = changeListeners.getListenerList(); // Process the listeners last to first // List is in pairs, Class and instance for (int i = listeners.length-2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { ChangeListener cl = (ChangeListener)listeners[i+1]; cl.stateChanged(c); } } } } Die Hauptklasse FirstMVC : * *between* components. The model is a Vector of * numbers. The views are a list of the numbers and * the average of the numbers. The Views do not * directly listen for changes from the model. Adaptors * are used to isolate type information (promoting * flexibility) from the model/views. * * Really the only Swing part is the ChangeListener * stuff (plus a BoxLayout). */ import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.util.*; public class FirstMVC extends JFrame { // The initial width and height of the frame public static int WIDTH = 300; public static int HEIGHT = 200; // a View ListView listView = new ListView(5); // Another View TextField avgView = new TextField(10); // the Model IntVectorModel model = new IntVectorModel(); // the Controller TextField controller = new TextField(10); /**Adaptor mapping IntVector to ListView; * Hide specific types in adaptor rather * than having view/model know about each other. * * A real system would allow the model to indicate * WHAT had changed (for efficiency of execution * and simpler design). */ private static class IntVectorToListviewAdaptor implements ChangeListener { IntVectorModel model; ListView view; public IntVectorToListviewAdaptor( IntVectorModel m, ListView v) { model = m; view = v; } public void stateChanged(ChangeEvent e) { view.changed(model.getData()); } } private static class IntVectorToAvgViewAdaptor implements ChangeListener { IntVectorModel model; TextField view; public IntVectorToAvgViewAdaptor( IntVectorModel m, TextField v) { model = m; view = v; } public void stateChanged(ChangeEvent e) { double avg = 0.0; Vector d = model.getData(); Enumeration enum = d.elements(); while (enum.hasMoreElements()) { Integer i = (Integer)enum.nextElement(); avg += i.intValue(); } if (d.size()>0) avg = avg / d.size(); view.setText(""+avg); } } private static class TextFieldToIntVectorAdaptor implements ActionListener { IntVectorModel model; TextField controller; public TextFieldToIntVectorAdaptor( TextField c, IntVectorModel m) { model = m; controller = c; } public void actionPerformed(ActionEvent e) { String n = controller.getText(); controller.setText(""); // clear txt field try { model.addElement(Integer.parseInt(n)); } catch(NumberFormatException nfe) { JOptionPane.showMessageDialog(null,"Bitte geben Sie eine Zahl an!"); } } } public FirstMVC(String lab) { super(lab); // Display Controller JPanel controlPanel = new JPanel(); controlPanel.setBorder ( BorderFactory.createEtchedBorder()); controlPanel.setLayout(new BoxLayout(controlPanel,BoxLayout.Y_AXIS)); JLabel ctitle = new JLabel("Control"); ctitle.setHorizontalTextPosition(JLabel.CENTER); controlPanel.add(ctitle); controlPanel.add(Box.createVerticalStrut(10)); controlPanel.add(controller); Container c = getContentPane(); c.setLayout (new FlowLayout ()); c.add(controlPanel); c.add(Box.createHorizontalStrut(30)); // Display Views JPanel viewPanel = new JPanel(); viewPanel.setBorder ( BorderFactory.createEtchedBorder()); viewPanel.setLayout( new BoxLayout(viewPanel,BoxLayout.Y_AXIS)); JLabel title = new JLabel("Views"); viewPanel.add(title); title.setHorizontalAlignment(JLabel.CENTER); title.setHorizontalTextPosition(JLabel.CENTER); viewPanel.add(Box.createVerticalStrut(10)); viewPanel.add(new JScrollPane(listView)); viewPanel.add(Box.createVerticalStrut(10)); viewPanel.add(avgView); c.add(viewPanel); // Hook the Controller up to the Model TextFieldToIntVectorAdaptor CM = new TextFieldToIntVectorAdaptor( controller, model); controller.addActionListener(CM); // Hook up the simple avg View up to the Model IntVectorToAvgViewAdaptor MV1 = new IntVectorToAvgViewAdaptor(model,avgView); model.addChangeListener(MV1); // Connect the View to the Model via the adapter, // which isolates type information from each other. IntVectorToListviewAdaptor MV2 = new IntVectorToListviewAdaptor(model,listView); model.addChangeListener(MV2); } public static void main(String args[]) { FirstMVC frame = new FirstMVC("First MVC Example"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.setSize(WIDTH, HEIGHT); frame.setVisible(true); } } ListView IntVectorModel FirstMVC Shortcours von Sun Weitere Tips und Erläuterungen Artin Avanes |
||||||