MVC trifft Swing

Eine gute Software-Benutzeroberfläche findet ihren Ursprung oft in den Benutzeroberflächen, die in der physischen Welt vorhanden sind. Betrachten Sie für einen Moment eine einfache Taste wie eine der Tasten auf der Tastatur vor Ihnen. Mit einem solchen Knopf gibt es eine saubere Trennung zwischen den Teilen, aus denen der Mechanismus des Knopfes besteht, und den Teilen, aus denen seine Fassade besteht. Der als Tastaturtaste bezeichnete Baustein besteht eigentlich aus zwei Teilen. Einmal Stück gibt es sein knopfartiges Verhalten. Das andere Stück ist für sein Aussehen verantwortlich.

Diese Konstruktion erweist sich als leistungsstarkes Designmerkmal. Es fördert eher die Wiederverwendung als die Neugestaltung. Da die Tasten Ihrer Tastatur so gestaltet wurden, können Sie das Design des Tastenmechanismus wiederverwenden und die Tastenoberseiten ersetzen, um eine neue Taste zu erstellen, anstatt jede Taste von Grund auf neu zu gestalten. Dies führt zu einer erheblichen Einsparung an Designaufwand und Zeit.

Es überrascht nicht, dass ähnliche Vorteile auftreten, wenn diese Technik auf die Softwareentwicklung angewendet wird. Eine häufig verwendete Implementierung dieser Technik in Software ist das Entwurfsmuster Model / View / Controller (MVC).

Das ist alles schön und gut, aber Sie fragen sich wahrscheinlich, wie dies mit den Komponenten der Swing-Benutzeroberfläche in den Java Foundation Classes (JFC) zusammenhängt. Nun, ich werde es dir sagen.

Während das MVC-Entwurfsmuster normalerweise zum Erstellen ganzer Benutzeroberflächen verwendet wird, wurde es von den Designern des JFC als Grundlage für jede einzelne Swing-Benutzeroberflächenkomponente verwendet. Jede Benutzeroberflächenkomponente (ob Tabelle, Schaltfläche oder Bildlaufleiste) verfügt über ein Modell, eine Ansicht und einen Controller. Darüber hinaus können sich Modell-, Ansichts- und Controller-Teile ändern, auch wenn die Komponente verwendet wird. Das Ergebnis ist ein Toolkit für die Benutzeroberfläche von nahezu unerreichter Flexibilität.

Lassen Sie mich Ihnen zeigen, wie es funktioniert.

Das MVC-Entwurfsmuster

Wenn Sie mit dem MVC-Entwurfsmuster nicht vertraut sind, empfehlen wir Ihnen, "Observer and Observable" zu lesen, einen meiner früheren Artikel, in dem dieses Thema ausführlicher behandelt wird und der die Grundlage für die Kolumne dieses Monats bildet.

Wie ich vorhin erwähnt habe, trennt das MVC-Entwurfsmuster eine Softwarekomponente in drei verschiedene Teile: ein Modell, eine Ansicht und einen Controller.

Das Modell ist das Teil, das den Zustand und das Verhalten der Komponente auf niedriger Ebene darstellt. Es verwaltet den Zustand und führt alle Transformationen in diesem Zustand durch. Das Modell kennt weder seine Controller noch seine Ansichten. Das System selbst verwaltet Verknüpfungen zwischen Modell und Ansichten und benachrichtigt die Ansichten, wenn das Modell den Status ändert.

Die Ansicht ist das Stück, das die visuelle Anzeige des vom Modell dargestellten Zustands verwaltet. Ein Modell kann mehr als eine Ansicht haben, dies ist jedoch im Swing-Set normalerweise nicht der Fall.

Der Controller ist das Teil, das die Benutzerinteraktion mit dem Modell verwaltet. Es bietet den Mechanismus, mit dem Änderungen am Status des Modells vorgenommen werden.

Am Beispiel der Tastaturtaste entspricht das Modell dem Mechanismus der Taste, und die Ansicht und der Controller entsprechen der Fassade der Taste.

Die folgende Abbildung zeigt, wie eine JFC-Benutzeroberflächenkomponente in ein Modell, eine Ansicht und einen Controller unterteilt wird. Beachten Sie, dass die Ansicht und der Controller in einem Stück zusammengefasst sind, eine übliche Anpassung des grundlegenden MVC-Musters. Sie bilden die Benutzeroberfläche für die Komponente.

Eine Schaltfläche im Detail

Um besser zu verstehen, wie sich das MVC-Muster auf die Komponenten der Swing-Benutzeroberfläche bezieht, gehen wir näher auf das Swing-Set ein. Genau wie im letzten Monat werde ich die allgegenwärtige Schaltflächenkomponente als Referenz verwenden.

