GUI-Paradigmen in den Programmiersprachen XOJO und Java

Transcription

GUI-Paradigmen in den Programmiersprachen XOJO und Java
e-Journal of Practical Business Research
GUI-Paradigmen in den Programmiersprachen XOJO und Java:
Ein Vergleich jeweiliger sprachlogischer Abstraktionen der Grafikprogrammierung sowie der werkzeuggestützten Entwicklungsprozesse
Philipp B. Koch
Fachbereich Duales Studium Wirtschaft • Technik
Hochschule für Wirtschaft und Recht Berlin
Alt-Friedrichsfelde 60
10315 Berlin
[email protected]
Erschienen im e-Journal of Practical Business Research
unter: http://www.e-journal-of-pbr.info
Die Programmiersprachen Java und XOJO sind beide geeignet, um Anforderungen an die Entwicklung
moderner, plattformübergreifender grafischer Benutzeroberflächen gerecht zu werden. Hierbei sind
jedoch unterschiedliche Ansätze verwirklicht, die sich sowohl in den inhärenten logischen Konzepten
beider Sprachen als auch ihren Entwicklungswerkzeugen niederschlagen. Die vorliegende Arbeit untersucht einige dieser Unterschiede: Zum einen werden XOJOs Integrated Development Environment
(IDE) sowie Javas Eclipse IDE in Kombination mit E(fx)clipse und Scene Builder im Zusammenhang mit
der GUI-Programmierung betrachtet, und zum anderen XOJOs modularisierte GUI-Programmierung
mittels ContainerControls, sein Cloning-Mechanismus und das Paradigma der Event-getriebenen Programmierung den durch Property-Bindings, Listenern und Lambda-Ausdrücken zur Verfügung stehenden Möglichkeiten in Java / JavaFX gegenüber gestellt. Die Überlegungen werden jeweils praktisch
verdeutlicht in Form der Implementierung einer Beispielanwendung – dem „Tourist Remover“ – in jeder der beiden Sprachen. Diese Beispielimplementierungen sind in kompilierter Form und als Quelltexte abrufbar unter der Internet-Adresse https://github.com/philipp-koch/tourist-remover.
Zitation: Koch, Philipp B. (2016): GUI-Paradigmen in den Programmiersprachen XOJO und Java: Ein
Vergleich jeweiliger sprachlogischer Abstraktionen der Grafikprogrammierung sowie der werkzeuggestützten Entwicklungsprozesse. In: e-Journal of Practical Business Research, Sonderausgabe Informatikkonzepte 2 (7/2016). DOI: 10.3206/0000000062
Inhalt
1 Einleitung ........................................................................................................................... 1
2 Beispielanwendung: Der „Tourist Remover“........................................................................ 2
3 Werkzeugunterstützung des Entwicklungsprozesses............................................................ 5
3.1 Die XOJO-IDE .............................................................................................................................. 5
3.2 Eclipse, E(fx)clipse und Scene Builder........................................................................................ 8
4 Sprachlogische Abstraktionen der Grafikprogrammierung mit XOJO .................................. 12
4.1 Wichtige Paradigmen im Grafik-Kontext in XOJO.................................................................... 12
4.1.1 ContainerControls und Cloning ............................................................................................. 12
4.1.2 Event-gesteuerte Programmierung ...................................................................................... 13
4.2 Angewandt: GUI-Implementierung des „Tourist-Removers“ in XOJO .................................... 15
5 Sprachlogische Abstraktionen der Grafikprogrammierung mit JavaFX ................................ 17
5.1 Wichtige Paradigmen im Grafik-Kontext in JavaFX ................................................................. 17
5.1.1 Trennung von Präsentationsschicht, Datenmodell und Controller ....................................... 17
5.1.2 Property-Binding und Listener .............................................................................................. 19
5.2 Angewandt: GUI-Implementierung des „Tourist-Removers“ in JavaFX .................................. 20
6 Konklusion ....................................................................................................................... 22
Verwendete Quellen ............................................................................................................. 24
Literaturverzeichnis ......................................................................................................................... 24
Internetverzeichnis .......................................................................................................................... 25
Download-Quellen........................................................................................................................... 29
Anhang ................................................................................................................................. 30
Screenshot 1: Tourist Remover (XOJO) – ContainerControls .......................................................... 31
Screenshot 2: Tourist Remover (JavaFX) – Scene-Graph ................................................................ 32
Quelltexte............................................................................................................................. 33
Listing 1: Java – Main.java ............................................................................................................... 34
Listing 2: Java – Controller.java ....................................................................................................... 35
Listing 3: Java – MedianFilter.java................................................................................................... 43
Listing 4: Java – Thumbnail.java ...................................................................................................... 47
Listing 5: Java – MessageWindow.java............................................................................................ 52
Listing 6: Java – MainWindowSplit.fxml .......................................................................................... 52
Listing 7: Java – application.css ....................................................................................................... 57
Listing 8: XOJO – App ....................................................................................................................... 59
Listing 9: XOJO – MainWindow ....................................................................................................... 60
Listing 10: XOJO - ImageViewer ....................................................................................................... 68
Listing 11: XOJO – PictureSelecterSidebar....................................................................................... 74
Listing 12: XOJO - BottomBarControl ............................................................................................. 77
Listing 13: XOJO - InterruptableProgressBar ................................................................................... 78
Listing 14: XOJO – ZoomSliderControl ............................................................................................. 81
Listing 15: XOJO – Minipic ............................................................................................................... 83
Listing 16: XOJO – PictureZoomLevel .............................................................................................. 88
Listing 17: XOJO – Observable ......................................................................................................... 91
Listing 18: XOJO – Observer ............................................................................................................ 91
1
1 Einleitung
Die Möglichkeit einer Interaktion zwischen Benutzer und Computer war eine der basalen Voraussetzungen, ohne die dieser nie hätte zu derjenigen ‚universellen Rechenmaschine‘ werden können, die
seinen Vordenkern als damals noch ferne Vision vorschwebte1. Von der Mensch-Maschine-Interaktion
qua Lochkarte über die ersten an Fernseher anschließbaren und mit Tastaturen versehenen, via Bildschirmkonsole zu bedienenden tatsächlichen ‚Personal Computer‘ bis zur Bedienung mittels einer grafischen Benutzeroberfläche im modernen Sinne war es ein weiter Weg2. Die Etablierung des Graphical
User Interface (GUI) als vorherrschende Bedienform und die stetige Weiterentwicklung der technischen Umgebungen, in denen dieses zum Einsatz kommt – in jüngerer Zeit etwa auch auf Smartwatches, die ihrerseits als physische und logische Erweiterungen von Smartphone-Bildschirmen fungieren3
– ist eine Entwicklung, die sich als ‚GUI-Paradigma‘ verstehen ließe. In der vorliegenden Arbeit soll
jedoch nicht diese technische, sondern vielmehr die strukturelle Facette in den Fokus gerückt werden:
Mit dem Siegeszug der Desktop-Metapher, im Rahmen derer beispielsweise Zugänge zu Information
oder Operationen ikonographisch repräsentierbar und Programminstanzen in ‚Fenstern‘ topologisch
organisierbar wurden4, ist die Möglichkeit zur Implementierung grafischer Benutzeroberflächen ein
selbstverständlicher Bestandteil vieler moderner Programmiersprachen geworden; im Folgenden wird
vergleichend dargestellt, auf jeweils welche Weise wesentliche Aspekte der Grafikprogrammierung
konzeptuell und sprachlogisch in zweien dieser Programmiersprachen, XOJO und Java, verortet sind.
Betrachtet werden dabei zum einen die Bedeutung grafischer Oberflächen für den Entwurfsprozess
und deren Niederschlag in die Entwicklungswerkzeuge der jeweiligen Sprache (Kapitel 3). Zum anderen
beleuchtet die Arbeit wichtige sprachlogische Abstraktionen der Grafikprogrammierung, verstanden
als implizite Ausdrücke der jeweils zugrundeliegenden GUI-Paradigmen. Diese Überlegungen werden,
um sie greifbarer zu machen, auf der Folie einer in beiden Sprachen implementierten Beispielanwendung (vorgestellt im zweiten Kapitel) exemplifiziert, indem wesentliche Aspekte der im Anhang vollständig wiedergegebenen Quelltexte im konkreten Zusammenhang betrachtet werden (Kapitel 4 und
5). Angesichts des gegebenen Rahmens ist eine über das Kursorische hinausgehende Behandlung der
Thematik hier jedoch nicht zu leisten; untersucht wird aus ebendiesem Grund auch ausschließlich der
Bereich der Desktop-Grafikprogrammierung, wenngleich sowohl mobile Apps als auch Online-Applikationen mit beiden Sprachen ebenfalls realisierbar wären.5
1
2
3
Vgl. Zemanek, Heinz [2004], S. 149 ff. und Hellige, Hans Dieter [2004a], S. 423 ff.
Vgl. Reimer, Jeremy [2005] (siehe Internetverzeichnis).
Vgl. Goldsmith, Gibson [2013] (siehe Internetverzeichnis).
4
Für eine detaillierte Problematisierung der ‚Desktop‘-Metaphorik in Hinblick auf Entwurfsprozesse, mentale
Funktionsmodelle des Anwenders und Nutzerinteraktion vgl. Neale, Dennis C. / Caroll, John M. [1997].
5
Mit XOJO können neben Konsolen- und Desktopanwendungen für die Betriebssysteme Microsoft Windows, diverse Linux-Distributionen und Apple MacOS auch iPhone-Apps und Webanwendungen erstellt werden; vgl. XOJO
(Hrsg.) [2015a] (siehe Internetverzeichnis). Mit Java sind über Konsolen- und Desktopanwendungen (auf Plattformen, für die ein Java Runtime Environment bereitsteht) hinaus auch die Erstellung von Webanwendungen sowie
2
2 Beispielanwendung: Der „Tourist Remover“
Zugunsten anschaulicher, in wechselseitigem Zusammenhang stehender Beispiele (und damit der Vermeidung einer Präsentation ‚synthetischer‘ Code-Fragmente) orientieren sich die Erläuterungen dieser
Arbeit an zwei Implementierungen – einmal in XOJO und einmal in Java – ein und desselben Programms, dem ‚Tourist Remover‘. Dieser soll Abhilfe schaffen für das häufige fotografische Problem
einer unvermeidbaren Anwesenheit ungewollter Objekte in einer Fotografie, eben beispielsweise Touristenströme vor einer zu fotografierenden, hoch frequentierten Sehenswürdigkeit.6 Lässt sich nicht
vermeiden, dass sich unerwünschte Objekte vor dem eigentlichen Motiv befinden, und handelt es sich
bei diesen um bewegte Objekte (vorbeifahrende Autos, Tiere oder Menschen in Bewegung usw.), ist
ein grundsätzlicher Lösungsansatz, eine ganze Serie des Motivs zu fotografieren, wobei sämtliche Bilder den identischen Bildausschnitt abbilden und von gleicher Auflösung sind. Aus diese Serie wird ein
‚Bildstapel‘ (image stack) gebildet7, und von den Einzelbildern des Stapels werden jeweils diejenigen
Regionen in eine ‚Ergebniskomposition‘ übernommen, in welchen die unerwünschten Objekte nicht zu
sehen sind. Kann dies bei bestimmten Motiven noch mit vertretbarem Aufwand manuell mittels AlphaMaskierung auf dem Bildstapel erreicht werden8, ist im Falle sehr vieler ‚Störobjekte‘ jedoch eine automatisierte Lösung wünschenswert, die in Form der Anwendung eines Medianfilters auf dem Bildstapel bereitsteht.9 Unter einem Medianfilter im engeren Sinne wird in der algorithmischen Bildverarbeitung ein univariater (auf ein einziges Bild angewandter), nicht-linearer Rangordnungsfilter verstanden,
der zur Bildglättung eingesetzt wird.10 Im vorliegenden Zusammenhang ist jedoch vom multivariaten
Medianfilter die Rede, der also auf alle Bilder eines Stapels angewandt wird11,12.
Android-Development möglich; vgl. Ullenboom, Christian [2011], S.59 - 63.
6
Offenbar gab es tatsächlich einmal ein Online-Tool namens „Tourist Remover“, welches Touristen von Fotos
‚entfernen‘ konnte, beschrieben in Sander, Ralf [2006] (siehe Internetverzeichnis). Die dort besprochene Internetseite http://www.snapmania.com ist allerdings seit Juni 2014 nicht mehr in Betrieb.
7
Image Stacking ist ein Oberbegriff für eine Reihe von Techniken, die u.a. in der Medizin- / Biologie- / Physik- (Fokuskomposition bei mikroskopischen Bildern) und Astronomie-Fotografie (Bildentrauschung) sehr bedeutsam ist
und insgesamt ein großes Anwendungsspektrum abdeckt, so dass hier nicht weiter darauf eingegangen werden
kann. Ein schneller Überblick als Einstieg in die Thematik findet sich jedoch z.B. bei Wehner, Martin [o. J.] (siehe
Internetverzeichnis).
8
Eine anschauliche Beschreibung der manuellen Alphamaskierung auf dem Bildstapel zum Zweck der ‚TouristenEntfernung‘ findet sich bei Sawchuk, Darby [2006] (siehe Internetverzeichnis). Die dort beschriebene Technik lässt
sich natürlich auch ‚andersherum‘ anwenden, um z.B. Bildelemente intentional zu replizieren – so z.B. in Koch,
Philipp [2007] (siehe Internetverzeichnis).
9
Eine illustrierte Darstellung der Vorgehensweise bei der Verwendung der Bildbearbeitungssoftware Adobe Photoshop bietet Hoffmann, Michael [2010] (siehe Internetverzeichnis).
10
Vgl. Burger, Wilhelm / Burger, Mark James [2015], S.113 – 117 sowie Jähne, Bernd [2012], S.329 – 331 und
356 – 358.
11
Die Idee der (multivariaten) Medianfilterung wurde ursprünglich durch die Forschung im Bereich analoger Signalverarbeitung mit dem Ziel der Minimierung von Bildstörungen durch Übertragungsfehler motiviert. Eine detaillierte Darlegung der theoretischen und mathematischen Hintergründe geben Gabbouj, Moncef et al. [1992] (siehe
Internetverzeichnis).
12
Eine Illustration des Prinzips findet sich bei David, Patrick [2013] (siehe Internetverzeichnis). Unter „Interesting
3
Vor dem Hintergrund, dass digitale Grauwert- und Farbbilder der Farbtiefe k im RGB-Farbraum als
zweidimensionale Matrizen positiv-ganzzahligen Inhalts im Wertebereich von 0 bis 21/3𝑘 − 1 organi𝑛
siert sind13, stellt er eine Operation dar, bei welcher an jeder Bildkoordinate 𝑃𝑥,𝑦
die Farbwerte des
Tripels 𝑝𝑛 (𝑟, 𝑔, 𝑏) jedes Bildes P1 bis Pn des n-elementigen Bildstapels aufsteigend sortiert und der
jeweilige Medianwert in den betreffenden Farbkanal an der gleichen Koordinate des Ergebnisbildes 𝑃̃′
geschrieben werden:
p11,1
𝑃 =Median(P1 , P2 , …, Pn )=Median ([ ⋮
p11,y
̃′
⋯
⋱
⋯
p1x,1 p21,1
⋮ ],[ ⋮
p1x,y p21,y
pn1,1
⋯ p2x,1
⋱
⋮ ],…,[ ⋮
pn1,y
⋯ p2x,y
⋯
⋱
⋯
pnx,1
⋮ ]),
pnx,y
wobei
der
𝑚𝑖+1 , 𝑖 𝑢𝑛𝑔𝑒𝑟𝑎𝑑𝑒
Median von i geordneten Werten {𝑚1 , … , 𝑚𝑖 } definiert ist als 𝑚
̃ ={1
2
2
(𝑚 𝑖 + 𝑚 𝑖 +1 ) , 𝑖 𝑔𝑒𝑟𝑎𝑑𝑒.
2
2
Da der Median statistische Ausreißer (hier: seltene Farbwerte an einer gegebenen Koordinate, relativ
zu allen Farbwerten im Stapel an dieser Koordinate) eliminiert, enthält das ‚Ergebnisbild‘, sofern eine
hinreichende Anzahl ‚Quellbilder‘ im Stapel vorhanden ist, nur noch genau diejenigen Farbwerte, die
mehrheitlich unverändert bleiben in allen Bildern. Im Falle einer vielbefahrenen Schnellstraße etwa
ergibt die Anwendung des multivariaten Median-Filters das Bild einer leeren Straße:
Abbildung 1: Wird auf einen hinreichend umfangreichen Bildstapel der multivariate Median-Filter angewandt, besteht das
resultierende Bild vollständig aus den auf allen Quellbildern unveränderten Bildteilen – hier insbesondere den jeweils unbefahrenen Abschnitten der Straße. Der Stapel umfasst tatsächlich 30 Quellbilder, von denen hier stellvertretend nur vier wiedergegeben sind. Für die Bildquelle vgl. Unbekannter Autor [2007] (siehe Internetverzeichnis).
Die Beispielanwendung ‚Tourist Remover‘ soll es dem Nutzer ermöglichen, diverse Bilder von Festplatte zu laden, einen multivariaten Median-Filter auf sie anzuwenden und das Ergebnis gegebenenfalls zu speichern. Er soll anhand von Miniaturen der geladenen Bilder (‚Thumbnails‘) diese zur genaueren Betrachtung auswählen und einzelne Bilder auf Wunsch wieder entfernen können, damit sie nicht
als Teil des Bildstapels in den Filterprozess einfließen. Das jeweils ausgewählte Bild soll vom Nutzer
beliebig skaliert (‚Zoom-Funktion‘) und, sofern es nicht vollständig angezeigt werden kann, verschoben
werden können, so dass nichtsdestotrotz die Gesamtheit des Bildes betrachtet werden kann
(‚Schwenk-Funktion‘). Diese Funktionalität soll von einer grafischen Benutzeroberfläche bereitgestellt
werden, die qualitativ den Anforderungen an ein modernes Desktop-GUI standhält. Es ist jedoch über-
Side Effects“ wird hierbei explizit auf den Einsatz von median stacking zur Entfernung bewegter Bildelemente eingegangen.
13
Vgl. Burger, Wilhelm / Burger, Mark James [2015], S.9 – 12.
4
raschend schwierig, allgemeingültige Aussagen zu finden, worin diese nun aber konkret bestehen, handelt es sich doch um eine Thematik hoher Komplexität, die beispielsweise neben der Psychologie
menschlicher Informationsverarbeitung, verschiedenen visuellen und akustischen Informationspräsentationsstilen und Fragen der Usability und der Accessability noch eine Vielzahl weiterer Gesichtspunkte überspannt.14 So würde etwa schon alleine eine geeignete Behandlung des Einzelaspekts der
Barrierefreiheit den Rahmen dieser Arbeit um ein Vielfaches sprengen.15 Es sei insofern an dieser Stelle
eine eigene, unvollständige Auflistung von im vorliegenden Zusammenhang wesentlichen Punkten gestattet:




Vielzahl unterschiedlicher Auflösungen, Seitenverhältnisse und Pixeldichten (z.B. individuelle
DPI-Einstellungen auf Betriebssystemebene; Retina-Displays) moderner IT-Systeme: Notwendigkeit eines flexiblen / elastischen Designs.
Sicherstellung eines performanten Antwortverhaltens: Rechenintensive Aufgaben müssen
mittels Threading parallelisiert werden, so dass das GUI stets auf Anwenderaktionen reagiert,
sich die Anwendung also nicht vermeintlich ‚aufhängt‘, und das Stattfinden parallelisierter
Operationen muss für den Anwender erkennbar sein.
Einfachheit und Erwartbarkeit der Bedienung: Das GUI soll selbsterklärend sein und sich hinsichtlich Bedienlogik an den Konventionen des Zielsystems orientieren.
Programmaktionen sollen sowohl über den Mauszeiger als auch über Tastenkombinationen
(‚Shortcuts‘) aufrufbar sein.
Konkret lassen sich hieraus eine Reihe von Vorgaben ableiten: So soll die Anwendung die gesamte
Funktionalität in einer singulären Sicht zur Verfügung stellen (One-Page-Design), anstatt beispielsweise
mittels einer Tabbed Pane o.ä. separate Sichten für die Thumbnails und den Bildbetrachter zu haben
[Einfachheit der Bedienung]. Datei-Dialoge (Laden und Speichern von Fotos) sollen in modalen Fenstern platziert werden [Erwartbarkeit: Konvention unter Windows, MacOS und Linux]. Die Anwendung
benötigt zudem eine Menü-Leiste [Erwartbarkeit; Shortcuts]. Die Berechnung des multivariaten Median-Filters auf dem Bildstapel ist eine rechenaufwendige Operation, die ‚im Hintergrund‘ vonstattengehen soll; um für den Anwender im Rahmen seines konzeptionellen Modells16 jedoch die Nachvollziehbarkeit der Filterberechnung zu gewährleisten, soll deren prozentueller Fortschritt in einem Fortschrittsbalken angezeigt werden [Threading]. Der abhängig vom Bildstapel durchaus zeitaufwendige
Filterungsprozess soll vom Anwender mittels eines Knopfes (Button) gestoppt werden können, der jedoch nur sichtbar ist, solange er auch betätigt werden kann (d.h. solange ein Filterprozess läuft) [Threading, Erwartbarkeit]. Der Fortschrittsbalken, der zugehörige Abbruch-Knopf sowie ein Schieberegler
zur Festlegung der Zoomstufe des Bildbetrachters (Zoom-Slider) sollen in einer Statusleiste am unteren
Bildschirmrand platziert werden [Erwartbarkeit, Einfachheit der Bedienung]. Die Thumbnails sollen un-
14
Stellvertretend für die Fülle an entsprechender Literatur sei hier auf den guten, praxisnahen Überblick verwiesen, den Heinecke, Andreas M. [2012] bietet.
15
Einen Einstieg in die Thematik findet sich zum Beispiel bei Rudlof, Christiane [2006] (siehe Internetverzeichnis),
S.94 - 101.
16
Vgl. Norman, Donald A. [2013], S.25 – 31.
5
tereinander einspaltig angezeigt und mit einer Scrollbar bzw. dem Mausrad verschoben werden können, sofern sie nicht vollständig ins Fenster passen [Erwartbarkeit, Einfachheit der Bedienung]. Der
Bildbetrachter soll der Fensterhauptbestandteil sein, so dass ein möglichst großer Teil des gewählten
Bildes sichtbar ist; wird die Größe des Fensters verändert, soll er horizontal/vertikal ‚wachsen‘ bzw.
‚schrumpfen‘, während Menü- und Statusleiste nur horizontal und die Thumbnail-Leiste nur vertikal
reagieren [elastisches Design]. Der Anwender soll in Bezug auf das im Bildbetrachter dargestellte Bild
so agieren können, wie es bei Bildbetrachtern zu erwarten ist: Ist das Bild größer als das zur Verfügung
stehende Sichtfenster des Bildbetrachters (Viewport), soll der Bildausschnitt mittels gedrückter Maustaste verschoben werden können (panning), und die Skalierung des Bildes (zooming) soll über den
Schieberegler in der Statusleiste oder das Mausrad gesteuert werden können [Erwartbarkeit, Einfachheit der Bedienung]. Alle Aktionen, die über die Maus getätigt werden können, sollen auch als Menüpunkt verfügbar sein und anders herum; das beinhaltet implizit die Notwendigkeit von Kontextmenüs
(z.B. zum Entfernen einen Thumbnails per Rechtsklick anstatt über das Menü) [Einfachheit der Bedienung, Erwartbarkeit]. Es ergibt sich damit folgender jeweils mittels XOJO und Java umzusetzender GUIEntwurf:
_
FENSTERTITEL
X
DATEI | ANSICHT | ...
Menü-Leiste
passt sich Größenänderungen
des Fensters horizontal an
SCROLLBAR
Bildbetrachter
passt sich Größenänderungen des
Fensters horizontal und vertikal an
Statusleiste
passt sich Größenänderungen
des Fensters horizontal an
ZOOM-SLIDER
STOPP
Thumbnail-Leiste
passt sich Größenänderungen
des Fensters vertikal an
FORTSCHRITT
Abbildung 2: Strukturell-funktionaler GUI-Entwurf der ‚Tourist Remover‘-Anwendung. Die Pfeile markieren ein mögliches
Wachsen bzw. Schrumpfen der GUI-Elemente bei Veränderung der Fenstergröße.
3 Werkzeugunterstützung des Entwicklungsprozesses
3.1 Die XOJO-IDE
XOJO ist eine objektorientierte, kompilierte, streng-typisierte Programmiersprache, die syntaktisch einen BASIC-Dialekt darstellt, jedoch in einer modernen Sprache zu erwartende Spracheigenschaften
wie Vererbung, Polymorphismus, Operator-Überladung, Introspektion etc. voll unterstützt.17 Die Sprache wurde 1997 öffentlich verfügbar gemacht unter dem Namen „CrossBasic“, aus lizenzrechtlichen
Gründen jedoch bald darauf in „REALbasic“ umbenannt. Um den mit „BASIC“ verknüpften Ruch des
Unprofessionellen, Veralteten abzuschütteln, wurde der Name 2013 schließlich in „XOJO“ geändert,
17
Vgl. Perlman, Geoff [2010] (siehe Internetverzeichnis). Geblieben vom ‚BASIC-Erbe‘ sind lediglich etliche Schlüsselwörter wie „Dim“, „Do .. Loop“ usw. sowie die Namenskonventionen (so wird etwa Groß-/Kleinschreibung
nicht unterschieden, d.h. „object“, „Object“ und „OBJECT“ referenzieren das Gleiche); vgl. XOJO (Hrsg.) [2014a]
(siehe Internetverzeichnis), S.75.
6
ein Akronym aus „X-platform [Cross-platform] Object Orientation“.18,19 Ließen sich mit CrossBasic noch
auf dem Macintosh und in der Java-Laufzeitumgebung ausführbare Programme erzeugen,20 kann mit
der aktuellen XOJO-Version (wie in Fußnote 5 beschrieben) für diverse Plattformen entwickelt werden,
wobei die Programme auf dem Zielsystem nativ lauffähig sind, d.h. sie benötigen keine zusätzliche
Laufzeitumgebung.21 XOJO ist eine proprietäre, lizenzpflichtige Sprache, deren Quelltext nicht offengelegt ist. Die Herstellerfirma XOJO Inc. positioniert ihr Produkt als unkompliziert zu erlernende, insbesondere für Programmiereinsteiger geeignete Sprache, die sich eher an ‚Hobby-Programmierer‘ als
an professionelle Entwickler richtet22. XOJOs konzeptuelle Ähnlichkeit zu Microsofts VisualBasic ist unverkennbar, und der Hersteller weist nicht nur ostentativ auf diese Tatsache hin, sondern stellt sogar
die Software ‚VB Migration Assistant‘ zur Verfügung in der Absicht, VB-Programmierer zu einem Umstieg zu bewegen.23
XOJO besitzt eine eigene Entwicklungsumgebung (Integrated Development Environment – IDE).24 Nur
mittels dieser kann in der Sprache programmiert werden – alternative IDEs existieren nicht, und eine
Entwicklung mittels bloßem Texteditor, wie etwa in Java theoretisch durchführbar, ist nicht möglich.
Der Grund hierfür ist ein zentrales Charakteristikum XOJOs: Die eminente Betonung des visuellen Zugangs zum Programmcode, sowohl hinsichtlich des Umgangs damit als auch in Bezug auf den Entwicklungsprozess selbst. Wird beispielsweise ein neues, leeres Desktop-Projekt angelegt, enthält dieses
automatisch eine Window-Instanz (also die Repräsentation eines Anwendungsfensters), und eine Sicht
des GUI-Editors wird angezeigt, denn XOJOs Entwurfsprozess sieht inhärent zuerst die Gestaltung der
Programmoberfläche und erst anschließend, darauf aufbauend, die der fachlichen Logik vor. Dahinter
steckt die Idee, mittels des Rapid-Application-Development-Konzepts schnell zu präsentablen und testbaren Prototypen zu gelangen – diese können dann agil weiterentwickelt werden25,26, ähnlich wie beim
18
Vgl. XOJO (Hrsg.) [2014b] (siehe Internetverzeichnis), S.2.
19
Vgl. XOJO (Hrsg.) [2015b] (siehe Internetverzeichnis).
20
Vgl. MacObserver [1997] (siehe Internetverzeichnis).
21
Vgl. Lefebvre, Paul [2015a] (siehe Internetverzeichnis).
22
Die Unternehmensmission der Herstellerfirma XOJO Inc. – „Enabling ordinary people to create extraordinary
apps“ – unterstreicht diesen Anspruch. Zudem stellt der Hersteller eine 363 Seiten starke Einführung für Programmieranfänger („Introduction to Programming with XOJO“) sowie ein zugehöriges Begleitbuch für Lehrkräfte als
kostenlosen Download zur Verfügung: Rhine, Brad [2014a] und Rhine, Brad [2014b] (jeweils: siehe Internetverzeichnis).
23
Vgl. Lefebvre, Paul [2013a] und VB Migration Assistant [o. J.]. Das XOJO-Nutzhandbuch widmet dem Thema gar
einen eigenen Abschnitt, vgl. XOJO (Hrsg.) [2014e], S.70 – 73 (jeweils: siehe Internetverzeichnis).
24
Diese kann kostenlos heruntergeladen werden unter XOJO-IDE [2015] (siehe Internetverzeichnis). Hierfür ist
allerdings ein (ebenfalls kostenloses) Nutzerkonto Voraussetzung.
25
Vgl. Lefebvre, Paul [2015a] (siehe Internetverzeichnis).
Dieses Vorgehen berührt thematisch zugleich Aspekte des Human-Centered-Design-Ansatzes (HCD), die hier
jedoch nicht näher ausgeführt werden können; vgl. Norman, Donald A. [2013], S.8 ff.
26
7
ungleich bekannteren „Extreme-Programming“.27 In (den nicht gerade zahlreichen) einführenden Publikationen zu XOJO (bzw. REALbasic) wird daher auch durchweg zunächst der Prozess des GUI-Designs
behandelt, bevor überhaupt auf sprachlogische Aspekte wie Datenstrukturen und dergleichen eingegangen wird.28 Das GUI-Design in der XOJO-IDE ist ein visuell-interaktiver, kein textueller Vorgang: Mittels Drag-and-Drop werden Control-Klassen auf dem jeweiligen Elternelement (z.B. einem Window) abgelegt (und damit instanziiert), wobei die geeignete Positionierung – im Sinne einer Einhaltung einheitlicher Abstände, Ausrichtung mehrerer Controls an einer gemeinsamen Basislinie usw. – durch eingeblendete Hilfslinien und ein ‚Einrasten‘ der Objekte an bestimmten Positionen aktiv von der IDE unterstützt wird. Die Arbeit findet somit nach dem WYSIWYG-Prinzip stets ‚im Layout‘ statt. Eigenschaften
eines Elements – etwa seine Höhe und Breite, initiale Beschriftung oder Positionierung – werden mithilfe einer Seitenleiste manipuliert, welche sämtliche im aktuellen Kontext bearbeitbaren Eigenschaften eines Objekts auflistet und deren Editierung ermöglicht.29
Das Layout einer GUI hat in XOJO keine für den Programmierer sichtbare Code-Entsprechung – es existiert lediglich visuell, bearbeitbar mittels GUI-Designer-Sicht in der IDE. 30 Zwar sind die Elemente des
GUI, die Controls, natürlich zur Laufzeit mittels Programmcode manipulierbar – sie müssen hierfür
aber zur Entwurfszeit visuell hinzugefügt worden sein (ein Aspekt, der in Kapitel 4.1.1 erneut aufgegriffen wird). Entsprechend enthält der dem Anhang dieser Arbeit beigefügte Programmcode der
XOJO-Variante des ‚Tourist Removers‘ keinerlei solchen Code – initiale Positionierungen und sonstige
Beeigenschaftung des GUI sind ausschließlich in der IDE sicht- und bearbeitbar. Dieser oben bereits
erwähnte „visuelle Zugang zum Entwicklungsprozess“ geht tatsächlich sogar soweit, dass eine ‚Gesamtsicht‘ des Codes (z.B. sämtliche Methoden einer Klasse) gar nicht vorgesehen ist, so dass für die
Erzeugung des XOJO-Quelltext-Anhangs dieser Arbeit ein Drittanbieter-Tool31 notwendig war.
Die Organisation von Code in der XOJO-IDE erfolgt insofern anhand seiner Zugehörigkeit zu grafischen
Entitäten: Methoden, Events und Properties, die ein Control (das ja eine Klasse ist) besitzt, sind zwar
in einem entsprechenden Code-Tab bearbeitbar – sie werden aber in der Entwicklungsumgebung als
Kindelemente des entsprechenden Objekts in einer Baumdarstellung organisiert (links zu sehen in Abbildung 3) und sind nur über diese zu erreichen. Wird z.B. ein Button im GUI-Editor mittels Copy-andPaste dupliziert, besitzt das Duplikat ebenfalls sämtliche Eigenschaften (also auch: Methoden etc.) des
27
Vgl. Sommerville, Ian [2012], S.94 – 102.
28
Vgl. stellvetretend Ford, Jerry Lee [2006], S.19 – 33; Neuburg, Matt [2001], S.8 – 27; sowie Piereck, Burkhard
[2009], S.34 – 63.
29
Vgl. XOJO (Hrsg.) [2014c] (siehe Internetverzeichnis), S.10 – 18.
Dies war auch schon in REALbasic (das eine andere IDE hatte) der Fall, da es zu den grundlegenden Paradigmen
der Sprache zählt, einen visuellen Zugang zu Code zu emphasieren; vgl. Choate, Mark S. [2006], S. 219.
30
31
„XjPrint for XOJO Desktop“, vgl. XjPrint [2015] (siehe Internetverzeichnis).
8
Originals. Diese sind keine Referenzen, sondern tatsächliche Kopien, die unabhängig vom Original existieren und ohne Einfluss auf dieses verändert werden können.
Abbildung 3: Die Layout-Sicht der XOJO-IDE. Am rechten Rand befindet sich die Eigenschafts-Leiste, links eine Baumdarstellung der Klassen, Methoden und Properties innerhalb des Projekts. Am oberen Rand sind die Tabs zu sehen, mittels derer
sich verschiedene Sichten auf Layouts und Code-Teile verwalten und nebeneinander verwenden lassen.
Beim Bearbeiten von Code bietet die IDE wie jede moderne Entwicklungsumgebung eine Autovervollständigung und Syntax Highlighting. Programmcode wird automatisch entsprechend seiner Verschachtelungstiefe eingerückt, wobei eine manuelle Veränderung etwaiger Einrückungen durch den Programmierer nicht möglich ist; dadurch lässt sich einerseits stets zweifelsfrei erkennen, auf welcher Ebene
(z.B. innerhalb voneinander abhängiger IF-Abfragen) sich ein Codeabschnitt befindet – andererseits
bleibt dem Entwickler so zugleich ein nicht unwesentliches gestalterisches Mittel verwehrt, die logische Organisation des Codes individuell zu verdeutlichen. Die IDE bietet integrierte Unterstützung für
Unit-Tests.32 Bei Verwendung von Versionskontrollsystemen muss auf externe Lösungen zurückgegriffen werden, eine direkte Unterstützung aus der Entwicklungsumgebung heraus existiert nicht.33
3.2 Eclipse, E(fx)clipse und Scene Builder
Nachdem Javas erstes GUI-Framework „AWT“ mit zahlreichen Problemen zu kämpfen hatte, wurde
der Nachfolger „Swing“ entwickelt. Mit ihm standen viele zusätzliche GUI-Komponenten (z.B. Tabellen
und Baumdarstellungen) zur Verfügung, die in AWT gefehlt hatten, und die Gestaltungsmöglichkeiten
32
Vgl. XOJO (Hrsg.) [2014e] (siehe Internetverzeichnis), S.92 – 96.
33
Vgl. ebd., S.83 – 90 sowie Lefebvre, Paul [2015b] (siehe Internetverzeichnis).
9
wurden insgesamt vielfältiger.34 Doch auch Swing geriet in die Kritik, da beispielsweise vielen die Positionierung von Elementen als notorisch unintuitiv und umständlich gilt.35 Die schließlich durch JavaHersteller Oracle entwickelte Ablösung „JavaFX“ ist, zunächst eher halbherzig integriert, seit Java 8 Teil
des Java-Classpath und wird, insbesondere im Zusammenspiel mit den ebenfalls seit Java 8 hinzugekommenen Lambda-Ausdrücken, von vielen Entwicklern als vielversprechend eingeschätzt.36 Wenngleich Swing in der „vorhersehbaren Zukunft in Java erhalten bleiben“ soll, empfiehlt Oracle ausdrücklich die Verwendung von JavaFX 8, das Swing nach und nach ersetzen soll.37 In dieser Arbeit liegt der
Fokus insofern entsprechend auf JavaFX hinsichtlich des ‚Java-Teils‘ der bearbeiteten Fragestellung.
Und, anders als bei XOJO, besteht in Javas Fall auch bei der Frage des Programmierwerkzeugs die Qual
der Wahl: Es gibt derzeit mindestens 17 aktiv entwickelte integrierte Entwicklungsumgebungen für
Java.38 Als IDEs mit der größten Signifikanz in der Software-Industrie werden zumeist Eclipse, Oracles
NetBeans sowie JetBrains‘ IntelliJ IDEA genannt.39 Üblicherweise wird im professionellen Umfeld die
Wahl zugunsten einer bestimmten IDE von äußeren Faktoren gelenkt sein, beispielsweise in Form einer
Vorgabe durch den Arbeit- oder Auftraggeber. Die vorliegende Arbeit unterliegt keinen solchen äußeren Abhängigkeiten, so dass die Entscheidung letztlich arbiträr ist. Die Erläuterungen im Folgenden
beziehen sich auf Eclipse – offenbar nach wie vor die verbreitetste Entwicklungsumgebung für Java im
professionellen Umfeld40 –, die JavaFX-Programmierung wird aber selbstverständlich auch von den anderen beiden Lösungen unterstützt.
Die Eclipse-IDE, im November 2001 ursprünglich von IBM ins Leben gerufen, ist eine kostenlos nutzbare Open-Source-Anwendung, die seit 2004 unter der Schirmherrschaft der Eclipse Foundation entwickelt wird.41 Es gibt sie in verschiedenen Varianten, mit unterschiedlichen Spezialisierungen: Es existieren Versionen für die Entwicklung mit PHP, C bzw. C++ und eben Java, wobei für letzteres wiederum
eine ‚Classic-IDE‘ („Eclipse IDE for Java Developers“) und eine ‚Enterprise-Edition‘ („Eclipse IDE for Java
EE Developers“) zur Verfügung steht, abhängig davon, ob eine Entwicklung mit Java-EE-Komponenten
34
Vgl. Ullenboom, Christian [2011], S.1005 – 1010.
35
Vgl. Sierra, Kathy / Bates, Bert [2008], S.xiii.
36
Vgl. stellvertretend die sehr ausführliche, differenzierte Abwägung der Alternativen von Rauh, Stephan [2013]
sowie die diesbezügliche StackOverflow-Diskussion unter StackOverflow (Hrsg.) [2013a] (jeweils: siehe Internetverzeichnis).
37
Vgl. Oracle (Hrsg.) [o. J., a] (siehe Internetverzeichnis) – das Zitat wurde aus ebendieser Quelle sinngemäß ins
Deutsche übersetzt vom Autor.
38
Vgl. Wikipedia (Hrsg.) [2015] (siehe Internetverzeichnis).
39
So z.B. auch im ‘de-facto-Standardkompendium’ im Bereich der deutschsprachigen Java-Literatur, dem ‚InselBuch‘ von Ullenboom, Christian [2011], S.77 f. Die IDEs können bezogen werden über Eclipse [2015],
NetBeans [2015] und IntelliJ [2015] (jeweils: siehe Internetverzeichnis).
40
Vgl. ZeroTurnaround (Hrsg.) [2014] (siehe Internetverzeichnis).
41
Vgl. Eclipse Foundation (Hrsg.) [2015a] (siehe Internetverzeichnis).
10
beabsichtigt ist oder nicht.42 Tatsächlich wird eine noch größere Anzahl43 weiterer sogenannter Eclipse
Packages angeboten, worunter Installationspakete mit vorkonfigurierten Funktionalitäten verstanden
werden. Im vorliegenden Zusammenhang der Entwicklung einer Desktop-Anwendung mittels JavaFX
sind die in der ‚Classic-IDE‘ angebotenen Möglichkeiten vollkommen ausreichend.
Eclipse ist modular aufgebaut und kann über Plug-Ins beliebig erweitert werden. Eines dieser Erweiterungspakete trägt den Namen „e(fx)clipse“44 und dient dazu, Eclipse mit den erforderlichen Voraussetzungen ‚nachzurüsten‘, um auch JavaFX-Projekte vollständig integriert damit entwickeln zu können.
Nach der Installation stellt Eclipse einen neuen Projekt-Typus namens „JavaFX-Project“ zur Verfügung:
In derart angelegten Projekten erkennt Eclipse beispielsweise die JavaFX-spezifischen CSS-Selektoren,
vermag FXML-Definitionen zu interpretieren und kann mit Databinding von JavaFX-Properties umgehen.45,46 Beim Entwickeln von JavaFX-GUI-Anwendungen validiert Eclipse also beispielsweise CSS-Definitionen und schlägt über seine via <STRG> + <Leertaste> aufrufbare Autovervollständigungsfunktion
„ContentAssist“ jeweils im Kontext sinnvolle Optionen vor (z.B. einen Farbauswahldialog, wenn als Attribut ein Farbwert erwartet wird, der die gewählte Farbe anschließend als Hexadezimalrepräsentation
ausgibt, bzw. alternativ transparent und die diversen CSS-Farbliterale wie aliceblue, aquamarine
etc.). Auch wird beim Verweilen mit dem Mauszeiger über einer JavaFX-Klasse im Quelltext das jeweilige JavaDoc als Hilfe-Tooltip eingeblendet. Und beim (ebenfalls direkt innerhalb von Eclipse möglichen) Editieren von FXML-Dokumenten kennt ContentAssist im Kontext zulässige Attribute wie beispielsweise onAction oder mnemonicParsing. Insgesamt unterstützt Eclipse dank e(fx)clipse den Entwickler in allen JavaFX-spezifischen Zusammenhängen nahtlos und in der gleichen Weise, wie er es in
‚normalen‘ Java-Anwendungen bereits gewohnt ist.
Direkt von Java-Hersteller Oracle wird die kostenlose Anwendung „Scene Builder“ bereitgestellt.47 Es
handelt sich dabei um einen (selbst mittels JavaFX programmierten) grafischen Layout-Editor, der es
erlaubt, anhand von Drag-and-Drop visuell Layouts zu gestalten, die als FXML-Definitionen gespeichert
42
Die Einstiegsseite zu den jeweiligen detaillierten Beschreibungen befindet sich unter Eclipse Foundation (Hrsg.)
[2015b] (siehe Internetverzeichnis).
43
Eine Übersicht über die nicht weniger als dreizehn verschiedenen Packages und der in ihnen jeweils (nicht) enthaltene Funktionalitäten bietet Eclipse Foundation (Hrsg.) [2015c] (siehe Internetverzeichnis).
44
Download-Möglichkeiten und Installationsanleitungen für verschiedene Eclipse-Versionen werden bereitgestellt
unter E(fx)clipse [2015] (siehe Internetverzeichis). Das Plug-In ist quelloffen und kostenlos verwendbar.
45
Diese für JavaFX zentralen Themen – CSS-Selektoren, FXML, Properties und Databinding – werden im fünften
Kapitel näher erläutert.
46
Für detaillierte Ausführungen zu den einzelnen Funktionalitätserweiterungen vgl. BestSolution.at EDV Systemhaus (Hrsg.) [2015] (siehe Internetverzeichnis).
47
Ist es ohnehin schon notorisch schwierig, sich seinen Weg zu bahnen durch die verschlungenen Seitenstrukturen von Oracles Internetauftritt, findet selbst die aktuellste Scene Builder-Version sich ausschließlich im „JavaFX
Scene Builder Archive“ und nicht auf der Hauptseite aller anderen JavaFX-Downloads: vgl. Scene Builder 2.0
[2015] (siehe Internetverzeichnis).
11
werden können.48 Er stellt dabei in der linken Bildschirmleiste einen Katalog sämtlicher verfügbaren
Elemente dar, die gemäß der Layout-Logik von JavaFX beliebig zu eigenen GUIs – in JavaFX „Scenes“
genannt, daher der Anwendungsname – kombiniert werden können. In der gleichen Leiste wird außerdem stets der aktuelle Scene-Graph49 angezeigt, so dass es zusätzlich zur visuellen WYSIWYG-Repräsentation des Layouts auch eine strukturelle Sicht desselben gibt. Am rechten Bildschirmrand können
die (sehr umfangreichen) Eigenschaften des aktuell ausgewählten Elements dialogisch manipuliert
werden.
Scene Builder ist, sofern alles korrekt installiert wurde, dank e(fx)clipse mit Eclipse direkt verbunden:
Eine im Projekt vorhandene (oder in Eclipse mittels e(fx)clipse neu erstellte) FXML-Definition kann per
Rechtsklick in Scene Builder geöffnet und bearbeitet werden, wobei jegliche Änderungen, die in Scene
Builder am Layout vorgenommen werden, nach dem Speichern in der FXML-Quelltext-Ansicht von Eclipse automatisch aktualisiert werden bzw. andersherum textuelle Änderungen im FXML-QuelltextEditor nach dem Speichern sofort innerhalb Scene Builders in Form einer Aktualisierung des geöffneten
Layouts sichtbar werden. Auf diese Weise wird ein effizientes Arbeiten an GUI-Entwürfen ermöglicht,
bei dem – insbesondere in einer Multi-Monitor-Umgebung – das abwechselnd visuelle und textuelle
Bearbeiten eines Layouts sehr einfach durchführbar ist.
Abbildung 4: Die durch e(fx)clipse erreichte Verknüpfung zwischen Eclipse und Scene Builder ermöglicht – wie hier in einem
Dual-Monitor-Setup – gleichzeitig einen textuellen und einen visuellen Zugang zur JavaFX-GUI-Entwicklung. Wird in der IDE
(links) im FXML-Dokument etwas geändert, aktualisiert sich das angezeigte Layout in Scene Builder (rechts) unmittelbar
nach dem Speichern der Änderungen in Eclipse, und andersherum.
Die Anbindung von Scene Builder an Eclipse geht jedoch noch viel weiter: Sobald in Scene Builder die
Controller-Klasse des jeweiligen Projektes registriert wurde, sind in den (ebenfalls in der rechten Sei-
tenleiste, unter der Kategorie „Code“ befindlichen) Drop-Down-Menüs zum Eintragen etwaiger
Listener für spezifische Events eines Objekts die öffentlichen Methoden der Controller-Klasse aus-
wählbar. Und wird eine fx:id vergeben, die im Controller jedoch gar nicht injiziert werden kann, warnt
Scene Builder vor diesem Umstand.50
48
49
50
Vgl. Oracle (Hrsg.) [o. J., b] (siehe Internetverzeichnis).
Auf das Scene-Graph-Konzept wird in Kapitel 5 näher eingegangen.
Die Themen „Controller-Klasse“ und „fx:id“ werden in Kapitel 5 im Zusammenhang eingeordnet und eingehend
12
Eclipse ist, zusammenfassend, ein ausgereiftes, sehr komplexes Produkt, das den Ansprüchen professioneller Entwickler(-Teams) gerecht wird (und daher für Einsteiger leicht überfordernd wirken kann).
Aufgrund seiner Modularität und hohen Marktdurchdringung steht ein Füllhorn an Erweiterungen bereit, so dass es unzählige Aufgaben im Development-Kontext abzudecken vermag: So können etwa
direkt aus Eclipse heraus mit diversen Versionsverwaltungen wie z.B. Git oder CVS gearbeitet51, Abhängigkeiten zu externem Code auf Teamebene mittels „Apache Maven“ organisiert52 oder die Einhaltung teamweit definierter Coding Conventions überprüft53 werden, um nur drei Beispiele zu nennen.
Eclipse beherrscht zudem ein tiefgehendes ‚Verständnis‘ von Quelltexten, indem es die tatsächliche
Programmierlogik nachzuvollziehen in der Lage ist; Fehlermeldungen und Warnmeldungen identifizieren daher häufig treffsicher potenzielle oder tatsächliche Probleme – beispielsweise den Aufruf einer
statischen Methode über ein instanziiertes Objekt anstatt auf der Klasse selbst – und schlagen dem
Entwickler mögliche Lösungen vor, die zudem mittels Mausklick oft direkt umgesetzt werden können.
Es steht insofern mit Eclipse, zumal in Kombination mit e(fx)clipse und Scene Builder, eine hervorragende kostenlose integrierte Entwicklungsumgebung zur Verfügung, die den gesamten JavaFX-Entwicklungszyklus umfangreich unterstützt. Wie auch bei XOJO ist dank der Gewährleistung eines visuellen Zugangs zum Layout-Entwurf sowie der tiefen Integration aller Komponenten ein Beginnen mit
dem GUI-Entwurf, d.h. eine von der Nutzer-Interaktion her gedachten Entwicklung und damit eine intuitiv-kreative, iterative Arbeitsweise auch in der GUI-Entwicklung mit JavaFX sehr gut durchführbar.
4 Sprachlogische Abstraktionen der Grafikprogrammierung mit XOJO
4.1 Wichtige Paradigmen im Grafik-Kontext in XOJO
4.1.1 ContainerControls und Cloning
Wie in Kapitel 3.1 dargestellt, ist die visuelle Ausrichtung des Entwicklungsprozesses ein zentrales Charakteristikum XOJOs; beim Anlegen eines neuen Desktop-Projektes wird automatisch ein WindowObjekt angelegt, und „[t]ypically, you will begin designing your application’s interface by adding controls to this window and enabling the controls by writing code.“54 In XOJOs Klassenhierarchie sind
sämtliche Klassen von der Basisklasse Object abgeleitet, und Klassen, die eine visuelle Repräsentation
besitzen, sind entweder ein Control (PushButton, Label, TextField usw.) oder ein Window, während
rein funktionale Klassen wie z.B. Thread oder Timer direkt von Object abgeleitet werden.55 ControlObjekte müssen zur Entwurfszeit deklariert werden, indem sie auf dem entsprechenden Elternelement
besprochen.
51
Vgl. EGit [2015] und CVS for Eclipse [2015] (siehe Internetverzeichnis).
52
Vgl. Maven Integration in Eclipse [2015] (siehe Internetverzeichnis).
53
Vgl. Eclipse Checkstyle Plugin [2015] (siehe Internetverzeichnis).
54
XOJO (Hrsg.) [2014c] (siehe Internetverzeichnis), S.20.
55
Für eine Übersicht der Klassenhierarchie vgl. ebd., S.31.
13
(z.B. einem Fenster) abgelegt werden. Ihre Instanziierung erfolgt mit Aufruf dieses Fensters. Es ist (anders als bei direkt die Basisklasse Object spezialisierenden, nicht-visuellen Klassen) nicht möglich,
Controls dynamisch zur Laufzeit zu instanziieren – für eine objektorientierte Sprache sicher etwas
überraschend. Stattdessen müssen sie aus einem bestehenden Control gleichen Typs geklont werden,
wobei dieses zu klonende Control eben bereits zur Entwurfszeit hinzugefügt worden sein muss.56 Mehrere Controls können in einem ContainerControl zusammengefasst werden, das sich logisch wie ein
komplexes Control neuen Typs verwenden lässt und auch so ‚verhält‘ – es muss also ebenfalls geklont
statt instanziiert werden. ContainerControls können alle Arten von Klassen (also auch andere
ContainerControls oder nicht-visuelle Klassen) beinhalten. Sie selbst haben keine visuelle Entspre-
chung, lediglich ihre (visuellen) Inhalte sind zur Laufzeit sichtbar. ContainerControls werden, ebenso
wie Windows, in der XOJO-IDE erzeugt und dann visuell per Drag-and-Drop mit den gewünschten
Controls oder anderen Objekten ‚gefüllt‘.57
Objekte mit einer visuellen Repräsentation (Controls, Windows und ContainerControls) können zur
Entwurfszeit ausschließlich absolut positioniert werden, und zwar stets bezogen auf die linke obere
Ecke ihres unmittelbaren Elternelements. Allerdings können Controls an den vier Kanten des sie umgebenden Elternelements (einem Window oder ContainerControl) ‚festgemacht‘ werden, so dass bei
einer Veränderung der Fenstergröße das betreffende Objekt in die entsprechende Richtung ‚mitwächst‘ oder ‚schrumpft‘ – der Abstand zwischen dem Rand des Objekts und der Kante, an der es ‚befestigt‘ wurde, bleibt also konstant.58 Darüber hinausgehende (berechnete) Positionierungen, wie sie
für dynamische Layouts nötig sind, sind nur qua Code in einem entsprechenden Event Handler realisierbar.
4.1.2 Event-gesteuerte Programmierung
Jede Klasse in XOJO besitzt eine Reihe vordefinierter Events, die zur Laufzeit auftreten können: Ein
Thread hat beispielsweise ein „Run“-Event (er wurde gestartet), ein PushButton ein „Action“-Event (er
wurde angeklickt) und ein TextField ein „TextChange“-Event (der in ihm enthaltene Text wurde vom
Anwender verändert).59 Ein Event in XOJO ist im Grunde eine geerbte Methode, die (ggf. parametrisiert) automatisch aufgerufen wird, sobald ein auslösendes Ereignis auftritt: Fährt beispielsweise der
Anwender zur Laufzeit mit dem Mauszeiger über ein Control, wird dessen „MouseEnter“-EventHandler aufgerufen, sofern er definiert (d.h. in der IDE angelegt und ‚mit Code gefüllt‘) wurde; dabei wer-
den in diesem Fall als Parameter die X- und Y-Koordinate des Mauszeigers relativ zur linken oberen
56
Das Cloning wird dann vollzogen, indem via Dim ein Objekt der gewünschten Klasse deklariert und mittels NewOperator instanziiert wird, dabei allerdings typisiert als das zu klonende Objekt; vgl. XOJO (Hrsg.) [2013a] (siehe
Internetverzeichnis).
57
Vgl. XOJO (Hrsg.) [2014c] (siehe Internetverzeichnis), S.110 – 112.
58
Vgl. ebd., S. 15, Abschnitt „Locking Properties“.
59
Vgl. XOJO (Hrsg.) [2013b], XOJO (Hrsg.) [2012a] und XOJO (Hrsg.) [2012b] (jeweils: siehe Internetverzeichnis).
14
Ecke des Controls übergeben. Jedes Objekt erbt seine potentiellen Events von seiner Klasse und ggf.
deren Super-Klasse(n), so dass, je spezialisierter die Objektklasse ist, deren Anzahl nicht unbeträchtlich
sein kann.60 Über die klasseneigenen Events hinaus können eigene Events definiert werden. 61
Alle Window-Objekte besitzen unter anderem Events, die in Zusammenhang mit etwaigen Änderungen
ihrer Dimensionen auftreten: “Resizing” (währenddessen), “Resized” (nach Abschluss einer Größenänderung), „Maximize“, „Minimize“ und „Restore“ (Wechsel zwischen bildschirmfüllender und nur einen
Teil der Bildschirmfläche abdeckender Größe sowie Wechsel vom bzw. in den minimierten Zustand).62
Um dynamische Layouts zu realisieren – etwa, dass ein ContainerControl stets 70% der Breite seines
Eltern-Fensters besitzt –, muss sich in diesen Events entsprechender Code befinden, der die
ContainerControl-Breite bei jeder Größenänderung des es enthaltenden Fensters geeignet neu fest-
legt. Nicht allen Events gehen jedoch Anwender-Interaktionen wie ein Mausklick o.ä. voraus; Events
können auch mittels Code ausgelöst werden – eine Größenänderung des Fensters durch Veränderung
seiner Width-Eigenschaft zur Laufzeit löst ebenso ein „Resized“-Event aus, und Events können weitere
Events kaskadieren (ein „Maximize“-Event zieht z.B. immer auch ein „Resized“-Event nach sich).
Dadurch kann es zu schwierig zu debuggenden Seiteneffekten kommen63, zumal das Debugging selbst
unter Umständen ebenfalls Events auslösen kann (z.B. ein Window.LostFocus). Events, korrekt eingesetzt, ermöglichen dem Programmierer aber zugleich eine sehr unmittelbare Kontrolle über (unter anderem) die Grafikprogrammierung: Das „Paint“-Event etwa, das alle Klassen mit grafischer Repräsentation besitzen, wird immer genau dann aufgerufen, wenn das betreffende Objekt vom Betriebssystem
neu gezeichnet werden muss, da es (durch Überlagerung mit einem anderen Fenster, Positionsveränderungen und dergleichen) invalidiert wurde. Es wäre beispielsweise lediglich die Zeile If Me.Width >
500 Then Me.BackColor = &cFFFFFF Else Me.BackColor = &c000000 im Paint-EventHandler eines
Window-Objekts nötig, um dessen Hintergrundfarbe abhängig von der jeweiligen Fensterbreite auto-
matisch und in Echtzeit festzulegen.
In XOJO gibt es kein vordefiniertes Layout-Verhalten der grafischen Entitäten, wie dies etwa in JavaFX
mittels spezialisierter Panes der Fall ist (vgl. Kapitel 5.1.1). Sämtliche dynamischen Layout-Adaptionen
an veränderte Fenstergrößen usw. müssen von Hand mittels entsprechender Events implementiert
werden. Das ist einerseits sehr einfach und intuitiv, da das Event-Konzept leicht zu verstehen ist und
sich konsequent durch die gesamte Sprache zieht. Auf der anderen Seite zieht es aber eine Tatsache
nach sich, die im Sinne der Wartbarkeit eine wohlüberlegte Architektur von XOJO-Anwendungen noch
60
Es sind derzeit 361 mögliche Events definiert; vgl. XOJO (Hrsg.) [2011] (siehe Internetverzeichnis).
61
Vgl. XOJO (Hrsg.) [2014a], S.147 – 149 (siehe Internetverzeichnis) und Choate, Mark S. [2006], S. 220 – 224.
62
Vgl. XOJO (Hrsg.) [2014f] (siehe Internetverzeichnis).
63
Vgl. Ballman, Aaron [2009], S.88 f.
15
empfehlenswerter macht als ohnehin im Softwareentwicklungszusammenhang stets der Fall: Da Programme anhand ihrer visuellen Elemente strukturiert und gemäß ihrer Eventhandler organisiert werden, gibt es keine Trennung von Präsentationsschicht und funktionaler Schicht.64
4.2 Angewandt: GUI-Implementierung des „Tourist-Removers“ in XOJO
Im Sinne einer Exemplifikation der beschrieben Paradigmen wird im Folgenden auf ausgewählter Aspekte der Implementierung eingegangen. Angesichts des vorgegebenen Rahmens kann dies nur kursorisch geschehen; der Quelltext ist jedoch ausführlich kommentiert und dadurch insgesamt im Kontext selbsterklärend. Aus den in Kapitel 3.1 dargelegten Gründen ist ein tatsächliches Nachvollziehen
eigentlich aber nur über den Aufruf des Projekts in der XOJO-IDE wirklich sinnvoll65 – so sind beispielsweise zwar die Menu Handlers der Menüleiste im Quelltext sichtbar (vgl. Listing 9, Zeilen 7 – 47 und
Listing 10, Zeilen 7 - 53; Quelltextverweise werden fortan in der Form [L.9, Z.7 – 47] notiert), die Zuordnung der Accelerator Keys (Shortcuts) und die Sortierung der Menüeinträge hingegen erfolgt ausschließlich über die IDE, so dass sie ‚unsichtbar‘ bleibt im Code.
Das elastische Layout der Anwendung wird anhand der ContainerControls ImageViewer,
PictureSelecterSidebar und BottomBarControl realisiert, deren Instanziierungen im GUI-Designer
der IDE an den jeweils geeigneten Kanten des Elternfensters MainWindow ‚befestigt‘ wurden (im Quelltext nicht sichtbar): Auf diese Weise verhält sich das Layout bei einer Größenänderung von MainWindow
‚automatisch‘ so wie in Kapitel 2 beschrieben und Abbildung 2 dargestellt – zusätzlicher Code ist nicht
notwendig.66 BottomBarControl fasst hierbei als komplexes Elternelement die beiden ContainerControls ZoomSliderControl und InterruptableProgressBarControl zusammen, die wiederum in-
nerhalb von BottomBarControl ‚befestigt‘ wurden. Das Ein- und Ausblenden der Thumbnail-Leiste
(also von PictureSelecterSidebar, in MainWindow instanziiert als rightSidebar) wird über die Methode toggleRightSidebar() [L.9, Z.252 - 264] vollzogen: Bei Sichtbarkeit der Thumbnail-Leiste wird
deren Breite von der Breite des Bildbetrachters ImageViewer (instanziiert als imgViewer) abgezogen
[L.9, Z.256]; wird sie hingegen ausgeblendet (ihr Visible-Property auf False gesetzt), wird die Breite
des Bildbetrachters der Breite des Elterncontainers gleichgesetzt (Me referenziert in diesem Kontext
MainWindow) [L.9, Z.261].
Die Funktionalität des Bildbetrachters ImageViewer wird über die entsprechenden Events implementiert: Die Anzeige erfolgt, indem im entsprechenden Paint-Event bei jedem notwendigen Update des
Containers die Grafik in der jeweils aktuellen Skalierung gezeichnet wird [L.10, Z.64 – 82]. Das Schwen-
64
Vgl. Choate, Mark S. [2006], S.224 f.
Die Projektdatei ist (ebenso wie die Java- Quelltexte und die kompilierten Anwendungen) abrufbar unter
https://github.com/philipp-koch/tourist-remover
65
66
Screenshot 1 (siehe Anhang) verdeutlicht die Lage der genannten ContainerControls grafisch.
16
ken (Panning) des Bildausschnitts (via gedrückter Maustaste und ‚Ziehen‘) ist im MouseDrag-Event umgesetzt (L.10, Z.104 – 121]: Die Mausbewegung wird in einen horizontalen und vertikalen Offset-Wert
(Abstände des Bildausschnitts zur Bildkante) übersetzt, die bei besagtem Neuzeichnen im Paint-Event
Berücksichtigung finden, so dass der jeweils korrekte Bildausschnitt gezeichnet wird. Dieses Konzept
des Offsets kommt auch bei der Umsetzung des Scrollings etwaiger Thumbnails zum Einsatz: Der mittels Mausrad manipulierte vertikale Offset-Wert wird intern anhand des Scrollbalken-Wertes abgebildet und bei der Klasse Minipic (die die einzelnen Thumbnails zur Laufzeit repräsentiert) eine Positionsaktualisierung angefordert [L.11, Z.22 - 27]. Die einzelnen Thumbnails errechnen und setzen daraufhin ihre Position anhand dieses Offsets und ihrer Array-Indizes eigenständig [L.15 , Z.176 - 184].
Das Hinzufügen neuer Thumbnails wird über das Cloning einer prototypischen Minipic-Instanz – mini
– realisiert, die der PictureSelecterSidebar zur Entwurfszeit in der IDE hinzugefügt wurde [L.11, Z.55
– 65].
Die Skalierung des angezeigten Bildes kann sowohl per ZoomSliderControl (mittels Schieberegler) als
auch per ImageViewer (mittels Mausrad) verändert werden, implementiert in den entsprechenden
Events [L.14, Z.9 – 15 und L.10, Z.83 – 97]. Um bei beiden Controls stets einen identischen Zoom-Wert
zu garantieren, wird dieser in der Klasse PictureZoomLevel zentralisiert und mittels Observer-Pattern67
jede Änderung synchronisiert: Die beiden Controls implementieren das Observer-Interface [L.17], und
PictureZoomLevel entsprechend Observable [L.18].
Die Durchführung der multivariaten Median-Filterung [L.9, Z.145 – 202] stützt sich im Kern auf die
Klasse RGBSurface, welche eine Manipulation von Picture-Objekten auf Pixelebene ermöglicht, indem
sie deren Farbwerte als 2D-Matrizen abstrahiert, auf denen dadurch koordinatenbasiert – auch direkt
auf Farbkanalebene – operiert werden kann68 [L.9, Z.176 – 189]. Um ein ‚Einfrieren‘ des GUI während
des Filterprozesses zu verhindern, läuft dieser in einem eigenen Thread thr_performMultivariateMedianFilter ab, einem Property von MainWindow. Damit der Anwender einerseits über den Prozess-
fortschritt informiert ist und es ihm andererseits ermöglicht wird, die eventuell langwierige Berechnung auf Wunsch abzubrechen, enthält das GUI am unteren rechten Rand (als Teil der Zoom- und Statusleiste BottomBarControl) das ContainerControl InterruptableProgressBar: Dieses besteht aus
einem Fortschrittsbalken und einem Abbruch-Knopf img_stopSign, der jedoch nur während einer laufenden Berechnung eingeblendet wird. Der Fortschrittsbalken darf als GUI-Element nicht direkt aus
einem Thread heraus manipuliert werden69 - daher wird während des Filterprozesses der Fortschritt in
eine globale Variable geschrieben, welche ein Timer periodisch ausliest und den Wert des Fortschrittsbalken entsprechend setzt [L.13, Z. 18 – 20]. Der Timer prüft außerdem, ob der Filterungsprozess er-
67
Vgl. Gamma, Erich et al. [2013], S. 293 – 303, bzw. XOJO-spezifisch: Ballman, Aaron [2009], S. 454 – 458.
68
Vgl. XOJO (Hrsg.) [2013c] (siehe Internetverzeichnis).
69
Vgl. Lefebvre, Paul [2013b] (siehe Internetverzeichnis).
17
folgreich beendet wurde, um daraufhin das Ergebnis anzuzeigen und den Abbruch-Knopf auszublenden [L.13, 22 – 36] oder aber, im Fehlerfall, eine entsprechende Meldung auszugeben [L.13, Z.38 – 45].
Drückt der Anwender im Verlauf der Filterberechnung den Abbruch-Knopf, stoppt dessen MouseDownEvent sowohl Timer als auch Thread, setzt die Fortschrittsbalken-Anzeige wieder auf den Wert null und
blendet schließlich sich selbst aus [L.13, Z.8 – 16].
5 Sprachlogische Abstraktionen der Grafikprogrammierung mit JavaFX
5.1 Wichtige Paradigmen im Grafik-Kontext in JavaFX
5.1.1 Trennung von Präsentationsschicht, Datenmodell und Controller
JavaFX ermöglicht sehr einfach die Implementierung des Model-View-Controller-Patterns70, indem
eine getrennte Definition von Layout, fachlichem Datenmodell und funktionaler Steuerung der Anwendungslogik explizit vorgesehen ist: Zwar ist es möglich, GUI-Elemente direkt via Java-Code zu erzeugen
und zu gestalten71, doch empfiehlt Hersteller Oracle als Best Practice, die strukturelle Layout-Definition
in eine FXML-Beschreibung auszulagern, die visuellen Eigenschaften des Layouts mittels Cascading
Style Sheets (CSS) zu deklarieren und eine eigene Controller-Klasse zu verwenden, die vom restlichen
Code (dem Datenmodell) gekapselt ist.72 FXML ist eine DTD- und schemalose, XML-basierte, skriptbare
narrative Auszeichnungssprache für JavaFX-Scene-Graphen:73 Mit ihr kann die Baumstruktur eines JavaFX-GUI eindeutig beschrieben werden. Die JavaFX-Klassen zur Deklaration einer grafischen Oberfläche folgen der Metapher eines Theaterstücks: Die sichtbare Oberfläche, also die Summe aller grafischen Elemente, ist eine Szene (Scene), die auf einer Bühne (Stage) platziert wird. Mit Stage ist hier
das vom Betriebssystem zur Verfügung gestellte ‚Fenster‘ gemeint, oder genauer dessen Außenlinien
und Titelleiste, das als Container für das GUI (Scene) dient. Ein Stage-Objekt kann mehrere Scene-Objekte beherbergen, so dass ein Fenster (nacheinander) verschiedene Sichten eines GUI darstellen
kann.74 Die Scene-Klasse organisiert die sie konstituierenden Elemente als Graph, d.h. die Knoten (Nodes) eines Scene-Graphs sind als hierarchischer Baum organisiert.75
Ein Node kann ein Control (z.B. ein Textfeld, ein Button o.ä.) oder eine Pane sein; sämtliche von der
Klasse Pane abgeleiteten Spezialisierungen (BorderPane, HBox, StackPane usw.76) fungieren hierbei als
70
Vgl. Gamma, Erich et al. [2013], S. 4 – 6 und Sommerville, Ian [2012], S.484.
71
Für eine Übersicht der verschiedenen Möglichkeiten und ihrer jeweiligen Vor- und Nachteile vgl. StackOverflow
(Hrsg.) [2013c] (siehe Internetverzeichnis).
72
Vgl. Oracle (Hrsg.) [2014a] (siehe Internetverzeichnis).
73
Vgl. Oracle (Hrsg.) [2012a] (siehe Internetverzeichnis); für eine einführende Abgrenzung zu Dokumenttyp-Definitionen (DTDs) und Schema-Verwendung vgl. Harold, Elliotte Rusty / Means, W. Scott [2005], S.29 – 61 und 283
– 289. Konzeptuell erinnert FXML stark an Microsofts bereits 2008 eingeführte Extensible Application Markup Language (XAML); vgl. Theis, Thomas [2013], S. 483 – 510.
74
Vgl. Vos, Johan u.a. [2014], S. 12 – 14.
75
Vgl. Oracle (Hrsg.) [2015a] (siehe Internetverzeichnis).
76
Vgl. Oracle (Hrsg.) [2015b] (siehe Internetverzeichnis).
18
logische Container, die Nodes (also Controls oder andere Panes) aufnehmen können und diesen ihre
inhärenten, vordefinierten Layout-Eigenschaften vererben. Alle Nodes einer HBox werden beispielsweise horizontal nebeneinander, von links nach rechts, aufgereiht, und diejenigen einer VBox hingegen
vertikal, von oben nach unten, gestapelt. Nach dem ‚Matroschka-Prinzip‘ enthalten Panes also Kindknoten, die gemäß der Layoutregeln ihres Elternknotens ausgerichtet werden: Eine VBox, die zwei
HBox-Instanzen enthält, von denen wiederum jede drei Controls enthält, stellt beispielsweise ein zwei-
zeiliges Layout dar, wobei in jeder Zeile dreispaltig die jeweiligen Controls aneinandergereiht sind.
Eine besondere Funktion kommt der Node-Spezialisierung Region zu77: Sie kann verwendet werden als
Füllelement, das den verbleibenden Platz zwischen seinen benachbarten Nodes einnimmt – also als
‚mitwachsender, unsichtbarer Lückenfüller‘, der seine benachbarten Nodes soweit wie möglich auseinanderdrängt –, indem ihr in der FXML-Definition (oder mittels ‚Layout‘-Dialog in ‚Scene Builder‘) ein
vom Elternknoten ererbbares Grow-Verhalten zugewiesen wird – in einer HBox also beispielsweise
<Region HBox.hgrow="ALWAYS"/>. Auf diese Weise lässt sich in Kombination mit entsprechenden
Panes das gleiche Verhalten erreichen, das in XOJO mittels Locking von Controls erreicht wird (vgl.
Kapitel 4.1.1).
Das FXML-Dokument enthält idealerweise lediglich den strukturellen Aufbau, also den Scene-Graph
(wenngleich es möglich ist, Style-Anweisungen zu inkludieren). Die visuellen Eigenschaften dieser
Scene-Graph-Nodes, also der Komponenten eines konkreten GUI, werden in eine CSS-Definition ausgelagert, in welcher über Selektoren die Standard-Eigenschaften von Nodes überschrieben werden können.78 Identifiziert werden sie entweder anhand ihrer Klasse oder mittels ihres im FXML-Dokument
vergebenen eindeutigen Identifikators, der so genannten fx:id. Diese ist das Bindeglied, so dass die
in FXML, CSS und der Controller-Klasse definierten und manipulierten Aspekte ein und derselben Entität dieser eindeutig zugeordnet werden können: Um die drei Teile zusammenzufügen, wird zur Laufzeit zunächst die JavaFX-Umgebung initialisiert [L.1, Z.14], welche anschließend automatisch die
start()-Methode aufruft. In dieser wird zunächst die FXML-Definition geladen [L.1, Z.24]. Die hierfür
verwendete Klasse FXMLLoader injiziert in der im FXML definierten Controller-Klasse alle mittels
„@FXML“-Annotation gekennzeichneten und nach ihrer jeweiligen fx:id benannten Nodes [L.2, Z.41 –
68] mit den für sie via FXML definierten Eigenschaften. Daraufhin ruft der FXMLLoader die
initialize()-Methode des Controllers [L.2, Z. 79 – 118] auf, so dass etwaiger die Nodes ‚vorberei-
tender‘ Code (z.B. zum Setzen von Standardwerten o.ä.) ausgeführt wird. Der Programmablauf setzt
sich danach mit den auf den FXML-Ladevorgang folgenden Anweisungen in Main.start() fort: Der
77
78
Vgl. Oracle (Hrsg.) [2015c] (siehe Internetverzeichnis).
Eine Behandlung von Cascading Stylesheets (CSS) würde den Rahmen dieser Arbeit sprengen. Für einen umfassenden Überblick vgl. stattdessen Harold, Elliotte Rusty / Means, W. Scott [2005], S. 222 – 237, und für eine detaillierte, praxisnahe Beschreibung der Anwendung von CSS-Selektoren vgl. Menzel, Robert [2015] (siehe Internetverzeichnis).
19
Scene-Graph wird instanziiert und die CSS-Definitionen zugewiesen (L.1, Z.28 f.], so dass die Scene auf
dem Stage platziert [Z.33] und dieser (in Form eines Fensters) schließlich angezeigt werden kann
[Z.36]79. Dieses Vorgehen mag zunächst vergleichsweise komplex erscheinen, eröffnet in seiner Auffächerung dem Programmierer jedoch eine immense Flexibilität – so ist es beispielsweise denkbar, zur
Laufzeit die geladene CSS-Definition durch eine andere auszutauschen, wodurch ein Skinning von Anwendungen ohne die Notwendigkeit eines Neustarts realisierbar wird80 und dennoch visuelle (CSS),
strukturelle (FXML) und interaktionelle (Controller) Informationen separat gehalten werden können.
5.1.2 Property-Binding und Listener
Eine Klasseneigenschaft (Property) in Java wird im Sinne der Kapselung üblicherweise privat deklariert
und durch Getter- und/oder Setter-Methoden nach außen zugänglich gemacht.81 Auf Eigenschaften
können Listener registriert werden, die bei etwaigen Änderungen Code ausführen.82 In JavaFX sind
sämtliche Properties observable, d.h. es kann auf jedes JavaFX-Property ein ChangeListener registriert
werden. Zudem unterstützen sämtliche dieser Properties Binding, können also (uni- oder bidirektional)
an den Wert eines anderen Property gekoppelt werden.83 Soll lediglich unidirektional gebunden werden, kann dies direkt im FXML via fx:id und gewünschtem Property geschehen: <Label copy =
"${otherLabel.text}"/> wird vom FXMLLoader zur Laufzeit in eine Referenz aufgelöst, so dass das
Label copy stets den gleichen Wert hat wie otherLabel – ändert sich dieser, wird auch der Inhalt von
copy dynamisch aktualisiert.84 In JavaFX sind allerdings, und das ist das Besondere, sämtliche Proper-
ties Objekte, die mittels der High-Level Binding API weit über ‚herkömmliches‘ Binding hinaus genutzt
werden können: So kann beispielsweise das Ergebnis von Berechnungen, deren Ausgangswert das zu
bindende Property bildet, statt nur der bloße Wert des Property selbst gebunden werden, wobei Konkatenationen solcher Operationen unterstützt werden.85 Auf diese Weise lassen sich sehr elegant dynamische Layouts realisieren, ohne auf ChangeListener zurückgreifen zu müssen: currentImage.
translateXProperty().bind(zoomPane.widthProperty().subtract(currentImage.getBoundsInPar
ent.divide(2));.
EventHandler, die Code ausführen, sobald auf einem Node ein definiertes Ereignis auftritt, können als
Methodenreferenz direkt in Scene Builder eingetragen werden und werden beim Parsen der FXMLDefinition zur Laufzeit referenziert. Dadurch kann z.B. eine (im Controller bereits vorhandene) Methode, die aufgerufen werden soll, wenn der Anwender zur Laufzeit ein bestimmtes MenuItem der
79
Vgl. Abschnitte „Controllers“ und nachfolgende in Oracle (Hrsg.) [2012a] (siehe Internetverzeichnis).
80
Vgl. Oracle (Hrsg.) [2013a] (siehe Internetverzeichnis).
Vgl. Ullenboom, Christian [2011], S. 340.
81
82
Vgl. ebd., S. 562 - 565.
83
Vgl. Sharan, Kishori [2015], S. 31 – 39.
84
Vgl. Oracle (Hrsg.) [2012a], Abschnitt „Expression Binding“ (siehe Internetverzeichnis).
85
Vgl. Oracle (Hrsg.) [2014b] (siehe Internetverzeichnis).
20
MenuBar auswählt, in der IDE per Maus hinzugefügt oder geändert werden, wobei Scene Builder die in
Frage kommenden Methoden des Controllers ausliest und in einem Drop-Down-Feld zur Auswahl
stellt86 – dies kann die Wartbarkeit gerade bei komplexeren Projekten beträchtlich erhöhen. Wird der
EventHandler eines Nodes hingegen ‚traditionell‘ per Code in der initialize()-Methode des
Controllers registriert, lässt sich dies durch die seit Java 8 verfügbare Möglichkeit, hier Lambda-Aus-
drücke statt anonymer Klassen zu verwenden87, sehr modern, kompakt und leicht nachvollziehbar umsetzen: aButton.setOnAction((event) -> {System.out.println(“Der Knopf wurde gedrückt.”); });
5.2 Angewandt: GUI-Implementierung des „Tourist-Removers“ in JavaFX
Um das im zweiten Kapitel skizzierte ‚elastische Layout‘ zu realisieren, wird in einem erstem Schritt
mittels ‚Scene Builder‘ der Scene-Graph als Verschachtelung mehrerer Panes konstruiert und als FXMLDefinition gespeichert [Listing 6]: Den äußeren Rahmen bildet eine VBox, in der eine MenuBar, eine
SplitPane und eine HBox übereinander gestapelt werden. Die MenuBar beinhaltet die MenuItems (d.h.
die anklickbaren Einträge der Menüleiste), und die HBox ist ein Container für die Controls der Zoomund Statusleiste am unteren Rand. Die SplitPane trennt die Bereiche für den Bildbetrachter (eine
ScrollPane, die einen in eine Group verpackten ImageView enthält) und die Thumbnail-Leiste (eben-
falls eine ScrollPane, die eine VBox umschließt) voneinander. Zusätzlich sind die Nodes des Kontextmenüs jeweils als Kinder desjenigen Elternknotens vorhanden, auf dem sie aufrufbar sein sollen.88 Die
in der Statusleiste verwendete Region sorgt in der in Kapitel 5.1.1 beschriebenen Weise dafür, dass
sich der Slider und das Label für die Zoomstufen-Anzeige am linken Fensterrand und der AbbruchButton sowie die ProgressBar unabhängig von der Fensterbreite stets so weit rechts wie möglich be-
finden [L.6, Z.167]. MenuBar und Statusleiste nutzen implizit (durch Abwesenheit einer konkreten Angabe) als Höhe ihre computed size, d.h. genau diejenige Anzahl Pixel, die benötigt wird, um ihre enthaltenen Nodes vollständig anzuzeigen;89 der SplitPane hingegen wird ein vertikales Grow-Verhalten
zugewiesen [L.6, Z.89], so dass die in ihr enthaltenen Bereiche (Bildbetrachter und Thumbnail-Leiste)
bei Veränderungen der Fenstergröße mitwachsen bzw. –schrumpfen.
Die jeweils aktuelle Zoom-Stufe (Skalierung des angezeigten Bildes) wird in einem DoubleProperty
zoomProperty [L.2, Z.72] zentralisiert, auf welchem ein ChangeListener registriert wird, der die Einhal-
tung der zulässigen Unter- und Obergrenze für den Zoom überwacht, das angezeigte Bild geeignet
skaliert und sowohl Slider als auch Zoomstufen-Label aktualisiert [L.2, Z.142 - 157]. Der Wert des
Zoom-Schiebereglers wird an zoomProperty gebunden, und auch beim Zoomen via Mausrad wird nur
86
Vgl. CodeMakery (Hrsg.) [2014] (siehe Internetverzeichnis).
87
Vgl. BlueSkyWorkshop (Hrsg.) [2015] (siehe Internetverzeichnis).
Screenshot 2 (siehe Anhang) bietet eine Visualisierung des Scene-Graphs. Zur Wahrung der Übersichtlichkeit
sind MenuItems und ContextMenu dort jedoch nicht aufgeführt.
88
89
Vgl. Oracle (Hrsg.) [2013b] (siehe Internetverzeichnis).
21
dieses verändert [L.2, Z.82 – 102]; auf diese Weise wird ein Observer-Pattern-ähnliches Konstrukt geschaffen, durch welches die aktuelle Zoomstufe und die Schieberegeler-Position synchronisiert sind
und die Skalierung sowohl über Schieberegler, als auch über Mausrad oder Menübefehl bzw. Tastenkombination verändert werden kann.
Anders als in XOJO muss die Schwenk- und Zoom-Funktionalität nicht extra implementiert werden, da
die eingesetzten Klassen ScrollPane und ImageView diese ohnehin besitzen: Gezoomt werden kann
über eine einzelne Methode [L.2, Z.152 f.], und das Verschieben des Bildausschnittes durch den Anwender (Panning) bedarf gar keines eigenen Codes, es ist Bestandteil des ScrollPane-Layoutverhaltens: JavaFX berechnet hierbei selbständig den jeweils möglichen horizontalen und vertikalen
Offset des in der ScrollPane enthaltenen zu schwenkenden Nodes (hier das Bild in Form eines
ImageView). Allerdings muss besagter Node zwingend von einer Group gekapselt werden [L.6, Z.96 ff.],
damit für die Offset-Kalkulation die boundsInParent (d.h. die tatsächlichen, visuellen Abmessungen
des skalierten Bildes) und nicht die layoutBounds (also die theoretische, unskalierte Größe) des Nodes
herangezogen werden.90 Entsprechend muss auch für die Thumbnail-Leiste, die ja ebenfalls eine
ScrollPane als Elternknoten besitzt, kein eigenes Scroll-Handling geschrieben werden. Sie ist als ei-
gene, HBox spezialisierende Klasse Thumbnail umgesetzt [Listing 4]. Während die restliche Anwendung
mittels CSS visuell gestaltet wird [Listing 7], kommt in Thumbnail das nichtsdestotrotz mögliche InlineStyling zum Einsatz [L.4, Z.69 – 84], um das Thumbnail-Aussehen zu kapseln.
Abweichend von der XOJO-Variante des ‚Tourist Remover‘ kann in der JavaFX-Variante die Breite der
Thumbnail-Leiste anhand des SplitPane-Dividers mit der Maus zur Laufzeit verändert werden, wobei
die Größe der Bild-Miniaturen sich dynamisch anpasst. Diese automatische Größenanpassung wird
mittels Property-Bindings in nur vier Code-Anweisungen realisiert: Indem Höhe und Breite der einzelnen Thumbnails einmalig unidirektional an das prefWidthProperty des Elternknotens gebunden werden, zugleich den für die vorgesehenen Seitenränder (Padding) benötigten Abstand subtrahierend, reagieren die Thumbnails dauerhaft ‚automatisch‘ auf Veränderungen des ihnen zur Verfügung stehenden Platzes. Dies ist ein gutes Beispiel für den Einsatz der in Kapitel 5.1.2 beschriebenen konkatenierten Rechenoperationen auf gebundene Properties, wodurch die Verwendung von ChangeListeners in
JavaFX stellenweise obsolet wird [L.4, Z.88 – 93]. Auch die Umsetzung der GUI-Updates aus einem
Prozess91 heraus (also die Aktualisierung des Fortschrittsbalkens während der Medianfilter-Berechnung) funktioniert, indem einfach das von der ProgressBar eigens zur Verfügung gestellte
progressProperty an dasjenige des Prozesses gebunden [L.3, Z.100] und letzteres während der Be-
90
91
Vgl. Oracle (Hrsg.) [2014c] (siehe Internetverzeichnis).
Tatsächlich kommt hier die JavaFX-eigene Nebenläufigkeits-Implementierung javafx.concurrent.Task zum
Einsatz, welche die Auslagerung von Prozessen stark vereinfacht; vgl. Oracle (Hrsg.) [2012b] (siehe Internetverzeichnis) bzw. [L.3, Z.46 - 97].
22
rechnung mit dem jeweiligen Fertigstellungsgrad der Berechnung aktualisiert wird [L.3, Z.87]. Die Berechnung des Median-Filters selbst erfolgt mit einer zur XOJO-Implementierung analogen Logik: Mittels der PixelReader-Klasse werden an jeder Koordinate die Pixelwerte aller Bildes des Stapels in je
einen Array pro Farbkanal gespeichert und der Median jedes Arrays (Farbwertes) für das Pixel an derselben Koordinate des Zielbildes verwendet [L.3, Z.51 – 83].
6 Konklusion
Die Unterstützung der Programmierung grafischer Benutzeroberflächen ist ein wichtiger Aspekt vieler
moderner Programmiersprachen. Die inhärenten Konzepte hinsichtlich der Verknüpfungen zwischen
grafischen Entitäten und fachlicher Logik unterscheiden sich von Sprache zu Sprache teils erheblich. In
der vorliegenden Arbeit wurden zwei Ansätze solcher sprachlogischen Abstraktionen der Grafikprogrammierung verglichen: Anhand einer Desktop-Beispielanwendung (vorgestellt im zweiten Kapitel),
die separat in den Programmiersprachen XOJO und Java (unter Verwendung des UI-Toolkits JavaFX)
implementiert wurde, konnte die Bedeutung wichtiger Paradigmen im Grafik-Kontext für die GUI-Programmierung mit XOJO (Kapitel 4) und JavaFX (Kapitel 5) erläutert werden, nachdem die Werkzeugunterstützung des Entwicklungsprozesses im Zusammenhang der beiden Sprachen ins Verhältnis gesetzt
wurde (Kapitel 3). Es wurde deutlich, inwiefern XOJOs visueller Zugang zum Entwicklungsprozess, der
im Sinne eines Rapid Application Development-Vorgehens den Entwurf der grafischen Oberfläche an
den Anfang setzt und bei dem erst in einem zweiten Schritt die Unterfütterung des GUI mit fachlicher
Logik folgt, einerseits eine einfache Möglichkeit darstellt, durch iteratives Vorgehen rasch zu testbaren
Prototypen zu gelangen, während angesichts einer in ebendiesem Paradigma begründeten Vermischung von GUI- und Anwendungslogik andererseits Erwartungen an die Wartbarkeit und architektonischen Flexibilität hintangestellt werden müssen. Mit einem zugänglichen, schlanken Framework,
aber auch ohne tatsächliche Unterstützung verteilter Development-Prozesse ist als XOJOs Zielgruppe
vornehmlich der ambitionierte Programmiereinsteiger oder semi-professionelle Einzelentwickler zu
sehen. Kontrastiert wird dieses Bild vom ungleich komplexeren Java / JavaFX, für das gleich mehrere
hochprofessionelle, in Teams nutzbare Entwicklungsumgebungen zur Verfügung stehen, die nach
Maßstäben der Software-Industrie selbstverständliche Werkzeuge wie Versionsmanagement-Lösungen und dergleichen in mannigfaltiger Ausführung bereitstellen. Mit seiner konsequenten Trennung
zwischen strukturellen und visuellen UI-Aspekten sowie Steuerungslogik und fachlichem Datenmodell
ermöglicht JavaFX zwar vergleichsweise komplexe, zugleich jedoch hochflexible GUI-Entwürfe.
Javas Ruf, über altbackene (AWT) oder kompliziert zu handhabende UI-Toolkits (Swing) zu verfügen,
ist spätestens mit JavaFX 8 nicht mehr haltbar; ebenso wenig treffen jedoch im Falle XOJOs etwaige
häufig mit BASIC-Dialekten verknüpfte Vorurteile einer sprachlogischen Obsoleszenz zu: Für beide Programmiersprachen gibt es hinsichtlich ihrer Verwendung zur Entwicklung grafischer Benutzeroberflächen gute Argumente: z.B. Modularität programmierter Benutzeroberflächen durch ContainerCon-
23
trols, Intuitivität des Entwurfsprozesses (XOJO); Property-Binding, mächtiges Klassen-Ökosystem (JavaFX). Es obliegt damit dem Entwickler, angesichts gegebener Umgebungsbedingungen ein geeignetes
Werkzeug auszuwählen – endgültige Aussagen für oder wider eine Sprache können in ihrer Absolutheit
mit hoher Wahrscheinlichkeit nur fehlgehen.
24
Verwendete Quellen
Literaturverzeichnis
Ballman, Aaron [2009]: Ramblings on REALbasic, o. O. 2009.
Burger, Wilhelm / Burge, Mark James [2015]: Digitale Bildverarbeitung. Eine algorithmische Einführung mit Java, 3., vollständig überarbeitete und erweiterte Auflage, Berlin / Heidelberg 2015.
Choate, Mark S. [2006]: REALbasic. Cross-Platform Application Development, Indianapolis 2006.
Ford, Jerry Lee [2006]: Beginning REALbasic. From Novice to Professional, New York 2006.
Gamma, Erich et al. [2013]: Design Patterns. Elements of Reusable Object-Oriented Software,
Nineteenth Impression, New Delhi 2013.
Harold, Elliotte Rusty / Means, W. Scott [2005]: XML in a Nutshell, [Deutsche Ausgabe der] 3.
Auflage, Beijing u.a. 2005.
Heinecke, Andreas M. [2012]: Mensch-Computer-Interaktion. Basiswissen für Entwickler und Gestalter, 2. Aufl., Berlin / Heidelberg 2012.
Helander, Martin G. et al. (Hrsg.) [1997]: Handbook of Human-Computer Interaction, 2. Aufl.,
Amsterdam u.a. 1997.
Hellige, Hans Dieter (Hrsg.) [2004b]: Geschichten der Informatik: Visionen, Paradigmen, Leitmotive, 1. Aufl., Berlin / Heidelberg / New York 2004.
Hellige, Hans Dieter [2004a]: „Die Genese von Wissenschaftskonzepten der Computerarchitektur. Vom ‚system of organs‘ zum Schichtenmodell des Designraums“, in: Ders. (Hrsg.): Geschichten der Informatik: Visionen, Paradigmen, Leitmotive, 1. Aufl., Berlin / Heidelberg / New York
2004, S.411 - 472.
Jähne, Bernd [2012]: Digitale Bildverarbeitung und Bildgewinnung, 7., neu bearbeitete Aufl., Berlin / Heidelberg 2012.
Neale, Dennis C. / Caroll, John M. [1997]: “The Role of Metaphors in User Interface Design”, in:
Martin G. Helander et al. (Hrsg.): Handbook of Human-Computer Interaction, 2. Aufl., Amsterdam
u.a. 1997, S.441 – 462.
Neuburg, Matt [2001]: REALbasic: The Definitive Guide, 2. Auflage, Beijing u.a. 2001.
Norman, Donald A. [2013]: The Design of Everyday Things. Revised and expanded edition,
New York 2013.
Piereck, Burkhard [2009]: REALbasic für Einsteiger und Umsteiger, Pfäffikon SZ 2009.
Sharan, Kishori [2015]: Learn JavaFX 8. Building User Experience and Interfaces with Java 8,
New York 2015.
Sierra, Kathy / Bates, Bert [2008]: Java von Kopf bis Fuß, 3., korrigierte Aufl. 2008.
Sommerville, Ian [2012]: Software Engineering, 9., aktualisierte Auflage, München u.a. 2012.
Thies, Thomas [2013]: Einstieg in Visual C# 2012, 2., aktualisierte und erweiterte Auflage,
Bonn 2013.
25
Ullenboom, Christian [2011]: Java ist auch eine Insel. Das umfassende Handbuch, 9., aktualisierte
Auflage, Bonn 2011.
Vos, Johan u.a. [2014]: Pro JavaFX 8. A Definitive Guide to Building Desktop, Mobile, and Embedded Java Clients, New York 2014.
Zemanek, Heinz [2004]: „Konrad Zuse und die Systemarchitektur, das Mailüfterl und der Turmbau zu Babel. Ein Essay“, in: Hans Dieter Hellige (Hrsg.): Geschichten der Informatik: Visionen, Paradigmen, Leitmotive, 1. Aufl., Berlin / Heidelberg / New York 2004, S.141 – 170.
Internetverzeichnis
BestSolution.at EDV Systemhaus (Hrsg.) [2015]: „About“ (2015), Abschnitt „Tooling“, URL:
http://www.eclipse.org/efxclipse/index.html, letzter Abruf am 20.08.2015.
BlueSkyWorkshop (Hrsg.) [2015]: “Java 8: Lambda Expression Basics” (2015),
URL: https://blueskyworkshop.com/topics/Java-Pages/lambda-expression-basics/,
letzter Abruf am 24.08.2015.
CodeMakery (Hrsg.) [2014]: “JavaFX 8 Event Handling Examples” (03.05.2014), URL:
http://code.makery.ch/blog/javafx-8-event-handling-examples/, letzter Abruf am 23.08.2015.
David, Patrick [2013]: “A Look at Reducing Noise in Photographs Using Median Blending”
(29.05.2013), URL: http://petapixel.com/2013/05/29/a-look-at-reducing-noise-in-photographsusing-median-blending/, letzter Abruf am 15.08.2015.
Eclipse Foundation (Hrsg.) [2015a]: „About the Eclipse Foundation” (2015), URL:
http://www.eclipse.org/org/, letzter Abruf am 19.08.2015.
Eclipse Foundation (Hrsg.) [2015b]: “Desktop IDEs” (2015), URL: http://www.eclipse.org/ide/,
letzter Abruf am 19.08.2015.
Eclipse Foundation (Hrsg.) [2015c]: “How To Combine Packages” (2015), URL:
http://www.eclipse.org/downloads/compare.php, letzter Abruf am 19.08.2015.
Gabbouj, Moncef et al. [1992]: “An Overview of Median and Stack Filtering” (1992), in: Circuits
Systems Signal Process, Vol. 11, 1/1992, URL: http://www.researchgate.net/publication/226454286_An_overview_of_median_and_stack_filtering (1992), letzter Abruf am
15.08.2015.
Goldsmith, Gibson [2013]: „The Smartwatch: A Useful Extension of the Smartphone”
(10.12.2014), URL: http://www.beyondtheflock.com/node/84, letzter Abruf am 12.08.2015.
Hoffmann, Michael [2010]: “Automatic Tourist Remover” (27.07.2010), URL:
http://www.tipsquirrel.com/automatic-tourist-remover/, letzter Abruf am 15.08.2015.
Koch, Philipp [2007]: Unbetiteltes Bild, URL: http://drei-celix.de/assets/images/Bilder/panobasic/schiller.jpg (2007), letzter Abruf am 15.08.2015.
Lefebvre, Paul [2013a]: “A Great Alternative to Visual Basic” (19.06.2013), URL:
http://www.xojo.com/blog/en/2013/06/a-great-alternative-to-visual-basic.php?lang=en,
letzter Abruf am 18.08.2015.
Lefebvre, Paul [2013b]: “Accessing the User Interface from a Thread” (24.06.2013), URL:
http://blog.xojo.com/2013/06/24/accessing_the_user_interface_from_a_thread/,
letzter Abruf am 22.08.2015.
26
Lefebvre, Paul [2015a]: “Rapid Application Development” (22.07.2015), URL:
http://blog.xojo.com/rapid-application-development, letzter Abruf am 18.08.2015.
Lefebvre, Paul [2015b]: “Source Control Solutions” (28.07.2015), URL:
http://blog.xojo.com/source-control-solutions, letzter Abruf am 18.08.2015.
MacObserver [1997]: „CrossBasic: Shareware BASIC Environment” (16.06.1997), URL:
http://www.macobserver.com/archive/1997/june.shtml, letzter Abruf am 18.08.2015.
Maharjan, Narayan G. [2012]: “Customize ScrollBar via CSS” (11.07.2012),
URL: http://blog.ngopal.com.np/2012/07/11/customize-scrollbar-via-css/,
letzter Abruf am 11.08.2015.
Menzel, Robert [2015]: “CSS-Selektoren” (20.08.2015), URL: http://blog.kulturbanause.de/
2012/07/css-selektoren/, letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [2012a]: “Introduction to FXML” (21.06.2012),
URL: http://docs.oracle.com/javafx/2/api/javafx/fxml/doc-files/introduction_to_fxml.html,
letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [2012b]: “Concurrency in JavaFX” (2012), URL: http://docs.oracle.com/
javafx/2/threads/jfxpub-threads.htm, letzter Abruf am 24.08.2015.
Oracle (Hrsg.) [2013a]: “How to update stylesheets at runtime?” (07.08.2013),
URL: https://community.oracle.com/thread/2566519, letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [2013b]: “Working With Layouts in JavaFX. 2 Tips for Sizing and Aligning Nodes”
(2013), URL: https://docs.oracle.com/javafx/2/layout/size_align.htm,
letzter Abruf am 24.08.2015.
Oracle (Hrsg.) [2014a]: “Implementing JavaFX Best Practices” (2014),
URL: https://docs.oracle.com/javafx/2/best_practices/jfxpub-best_practices.htm,
Abschnitte “Best Practice: Enforce Model-View-Controller (MVC) with FXML” und “Best Practice:
Use Cascading Style Sheets (CSS)”, letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [2014b]: “JavaFX: Properties and Binding Tutorial ” (2014),
URL: https://docs.oracle.com/javase/8/javafx/properties-binding-tutorial/binding.htm,
letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [2014c]: “javafx.scene.control. Class ScrollPane” (2014),
URL: https://docs.oracle.com/javafx/2/api/javafx/scene/control/ScrollPane.html,
letzter Abruf am 24.08.2015.
Oracle (Hrsg.) [2015a]: “javafx.scene. Class Scene” (2015), URL: https://docs.oracle.com/
javase/8/javafx/api/javafx/scene/Scene.html, letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [2015b]: “javafx.scene.layout. Class Pane” (2015),
URL: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Pane.html,
letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [2015c]: “javafx.scene.layout. Class Region” (2015),
URL: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/layout/Region.html,
letzter Abruf am 23.08.2015.
Oracle (Hrsg.) [o. J., a]: “Is JavaFX replacing Swing as the new client UI library for Java SE?” (o. J.),
URL: http://www.oracle.com/technetwork/java/javafx/overview/faq-1446554.html#6, letzter
Abruf am 18.08.2015.
27
Oracle (Hrsg.) [o. J., b]: “JavaFX Scene Builder. A Visual Layout Tool for JavaFX Applications” (o.
J.), URL: http://www.oracle.com/technetwork/java/javase/downloads/javafxscenebuilder-info2157684.html, letzter Abruf am 20.08.2015.
Perlman, Geoff [2010]: “The stigma of BASIC” (11.09.2010), URL: http://www.realsoftwareblog.com/2010/09/stigma-of-basic.html, letzter Abruf am 18.08.2015.
Rauh, Stephan [2013]: “Which Is the Hottest GUI Framework in the Java World: JSF or JavaFX?”
(12.05.2013), URL: http://www.beyondjava.net/blog/hottest-gui-framework-java-world-jsf-javafx/, letzter Abruf am 18.08.2015.
Reimer, Jeremy [2005]: „A History of the GUI“ (05.05.2005), URL: http://arstechnica.com/
features/2005/05/gui/1/, letzter Abruf am 12.08.2015.
Rhine, Brad [2014a]: “Introduction to Programming with XOJO” (2014), URL:
http://41160df63757fc043cfd-66287f38a83954e31a54d1dbe33e0650.r4.cf2.rackcdn.com/Documentation/Introduction%20to%20Programming%20with%20Xojo.pdf, letzter Abruf am
18.08.2015.
Rhine, Brad [2014b]: “Introduction to Programming with XOJO. Teacher Guide” (2014), URL:
http://41160df63757fc043cfd-66287f38a83954e31a54d1dbe33e0650.r4.cf2.rackcdn.com/Documentation/Xojo%20Teacher%20Guide.pdf, letzter Abruf am 18.08.2015.
Rudlof, Christiane [2006]: Handbuch Software-Ergonomie. Usability Engineering (2006), URL:
http://www.ukpt.de/pages/dateien/software-ergonomie.pdf, letzter Abruf am 16.08.2015.
Sander, Ralf [2006]: “Tourist Remover. Entmenschte Urlaubsfotos” (04.09.2006), URL:
http://www.stern.de/digital/online/-tourist-remover--entmenschte-urlaubsfotos-3595374.html,
letzter Abruf am 14.08.2015.
StackOverflow (Hrsg.) [2013a]: “Java GUI frameworks. What to choose? Swing, SWT, AWT,
SwingX, JGoodies, JavaFX, Apache Pivot? [closed]” (16.11.2013), URL: http://stackoverflow.com/
questions/7358775/java-gui-frameworks-what-to-choose-swing-swt-awt-swingx-jgoodies-javafx,
letzter Abruf am 19.08.2015.
StackOverflow (Hrsg.) [2013b]: “Image saved in JavaFX as jpg is pink toned” (23.10.2013), URL:
http://stackoverflow.com/questions/19548363/image-saved-in-javafx-as-jpg-is-pink-toned,
letzter Abruf am 11.08.2015.
StackOverflow (Hrsg.) [2013c]: “In JavaFX, should I use CSS or setter methods to change properties on my UI Nodes?” (07.11.2013), URL: http://stackoverflow.com/questions/14757931/
in-javafx-should-i-use-css-or-setter-methods-to-change-properties-on-my-ui-node,
letzter Abruf am 23.08.2015.
Sawchuck, Darby [2006]: „How to Remove Tourists from Your Photos“ (05.12.2006), URL:
http://dsphotographic.com/2006/12/how-to-remove-tourists-from-your-photos/, letzter Abruf
am 15.08.2015.
Unbekannter Autor [2007]: Bilder einer vielbefahrenen Schnellstraße (2007),
URL: http://wiki.panotools.org/images/6/69/Cs3_stacking_source.zip, letzter Abruf am
15.08.2015.
VB Migration Assistant [o. J.]: “Easily migrate your VB project to Xojo using this VB Migration Assistant” (o. J.), URL: http://www.xojo.com/download/extras.php, letzter Abruf am 18.08.2015.
28
Wehner, Martin [o. J.]: „Digitalfotografie: Überlagerung und Verschmelzung“ (o. J.), URL:
http://mawe-web.de/image_stacking.html, letzter Abruf am 15.08.2015.
Wikipedia (Hrsg.) [2015]: „Comparison of integrated development environments”(19.08.2015),
Abschnitt “Java”, URL: https://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments#Java, letzter Abruf am 19.08.2015.
XOJO (Hrsg.) [2011]: „Category: Events“ (19.04.2011),
URL: http://docs.xojo.com/index.php?title=Category:Events&pagefrom=MemberJoined
%0AAutoDiscovery.MemberJoined#mw-pages, letzter Abruf am 21.08.2015.
XOJO (Hrsg.) [2012a]: „PushButton.Action“ (27.11.2012),
URL: http://docs.xojo.com/index.php/PushButton.Action, letzter Abruf am 21.08.2015.
XOJO (Hrsg.) [2012b]: „TextField.TextChange“ (26.01.2012),
URL: http://docs.xojo.com/index.php/TextField.TextChange, letzter Abruf am 21.08.2015.
XOJO (Hrsg.) [2013a]: „New“ (09.11.2013), Abschnitt „Examples“,
URL: http://docs.xojo.com/index.php/New, letzter Abruf am 21.08.2015.
XOJO (Hrsg.) [2013b]: „Thread.Run event“ (13.08.2013),
URL: http://docs.xojo.com/index.php/Thread.Run_event, letzter Abruf am 21.08.2015.
XOJO (Hrsg.) [2013c]: „RGBSurface“ (13.09.2013),
URL: http://docs.xojo.com/index.php/RGBSurface, letzter Abruf am 22.08.2015.
XOJO (Hrsg.) [2014a]: “XOJO User Guide. Book 1. Fundamentals” (2014), Release 3, URL:
http://41160df63757fc043cfd-66287f38a83954e31a54d1dbe33e0650.r4.cf2.rackcdn.com/
Documentation/EN-PDF/UserGuide-Fundamentals.pdf (2014), letzter Abruf am 18.08.2015.
XOJO (Hrsg.) [2014b]: “XOJO Upgrade Guide.” (2014), Release 3, URL:
http://41160df63757fc043cfd-66287f38a83954e31a54d1dbe33e0650.r4.cf2.rackcdn.com/
Documentation/EN-PDF/UpgradeGuide.pdf, letzter Abruf am 18.08.2015.
XOJO (Hrsg.) [2014c]: „XOJO User Guide. Book 2. User Interface“ (2014), Release 3, URL:
http://41160df63757fc043cfd-66287f38a83954e31a54d1dbe33e0650.r4.cf2.rackcdn.com/
Documentation/EN-PDF/UserGuide-UserInterface.pdf, letzter Abruf am 18.08.2015.
XOJO (Hrsg.) [2014d]: „XOJO User Guide. Book 3. Framework“ (2014), Release 3, URL:
http://41160df63757fc043cfd-66287f38a83954e31a54d1dbe33e0650.r4.cf2.rackcdn.com/
Documentation/EN-PDF/UserGuide-Framework.pdf, letzter Abruf am 18.08.2015.
XOJO (Hrsg.) [2014e]: „XOJO User Guide. Book 4. Development“ (2014), Release 3, URL:
http://41160df63757fc043cfd-66287f38a83954e31a54d1dbe33e0650.r4.cf2.rackcdn.com/
Documentation/EN-PDF/UserGuide-Development.pdf, letzter Abruf am 18.08.2015.
XOJO (Hrsg.) [2014f]: “Window” (10.04.2014), URL: http://docs.xojo.com/index.php/Window,
letzter Abruf am 22.08.2015.
XOJO (Hrsg.) [2015a]: „How to Get Started with XOJO” (2015), URL: http://developer.xojo.com/
get-started (2015), letzter Abruf am 14.08.2015.
XOJO (Hrsg.) [2015b]: “Häufige Fragen und Antworten. XOJO FAQ”, Frage 4 (2015), URL:
http://www.xojo.com/support/faq_xojonew.php, letzter Abruf am 18.8.2015.
29
ZeroTurnaround (Hrsg.) [2014]: „Java Tools and Technologies Landscape for 2014“ (21.05.2014),
URL: http://zeroturnaround.com/rebellabs/java-tools-and-technologies-landscape-for-2014/6/,
letzter Abruf am 19.08.2015.
Download-Quellen
CVS for Eclipse [2015]: „CVS. Platform CVS Support“ (2015), URL: https://www.eclipse.org/
eclipse/platform-cvs/, letzter Abruf am 20.08.2015.
Eclipse [2015]: “Package Solutions” (2015), URL: https://eclipse.org/downloads/, letzter Abruf
am 19.08.2015.
Eclipse Checkstyle Plugin [2015]: „Eclipse Checkstyle Plugin. Checkstyle integration into the
Eclipse IDE. Coding standards made easy” (2015), URL: http://eclipse-cs.sourceforge.net/#!/,
letzter Abruf am 21.08.2015.
E(fx)clipse [2015]: „e(fx)clipse. JavaFX Tooling and Runtime for Eclipse and OSGi” (2015),
Abschnitt “Download & Install”, URL: http://www.eclipse.org/efxclipse/install.html, letzter Abruf
am 20.08.2015.
EGit [2015]: „EGit. About This Project“ (2015), URL: http://www.eclipse.org/egit/, letzter Abruf
am 20.08.2015.
IntelliJ [2015]: „Download IntelliJ IDEA 14.1“ (2015), URL: https://www.jetbrains.com/
idea/download/, letzter Abruf am 19.08.2015.
Maven Integration in Eclipse [2015]: „M2Eclipse: First-class Apache Maven support in the Eclipse
IDE“ (2015), URL: http://www.eclipse.org/m2e/, letzter Abruf am 20.08.2015.
NetBeans [2015]: “NetBeans IDE 8.0.2 Download” (2015), URL: https://netbeans.org/downloads/, letzter Abruf am 19.08.2015.
Scene Builder 2.0 [2015]: “JavaFX Scene Builder Archive” (2015), Abschnitt „JavaFX Scene Builder
2.0 Related Downloads”, URL: http://www.oracle.com/technetwork/java/javafxscenebuilder-1xarchive-2199384.html#javafx-scenebuilder-2.0-oth-JPR, letzter Abruf am 20.08.2015.
XjPrint [2015]: Kommerzielle Drittanbieter-Software zur Erzeugung druckbarer Quelltexte aus
XOJO-Projekten, URL: http://www.rdsisemore.com/xjprint/, letzter Abruf am 18.08.2015.
XOJO-IDE [2015]: “Download Xojo 2015r2.4” (2015), http://xojo.com/download/, letzter Abruf
am 20.08.2015.
30
Anhang
31
Screenshot 1: Tourist Remover (XOJO) – ContainerControls
BottomBarControl (enthält ZoomSliderControl und InterruptableProgressBarControl )
ImageViewer
PictureSelecterControl
32
Screenshot 2: Tourist Remover (JavaFX) – Scene-Graph
ScrollPane
zoomPane
MenuBar
menuBar
ScrollPane
thumbsPane
Scene-Graph (vereinfacht)
VBox (ohne fx:id)
MenuBar MenuBar
SplitPane splitpane
ScrollPane zoomPane
Group imageContainer
ImageView currentImage
ScrollPane thumbsPane
Vbox vbox
HBox (ohne fx:id)
1 Slider sld_zoomSlider
2 Label lbl_zoomLevel
3 Region (ohne fx:id)
4 Button btn_stopProcess
5 ProgressBar prg_numberCrunching
VBox
(ohne fx:id)
Group
imageContainer
SplitPane
splitpane
ImageView
currentImage
1
2
3
VBox
vbox
4
HBox
(ohne fx:id)
5
Der Übersichtlichkeit halber werden
weder die in MenuBar enthaltenen
MenuItems noch das ContextMenu
und dessen MenuItems abgebildet.
33
Quelltexte
Auf den folgenden Seiten sind – zugunsten einer besseren Lesbarkeit im Querformat – die Quelltexte der beiden „Tourist Remover“-Implementierungen abgedruckt.
Beide Anwendungen sind (inklusive Beispielbildern) sowohl als Projektdateien der jeweiligen IDE
als auch in kompilierter Form (XOJO: native Windows-, Macintosh- und Linux-Anwendungen; Java:
ausführbares .jar-Archiv) im Internet abrufbar unter folgender Adresse:
https://github.com/philipp-koch/tourist-remover
34
Listing 1: Java – Main.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package touristRemover;
import
import
import
import
import
javafx.application.Application;
javafx.fxml.FXMLLoader;
javafx.stage.Stage;
javafx.scene.Parent;
javafx.scene.Scene;
public class Main extends Application {
/* (1) program entry point */
public static void main(String[] args) {
System.out.println("[Main.main] has been called."); // TODO remove debug
launch(args);
}
// call launcher (which then calls start)
/* (2) initializes root window and displays it */
@Override
public void start(Stage primaryStage) {
try {
System.out.println("[Main.start] has been called."); // TODO remove debug
// MainWindow
Parent root = FXMLLoader.load(getClass().getResource("MainWindowSplit.fxml")); // get FXML (i.e. structural layout definition); also
// calls Controller.initialize() to inject values
System.out.println("FXML has been loaded."); // TODO remove debug
Scene scene = new Scene(root, 700, 500);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setMinWidth(700);
primaryStage.setMinHeight(500);
primaryStage.setScene(scene);
// display main window
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
}
// instantiate scene graph (i.e. content of window)
// get CSS (i.e. additional visual style definition)
// instantiate stage (i.e. the window frame)
35
Listing 2: Java – Controller.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package touristRemover;
import
import
import
import
import
import
import
import
java.awt.Graphics2D;
java.awt.image.BufferedImage;
java.io.File;
java.io.IOException;
java.net.URL;
java.util.ArrayList;
java.util.List;
java.util.ResourceBundle;
import javax.imageio.ImageIO;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
javafx.beans.property.DoubleProperty;
javafx.beans.property.SimpleDoubleProperty;
javafx.embed.swing.SwingFXUtils;
javafx.fxml.FXML;
javafx.fxml.Initializable;
javafx.scene.Group;
javafx.scene.control.Button;
javafx.scene.control.CheckMenuItem;
javafx.scene.control.Label;
javafx.scene.control.MenuBar;
javafx.scene.control.MenuItem;
javafx.scene.control.ProgressBar;
javafx.scene.control.ScrollPane;
javafx.scene.control.Slider;
javafx.scene.control.SplitPane;
javafx.scene.image.ImageView;
javafx.scene.input.ScrollEvent;
javafx.scene.layout.VBox;
javafx.stage.FileChooser;
/* Controller class: Manages program logic */
public class Controller implements Initializable{
// The @FXML annotation enables the FXMLLoader instance to inject values defined in the FXML file into references in this controller class;
// mandatory for private objects, can be omitted if object is public, but it's good practice to always use them
// MainWindow: containers and controls
@FXML private MenuBar menuBar;
@FXML private ProgressBar prg_numberCrunching;
36
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
@FXML
private
private
private
private
private
private
private
private
private
Button btn_stopProcess;
Slider sld_zoomSlider;
Label lbl_zoomLevel;
SplitPane splitpane;
ScrollPane zoomPane;
ScrollPane thumbsPane;
VBox vbox;
ImageView currentImage;
Group imageContainer;
// MainWindow: menubar items
@FXML private MenuItem MenuFileCloseImage;
@FXML private MenuItem MenuFileSaveImageAs;
@FXML private MenuItem MenuView100Percent;
@FXML private MenuItem MenuViewFitInWindow;
@FXML private MenuItem MenuViewZoomIn;
@FXML private MenuItem MenuViewZoomOut;
@FXML private CheckMenuItem MenuViewShowSidebar;
@FXML private MenuItem MenuFilterMultivariateMedianFilter;
// MainWindow: contextmenu items:
@FXML private MenuItem ContextmenuView100Percent;
@FXML private MenuItem ContextmenuViewFitInWindow;
@FXML private MenuItem ContextmenuFileSaveImageAs;
@FXML private MenuItem ContextmenuFileCloseImage;
@FXML private CheckMenuItem ContextmenuViewShowSidebar;
// locally used properties
ArrayList<File> loadedImageFiles = new ArrayList<File>(); // used for image duplicates avoidance
DoubleProperty zoomProperty = new SimpleDoubleProperty();
final Double ZOOM_MIN = 0.02; // i.e. 2%
final Double ZOOM_MAX = 4.0;
// i.e. 400%
double lastDividerPosition;
/* (3) Initializes nodes of the window instantiated in step 2 at Main.start(). */
@Override
public void initialize(URL location, ResourceBundle resources) {
System.out.println("[Controller.initialize] has been called."); // TODO remove debug
// declare globally used zoom level and initialize as 100%; add a change listener so that whenever
// its value changes, both the displayed image and the zoomSlider value get updated accordingly
zoomProperty.addListener(z -> zoomPropertyChanged(zoomProperty));
zoomProperty.set(1.0); // set initial zoom value (calls listener zoomPropertyChanged)
// initialize zoom slider and register listener to apply slider position changes as new zoom level
37
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
sld_zoomSlider.setMin(ZOOM_MIN);
sld_zoomSlider.setMax(ZOOM_MAX);
sld_zoomSlider.valueProperty().addListener((slider, oldValue, newValue) // onValueChange:
-> zoomProperty.set((double) newValue)
// update global zoomLevel
);
// enable mouse wheel zoom
zoomPane.addEventFilter(ScrollEvent.ANY, e -> {
double zoomChange = 1;
if (e.getDeltaY() > 0) { zoomChange = 1.1; }
else if (e.getDeltaY() < 0) { zoomChange = 1 / 1.1; }
// zoom in
// zoom out
zoomProperty.set(zoomProperty.get() * zoomChange);
// apply zoom change
e.consume(); // prevent mouse-wheel event from being processed any further (e.g. scrolling the image)
});
// center displayed zoomPane image on window resize
zoomPane.widthProperty().addListener((wp, oldWidth, newWidth) -> { centerImageHorizontally(newWidth); });
zoomPane.heightProperty().addListener((hp, oldHeight, newHeight) -> { centerImageVertically(newHeight); });
// once the displayed image has changed, change its size so that it fits into the viewport
currentImage.imageProperty().addListener((o, oldValue, newValue) -> {
if (newValue != null) { fitImageInViewport(); }
sld_zoomSlider.setDisable(newValue == null);
// en-/disable zoom slider
lbl_zoomLevel.setVisible(newValue != null);
// only show zoom level if image is displayed
});
// synchronize selection status of both menubar and contextmenu items "show sidebar" via bidirectional property binding
MenuViewShowSidebar.selectedProperty().bindBidirectional(ContextmenuViewShowSidebar.selectedProperty());
}
/* Centers the image horizontally in the zoomPane. Gets called on zoomPane.WidthChange. */
private void centerImageHorizontally(Number viewportWidth) {
if (currentImage.getImage() != null) {
if (currentImage.getImage().getWidth() * zoomProperty.get() < (double) viewportWidth) { // viewport is wider than image (i.e. horizontal gaps)
imageContainer.setTranslateX((zoomPane.getWidth() - currentImage.getImage().getWidth() * zoomProperty.get()) / 2);
} else {
// image is wider than viewport, ...
imageContainer.setTranslateX(0); // ... so there must not be any horizontal gap
}
}
}
/* Centers the image vertically in the zoomPane. Gets called on zoomPane.HeightChange. */
private void centerImageVertically(Number viewportHeight) {
38
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
if (currentImage.getImage() != null) {
if (currentImage.getImage().getHeight() * zoomProperty.get() < (double) viewportHeight) {
imageContainer.setTranslateY((zoomPane.getHeight() - currentImage.getImage().getHeight() * zoomProperty.get()) / 2);
} else {
// image is taller than viewport, ...
imageContainer.setTranslateY(0); // ... so there must not be any vertical gap
}
}
}
/* Event handler for zoomProperty.ValueChange. */
public void zoomPropertyChanged(DoubleProperty newZoom) {
// ensure zoom level doesn't exceed defined maximum value or fall below minimum value
if (newZoom.get() > ZOOM_MAX) { newZoom.set(ZOOM_MAX); }
if (newZoom.get() < ZOOM_MIN) { newZoom.set(ZOOM_MIN); }
zoomProperty.set(newZoom.get());
sld_zoomSlider.setValue(newZoom.get());
lbl_zoomLevel.setText((Math.round(newZoom.get() * 100)) + "%");
if (currentImage.getImage() != null) {
currentImage.setScaleX(newZoom.get());
currentImage.setScaleY(newZoom.get());
centerImageHorizontally(zoomPane.getWidth());
centerImageVertically(zoomPane.getHeight());
}
// update zoomSlider position
// update zoomLevel label
}
/* Opens a (multiple) file(s) selection dialog. Triggered via menu or keyboard shortcut. */
public void loadPictures() {
FileChooser filechooser = new FileChooser();
List<File> chosenFiles;
// declare file type filters
filechooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("Alle Bilder", "*.jpg;*.png"),
new FileChooser.ExtensionFilter("JPG", "*.jpg"),
new FileChooser.ExtensionFilter("PNG", "*.png"));
// have user select one or multiple image files
chosenFiles = filechooser.showOpenMultipleDialog(menuBar.getScene().getWindow());
// add new Thumbnails if they haven't already been added sometime earlier
if (chosenFiles != null) {
for (int i=0; i < chosenFiles.size(); i++) {
if (!loadedImageFiles.contains(chosenFiles.get(i))) { // avoid duplicates
39
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
loadedImageFiles.add(chosenFiles.get(i));
// keep track of image filepath
Thumbnail thumb = new Thumbnail(chosenFiles.get(i).getAbsolutePath(), thumbsPane, vbox, currentImage);
vbox.getChildren().add(thumb);
// add Thumbnail to GUI
}
}
// select Thumbnail representing the first loaded image (and thus display it)
Thumbnail.get(0).setSelected();
}
}
/* Opens a save file dialog where the user can store the currently displayed image on disk. */
public void savePicture() {
FileChooser filechooser = new FileChooser();
File chosenFile;
// declare file type filters
filechooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("Alle Bilder", "*.jpg;*.png"),
new FileChooser.ExtensionFilter("JPG", "*.jpg"),
new FileChooser.ExtensionFilter("PNG", "*.png"));
// have user select one or multiple image files
chosenFile = filechooser.showSaveDialog(menuBar.getScene().getWindow());
if (chosenFile != null) {
// determine desired image format
String format;
switch ((String) chosenFile.getName().subSequence(chosenFile.getName().length() -4, chosenFile.getName().length())) {
case ".jpg":
case "jpeg":
format = "jpg";
break;
case ".png":
format = "png";
break;
default:
format = "jpg";
}
// save image under the user specified location
BufferedImage bimage = SwingFXUtils.fromFXImage(currentImage.getImage(), null);
// remove alpha-channel from buffered image; necessary work-around due to alpha channel bug
40
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
// (see http://stackoverflow.com/questions/19548363/image-saved-in-javafx-as-jpg-is-pink-toned):
BufferedImage imageRGB = new BufferedImage(bimage.getWidth(), bimage.getHeight(), BufferedImage.OPAQUE);
Graphics2D graphics = imageRGB.createGraphics();
graphics.drawImage(bimage, 0, 0, null);
graphics.dispose(); // free up memory
try {
ImageIO.write(imageRGB, format, chosenFile);
} catch (IOException e) {
MessageWindow.show( "Fehler!",
"Das Bild konnte nicht gespeichert werden.",
"Es ist ein Fehler aufgetreten: " + e.getLocalizedMessage());
}
}
}
/* Increases zoom. Helper method so that the zoom-in menu entry can be assigned directly via FXML. */
public void increaseZoomLevel() {
zoomProperty.set(zoomProperty.get() +0.01);
}
/* Decreases zoom. Helper method so that the zoom-out menu entry can be assigned directly via FXML. */
public void decreaseZoomLevel() {
zoomProperty.set(zoomProperty.get() -0.01);
}
/* Sets zoom to 100%. Helper method so that the respective menu entry can be assigned directly via FXML. */
public void setZoomLevelTo100percent() {
zoomProperty.set(1.0);
}
/* Shows or hides the Thumbnails bar. Helper method so that the respective menu entry can be assigned directly via FXML. */
public void toggleSidebar() {
// hide sidebar
if (!MenuViewShowSidebar.isSelected()) {
thumbsPane.setMinWidth(0);
thumbsPane.setMaxWidth(0);
lastDividerPosition = splitpane.getDividerPositions()[0]; // remember current position for restoring it later
splitpane.setDividerPositions(1.0);
// show sidebar
} else {
thumbsPane.setMinWidth(85);
thumbsPane.setMaxWidth(350);
splitpane.setDividerPositions(lastDividerPosition); // restore position before the divider was hidden
41
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
}
}
/* Adjusts image size so that it fits into the viewport. Helper method so that the respective menu entry can be assigned directly via FXML. */
public void fitImageInViewport() {
double viewportWidth = zoomPane.getWidth();
double viewportHeight = zoomPane.getHeight();
double imageWidth = currentImage.getImage().getWidth();
double imageHeight = currentImage.getImage().getHeight();
double suitableZoomLevel = Math.min(viewportHeight / imageHeight, viewportWidth / imageWidth);
zoomProperty.set(suitableZoomLevel);
centerImageHorizontally(viewportWidth);
centerImageVertically(viewportHeight);
}
/* Activates menu items if suitable. Gets called prior to when a menu is shown and also when using accelerator keys of certain
* entries to access the respective menu item. */
public void toggleMenuItemAvailability() {
// en-/disables (Context-)Menu items depending on if there is an image being displayed in the zoomPane or not
MenuFileCloseImage.setDisable(currentImage.getImage() == null);
// Menubar: ~ "close image"
ContextmenuFileCloseImage.setDisable(currentImage.getImage() == null); // Contextmenu: ~ "close image"
MenuFileSaveImageAs.setDisable(currentImage.getImage() == null);
// Menubar: ~ "save as"
ContextmenuFileSaveImageAs.setDisable(currentImage.getImage() == null); // Contextmenu: ~ "save as"
MenuView100Percent.setDisable(currentImage.getImage() == null);
// Menubar: ~ "set zoom to 100%"
ContextmenuView100Percent.setDisable(currentImage.getImage() == null); // Contextmenu: ~ "set zoom to 100%"
MenuViewFitInWindow.setDisable(currentImage.getImage() == null);
// Menubar: ~ "fit in window"
ContextmenuViewFitInWindow.setDisable(currentImage.getImage() == null); // Contextmenu: ~ "fit in window"
MenuViewZoomIn.setDisable(currentImage.getImage() == null);
// Menubar: ~ "zoom in"
MenuViewZoomOut.setDisable(currentImage.getImage() == null);
// Menubar: ~ "zoom out"
// disables Menubar: ~ "median filter" as long as not at least 3 images have been loaded
MenuFilterMultivariateMedianFilter.setDisable(Thumbnail.getInstancesTotal() < 3);
}
/* Removes currently displayed zoomPane image and its Thumbnail. Helper method so that the respective menu entry can be
* assigned directly via FXML. */
public void removeCurrentZoomPaneImage() {
Thumbnail.remove(currentImage.getImage()); // also removes the corresponding zoomPane image
}
/* Performs multivariate median filter on all currently loaded images and displays the result. */
public void performMedianFilter() {
try {
// perform Median filtering: The calculation is done in its own thread. Once finished, the result gets displayed in the zoomPane.
42
313
314
315
316
317
318
319
320
321
322
323
MedianFilter medianfilter = new MedianFilter(Thumbnail.getAllImages(), prg_numberCrunching, currentImage, btn_stopProcess);
medianfilter.performThreadedFiltering();
} catch (Exception e) {
// filtering not possible: Show error to the user
MessageWindow.show("Fehler",
"Der Filter kann nicht angewandt werden.",
"Es müssen mindestens drei Bilder vorhanden sein, und sämtliche Bilder müssen die gleiche Dimension (Höhe und Breite) aufweisen.");
}
}
}
43
Listing 3: Java – MedianFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package touristRemover;
import java.util.ArrayList;
import
import
import
import
import
import
import
import
import
javafx.concurrent.Task;
javafx.concurrent.WorkerStateEvent;
javafx.event.EventHandler;
javafx.scene.control.Button;
javafx.scene.control.ProgressBar;
javafx.scene.image.Image;
javafx.scene.image.ImageView;
javafx.scene.image.WritableImage;
javafx.scene.paint.Color;
/* Performs a multivariate median filtering of provided source images. All images must be of identical sizes, and there must be
* at least 3 of them for a meaningful result. If this criteria is not met, a modal error message is shown to the user.
*
* The filtering is performed in its own thread, updating the parameterized progressbar with the respective state of progress.
* During image processing, a button next to the proressbar is shown so that the user can terminate the process. Because of the
* filtering being performed in its own thread, the GUI stays responsive during the process and is usable (e.g. for zooming,
* switching images etc.)
*
* Once the filtering has been performed successfully, the resulting image gets displayed in the parameterized ImageView.*/
public class MedianFilter {
ArrayList<Image> pictures;
WritableImage result;
int pictureWidth;
int pictureHeight;
ProgressBar progressBar;
ImageView currentImage;
Button stopButton;
Task<Void> imageProcessing;
/* Constructor */
public MedianFilter(ArrayList<Image> thumbnails, ProgressBar progressbar, ImageView currentImage, Button stopButton) throws Exception {
pictures = thumbnails;
this.progressBar = progressbar;
this.stopButton = stopButton;
this.currentImage = currentImage;
checkPrerequisites();
44
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
}
/* Performs multivariate median filtering */
public void performThreadedFiltering() {
// instantiate background task that is computed in its own thread;
// see http://docs.oracle.com/javafx/2/threads/jfxpub-threads.htm for more information on concurrency in JavaFX
imageProcessing = new Task<Void>() {
@Override public Void call() {
result = new WritableImage(pictureWidth, pictureHeight);
ArrayList<Double> red = new ArrayList<Double>();
ArrayList<Double> green = new ArrayList<Double>();
ArrayList<Double> blue = new ArrayList<Double>();
int pixelsFinished = 0;
int pixelsTotal = pictureWidth * pictureHeight;
for (int x=0; x < pictureWidth; x++) {
for (int y=0; y < pictureHeight; y++) {
for (int idx=0; idx < pictures.size(); idx++) {
// cancel thread if user has clicked
if (imageProcessing.isCancelled()) {
stopButton.setVisible(false); //
updateProgress(0, 0);
//
return null;
//
}
the respective button next to the progressbar
hide stop button
reset progressbar
quit method immediately
// store RGB values at the current pixel position of all images separately
red.add(pictures.get(idx).getPixelReader().getColor(x, y).getRed());
green.add(pictures.get(idx).getPixelReader().getColor(x, y).getGreen());
blue.add(pictures.get(idx).getPixelReader().getColor(x, y).getBlue());
}
// write median pixel values to resulting image
Color pixelValue = new Color(getMedianValue(red), getMedianValue(green), getMedianValue(blue), 1.0); // RGBa
result.getPixelWriter().setColor(x, y, pixelValue);
red.clear();
green.clear();
blue.clear();
// update progress bar in GUI
pixelsFinished++;
updateProgress(pixelsFinished, pixelsTotal);
45
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
}
}
// process finished successfully: GUI clean-up
stopButton.setVisible(false); // hide stop button
updateProgress(0, 0);
// reset progressbar
return null; // necessary due to the usage of the placeholder class Void as return type
}
}; // end: task
// bind GUI's progress bar to thread's progressProperty in order to have the UI reflect the respective current state
progressBar.progressProperty().bind(imageProcessing.progressProperty());
// register event handler to display the resulting filtered image in the zoomPane once the thread has ended
imageProcessing.addEventHandler(WorkerStateEvent.WORKER_STATE_SUCCEEDED, new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent t) { currentImage.setImage(result); }
});
// show button for process termination and register click handler
stopButton.setVisible(true);
stopButton.setOnAction(a -> {
imageProcessing.cancel();
stopButton.setVisible(false);
});
// start a thread to perform the background processing
new Thread(imageProcessing).start();
}
/* Checks if all images share the same dimension and if at least three images are provided. */
private void checkPrerequisites() throws Exception {
if (pictures.size() < 3) { filterNotApplicable(); } // at least 3 images necessary
pictureWidth = (int) pictures.get(0).getWidth();
pictureHeight = (int) pictures.get(0).getHeight();
for (int i=1; i<pictures.size(); i++) {
if (pictures.get(i).getWidth() != pictureWidth) { filterNotApplicable(); }
if (pictures.get(i).getHeight() != pictureHeight) { filterNotApplicable(); }
}
}
/* Throws exception if provided images are not suitable for filtering. */
46
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
private void filterNotApplicable() throws Exception {
throw new Exception("Illegal image properties. Filter cannot be applied.");
}
/* Returns the median of given numbers. */
private Double getMedianValue(ArrayList<Double> input) {
input.sort(null); // sort using 'natural order', i.e. numerically ascending
// even number of elements
if (input.size() % 2 == 0) {
double leftValue = input.get((input.size() / 2) -1);
double rightValue = input.get(input.size() / 2);
// zero-based indices! E.g. 4 elements -> [.*..]
// ...and
[..*.]
return (leftValue + rightValue) / 2;
}
// odd number of elements
return input.get((int) Math.floor(input.size() / 2));
}
}
// e.g. 5 elements --> floor(5/2) = 2 (zero-based!) [..*..]
47
Listing 4: Java – Thumbnail.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package touristRemover;
import java.util.ArrayList;
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
import
javafx.geometry.Pos;
javafx.scene.control.ContextMenu;
javafx.scene.control.MenuItem;
javafx.scene.control.ScrollPane;
javafx.scene.effect.ColorAdjust;
javafx.scene.image.Image;
javafx.scene.image.ImageView;
javafx.scene.input.MouseButton;
javafx.scene.layout.Background;
javafx.scene.layout.BackgroundFill;
javafx.scene.layout.Border;
javafx.scene.layout.BorderStroke;
javafx.scene.layout.BorderStrokeStyle;
javafx.scene.layout.BorderWidths;
javafx.scene.layout.CornerRadii;
javafx.scene.layout.HBox;
javafx.scene.layout.VBox;
javafx.scene.paint.Color;
/* A Thumbnail is a custom styled HBox wrapping an ImageView, handling mouse click actions.
* Selections (via left mouse click) are treated exclusively, i.e. in a radio-button-like manner. */
public class Thumbnail extends HBox {
// data management
private String imagePath;
private ImageView imageview;
ContextMenu contextmenu;
// instance management
private static ArrayList<Thumbnail> instances = new ArrayList<Thumbnail>();
private Boolean selected = false;
private ScrollPane parentScrollPane;
private VBox thumbnailsContainerInParent;
private ImageView currentlyShownZoomableImage;
// style
private double borderWidth = 1;
private double horizontalPadding = 12;
// px
// px; space between left/right thumbnail border and parent pane border
48
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
private CornerRadii cornerradii = new CornerRadii(10); // amount of px the corners are to be rounded
Color borderColor = new Color(.5, .5, .5, 1.0);
// RGBa
Color backgroundColor = new Color(.9, .9, .9, 1.0);
// RGBa
/* Constructor */
public Thumbnail(String imagePath, ScrollPane parentScrollpane, VBox thumbnailContainer, ImageView currentImage) {
this.parentScrollPane = parentScrollpane;
this.thumbnailsContainerInParent = thumbnailContainer;
this.currentlyShownZoomableImage = currentImage;
this.imagePath = imagePath;
loadImage();
stylize();
declareBindings();
createContextMenu();
registerClickHandler();
instances.add(this);
//
//
//
//
//
//
load image from parameterized path into ImageView
define layout of the Thumbnail (alignment, borders etc.)
make Thumbnail auto-sizing
shows up if Thumbnail is right-clicked
handle left- and right-clicks
keep track of this instance internally
}
/** INITIALIZATION **/
/* Opens image file parameterized via Constructor and assigns its content to the ImageView. */
private void loadImage() {
imageview = new ImageView(new Image("file:" + imagePath));
imageview.setPreserveRatio(true);
}
/* Defines the Thumbnail's layout in the GUI. */
private void stylize() {
// initialize HBox
this.getChildren().add(imageview);
this.alignmentProperty().set(Pos.CENTER);
// define background
BackgroundFill fill = new BackgroundFill(backgroundColor, cornerradii, null);
Background back = new Background(fill);
this.setBackground(back);
// define borders
BorderStroke stroke = new BorderStroke (borderColor, BorderStrokeStyle.SOLID, cornerradii, new BorderWidths(borderWidth));
Border border = new Border(stroke);
this.setBorder(border);
}
/* Declares property bindings for automatic, dynamic Thumbnail size calculation. */
private void declareBindings() {
49
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
this.prefWidthProperty().bind(parentScrollPane.widthProperty()
.subtract(2 * borderWidth).subtract((2 * horizontalPadding)));
this.prefHeightProperty().bind(parentScrollPane.widthProperty()
.subtract(2 * borderWidth).subtract((2 * horizontalPadding)));
imageview.fitWidthProperty().bind(this.prefWidthProperty());
imageview.fitHeightProperty().bind(this.prefWidthProperty());
// also bound to width -->
square shape!
// also bound to width --> square shape!
}
/* Initiates context menu. */
private void createContextMenu() {
contextmenu = new ContextMenu();
MenuItem ContextRemoveImage = new MenuItem();
ContextRemoveImage.setText("Bild entfernen");
// menu item text
ContextRemoveImage.setOnAction(a -> remove(this)); // register action hander
contextmenu.getItems().add(ContextRemoveImage);
}
/* Registers moue click handler. */
private void registerClickHandler() {
this.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY ) {
this.setSelected();
showRepresentedImageInZoomPane();
} else if (e.getButton() == MouseButton.SECONDARY) {
contextmenu.show(this, e.getScreenX(), e.getScreenY());
}
});
}
/** INTERACTION: by instance **/
/* Returns the assigned image in full resolution. */
public Image getImage() {
return imageview.getImage();
}
/* Returns the assigned image's path. */
public String getImagePath() {
return imagePath;
}
/* Returns the number of Thumbnails currently instantiated. */
public static int getInstancesTotal() {
return instances.size();
// left mouse-click:
// update which Thumbnail is currently selected...
// ...and display its image in the zoomPane
// right mouse-click
// display context menu
50
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
}
/* Returns the thumbnail's index number. Used for handling selections. */
public int getIndex() {
return instances.indexOf(this);
}
/* Returns the Thumbnail's context menu object. */
public ContextMenu getContextMenu() {
return contextmenu;
}
/* Sets Thumbnail status to "selected" and unselects all other instances. */
public void setSelected() {
this.selected = true;
this.setEffect(null);
showRepresentedImageInZoomPane();
for (Thumbnail t : instances) {
if (!t.equals(this)) {
t.selected = false;
t.setEffect(new ColorAdjust(0, -.85, 0, 0)); // desaturate almost completely, i.e. simulate "high-key image"
}
}
}
/* Displays the image represented by a Thumbnail in the zoomPane. */
private void showRepresentedImageInZoomPane() {
currentlyShownZoomableImage.setImage(this.getImage());
}
/* Removes the currently selected Thumbnail. Called via right-click handler. */
private void remove(Thumbnail t) {
// if Thumbnail-to-be-deleted represents the current zoomPane image, remove that as well
ImageView i = currentlyShownZoomableImage;
if (i.getImage() != null && i.getImage().equals(t.getImage())) { i.setImage(null); }
int idx = instances.indexOf(t);
instances.remove(t);
thumbnailsContainerInParent.getChildren().remove(t);
// select another Thumbnail close by if possible:
if (Thumbnail.getInstancesTotal() > 0) {
if (Thumbnail.getInstancesTotal() -1 >=idx) {
// delete instance from internal ArrayList
// delete Thumbnail node in GUI
51
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
instances.get(idx).setSelected();
} else if (Thumbnail.getInstancesTotal() -1 == idx -1) {
instances.get(idx -1).setSelected();
}
}
}
/** INTERACTION: public static methods **/
/* Returns an ArrayList<Image> containing all currently loaded images. */
public static ArrayList<Image> getAllImages() {
ArrayList<Image> images = new ArrayList<Image>();
for (Thumbnail t : instances) { images.add(t.getImage()); }
return images;
}
/* Returns a reference of the Thumbnail with the parameterized index number. */
public static Thumbnail get(int index) {
return instances.get(index);
}
/* Removes Thumbnail representing the parameterized image. */
public static void remove(Image image) {
if (image != null) {
// find Thumbnail instance representing the image
int i = 0;
while (!instances.get(i).getImage().equals(image)) { i++; }
// remove it
instances.get(i).remove(instances.get(i));
}
}
}
52
Listing 5: Java – MessageWindow.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package touristRemover;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
public class MessageWindow {
public static void show(String windowTitle, String headline, String message) {
Alert infoWindow = new Alert(AlertType.ERROR);
infoWindow.setTitle(windowTitle);
infoWindow.setHeaderText(headline);
infoWindow.setContentText(message);
infoWindow.showAndWait(); // show modally
}
}
Listing 6: Java – MainWindowSplit.fxml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<?import
<?import
<?import
<?import
<?import
<?import
<?import
<?import
<?import
java.lang.*?>
javafx.scene.*?>
javafx.scene.control.*?>
javafx.scene.layout.*?>
javafx.scene.layout.VBox?>
javafx.scene.effect.*?>
javafx.scene.input.*?>
javafx.scene.image.*?>
javafx.geometry.*?>
<VBox minHeight="500.0" minWidth="700.0" stylesheets="@application.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="touristRemover.Controller">
<children>
<!-- menu bar -->
<MenuBar fx:id="menuBar">
<menus>
53
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<Menu onShowing="#toggleMenuItemAvailability" text="_Datei">
<items>
<MenuItem mnemonicParsing="false" onAction="#loadPictures" text="Bilddatei(en) öffnen">
<accelerator>
<KeyCodeCombination alt="UP" code="O" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<MenuItem fx:id="MenuFileCloseImage" mnemonicParsing="false" onAction="#removeCurrentZoomPaneImage"
onMenuValidation="#toggleMenuItemAvailability" text="Bilddatei schließen">
<accelerator>
<KeyCodeCombination alt="UP" code="W" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="MenuFileSaveImageAs" mnemonicParsing="false" onAction="#savePicture" onMenuValidation="#toggleMenuItemAvailability"
text="Bild speichern unter...">
<accelerator>
<KeyCodeCombination alt="UP" code="S" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" text="Beenden" />
</items>
</Menu>
<Menu onShowing="#toggleMenuItemAvailability" text="_Ansicht">
<items>
<MenuItem fx:id="MenuView100Percent" mnemonicParsing="false" onAction="#setZoomLevelTo100percent"
onMenuValidation="#toggleMenuItemAvailability" text="100%">
<accelerator>
<KeyCodeCombination alt="DOWN" code="DIGIT0" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<MenuItem fx:id="MenuViewFitInWindow" mnemonicParsing="false" onAction="#fitImageInViewport"
onMenuValidation="#toggleMenuItemAvailability" text="In Fenstergröße einpassen">
<accelerator>
<KeyCodeCombination alt="UP" code="DIGIT0" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="MenuViewZoomIn" mnemonicParsing="false" onAction="#increaseZoomLevel" onMenuValidation="#toggleMenuItemAvailability"
text="Hereinzoomen">
<accelerator>
<KeyCodeCombination alt="UP" code="PERIOD" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
54
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<MenuItem fx:id="MenuViewZoomOut" mnemonicParsing="false" onAction="#decreaseZoomLevel"
onMenuValidation="#toggleMenuItemAvailability" text="Herauszoomen">
<accelerator>
<KeyCodeCombination alt="UP" code="COMMA" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" />
<CheckMenuItem fx:id="MenuViewShowSidebar" mnemonicParsing="false" onAction="#toggleSidebar" selected="true" text="Seitenleiste
anzeigen">
<accelerator>
<KeyCodeCombination alt="UP" code="TAB" control="UP" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</CheckMenuItem>
</items>
</Menu>
<Menu onShowing="#toggleMenuItemAvailability" text="_Filter">
<items>
<MenuItem fx:id="MenuFilterMultivariateMedianFilter" mnemonicParsing="false" onAction="#performMedianFilter" text="Multivariaten
Median-Filter anwenden" />
</items>
</Menu>
</menus>
</MenuBar>
<SplitPane fx:id="splitpane" dividerPositions="0.8" VBox.vgrow="ALWAYS">
<items>
<!-- zoomPane (big image view) -->
<ScrollPane fx:id="zoomPane" hbarPolicy="NEVER" pannable="true" vbarPolicy="NEVER">
<content>
<!-- The ImageView node must be wrapped in a Group parent, so that
its visualBounds (BoundsInParent) are used for calculating the Scrollpane's
panning bounds rather than its layoutBounds (i.e. its unscaled 'original'
size) -->
<Group fx:id="imageContainer">
<children>
<ImageView fx:id="currentImage" preserveRatio="true" />
</children>
</Group>
</content>
<!-- zoomPane: Context menu -->
<contextMenu>
<ContextMenu onShowing="#toggleMenuItemAvailability">
55
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<items>
<MenuItem fx:id="ContextmenuView100Percent" mnemonicParsing="false" onAction="#setZoomLevelTo100percent"
onMenuValidation="#toggleMenuItemAvailability" text="100%">
<accelerator>
<KeyCodeCombination alt="DOWN" code="DIGIT0" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<MenuItem fx:id="ContextmenuViewFitInWindow" mnemonicParsing="false" onAction="#fitImageInViewport"
onMenuValidation="#toggleMenuItemAvailability" text="In Fenstergröße einpassen">
<accelerator>
<KeyCodeCombination alt="UP" code="DIGIT0" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem fx:id="ContextmenuFileSaveImageAs" mnemonicParsing="false" onAction="#savePicture"
onMenuValidation="#toggleMenuItemAvailability" text="Bild speichern unter...">
<accelerator>
<KeyCodeCombination alt="UP" code="S" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<MenuItem fx:id="ContextmenuFileCloseImage" mnemonicParsing="false" onAction="#removeCurrentZoomPaneImage"
onMenuValidation="#toggleMenuItemAvailability" text="Bilddatei schließen">
<accelerator>
<KeyCodeCombination alt="UP" code="W" control="DOWN" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</MenuItem>
<CheckMenuItem fx:id="ContextmenuViewShowSidebar" mnemonicParsing="false" onAction="#toggleSidebar" selected="true"
text="Seitenleiste anzeigen">
<accelerator>
<KeyCodeCombination alt="UP" code="TAB" control="UP" meta="UP" shift="UP" shortcut="UP" />
</accelerator>
</CheckMenuItem>
</items>
</ContextMenu>
</contextMenu>
</ScrollPane>
<!-- thumbsPane (shows thumbnails) -->
<ScrollPane fx:id="thumbsPane" hbarPolicy="NEVER" maxWidth="350.0" minWidth="85.0" styleClass="styled-scrollbar"
BorderPane.alignment="TOP_CENTER">
<content>
<VBox fx:id="vbox" alignment="TOP_CENTER" spacing="8.0">
<padding>
<Insets bottom="0" left="9.0" right="10.0" top="6.0" />
</padding>
56
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
</VBox>
</content>
</ScrollPane>
</items>
</SplitPane>
<!-- bottomBar (zoom slider, progress bar) -->
<HBox alignment="CENTER_LEFT" spacing="5.0" BorderPane.alignment="CENTER">
<children>
<Slider fx:id="sld_zoomSlider" blockIncrement="0.1" disable="true" minWidth="120.0" prefWidth="120.0" />
<Label fx:id="lbl_zoomLevel" text="Label" visible="false" />
<Region HBox.hgrow="ALWAYS" />
<Button fx:id="btn_stopProcess" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="15.0" minWidth="15.0" mnemonicParsing="false"
prefHeight="15.0" prefWidth="15.0" styleClass="stop-sign-button" text="" visible="false">
<cursor>
<Cursor fx:constant="HAND" />
</cursor>
</Button>
<ProgressBar fx:id="prg_numberCrunching" maxHeight="12.0" minHeight="10.0" prefHeight="12.0" progress="0.0" />
</children>
<padding>
<Insets left="5.0" right="5.0" />
</padding>
<BorderPane.margin>
<Insets bottom="2.0" top="2.0" />
</BorderPane.margin>
</HBox>
</children>
</VBox>
57
Listing 7: Java – application.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/* use a png image as stop-button next to progressbar */
.stop-sign-button {
-fx-background-image: url('stopp.png');
-fx-background-repeat: no-repeat;
-fx-background-position: center;
}
/* make SplitPane divider a thin line */
.split-pane *.split-pane-divider {
-fx-padding: 0 1 0 1;
}
/* adjust scrollbar style in thumbsPane;
* see http://blog.ngopal.com.np/2012/07/11/customize-scrollbar-via-css/ for the concept */
.styled-scrollbar .scroll-bar:horizontal .track,
.styled-scrollbar .scroll-bar:vertical .track{
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-background-radius: 0em;
-fx-border-radius: 2em;
}
/* The increment and decrement button CSS class of scrollbar */
.styled-scrollbar .scroll-bar:horizontal .increment-button ,
.styled-scrollbar .scroll-bar:horizontal .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 0 10 0;
}
/* The increment and decrement button CSS class of scrollbar;
* their widths determine the overall-width of the scrollbar. */
.styled-scrollbar .scroll-bar:vertical .increment-button ,
.styled-scrollbar .scroll-bar:vertical .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 10 0 0;
/* i.e. scrollbar width = 10px */
}
.styled-scrollbar .scroll-bar .increment-arrow,
.styled-scrollbar .scroll-bar .decrement-arrow {
-fx-shape: " ";
58
43
44
45
46
47
48
49
50
51
52
-fx-padding: 0;
}
/* The scrollbar "thumb" which can be dragged to adjust scroll position */
.styled-scrollbar .scroll-bar:horizontal .thumb,
.styled-scrollbar .scroll-bar:vertical .thumb {
-fx-background-color: derive(black,90%);
-fx-background-insets: 2, 0, 0;
-fx-background-radius: 2em;
}
59
Listing 8: XOJO – App
60
Listing 9: XOJO – MainWindow
61
62
63
64
65
66
67
68
Listing 10: XOJO - ImageViewer
69
70
71
72
73
74
Listing 11: XOJO – PictureSelecterSidebar
75
76
77
Listing 12: XOJO - BottomBarControl
78
Listing 13: XOJO - InterruptableProgressBar
79
80
81
Listing 14: XOJO – ZoomSliderControl
82
83
Listing 15: XOJO – Minipic
84
85
86
87
88
89
Listing 16: XOJO – PictureZoomLevel
90
91
Listing 17: XOJO – Observable
Listing 18: XOJO – Observer