Carsten Kelling 1997 (); letzte Änderung: 20. Januar 1998 |
Abbildung 36 zeigt die Klassenhierarchie der Unterstützungsklassen. Die Anwendung der einzelnen Klassen wird in den folgenden Abschnitten praxisnah erläutert.
Zeigt in einem Fenster Informationen zu dem laufenden Applet an, entsprechend einem Menüpunkt wie "Über dieses Programm", "About" oder "Info" im "Hilfe"-Menü der meisten Programme, die unter einer graphischen Benutzeroberfläche laufen.
Aus einer von java.awt.Frame abgeleiteten Klasse, wie SimControl (siehe 4.12.7), genügt ein Aufruf der Form
(new AboutRechnerDialog(parent, this)).show();
wobei parent eine Instanz von Rechner sein muß.
Kein weiterer Code ist notwendig; AboutRechnerDialog zeigt, von oben nach unten, die Ausgaben der Methoden getVersionString(), getAppletInfo() und getAuthorString() des übergebenen Rechner-Objekts (erster Parameter) an. Das Fenster zentriert sich automatisch über dem aufrufenden Fenster (zweiter Parameter) und ist nicht modal (blockiert nicht andere Fenster).
Diese Klasse wird von SimControl (siehe 4.12.7) für den Menüpunkt "Info" aus dem "Hilfe"-Menü verwendet.
Stellt ein Konsolenfenster dar (ein Fenster, in dem Text ausgegeben werden kann, um Vorgänge im Rechner mitzuprotokollieren); verfügt über einen Knopf zum Löschen und einen, um festzulegen, ob das Protokoll "ausführlich" sein soll.
In Rechner ist schon ein ConsoleWindow vordefiniert:
public static ConsoleWindow traceWindow;
Dieses wird in initialize() (siehe 4.2 Das Package ckelling.baukasten) auch initialisiert:
traceWindow = new ConsoleWindow();
Danach ist die Konsole noch unsichtbar. Sie leitet sich von java.awt.Frame ab, die sonstige Behandlung erfolgt wie von diesem gewohnt (Größe per resize() beliebig änderbar, Methoden hide() und show()).
Die Methode clear() löscht den Inhalt der Konsole, append(String text) fügt einen Text an den vorhandenen an - es wird kein automatischer Zeilenvorschub generiert! setVerbose(boolean b) kann die Konsole "geschwätzig" machen, so daß auch bei Aufrufen von appendIfVerbose(String text) Text hinzugefügt wird. Anderenfalls würden diese Aufrufe ignoriert werden. Der "Geschwätzigkeitsstatus" läßt sich mit isVerbose() abfragen.
Aus Geschwindigkeitsgründen empfiehlt es sich, vor größeren Ausgaben (z.B. Ausführen vieler Maschinenbefehle durch goUntilBreakpoint()), lock() aufzurufen, danach unlock() - der Text wird auf einmal hinzugefügt, auf langsamen Systemen ohne just in time-compiler erspart das sehr viel Wartezeit.
Analog dazu geht Text, der an eine gerade nicht sichtbare Konsole geschickt wird, nicht verloren, wird aber erst bei Wiedererscheinen der Konsole durch einen einzigen Methodenaufruf dargestellt.
SimControl (siehe 4.12.7) besitzt bereits den Menüpunkt "Befehlsgeschichte anzeigen", um Rechner.traceWindow ein- und auszublenden; Rechner selbst stellt folgende Methoden zur Verfügung:
public static void outToTrace(String message) { traceWindow.append(message); // Text in Konsole ausgeben } public static void outToTraceln(String message) { traceWindow.append(message + "\n"); // Text ausgeben und neue Zeile anfangen } public static void outToTraceS(String message) { traceWindow.appendIfVerbose(message); // Text nur ausgeben, falls } // "ausführlich" eingestellt public static void outToTracelnS(String message) { traceWindow.appendIfVerbose(message + "\n"); // wie outToTraceS(), } // aber neue Zeile beginnenpublic void outToTrace(String always, String supplement) // Falls Konsole nicht "ausführlich" nur String always ausgeben und neue // Zeile beginnen, sonst erst always, dann String supplement ausgeben und // keine neue Zeile beginnen. { if (! traceWindow.isVerbose()) traceWindow.append(always + "\n"); else traceWindow.appendIfVerbose(always + supplement); }
Falls es nötig sein sollte, Unterdemonstrationsschritte einzuführen, ist es sicherer und komfortabler, eine Instanz von DemonstrationStep statt zweier int-Variablen zu verwenden.
demonstrationStep = new DemonstrationStep((Rechner) this);
private void demonstrate() { if (demonstrationReady) { demonstrationReady = false; c_state = n_state; demonstrationStep.reset(); // Setzt Schritt auf 1, Unterschritte aus. } else { switch(c_state) { case READ: switch(demonstrationStep.get()) // Liefert den Schritt, egal, // ob Unterschritte aktiviert. { case 1: ... break; case 2: if (tagResult == READ_HIT) {...} else if (tagResult == READ_MISS_SYNC) // Unterschritte sind notwendig, weil hier mehr zu erklären ist. { if (! demonstrationStep.subStepEnabled()) demonstrationStep.subStep(3); // Drei Unterschritte machen, dann werden Unterschritte // automatisch wieder ausgeschaltet; subStep() bei schon // aktivierten Unterschritten produziert sicherheitshalber // eine Fehlermeldung. switch(demonstrationStep.getSub()) // Liefert den Unterschritt { ... } // Ende Verzweigung nach Demonstrationsunterschritt } break; ... } // Ende Verzweigung nach Demonstrationsschritt ... } // Ende Verzweigung nach c_state demonstrationStep.inc(); // erhöht Schritt ODER Unterschritt } // Ende "else"-Fall (demonstrationReady == false) } // Ende von demonstrate()
Neben inc() gibt es noch dec(), welches automatisch Schritt oder Unterschritt um eins vermindert. Zusätzlich kann noch die Methode stepsSinceReset() nützlich sein - sie liefert die Anzahl der nach dem letzten Aufruf von reset() gemachten Schritte, also die Anzahl der Aufrufe von inc() weniger die von dec(), zurück.
Eine Instanz hiervon wird in der Klasse Speicherhierarchie (der Cache-Demonstration) verwendet.
Mit ErrorMessage können Applets standardisierte Fehlermeldungen mit beliebig langem Text ausgeben. Dieses fällt mehr ins Auge, als eine Ausgabe in der Java-Konsole (die im übrigen im MS Internet Explorer gar nicht existiert).
Eine allgemeine Meldung erzeugt die Codezeile
(new ErrorMessage(title, subTitle, message)).show();
title ist dabei der Titel des Fensters, subTitle wird im Fenster über dem Text der Mitteilung angezeigt. Jede ErrorMessage plaziert sich selbsttätig und sorgt dafür, daß sie sich nicht mit schon existierenden Fehlermeldungen vollständig überdeckt.
Man verwende statt System.out.println(String message) einfach die als static deklarierte Methode (Rechner.)out(String message). Diese erzeugt eine ErrorMessage mit dem Fenstertitel "FEHLER", der Überschrift "Es ist ein Fehler aufgetreten:" und dem entsprechenden Text unter zwei Bedingungen:
Die Klasse ErrorMessage wird von allen Komponenten des Rechner-Baukastens und Demonstrations-Applets für diverse Fehlermeldungen benutzt.
RKWrapper ist ein sogenannter wrapper für die beiden Klassen RechnerKomponente und RechnerKomponentePanel, von denen sich die Elemente des Rechner-Baukastens ableiten (siehe 4.4.4 Die zwei Arten von Komponenten). Eine Instanz von RKWrapper verbirgt in ihrem Inneren eine Instanz der einen oder der anderen Klasse, kennt alle Methoden, die RechnerKomponente und RechnerKomponentePanel gemeinsam sind und ruft bei einem Aufruf einer Methode die korrespondierende Methode des Objektes in ihrem Inneren auf (und liefert gegebenenfalls den Wert zurück, den das "umfaßte" Objekt liefert).
Es gibt drei Konstruktoren:
public RKWrapper(RKWrapper source) public RKWrapper(RechnerKomponente rk) public RKWrapper(RechnerKomponentePanel rkp)
Folgende Methoden sind RKWrapper bekannt - deren Semantik ist natürlich dieselbe wie bei den Rechner-Komponenten:
public synchronized void paint(java.awt.Graphics onScreenGC) public synchronized void paintActivated(java.awt.Graphics onScreenGC) public synchronized void activate() public synchronized void deactivate() public boolean isActivated() public void lock() public void unlock() public void setValue(int newValue) public int getValue() public long getMaxValue() public void setCoordinates(int x, int y, String grabMode) public java.awt.Point getCoordinates(String qualifier) public String[] getPossibleQualifiers() public void reshape(int x, int y, int width, int height) public void reshape(int x, int y, int width, int height, String grabMode) public String[] getLabel() public void setLabel(String[] newLabel) // bei einer RechnerKomponentePanel // 'setLabel(newLabel[0])' aufrufen public void setLabel(String newLabel) public boolean intersectsWith(java.awt.Point p) public String getInfoTipText(java.awt.Point p)
Zusätzlich gibt es noch die Methoden
public RechnerKomponente getRK() public RechnerKomponentePanel getRKP() public boolean equals(RechnerKomponente toBeCompared) public boolean equals(RechnerKomponentePanel toBeCompared) public boolean equals(RKWrapper toBeCompared)
Mit Hilfe der ersten beiden kann man das "umfaßte" Objekt direkt ansprechen. Diese Vorgehensweise ist dann sinnvoll, wenn man genau weiß, daß es sich beispielsweise um eine ALU handelt - dann kann man (bei einer Instanz source von RKWrapper) mit der Zeile
((ALU) source.getRK()).calculate(...);
auch auf Methoden zurückgreifen, die RKWrapper nicht, das enthaltene Objekt aber sehr wohl kennt.
Mit den nächsten beiden Methoden läßt sich das "umfaßte" Objekt mit einer anderen Rechnerkomponente vergleichen; beide liefern false, falls eine Instanz von RechnerKomponente mit einer von RechnerKomponentePanel verglichen wird. Die letzte Methode vergleicht selbsttätig nur RechnerKomponente mit RechnerKomponente und RechnerKomponentePanel mit RechnerKomponentePanel (sofern jeweils vorhanden).
RKWrapper wird ausgiebig von dem Editor-Applet verwendet.
1.1-Nachtrag 20.01.98 :
Anzeigen von längeren Texten; der Text wird wortweise umgebrochen und erscheint auf Wunsch im Blocksatz. Sollte der Text nicht in den Ausgabebereich der RTFTextArea passen, erscheint ein Scrollbalken, um sich im Text zu bewegen. Text kann fett und kursiv ausgezeichnet werden, die dazu notwendigen Formatanweisungen entsprechen denen von HTML. Die relativ aufwendigen Umbruch- und Formatinformationen werden selbsttätig in einem internen Cache abgelegt.
Der Hilfetext im Von-Neumann-Rechner-Applet wird mit einer folgendermaßen initialisierten RTFTextArea realisiert:
helpText = new RTFTextArea(SMALLFONT); // Diese Schriftart wird kleiner, falls der Bildschirm kleiner als 800x600 // Pixel ist und ist andernfalls identisch mit NORMALFONT // (Helvetica, 12 Punkt, nicht fett). add(helpText); // RTFTextArea leitet sich von java.awt.Panel ab. coord = dbus.getCoordinates("end"); helpText.setLocation(0, coord.y + 10); helpText.setSize(Math.min(WIDTH, getScreenSize().width), HEIGHT - coord.y - 10); helpText.setText("Hier werden Hilfetexte erscheinen.");
Die Methoden setText(String text) und appendText(String text) funktionieren wie zu erwarten
und zeichnen obendrein den Text neu (Neuzeichnen kann aber auch durch repaint() erzwungen werden). Der
Blocksatz wird über setBlockMode(boolean b) und isBlockMode() kontrolliert. Unterstützte
Formatanweisungen im Text sind:
Formatanweisung |
Bedeutung |
<I> | Text ab hier kursiv |
</I> | Text ab hier nicht kursiv |
<B> | Text ab hier fett |
</B> | Text ab hier nicht fett |
<BR> oder <P> | Neue Zeile beginnen |
&< | Das Zeichen "<" |
Alle Hilfetexte der Demonstrations-Applets sind Instanzen von RTFTextArea. Diese Klasse wird außerdem für die Anzeige der Fehlerbeschreibung in ErrorMessage verwendet. Sie benutzt Instanzen von RTFParsedText für den internen Cache. Nach einem setText() wird die erste Zeile des neuen Textes angezeigt, appendText() hingegen scrollt nicht.
SimControl stellt das aus den Demonstrations-Applets bekannte Fenster zum Steuern der Simulationen über Schaltflächen und Menüs dar. Bei einigen der Knöpfe läßt sich verhindern, daß sie angezeigt werden, so daß die Leser erst nach und nach mit allen Steuerfunktionen bekannt gemacht werden können (wie in 3.3.2 Simulation des Von-Neumann-Rechners getan). Abbildung 37 zeigt einen "Vollausbau" einer Instanz von SimControl sowie die beiden Menüs; im "Einstellungen"-Menü sind dabei die Standardwerte gezeigt, welche während der Initialisierung und durch einen Klick auf "Standardwerte benutzen" eingestellt werden.
Eine Zeile der Form
scrollControl = new SimControl((Rechner) this);
ist ausreichend; das Steuerfenster wird angezeigt und bei der rechten oberen Ecke des aufrufenden Applets plaziert.
Für die fünf obersten Knöpfe werden, von links nach rechts, folgende Bilddateien benötigt: "Bilder/Pfeile/doppellinks.gif", "links.gif", "rechts.gif", "doppelrechts.gif" und "breakpoint.gif".
Die nachstehende Tabelle zeigt, welche Variablen aus Rechner vor dem Erzeugen einer SimControl
auf false gesetzt werden können, damit bestimmte Elemente nicht erscheinen:
Auf false gesetzte Variable |
Nicht erscheinende(s) Element(e) |
buttonFast | , und |
manualRedraw | Knöpfe "Repaint" und "Update" |
programChoice | Auswahlfeld für Programme ("Bubble Sort" oben) |
buttonDecode | Auswahlknopf "DECODE-Phase zeigen" |
buttonOpcodes | Auswahlknopf "Opcodes anzeigen" |
Angeklicktes Objekt |
Ausgeführter Code |
|
if (parent.firstTime == true) // allererster Klick { parent.firstTime = false; parent.demonstrate(false); parent.demonstrate(true); } else parent.demonstrate(true); parent.paintActivated(parent.onScreenGC); |
|
parent.firstTime = false; parent.demonstrateBack(); |
|
if (parent.firstTime == true) { parent.firstTime = false; parent.demonstrate(true); } parent.singleInstruction(); |
|
parent.firstTime = false; parent.singleInstructionBack(); |
|
if (parent.firstTime == true) { parent.firstTime = false; parent.demonstrate(true); } parent.goUntilBreakpoint(); |
Knopf "Simulation neu starten" | parent.stopSimulation() parent.updateAll(); parent.deactivateAll(); parent.paint(parent.onScreenGC); |
Knöpfe "Repaint" oder "Update" | parent.scrollThread neu starten if (<Knopf "Update" gedrückt>) { parent.updateAll(); parent.deactivateAll(); } parent.paint(parent.onScreenGC); |
Auswahlknopf "DECODE-Phase zeigen" | parent.showDecodeCycle wird entsprechend gesetzt. |
Auswahlknopf "Opcodes anzeigen" | Die Methode parent.showAllOpcodes mit entsprechendem booleschen Parameter wird aufgerufen. |
Menüpunkt "Auf n Stellen erweitern" | parent.expandNumbers wird entsprechend gesetzt. parent.updateAll(); parent.paint(parent.onScreenGC); |
Menüpunkt "Dezimalzahlen" | parent.NUMBERBASE wird entsprechend auf 10 oder 16 gesetzt. parent.updateAll(); parent.paint(parent.onScreenGC); |
Menüpunkt "Standardwerte benutzen" | parent.expandNumbers = true; parent.NUMBERBASE = 16; Setzen oder Entfernen der Häkchen vor den beiden Menüpunkten. parent.updateAll(); parent.paint(parent.onScreenGC); |
Menüpunkt "Befehlsgeschichte anzeigen" | Rechner.traceWindow wird (bei Bedarf initialisiert und) entweder angezeigt oder versteckt. |
Menüpunkt "Info" | (new AboutRechnerDialog(parent, this)).show(); (siehe 4.12.1) |
Auswahlfeld für Programme Die Methode initRam() ist in Rechner leer vordefiniert; im Von-Neumann-Rechner-Applet lautet sie protected void initRam(String program) { PROGRAM = program; ram.initRam(program); } wobei ram der Hauptspeicher (eine Instanz von EditableMemory) ist. |
parent.stopSimulation(); Die Zeichenkette label entspricht der angeklickten Zeile. if (label.equals("Alles 0000")) parent.PROGRAM = "zero"; else if (label.equals("Alles 00ff")) parent.PROGRAM = ""; else if (label.equals("Nur LDA abs.")) parent.PROGRAM = "vnr_loadabs"; else if (label.equals("Alle Lade-/Speicherbefehle")) parent.PROGRAM = "vnr_loadstore"; else if (label.equals("Einfaches Rechnen")) parent.PROGRAM = "vnr_calculate"; else if (label.equals("Sprungbefehle")) parent.PROGRAM = "vnr_jump"; else if (label.equals("Alle Befehle")) parent.PROGRAM = "allcommands"; else if (label.equals("Cachetest 1")) parent.PROGRAM = "cachetest"; else if (label.equals("Cachetest 2")) parent.PROGRAM = "cachetest2"; else if (label.equals("Verdrängungstest")) parent.PROGRAM = "replacetest"; else if (label.equals("Bubble Sort")) parent.PROGRAM = "bubblesort"; else if (label.equals("Laden/Speichern")) parent.PROGRAM = "loadstore"; else if (label.equals("Laden/Speichern 2")) parent.PROGRAM = "loadstore2"; parent.initRam(parent.PROGRAM); parent.paint(parent.onScreenGC); |
SimControl verwendet die Klasse AboutRechnerDialog (siehe 4.12.1).
Zeigt ein einfaches Fenster an, um zu verdeutlichen, wie lange es noch dauern wird, bis alle Klassen für ein Applet geladen sein werden - ein Rechteck füllt sich allmählich von links nach rechts mit senkrechten Strichen.
wm = new LoadProgress("Bitte warten, die Klassen für das Applet\n'" + VERSIONSTRING + "'\nwerden geladen.", 1. LoadProgress.WAITLENGTH, 2. (Rechner) this); 3.
Zu den Parametern:
Vorrangiges Entwicklungsziel war die Einfachheit dieser Klasse, damit sie schnell geladen und möglichst früh angezeigt werden kann. Es gibt aber auf jeden Fall kaum einen praktikablen Automatismus, um die gesamte oder verbleibende Wartezeit zu ermitteln; LoadProgress führt deswegen empirische Messungen der Wartezeit durch und ist darauf angewiesen, daß der Programmautor diese in Anweisungen im Code, wann die Warteanzeige wie weit fortzuschreiten habe, umsetzt:
public void init() { wm = new LoadProgress(...); initialize(new Dimension(510, 490), new Dimension(510, 380)); wm.inc(9); ... scrollControl = new SimControl((Rechner) this); wm.inc(47); ... dbus = new SimpleBus("Datenbus", dbusStartX, dbusStartY, 0, 185, (Rechner) this); wm.inc(1); ... wm.hide(); wm.dispose(false); // "true" während der Testphase, um Statistik auszugeben }
Wird dispose() mit true als Parameter aufgerufen, gibt die LoadProgress, vor ihrer Auflösung, eine Statistik darüber aus, wann inc() mit welchem Parameter aufgerufen wurde, und welcher Parameter am besten zu der ermittelten Wartezeit gepaßt hätte. Achtung, diese Werte sind natürlich von dem verwendeten Computer (Geschwindigkeit, Betriebssystem und VM) und der Herkunft des Bytecodes (von einer lokalen Festplatte oder über eine Netzanbindung) abhängig! Hier gilt es, einen Kompromiß zu finden.
Diese Klasse wird von den Applets Von_Neumann_Rechner und Speicherhierarchie benutzt.
Ein TimerThread kann verwendet werden, um in gleichen Abständen bestimmte Methoden "anzustoßen", damit z.B. die Punkte auf allen aktivierten Bussen sich gleichmäßig weiterbewegen: Eine Instanz von TimerThread hat einen Namen und kennt eine Wartezeit und eine Instanz von Rechner. Sie "schläft" immer wieder für die Dauer der Wartezeit und ruft dann timerWokeUp(String timerTitle) des Rechner-Objektes auf, wobei sie als timerTitle ihren Namen übergibt.
scrollThread = new TimerThread(SCROLLTIME, "scrolltimer", (Rechner) this); scrollThread.start();
Der erste Parameter ist die Zeit (in ms) die der TimerThread "schläft", der zweite sein Titel, der dritte das zu benachrichtigende Rechner-Objekt. Angehalten wird ein TimerThread über Code wie diesen:
if ((scrollThread != null) && (scrollThread.isAlive())) scrollThread.stop(); scrollThread = null;
Modifizieren sie timerWokeUp() ihres Rechners nach Wunsch:
public synchronized void timerWokeUp(String title) { if (title.equalsIgnoreCase("scrolltimer")) scrollAll(); else if (title.equalsIgnoreCase("blinktimer")) { blinkAll(); onScreenGC.drawImage(offScreenImage, 0, 0, this); } } /* end timerWokeUp */
Damit sich Punkte auf den Bussen Ihres Rechneraufbaus bewegen, benötigen Sie lediglich eine Methode scrollAll() nach folgender Art:
public synchronized void scrollAll() { dbus.scroll(); // hier alle Busse aufführen abus.scroll(); abus.paintActivated(offScreenGC); // hier alle Busse aufführen dbus.paintActivated(offScreenGC); onScreenGC.drawImage(offScreenImage, 0, 0, this); } /* end scrollAll */
In Rechner ist bereits der TimerThread scrollThread wie oben aufgeführt vordefiniert, d.h. seine Wartezeit beträgt Rechner.SCROLLTIME (100 ms), nach welcher Zeitdauer er timerWokeUp( "scrolltimer") aufruft, welche Methode ebenfalls vordefiniert ist, und zwar eben so, daß scrollAll() aufgerufen wird.
Falls sich die Punkte langsamer oder schneller bewegen sollen, verändern sie einfach SCROLLTIME,
bevor sie initialize() aufrufen.
Carsten Kelling 1997 (); letzte Änderung: 17. September 1997 |