Wir werden mit dem Modell beginnen.

Das Model

Das Verhalten des Modells in der obigen Schaltflächenabbildung wird von der ButtonModelSchnittstelle erfasst . Eine Schaltflächenmodellinstanz kapselt den internen Status einer einzelnen Schaltfläche und definiert das Verhalten der Schaltfläche. Seine Methoden können in vier Kategorien eingeteilt werden:

  • Internen Status abfragen
  • Internen Zustand manipulieren
  • Hinzufügen und Entfernen von Ereignis-Listenern
  • Feuerereignisse

Andere Komponenten der Benutzeroberfläche verfügen über eigene, komponentenspezifische Modelle. Sie alle bieten jedoch die gleichen Gruppen von Methoden.

Die Ansicht und Steuerung

Das Verhalten der Ansicht und des Controllers in der obigen Schaltflächenabbildung wird von der ButtonUISchnittstelle erfasst . Klassen, die diese Schnittstelle implementieren, sind sowohl für die Erstellung der visuellen Darstellung einer Schaltfläche als auch für die Verarbeitung von Benutzereingaben über Tastatur und Maus verantwortlich. Seine Methoden können in drei Kategorien eingeteilt werden:

  • Farbe
  • Geometrische Informationen zurückgeben
  • AWT-Ereignisse behandeln

Andere Komponenten der Benutzeroberfläche verfügen über eigene, komponentenspezifische Ansichten / Controller. Sie alle bieten jedoch die gleichen Gruppen von Methoden.

Das Gerüst

Programmierer arbeiten normalerweise nicht direkt mit Modell- und Ansichts- / Controller-Klassen. Tatsächlich ist für den zufälligen Beobachter ihre Anwesenheit verschleiert. Sie verstecken sich hinter einer gewöhnlichen Komponentenklasse - einer Unterklasse von java.awt.Component. Die Komponentenklasse fungiert als Klebstoff oder Gerüst, das die MVC-Triade zusammenhält. Viele der in der Komponentenklasse vorhandenen Methoden ( paint()z. B.) sind nichts anderes als Wrapper, die den Methodenaufruf entweder an das Modell oder an die Ansicht / den Controller weiterleiten.

Da die Komponentenklassen Unterklassen von Klassen sind Component, kann ein Programmierer Swing-Komponenten frei mit regulären AWT-Komponenten mischen. Da das Swing-Set jedoch Komponenten enthält, die die regulären AWT-Komponenten funktional nachahmen, ist ein Mischen der beiden normalerweise nicht erforderlich.

Ein konkretes Beispiel

Nachdem wir verstanden haben, welche Java-Klassen welchen Teilen des MVC-Musters entsprechen, können wir die Box öffnen und einen Blick hineinwerfen. Was folgt, ist eine verkleinerte Tour durch eine Reihe von Modellklassen, die gemäß den oben beschriebenen MVC-Prinzipien entworfen und gebaut wurden. Da die JFC-Bibliothek so komplex ist, habe ich den Umfang meiner Tour auf nur eine Benutzeroberflächenkomponente beschränkt (wenn Sie davon ausgehen, dass es sich um die handelt

Button

Klasse, du hättest recht).

Werfen wir einen Blick auf alle wichtigen Akteure.

Die ButtonKlasse

Der naheliegendste Ausgangspunkt ist der Code für die Schaltflächenkomponente selbst, da dies die Klasse ist, mit der die meisten Programmierer arbeiten werden.

As I mentioned earlier, the Button user interface component class acts as the scaffolding for the model and the view/controller. Each button component is associated with one model and one view/controller. The model defines the button's behavior and the view/controller defines its appearance. The application can change either at any time. Let's look at the code for making the change.

public void setModel(ButtonModel buttonmodel) { if (this.buttonmodel != null) { this.buttonmodel.removeChangeListener(buttonchangelistener); this.buttonmodel.removeActionListener(buttonactionlistener);

buttonchangelistener = null; buttonactionlistener = null; }

this.buttonmodel = buttonmodel;

if (this.buttonmodel != null) { buttonchangelistener = new ButtonChangeListener(); buttonactionlistener = new ButtonActionListener();

this.buttonmodel.addChangeListener(buttonchangelistener); this.buttonmodel.addActionListener(buttonactionlistener); }

updateButton(); }

public void setUI(ButtonUI buttonui) { if (this.buttonui != null) { this.buttonui.uninstallUI(this); }

this.buttonui = buttonui;

if (this.buttonui != null) { this.buttonui.installUI(this); }

updateButton(); }

public void updateButton() { invalidate(); }

Take a moment to peruse the rest of the Button class before you move on.

