Universität Stuttgart Institut für Visualisierung und Interaktive Systeme Fachpraktikum Graphische Benutzungsoberflächen Aufgabe 4: Diagramm-Editor Mark Giereth In dieser Aufgabe werden Sie einen Editor für einfache UML Klassendiagramme realisieren. Sie lernen dabei den Umgang mit elementaren Java Swing Komponenten kennen und fügen diese zu einer Graphischen Benutzungsoberfläche zusammen. Dabei arbeiten Sie mit objektorientierten Entwurfsmustern, wie das ModelView-Controller (MVC) oder das Beobachter Muster, die in Swing allgegenwärtig sind. Sie implementieren Routinen zur EventBehandlung, um beispielsweise auf Benutzereingaben mit der Maus zu reagieren und entwickeln schließlich eine eigene Komponente, die das Zeichnen und Bearbeiten einfacher Diagramme erlaubt. Dabei lernen Sie das Java 2D Graphics API kennen. Wichtige Lernziele sind unter anderem • Swing Komponenten • Swing Event Handling • Objektorientierte Entwurfsmuster • Java2D API Programme nur laufen lassen möchten und keine Entwicklungstools benötigen. Eclipse Die Entwicklungsumgebung Eclipse unterstützt einige Aufgaben im Entwicklungsprozess und bietet eine graphische Oberfläche hierfür. Eclipse ist selbst zum größten Teil in Java programmiert und benötigt mindestes eine JRE in der Version 1.4. Weiter Informationen zu Eclipse erhalten Sie unter http://www.eclipse.org Anlegen eines neuen Projekts in Eclipse Um mit Eclipse arbeiten zu können, müssen Sie zunächst ein neues Java Projekt anlegen (File|New|Java Project). Geben Sie ihrem Projekt im nachfolgend angezeigten Dialog einen Namen und bestätigen Sie mit Finish. Es wird ein neues Verzeichnis in ihrem Workspace eingerichtet, das aller Dateien zum Projekt enthält. Java Im Gegensatz zu herkömmlichen kompilierten Programmiersprachen, erzeugt der JavaCompiler so genannten Bytecode. Damit dieser ausgeführt werden kann, führt nach der Übersetzungsphase ein Java Laufzeitumgebung, die Java Virtual Machine (JVM), den Bytecode aus. Mit dem Java Development Kit (JDK) lassen sich Java Programme entwickeln. Dem JDK sind Hilfsprogramme beigelegt, die für die Java-Entwicklung nötig sind, z.B. der Java Compiler javac, ein Programm zum Testen von Java Applets, appletviewer, oder ein Tool zum Generieren von HTML Seiten aus Programmkommentaren, javadoc. Beim Java Runtime Environment (JRE) ist nur die JVM zusammen mit den nötigen Bibliotheken gebündelt, für alle diejenigen, die Java- Als Ergebnis sehen Sie im Package Explorer Tab folgenden Eintrag (Test ist hier der Name des Projekts): Legen Sie nun ihr erstes Java Programm an. Erzeugen Sie eine neue Klasse, indem Sie im 1 Kontextmenü (Rechtsklick auf src Eintrag) New|Class auswählen. Es erscheint folgender Dialog: Java-API sind können unter [4] eingesehen werden. Diagram Editor In dieser Aufgabe soll ein einfacher DiagramEditor erstellt werden, der sich an der UML Notation für Klassendiagramme anlehnt. Der Editor soll es erlauben Diagramme zu neu erstellen und zu speichern. Bereits bestehende Diagramme sollen geladen und verändert werden können. Dazu sollen Sie im Rahmen dieser Aufgabe die Oberfläche sowie die notwendige Funktionalität realisieren. Der Diagramm Editor sollte wie folgt aussehen: Geben Sie als Package fapra.diagram an und als Name der Klasse Main. Bitte stellen Sie alle weiteren von Ihnen entwickelten Klassen in dieses Package. Die Main Klasse dient Ihnen später zum Starten der Anwendung. Selektieren Sie die Optionen wie oben ersichtilich. Das Ergebnis ist der folgender Java Code: package fapra.diagram; /** * @author * */ public class Main { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub } UML Klassendiagramm Ein UML Klassendiagramm ist eine Kollektion von Klassen, die Beziehungen untereinander haben können. Klassen werden als Knoten, Beziehungen als Kanten dargestellt. Ein Klassen-Knoten enthält weitere Unterabschnitte: Klassennamen, Attribute, Methoden. Attribute und Methoden werden zeilenweise untereinander geschrieben. } Fügen Sie in der main-Methode folgende Zeile ein: System.out.println("Hallo FaPra"); und speichern Sie. Speichern löst automatisch eine Neukompilierung aus. Starten Sie nun das Programm, indem Sie im Kontextmenü (Rechtsklick auf Main.java) Run As| Java Application auswählen. Es sollte eine Ausgabe („Hallo FaPra“) in einem Tab erfolgen, das den Namen Console hat. Sofern Sie noch keine Erfahrung mit der Programmierung in Java haben, sollten Sie sich per Tutorien und Bücher in das Thema einlesen. Gute Ausgangspunkte sind [1], [2] und [3]. Dokumentation zu allen Klassen die Teil der (Quelle: Wikipedia) Es gibt verschiedene Arten von Beziehungen, unter anderem: • Assoziation: beschreibt eine Beziehung zwischen zwei Klassen (auch zwischen mehreren Klassen; dies wird jedoch hier nicht berücksichtigt) 2 • Generalisierung: gerichtete Beziehung zwischen einer generelleren und einer spezielleren Klasse • Komposition: modelliert die Beziehung zwischen einem Ganzen und seinen Teilen • Aggregation: Spezialfall der Komposition, bei dem die Teile ohne das Ganze existieren können (auch schwache Komposition genannt) Beispiel: Die obige Abbildung zeigt zwei Klassen (Test und Log). Die Test Klasse besitzt ein Attribut vom Typ Log (ohne Angabe eines Attributnamens) und eine Methode main mit Argument String[]. Zwischen beiden Klassen besteht eine AssoziationsBeziehung. Die Klassen können per Maus im Diagrammbereich frei positioniert werden. Die Kanten werden dabei mitgeführt. Das Hauptfenster Um das Hauptfenster mit Inhalt zu füllen, erzeugen Sie zunächst eine JFrame Instanz. Der nachfolgende Code tut dies in Zeile 1. Nach der Initialisierung muss angegeben werden welche Aktion ausgeführt werden soll, wenn das Fenster geschlossen wird (unter Windows das rote X rechts oben). In unserem Fall soll zunächst die Anwendung beendet werden. Dieses Verhalten kann später abgefangen werden, um ein versehentliches Beenden der Anwendung zu vermeiden. Es wird anschließend die Fenstergröße festgelegt und das Fenster angezeigt, indem der Status auf visible=true gesetzt wird. (Quelle: [3]) Für die weitere Realisierung der graphischen Oberfläche werden folgende Swing Komponenten in das Hauptfenster integriert: • JMenuBar: die Hauptmenüleiste • JToolBar: die Werkzeugleiste • JPanel: der Content-Bereich in dem das Diagramm angezeigt wird ActionListener und Action Die Ereignisbehandlung von Buttons und davon abgeleiteten Kombonenten wie Menu Items, etc. wird in Swing durch ActionListener realisiert. Jede von Abstract Button Komponente, besitzt die Möglichkeit ActionListener Instanzen zu registrieren. Im folgenden Beispiel wird ein ActionListener für einen Button registiert (wie hier i.d.R. implementiert als anonyme Klasse). Wird der Button geklickt, so wird "clicked" ausgegeben. JButton b = new JButton("click me"); b.addActionListener(new ActionListener() { public void actionPerformed( ActionEvent e) { System.out.println("clicked"); } JFrame frame = new JFrame("FaPra2); ... frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE); frame.setSize(800, 600); frame.setVisible(true); Swing Fenster sind hierarchisch angeordnet wie folgende Abbildung zeigt (Quelle: Swing Tutorial) }); Im Diagramm-Editor soll zur Implementierung der Ereignisbehandlung das Action Interface verwendet werden. Dieses Interface erweitert das ActionListener Interface, indem es das Setzen und Auslesen von Eigenschaften wie Name, Icon, Tooltip, etc. bereitstellt. Als Erweiterung von ActionListener muss auch die actionPerformed(Action Event) Metode implementiert werden. Das folgende Codebeispiel zeigt die Erzeugung einer Action Instanz unter Verwendung der abstrakten Klasse AbstractAction. 3 public Action createAction() { Action a = new AbstractAction() { public void actionPerformed( ActionEvent e) { // TODO implement handler System.out.println("invoked "+a); } }; a.putValue(Action.NAME, "edit"); a.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_E); a.putValue(Action.SMALL_ICON, getIcon("edit.png"))); a.putValue(Action.SHORT_DESCRIPTION, "this is the tooltip"); return action; } Die Verwendung von Action ist insbesondere dann hilfreich, wenn mehrere unterschiedliche Möglichkeiten zum Triggern einer Funktion bereitgestellt werden, beispielsweise in Hauptmenü, Werkzeugleiste, Kontextmenü oder durch Tastaturkürzel. Bitte verwenden Sie sinnvolle im Programmskelett mitgelieferten Icons! Menü- und Werkzeugleiste Fügen Sie drei Menüs in die Menüleiste ein: • • das Menü „Datei“ mit Einträgen: o o o o „Neues Diagramm“ „Öffnen“ „Speichern“ „Exportieren als PNG“ o „Beenden“ das Menü „Bearbeiten“ mit Punkten: o „Neue Klasse“ o „Neue Beziehung“ o „Löschen“ Erstellen Sie anschließend eine Werkzeugleiste und fügen Sie dieser ebenfalls alle Aktionen hinzu. Verwenden Sie für die Einträge in der Werkzeugleiste dieselben Action Objekte wie in den Menüs. Während die Menüleiste direkt dem Hauptfenster zugeordnet ist, ist die Werkzeugleiste dem Content-Bereicht zugeordnet. In der Regel wird daher für den Content-Bereicht ein BorderLayout verwendet. JFrame frame; JMenuBar bar; JToolBar toolbar; // init... frame.setJMenuBar(bar); frame.setLayout(new BorderLayout()); frame.getContentPane().add(toolbar, BorderLayout.NORTH); Der Diagramm-Bereich Das bisher Beschriebene behandelte im Wesentlichen das Zusammenfügen bestehender Komponenten. Im Folgenden wird nun der spezifische Aufbau des von Ihnen zu entwickelnden Diagramm Editors beschrieben. Zu entwickeln ist ein DiagramPanel, das einfache UML Klassendiagramme anzeigt. Das Panel greift dabei auf ein UMLModel zu, das die Klassen und deren Beziehungen beschreibt. Ein UMLModel besteht aus zwei Arten von Elementen: Node Elemente (Klassen) und Edge Elemente (Beziehungen zwischen je zwei Klassen). UMLModel-Elemente besitzen wiederum Komponenten (NodeComponent und EdgeComponent), die diese darstellen. Folgendes Diagramm verdeutlicht den Sachverhalt. JLabel UMLModel • das Menü „Hilfe“ mit Eintrag: o „Info Über“ Das folgende Codefragment zeigt die Erzeugung eines Menüeintrags, eines Menüs und einer Menüleiste. JComponent extends extends Part-of Node Edge implements NodeComponent Has-model EdgeComponent Part-of JMenuBar bar = new JMenuBar(); JMenu menu = add(new JMenu("Datei")); Action a = ... create the Action menu.add(new JMenuItem(a)); // add others menu items... bar.add(menu); DiagramPanel extends JPanel 4 Aufgabe des DiagramPanel ist es das Zeichnen zu steuern, sowie Benutzereingaben zu delegieren. Bearbeiten von Klassen Nach dem Hinzufügen von Klassen zum UMLModel wird ein Mechanismus benötigt, um diese bearbeiten zu können. Das Bearbeiten von Klassen soll in userem Fall über einen Dialog (UMLClassDialog) erfolgen, in dem Attribute und Methoden hinzugefügt, gelöscht und bearbeitet werden können. Ferner soll der Klassenname änderbar sein. Die folgende Abbildung zeigt den Aufbau des zu entwickelnden Klassendialogs. Swing stellt einige Widgets bereit, die bereits konkrete Zeichenfunktionalität implementieren, z.B. das Zeichnen von Labels. Alle Swing Widgets erben von JComponent. Da die Diagramm-Knoten im wesentlichen Labels mit einem Rahmen sind, werden sie von JLabel abgeleitet. Für diese Aufgabe Diagramme wollen wir uns eine interessante Eigenschaft von Swing, nämlich die HTML Unterstützung innerhalb von Swing Komponenten, zu Nutze machen. protected void paintComponent(Graphics g) { // do initialization of g... View view = (View)getClientProperty( BasicHTML.propertyKey); if (view != null) { Rectangle bounds = getBounds(); Border b = getBorder(); view.paint(g, bounds); } // else no HMTL // do restoration of g } Wird der Dialog mit OK geschlossen werden die Änderungen übernommen, andernfalls werden die Änderungen verworfen und der alte Zustand bleibt erhalten. Zeichnen von DiagrammElementen Nachdem das UMLModel Elemente enthält und Klassen Attribute und Methoden, können diese in UML Notation im DiagramPanel gezeichnet werden. Der klassische Ablauf zum Zeichnen von Komponenten in Swing hierbei ist wie folgt. Nach beispielsweise einer Invalidierung des Zeichenbereicht wird die paint(Graphics) der entsprechenden Komponente aufgerufen (beispielsweise des DiagramPanel). Diese verteilt die Aufgabe auf drei Methoden: paintComponent(Graphics), paintBorder(Graphics) und paintChildren(Graphics). Alle Methoden bekommen den aktuellen Graphics Kontext übergeben und können unter Verwendung des Java Graphics 2D API beispielsweise Linien, Text, Polygone, etc. schreiben. Obiges Codefragment überschreibt die paintComponent(Graphics) Methode von JLabel. Wird ein JLabel mit einem HTML String initialisiert, so wird implizit ein View Object mit dem Label assoziiert. An dieses View Object wird nun das Zeichnen delegiert. Die Zeichenmethode benötigt als Argument noch den Bereicht in den sie Zeichnen soll, was in obigem Fall die Bounding Box des Labels ist. Bitte beachten Sie, dass die Bounding Box des Labels entsprechen initialisiert wurde. Dies erlaubt es auf sehr einfache Weise Klassenknoten im Diagramm nach UML Notation zu zeichnen. Die Test Klasse vom Anfang wurde beispielsweise mit folgendem HTML erzeugt: <html>Test<hr>Log<hr>+main(String[]) :void</html> Ihre Aufgabe für das Zeichnen von Klassen besteht somit im Wesentlichen in der Generierung von geeignetem HTML Code. Laden und Speichern von Diagrammen Das Laden und Speichern von Diagrammen als Java Object-Stream ist verhältnismäßig einfach, da Java die gesamte Funktionalität in der Klasse ObjectInputStream und Object OutputStream zur Verfügung stellt. 5 Implementieren Sie die Event Handler der entsprechenden Actions zum Speichern und Laden so, dass diese zunächst mit Hilfe des Standard-Datei-Dialogs den Dateinamen abfragen und die Datei dann serialisieren oder deserialisieren. Das Programmskelett Das Programmskelett für diese Aufgabe steht in Form eines Eclipse Projects zur Verfügung. Darin enthalten sind insbesondere folgende Dateien: • Main.java enthält die main()-Methode zum Starten des Diagramm-Editors, sowie Methoden zur Initialisierung von der Menü- und Werkzeugleiste. • ActionMap.java definitert Action Objekte, die die Interaktion mit der Anwendung realisieren. • DiagramPanel.java Panel, welches das Zeichnen der Diagramme steuert. Die View im Sinne von MVC. Verwendet ein UMLModel, das die Daten hält. • UMLModel.java Verwaltung von Knoten und Kanten des Diagramms. Das Model im Sinne von MVC. • MouseControler.java Reagiert auf entsprechende Maus Ereignisse. Der Controler im Sinne von MVC. • Messages.java Verwaltet den Zugriff auf Properties, die in eine Properties-Datei ausgelagert wurden. Internationalisierung Internationalisierung ist heutzutage ein wichtiger Schritt bei der Programmentwicklung. In Java werden lokalisierte Ressourcen, wie Zeichenketten, über sogenannte Property Dateien bereitgestellt. Literatur [1] Christian Ullenboom: „Java ist auch eine Insel“, Aufl. 6, Galileo Computing, 2007, erhältlich als eBook unter: http://www.galileocomputing.de/ openbook/javainsel6/ [2] Sharon Zakhour et al.: „The Java Tutorial“, Prentice Hall, 2006, erhältlich als eBook unter: http://java.sun.com/docs/books/ tutorial/ [3] The Swing Tutorial: http://java.sun.com/docs/books/tutorial/ uiswing/index.html [4] Java Platform Standard Edition 6 API: http://java.sun.com/javase/6/docs/api/ Programmierrichtlinien Bitte halten Sie sich beim Programmieren an die nachstehenden Richtlinien – die Richtlinienkonformität geht in die Bewertung Ihrer Lösung mit ein! Bitte halten Sie sich an die Java Code Conventions! Sie finden sie unter diesem Link: http://java.sun.com/docs/codeconv/ Sprache Kommentare und sämtliche Bezeichner sind in Englisch verfassen. Zeilenlänge und Einrückung Die Zeilenlänge darf 80 Zeichen nicht überschreiten. Die Einrückungstiefe beträgt vier Leerzeichen. Kurze Kommentare Kurze Kommentare dienen zur Verbesserung der Lesbarkeit des Codes: if (a == 2) { return TRUE; // special case } else { return isPrime(a); /* works only for odd 'a' */ } Der ersten Kommentar sollte beispielsweise nicht lauten: /* if a=2 then return true */. Klassen-, Methoden- und Attributkommentare Verwenden Sie die Javadoc Kommentare für Klassen, Attribute und Methoden. Wenn Sie über einer Deklaration „/**“ eingeben, erzeugt Eclipse ein Dokumentationsskelett automatisch. Bei Methoden sind die Parameter zu erklären (vorausgehende @param Anweisung). Weitere Informationen zu Javadoc finden Sie unter: http://java.sun.com/j2se/javadoc/writingdocc omments/index.html 6 /** * Adds a new method to this model. * @param modifiers values are +,#,* @param name name of the method * @param params method parameters * @param returnType type of the object returned by the method * @return the new String */ public String addMethod(String modifiers, String name, String[] params, String returnType) {... Namenskonventionen • Generell: Bitte „CamelCase“ verwenden, zum Beispiel CamelCaseLooksLikeThis. • Klassen und Interfaces: Einfache Substantive in UpperCamelCase. • Methoden und Attribute: Einfache Verben oder sprechende Bezeichnungen, in lowerCamelCase. • Variablen: Möglichst kurze „sprechende“ Bezeichnungen in lowerCamelCase. • Konstanten: Großbuchstaben mit „_“ als Worttrennzeichen wie zum Beispiel static readonly int MAX_WIDTH = 100; 7 Bewertungskriterien Punkte Der komplette Quellcode ist standardkonform und objektorientiert. Er enthält keine offensichtlichen Speicher- und Ressourcenlecks oder offensichtlich semantisch falsch verwendete Sprachkonstrukte. 1 Das Programm ist in sinnvoller Weise dokumentiert, insbesondere existieren Javadoc Kommentare für alle Klassen, Methoden und Attribute. 1 Das Programm lässt sich ohne Speicherschutzverletzungen, Exceptions oder andere Laufzeitfehler im VISGS-Pool ausführen. Das Programm befolgt die Programmierrichtlinien. Es lässt sich im VISGS-Pool mit den vorgegebenen Compiler-Einstellungen fehler- und warnungsfrei übersetzen (keine @SuppressWarnings!). 1 Javadoc HTML Seiten wurden erstellt. 1 Es gibt ein Menü und eine Toolbar die alle Programmfunktionen anbieten. Auswählbare Funktionen in Menü, Toolbar und Kontextmenü haben dieselben Namen, Icons, und Tooltips und beruhen auf Action Objekten. 1 Bei Rechtsklick auf die leere Diagramm-Fläche wird ein Kontextmenü mit den Einträgen "Neue Klasse" und "Neue Beziehung" angezeigt. Bei Rechtsklick auf eine Klasse ermöglicht das Kontextmenü die Auswahl zwischen: "Klasse Bearbeiten" und "Löschen". 1 "Bearbeiten" einer Klasse zeigt einen Dialog an, in dem Attribute und Methoden der Klasse hinzugefügt, gelöscht oder verändert werden können. Der Dialog soll zwei Listen verwalten in denen Attribute bzw. Methoden hinzugefügt, geändert und gelöscht werden können. 4 "Löschen" löscht alle selektierten Klassen sowie Beziehungen von und zu diesen Klassen. 2 Mit Linksklick auf eine Klasse wird diese selektiert. Alle anderen eventuell selektierten Klassen werden deselektiert. Selektierte Klassen werden in einer anderen Farbe dargestellt. 1 Mit Shift+Linker Maus Taste können mehrere Elemente gleichzeitig selektiert werden. Selektierte Elemente lassen sich gemeinsam verschieben und löschen. 1 Klassen können per Drag&Drop auf der Diagrammfläche verschoben werden. Die Kanten passen sich an. 2 Klassen werden in einer sich an UML orientierenden Notation präsentiert. Das Zeichnen der Klassen kann über HTML Labels erfolgen. 3 Es werden alle in der Dokumentation beschriebenen Beziehungsarten zwischen Klassen unterstütz. Diese werden UML-konform mit Pfeilspitzen bzw. anderen Symbolen angezeigt. Die Symbole richten sich entsprechend aus, wenn die Klassen verschoben werden. 5 Die Farbe von selektierten Klassen kann vom Benutzer gesetzt werden. Dabei kommt ein Standard-Farbauswahldialog zum Einsatz. 2 Ein Diagramm lässt sich unter Verwendung eines ObjectOutputStream in einer Datei speichern. Zur Auswahl des Dateinamens soll ein Standard-Datei-Speichern-Dialog verwendet werden. 1 Ein abgespeichertes Diagramm lässt sich unter Verwendung eines ObjectInput Stream aus einer Datei laden. Der Zustand soll derselbe wie vor dem Abspeichern sein. 1 8 Zur Auswahl des Dateinamens soll ein Standard-Datei-Öffnen-Dialog verwendet werden. Ein Diagramm lässt sich als PNG exportieren. Zur Auswahl des Dateinamens soll ein Standard-Datei-Speichern-Dialog verwendet werden. 1 Die gesamte Anwendung ist in Englisch und Deutsch verfügbar (alle Namen, Tooltips, Dialoge, etc.). Die Sprache kann im Programm mit einem Mechanismus Ihrer Wahl umgestellt werden. Die Realisierung beruht auf Resource Bundles. 1 Gesamtpunktzahl 30 9