neo's nice place neo's nice place sitemap
neo's nice place
main
about me
berlin
guestbook
links
links
miscellaneous
sitemap
 

 

Die Model/View/Controller-Architektur

(von Artin Avanes)

=>Was ist MVC?

  • Aufgabenstellung: das Einbetten der GUI in das Programm
    => Für das Design dieser Aufgabe gibt es eine Standartlösung, das sogenannte Model-View-Controller-Pattern,das in Swing integriert ist!
  • Model/View/Controller (kurz: MVC) -Architektur gibt uns die Möglichkeit zu kontrollieren, wie die "widgets",d.h. die vorstehendsten Bausteine in den graphischen Benutzerschnittstellen aussehen, wie sie auf Eingaben reagieren und vor allem für komplexere "widgets", wie Daten dargestellt werden.
  • Im ersten Teil des Referates über Swing werden einige dieser Komponenten bzw. "widgets" vorgestellt und der allgemeine Vorteil von Swing-Komponenten gegenüber AWT erläutert! Einer der wichtigsten Vorteile von Swing ist der Gebrauch von MVC, welches ein Design-Pattern ist, was zur Bildung bzw. Erstellung von User Interfaces (UI) verwendet wird.!


  • 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 :

    Input -------> Processing -------> Output
    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.

  • Vorteile der Trennung von Zustand und Präsentation

    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 the symbols from AWT and Swing packages
    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);
    }
    }


  • Probleme und Alternativen


  • 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

  • Der Gebrauch von MVC:


  • Die Lösung für dieses Problem ist die MVC-Architektur.
    Die 1.Variante (schlecht):
  • Wir nennen zunächst das Model list.

  • textList und avgField stellen die beiden Views dar.

  • textField agiert als Controller, welcher Userinput dem list-Objekt übergibt.

    => 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 java.util.*;
    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 java.util.*;
    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 :

    /* Demonstrates use of MVC for GUI design: interaction
    * *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);
    }
    }

  • Die Beispielprogramme zum Downloaden
  • :
    1. Beispiel : SimpleEvents
    ListView
    IntVectorModel
    FirstMVC

  • Links:


  • Shortcours von Sun
    Weitere Tips und Erläuterungen


    Artin Avanes

    top

    sitemap