The ButtonModel class

The button model maintains three pieces of state information: pressed/not-pressed, armed/not-armed, and selected/not-selected. These are boolean quantities.

A button is pressed if the user has pressed the mouse button while the mouse cursor is over the button, but has not yet released the mouse button. The button is pressed even if the user drags the mouse cursor off the button.

A button is armed if the button is pressed and the mouse cursor is over the button.

Some buttons may also be selected. This value is typically toggled on and off by repeatedly pressing the button.

The code below shows the default implementation for the pressed state. The code for the armed and selected states are similar. The ButtonModel class should be subclassed in order to redefine the default behavior.

private boolean boolPressed = false;

public boolean isPressed() { return boolPressed; }

public void setPressed(boolean boolPressed) { this.boolPressed = boolPressed;

fireChangeEvent(new ChangeEvent(button)); }

The button model also notifies other objects (called event listeners) about interesting events that occur. From the code above we see that a change event is fired every time the button's state changes. Here's how it happens.

private Vector vectorChangeListeners = new Vector();

public void addChangeListener(ChangeListener changelistener) { vectorChangeListeners.addElement(changelistener); }

public void removeChangeListener(ChangeListener changelistener) { vectorChangeListeners.removeElement(changelistener); }

protected void fireChangeEvent(ChangeEvent changeevent) { Enumeration enumeration = vectorChangeListeners.elements();

while (enumeration.hasMoreElements()) { ChangeListener changelistener = (ChangeListener)enumeration.nextElement();

changelistener.stateChanged(changeevent); } }

Take a moment to review the remainder of the ButtonModel class before you move on.

The ButtonUI class

The button view/controller is responsible for presentation. By default it simply draws a rectangle of the same color as the background. Subclasses redefine these methods to provide alternate look-and-feel's -- such as Motif, Windows 95, and Java provided with the JFC.

public void update(Button button, Graphics graphics) { ; }

public void paint(Button button, Graphics graphics) { Dimension dimension = button.getSize();

Color color = button.getBackground();

graphics.setColor(color);

graphics.fillRect(0, 0, dimension.width, dimension.height); }

The ButtonUI class doesn't handle AWT events itself. Instead, it employs a custom event listener class to translate low-level AWT user interface events into the high-level semantic events the button model expects. Here's the code to install and uninstall the event listener.

private static ButtonUIListener buttonuilistener = null;

public void installUI(Button button) { button.addMouseListener(buttonuilistener); button.addMouseMotionListener(buttonuilistener); button.addChangeListener(buttonuilistener); }

public void uninstallUI(Button button) { button.removeMouseListener(buttonuilistener); button.removeMouseMotionListener(buttonuilistener); button.removeChangeListener(buttonuilistener); }

The view/controller classes are really just bundles of methods. They don't contain any of their own state. Therefore, many instances of Button can share a single instance of ButtonUI. This code distinguishes between buttons by adding a special button identifier argument to each function call.

Take a moment to review the rest of the ButtonUI class before you move on.

The ButtonUIListener class

This ButtonUIListener class assists the Button class in converting mouse and keyboard input into operations on the underlying button model.

The listener class implements the MouseListener, MouseMotionListener, and ChangeListener interfaces and handles for the following events:

public void mouseDragged(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

if (buttonmodel.isPressed()) { if (button.getUI().contains(button, mouseevent.getPoint())) { buttonmodel.setArmed(true); } else { buttonmodel.setArmed(false); } } }

public void mousePressed(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

buttonmodel.setPressed(true);

buttonmodel.setArmed(true); }

public void mouseReleased(MouseEvent mouseevent) { Button button = (Button)mouseevent.getSource();

ButtonModel buttonmodel = button.getModel();

buttonmodel.setPressed(false);

buttonmodel.setArmed(false); }

public void stateChanged(ChangeEvent changeevent) { Button button = (Button)changeevent.getSource();

button.repaint(); }

Take a moment to peruse the rest of the ButtonUIListener class before you move on.

The code in action

Für diejenigen unter Ihnen, die die Quellcodelisten oben etwas zu steril fanden, und für diejenigen unter Ihnen, die gespannt sind, wie dies alles zusammenpasst, präsentiere ich das Applet.

Sie benötigen einen Java-fähigen Browser, um dieses Applet anzuzeigen.

Für dieses Applet ist ein JDK 1.1-kompatibler Browser erforderlich. Internet Explorer 4.0 ist qualifiziert, Netscape 4.0 jedoch nicht - zumindest sofort. Wenn Sie Netscape 4.0 verwenden, surfen Sie zu //developer.netscape.com/software/jdk/download.html und aktualisieren Sie.