Java-Programmierung mit Netbeans: JFrame mit Menü, Info-Dialog, Methoden zum Speichern und Öffnen Wir schreiben ein Demo-Programm. 1. Start: New Project / Java Application Project Name = MenuDemo Project Location: (Browse) P:\Informatik\Java\Netbeans Der Haken für Create Main Class bleibt gesetzt! (Finish) Hauptfenster (JFrame): New File / Swing GUI Forms / JFrame Form, Class Name = MenuDemoFrame (Finish) Datei MenuDemo.java bearbeiten: Die bisher noch leere main-Methode muss die main-Methode von MenuDemoFrame aufrufen, also: public static void main(String[] args) { MenuDemoFrame.main(args); } MenuDemo.java kann jetzt geschlossen werden. Das Programm muss sich starten lassen und einen leeres JFrame-Fenster zeigen. 2. Layout-Gestaltung (Design) von MenuDemoFrame: Die Eigenschaft „title“ wird auf „Menü-Demo“ eingestellt. Ein JTextArea wird auf dem JFrame platziert (jTextArea1). Dann wird ein JMenuBar an den oberen Fensterrand gesetzt. Der Menübalken besitzt bereits die Menüs „File“ und „Edit“. Durch Bearbeiten der Eigenschaften (Properties) werden die Menütexte auf „Datei“ und „Hilfe“ geändert. Zuerst erzeugen wir im Menü „Hilfe“ einen Menüpunkt: „Hilfe“ anklicken und mit Kontextmenü (rechte Maustaste) „Add From Palette / Menu Item“ wählen. Die Eigenschaften des so erhalten jMenuItem1 werden bearbeitet: Der Text (Registerkarte „Properties“) soll „Info“ lauten, der Name (Registerkarte „Code“) wird auf jmiInfo geändert. Auf der Registerkarte „Events“ richten wir eine „actionPerformed“-Methode ein (das geht auch per Doppelklick auf den im Design dargestellten Menüpunkt). Wir erhalten im Quelltext auf diese Weise private void jmiInfoActionPerformed(java.awt.event.ActionEvent evt) { // TODO add your handling code here: } 3. Info-Dialog: Für den Info-Dialog erzeugen wir ein neues Fenster, indem wir erneut New File wählen: New File / Swing GUI Forms / JDialog Form Class Name = InfoDialog, Finish Das Dialogfenster wird ungefähr so gestaltet, wie die Abbildung zeigt. Eigenschaften vom InfoDialog: „title“ wird auf „Info“ gesetzt (= Fenstertitel), „resizable“ auf false (d.h. das Häkchen wird entfernt). Für den OK-Schalter (jButton1) wird eine actionPerformed-Methode erzeugt. Dann wird der Quelltext von InfoDialog.java bearbeitet. Zunächst die actionPerformed-Methode: private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { dispose(); // schließt das Fenster } Die main Methode des Dialogfensters ist überflüssig, sie gibt aber einen Hinweis darauf, wie der Dialog erzeugt und angezeigt wird. Sie enthält außerdem einen Quelltext für das Beenden der ganzen Anwendung. Wir löschen diese Methode deshalb erst am Ende von Punkt 4. Als nächstes wird der Aufruf des Info-Dialogs durch den Menüpunkt des Hauptfensters implementiert: Die vorbereitete actionPerformed-Methode des Info-Menüpunkts erhält folgendes Aussehen: private void jmiInfoActionPerformed(java.awt.event.ActionEvent evt) { InfoDialog myInfo = new InfoDialog (this, true); myInfo.setVisible(true); } Nun wird der Aufruf des Info-Dialogs durch den entsprechenden Menüpunkt getestet. Wenn es funktioniert, geht es weiter. 4. Erweiterung des Menüs Datei: Beenden des Programms Wie im Punkt 2 (Layout) beschrieben wird auch im Menü Datei ein Menüpunkt ergänzt. Erneut erhalten wir jMenuItem1 und bearbeiten dieses Objekt wie folgt: Der Name wird auf jmiEnde geändert, der angezeigte Text wird „Beenden“, eine actionPerformed-Methode wird erzeugt: private void jmiEndeActionPerformed(java.awt.event.ActionEvent evt) { System.exit(0); } Die Methode main im InfoDialog (nicht in MenuDemoFrame!!) jetzt löschen, Programm testen! 5. Erweiterung des Menüs Datei: Speichern des Textes von jTextArea1 Im Menü Datei wird ein weiterer Menüpunkt ergänzt. Der Name dieser Komponente wird in jmiSpeichUnter umbenannt, der Text wird in „Speichern unter...“ geändert. Die Abbildung zeigt den Navigator, der bei der DesignAnsicht angezeigt wird (falls nicht: im Netbeans-Fenster das Menü „Windows“ wählen und dort suchen). Im Navigator lassen sich manchmal Komponenten gezielter auswählen, auch hier kann der Menüpunkte jmiSpeichUnter verschoben werden, falls der neue Menüpunkt unter jmiEnde geraten ist. Außerdem zeigt diese Übersicht, dass es wohl sinnvoll ist, jMenu1 und jMenu2 zu aussagekräftigeren Namen umzubenennen: jmDatei und jmHilfe wird vorgeschlagen. Dazu wird das jeweilige Properties-Fenster für diese Komponenten herangezogen, dann sollte es gelingen. Eine bequemer Weg zur actionPerformed-Methode von jmiSpeichUnter ist der Doppelklick auf den Eintrag im Navigator, und wir erhalten private void jmiSpeichUnterActionPerformed(java.awt.event.ActionEvent evt){ // TODO add your handling code here: } 6. Methode, die einen String speichert Dieser Methode werden zwei Parameter übergeben: die ausgewählte Datei, in die Daten gespeichert werden sollen, und der zu speichernde String. Schreiben Sie zuerst den Rumpf der Methode: private void speichern(java.io.File datei, String daten) { } Der Quelltext muss immer noch fehlerfrei zu compilieren sein! Jetzt fügen Sie zwischen die geschweiften Klammern die folgende Zeilen ein: PrintWriter pw; pw = new PrintWriter(datei); pw.write(daten); pw.close(); // AusgabeStream erzeugen // Daten schreiben // Schreiben abschließen Es erscheinen Fehlerhinweise (Glühlampe mit Ausrufezeichen) am linken Editor-Rand: Zunächst können Sie durch den Klick auf das Lämpchen den Import für die Klasse PrintWriter einrichten. Das weitere Lämpchen steht für die Meldung unreported exception FileNotFoundException; must be caught or declared to be thrown. Grund: Das Programm kann zu einem Fehler führen, wenn die Datei nicht angelegt werden kann. Netbeans fordert, dass Exceptions (=Ausnahmen, Fehler) vom Programmierer behandelt werden müssen, damit nicht später der Anwender ratlos vor einem abgestürzten Programm sitzt. Ein Klick auf die Glühlampe bietet an, eine throws-Anweisung zu ergänzen, diese würde aber nur das Problem in die Methode verlagern, von der aus wir das Speichern aufrufen wollen. Wir wählen die Abhilfe Surround Block with try-catch, damit wird der Fehler abgefangen, der Quelltext lautet nun: private void speichern(java.io.File datei, String daten) { try { PrintWriter pw; pw = new PrintWriter(dateiName); pw.write(daten); pw.close(); } catch (FileNotFoundException ex) { // FileNotFoundExceptions abfangen Logger.getLogger(MenuDemoFrame.class.getName()).log(Level.SEVERE,null, ex); } } Die hier beschriebene Methode kann nur Strings speichern. Für das Speichern anderer Daten (int, byte, double) informieren Sie sich im Internet oder bei Ihrer Informatik-Lehrkraft. 7. jmiSpeichUnterActionPerformed vervollständigen Wir benötigen ein Dialogfenster, das uns die Auswahl des Speicherziels und des Dateinamens ermöglicht. Dieses stellt uns JAVA mit der Klasse JFileChooser zur Verfügung. Instanzen dieser Klasse besitzen die Methoden showOpenDialog und showSaveDialog, die sich fast nur im vordefinierten Fenstertitel unterscheiden. Mit dem Schließen des Dialogfensters hat der Anwender nicht unbedingt eine Datei ausgewählt, er könnte auch die Auswahl abgebrochen haben. Deshalb geben die show-Methoden einen int-Wert zurück, aus dem hervorgeht, ob der Dialog mit OK beendet wurde. Der entsprechende int-Wert ist statisch in der Klasse JFileChooser als Konstante festgelegt, so dass wir den Zahlenwert nicht wissen müssen. Nur bei OK führen wir das Speichern durch: private void jmiSpeichUnterActionPerformed(java.awt.event.ActionEvent evt) { JFileChooser jfc = new JfileChooser(); if (jfc.showSaveDialog(this)==JFileChooser.APPROVE_OPTION) { speichern(jfc.getSelectedFile(),jTextArea1.getText()); } } Jetzt kann ein Text in JTextArea1 geschrieben und dann gespeichert werden (bitte testen!). Beim Speichern wird nicht gewarnt, wenn eine Datei dieses Namens bereits besteht und daher überschrieben wird. Wenn Sie diese Warnung wünschen, müssen Sie dafür sorgen, dass nur dann gespeichert wird, wenn die Datei nicht existiert ODER wenn im Falle der Existenz die Rückfrage bestätigt wird (hier können Sie sich den Unterschied zwischen | und || deutlich machen!). Die eine Zeile zum Speichern aus dem letztgenannten Quelltextabschnitt wird durch die folgenden Zeilen ersetzt: if (!jfc.getSelectedFile().exists() || JOptionPane.showConfirmDialog(this, "Möchten Sie die vorhandene Datei überschreiben?", "Datei existiert bereits", JOptionPane.YES_NO_OPTION) ==JOptionPane.YES_OPTION) { speichern(jfc.getSelectedFile(), jTextArea1.getText()); } Ergänzende Information: JAVA speichert als Standard im UTF-8-Format. Word2003 erkennt die Umlaut-Codierung und bietet das Öffnen als UTF-8-Text an. Libre-Office4 liest die Umlaute des gespeicherten Textes unter Windows standardmäßig falsch ein, jedoch kann man beim Öffnen als Dateityp „Text kodiert (*.txt)“ wählen und so auch UTF-8-kodierten Text fehlerfrei öffnen. Es gibt noch ein weiteres Kompatibilitätsproblem: JAVA speichert die Zeilenumbrüche durch den Code 10=0x0a (new line "\n") , während Windows üblicherweise die zwei Codes 13=0x0d und 10=0x0a (return + new line "\r\n") verwendet. Die genannten Word- und Libre-Office-Versionen stellen die Zeilenumbrüche korrekt dar. Wordpad und der Windows-Editor verhalten sich unterschiedlich: Wordpad stellt die Umbrüche korrekt dar, aber nicht die Umlaute (dazu fehlen 3 UTF8-Kennbytes am Dateianfang). Der Editor von Windows7 macht es genau umgekehrt, er ignoriert Zeilenumbrüche, die nicht als 0x0d0a codiert sind, aber er stellt die UTF-8-Umlaute korrekt dar. Die Verwendung von UTF-8 hat Vorteile: In diesem Format können Sie Texte mit allen Zeichensätzen speichern, die Ihr System verwalten kann, also auch z.B. chinesische Zeichen, während im WindowsFormat (genauer: windows-1252) nur weniger als 256 verschiedene Zeichen möglich sind. Der Konstruktor des PrintStreams kann mit Angabe eines Charset (Zeichensatz) verwendet werden, wenn Windows-Kompatibilität gewünscht wird: pw = new PrintWriter(datei, "windows-1252") Windows-Zeilenumbrüche geben Sie dem String durch folgende Formulierung: jTextArea1.getText().replaceAll("\n","\r\n") 8. Öffnen einer Textdatei Ergänzen Sie einen Menüpunkt im Menü Datei. Wenn er nicht an oberster Stelle im Menü erscheint, verschieben Sie ihn dorthin. Nennen Sie die Komponente jmiOeffnen, geben Sie ihr den Anzeigetext „Öffnen...“. Richten Sie die zugehörige actionPerformed-Methode ein: private void jmiOeffnenActionPerformed(java.awt.event.ActionEvent evt) { JFileChooser jfc = new JFileChooser(); if (jfc.showOpenDialog(this)==JFileChooser.APPROVE_OPTION) { jTextArea1.setText(oeffnen(jfc.getSelectedFile())); } } Natürlich wird für die Methode oeffnen noch ein Fehler angezeigt, diese schreiben wir als nächstes: private String oeffnen(java.io.File datei) { String s = ""; try { byte[] lesePuffer = new byte[200]; ByteArrayOutputStream baos = new ByteArrayOutputStream(); FileInputStream fis; fis = new FileInputStream(datei); int z = fis.read(lesePuffer); while (z!=-1) { baos.write(lesePuffer, 0, z); z = fis.read(lesePuffer); } fis.close(); s = baos.toString(); } catch (IOException ex) { Logger.getLogger(MenuDemoFrame.class.getName()).log(Level.SEVERE,null,ex); } return s; } Die Methode ist komplizierter als die Speicher-Methode, weil beim Öffnen noch nicht feststeht, wie viele Zeichen eingelesen werden müssen. Die Daten werden in eine Lesepuffer eingelesen (Sie können ihn gern auch mehrere Kilobyte groß machen) und von dort in einen ByteArrayOutputStream geschrieben. z erhält jeweils die Anzahl der eingelesenen Bytes bzw. -1, wenn das Ende erreicht wurde. Der ByteArrayOutputStream gestattet die einfache Umwandlung der hinein geschriebenen Daten in einen String. Da JAVA vom UTF-8-Format ausgeht, werden Umlaute aus einem Text mit Windows-Codierung nicht korrekt eingelesen. Sie können aber mit baos.toString("windows-1252") einen CharSet-Namen angeben, um dieses Verhalten zu ändern. Beim eingelesenen String können Sie "\r\n" durch "\n" ersetzen – testen Sie mal, ob das nötig ist. 9. Verbessern der Fehlerbehandlung Beim Öffnen einer Datei lässt sich leicht eine FileNotFoundException erzeugen, indem Sie einen Dateinamen eingeben, der nicht existiert. Netbeans gibt dann die Fehlermeldung im Ausgabefenster aus – das nützt aber nichts, wenn Sie das Programm ohne Netbeans starten. Schöner ist also ein Meldungsfenster, ähnlich wie oben bei dem Warnhinweis vor dem Überschreiben einer bestehenden Datei. Schreiben Sie deshalb folgende Methode: private void fehlerMelden(Exception e) { JOptionPane.showMessageDialog(this, e.getMessage(), "Fehler:", JOptionPane.ERROR_MESSAGE); } Nun können Sie alle Zeilen Logger.getLogger(MenuDemoFrame.class.getName()).log(Level.SEVERE,null,ex) ersetzen durch fehlerMelden(ex); Wenn alle Logger-Anweisungen ersetzt sind, können Sie ganz oben im Quelltext bei den Importen aufräumen und unbenutzte Importe über Klick auf ein Warn-Glühbirnchen entfernen. 10. Look & Feel Dieses Thema ist eigentlich zu umfangreich, um in ein Menü-Demo-Programm aufgenommen zu werden. Da dieses Programm jedoch viele Features besitzt, die Sie von Windows-Programmen kennen, wünschen Sie vielleicht das Aussehen des Programms und seiner Dialogfenster im gewohnten Design (sofern Sie nicht begeisterter Nutzer eines anderen Betriebssystems wie z.B. Linux sind). Das erreichen Sie ganz einfach, indem Sie in der main-Methode von JMenuDemoFrame.java das Design von "Nimbus" auf "Windows" setzen (öffnen Sie den ausgeblendeten Text durch Klick auf das Plus-Zeichen am Rand bei „Look and feel setting code (optional)“). Für die Weitergabe eines fertigen Programms ist es aber sinnvoller, sich über die Einrichtung eines zum jeweiligen Betriebssystem passenden Designs zu informieren. 11. Fensterposition Das Programmfenster erscheint (wie auch der Info-Dialog) bisher stets in der oberen linken Bildschirmecke, also an der Position (0|0). Dieses Verhalten können Sie durch die Angabe einer Position ändern, fügen Sie z.B. unmittelbar nach der initComponents(); -Zeile im Konstruktor die folgende Zeile ein: setLocation(200,50); Sie können auch die Bildschirmgröße auslesen, um das Fenster zentral zu positionieren. Das können Sie z.B. beim Info-Dialog einrichten (aber auch beim Hauptfenster), und zwar wieder unmittelbar nach dem Aufruf von initComponents();: Toolkit tk = getToolkit(); setLocation((tk.getScreenSize().width-this.getWidth())/2, (tk.getScreenSize().height-this.getHeight())/2); (Für die Klasse Toolkit müssen Sie java.awt.Toolkit importieren). Sp/2014