Translate this page...

BeanDev: GUI Beans, ein Custom Property-Editor

In den letzten drei Teilen nahm das JImagePanel durchaus schöne Formen an. Nur fehlt mir noch ein Editor für das Hintergrundbild. Die einfachste Möglichkeit per Dateipfad ein Bild auszuwählen möchte ich hier besprechen.

Vorweg auch der größte Nachteil: Der Pfad ist natürlich unabhängig vom Programm. Das ist für eine Anwendung, die bei einem Nutzer installiert wird, nicht praktikabel. Zu einem späteren Zeitpunkt zeige ich aber die Möglichkeit, Bilder aus dem Projekt zu wählen. Hier im letzten Teil geht es aber zunächst nur um den Custom Editor. Eine weitere Besonderheit ist der Typ des Property: ImageIcon. Der PropertyEditor verwaltet aber nur eine Pfadangabe. Bestimmt wäre es einfacher das Property von Anfang an als Pfadangabe zu betrachten, aber im realen Leben ist es leider nicht immer zu vermeiden, dass man solche Sonderfälle zu meistern hat. Deswegen auch hier ein komplizierteres Beispiel.

Unser JavaBean JImagePanel hat das Property backgroundImage und erwartet den Typ ImageIcon. Da man dem GUI-Design-Anwender nicht zumuten kann, das Bild jedes mal neu zu zeichnen, müssen wir eine Möglichkeit bieten, dieses von der Festplatte zu laden. Natürlich könnte der Anwender auch den Pfad als Text eingeben, aber das soll ja bequem gehen.

Zunächst brauchen wir wieder einen PropertyEditor für ImageIcons. Strg+N -> Kategorie "JavaBeans Objects" -> Property Editor. Name: ImageIconPropertyEditor.

Die Java-Klasse wird dann folgendermaßen erweitert: 

package org.sepix.beandemo;

import java.beans.*;
import javax.swing.ImageIcon;

public class ImageIconPropertyEditor extends PropertyEditorSupport {
private String pathToImage;

@Override
public String getJavaInitializationString() {
if ( pathToImage != null ) {
return "new javax.swing.ImageIcon (\"" + pathToImage.replace ('\\', '/') + "\")";
}
return "null";
}

@Override
public String getAsText() {
ImageIcon ii = (ImageIcon) getValue();
if ( ii == null ) return null;
return pathToImage;
}

@Override
public void setAsText(String text) throws IllegalArgumentException {
pathToImage = text;
setValue(new ImageIcon (pathToImage));
}
}

Im Gegensatz zum letzen PropertyEditor wird hier getTags() nicht überschrieben. Damit kann ein Anwender auch direkt einen Pfad in der Wertfeld des Properties eingeben. Die Methode getAtText() und setAsText(String) konvertieren eine Pfadangabe in ein ImageIcon. Eingehende Prüfungen auf korrekte Pfadangaben ignoriere ich erstmal (Nur Backslash wird in Slash gewandelt)

Der Property-Editor wird wieder (per BeanInfo-Editor), in der JImagePanelBeanInfo-Klasse registriert.

Gewünscht ist aber die Auswahl per Custom Editor. Dazu erstelle ich zunächst ein Customizer Panel. Strg+N -> Kategorie "JavaBeans Objects" -> Customizer. Der Name ist: ImageIconCustomizer.

So sollte der minimale Customizer dann aussehen:

package org.sepix.beandemo;

public class ImageIconCustomizer extends javax.swing.JPanel implements java.beans.Customizer {
private Object bean;

public ImageIconCustomizer() {
initComponents();
}
public void setObject(Object bean) {
this.bean = bean;
}
private void initComponents() {
setLayout(new java.awt.BorderLayout());
}
}

Im Design-Modus habe ich den Customzier in dieser Art aufgebaut:

Ansonsten ist noch keine Funktion hinterlegt. Der Customizer muss aber nun im PropertyEditor hinterlegt werden:

public class ImageIconPropertyEditor extends PropertyEditorSupport {
private String pathToImage;
private ImageIconCustomizer customizer;
public ImageIconPropertyEditor () {
customizer = new ImageIconCustomizer();
}

und am Ende folgende Methoden hinzufügen:

    @Override
public Component getCustomEditor() {
return customizer;
}

@Override
public boolean supportsCustomEditor() {
return true;
}

Das war es auch schon erstmal. "Clean & Build" des Projektes. Im Test-JFrame das alte Bean entfernen und aus der Bean-Kategorie erneut hinzufügen. Jetzt kann man im Property backgroundImage den Button [...] anklicken und bekommt den selbst programmierten Customizer angeboten. Zusätzlich kann man natürlich über die ComboBox alle weiteren NetBeans-Editoren auswählen.

Die Kommunikation zwischen PropertyEditor und Customizer muss über PropertyChangeEvents erfolgen. Wenn der Customizer etwas ändert, muss dieser ein Event feuern. Der PropertyEditor setzt ohne zu zögern den Wert in das Bean ein. Zwar kann der Anwender den Customizer-Dialog abbrechen, aber um das Zurücksetzen des alten Wertes kümmert sich dann NetBeans selber.

Also erweitere ich schon mal den ImageIconPropertyEditor, um das Event-Handling:

    public ImageIconPropertyEditor() {
customizer = new ImageIconCustomizer();
customizer.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ( "backgroundImage".equals(evt.getPropertyName()) ) {
setAsText (customizer.getPath());
}
}
});
}

Die Methode getPath() existiert im Customizer noch nicht, wird aber gleich hinzugefügt. Ich lege dazu das Feld path an, dazu noch setPath und getPath und weitere Hilfsmethoden zum Anpassen der Customizer-GUI-Ausgabe:

public class ImageIconCustomizer extends javax.swing.JPanel implements java.beans.Customizer {

private Object bean;
private String path;

// ... Constructor und setObject bleibt wie es ist

public void setPath (String path) {
this.path = path;
updateEditor();
updateImage();
}

public String getPath () {
return path;
}

private void updateUserChange (String newPath) {
path = newPath;
firePropertyChange("backgroundImage", null, null);
updateEditor();
updateImage();
}

private void updateEditor() {
jTF_Path.setText (path != null ? path : "");
}

public void updateImage() {
jLB_IconViewer.setIcon(path != null ? new ImageIcon(path) : null);
}

Die obige Methode updateUserChange() kümmert sich um das Feuern des Events und um die passende Aktualisierung im Customizer-Dialog. Damit der Dialog überhaupt auf Benutzereingaben reagieren kann, setze ich einfach zwei Action-Listener an das Textfeld und den Button für die Pfadauswahl. In NetBeans ist das sehr einfach gemacht. Im Designer des Customizers das Textfeld markieren -> Property Window -> View Events -> In das Wertfeld von actionPerformed klicken -> jTF_PathActionPerformed mit [Enter] bestätigen. NetBeans wechselt sofort in den Quelltexteditor und ich fülle die neue Methode aus:

    private void jTF_PathActionPerformed(java.awt.event.ActionEvent evt) {                                         
updateUserChange (jTF_Path.getText());
}

Der gleiche Vorgang mit dem Button. Im Designer des Customizers den Button markieren -> Property Window -> View Events -> In das Wertfeld von actionPerformed klicken -> jPB_ChooserActionPerformed mit [Enter] bestätigen. NetBeans wechselt erneut in den Quelltexteditor und ich fülle die zweite Methode so aus:

    private void jPB_ChooserActionPerformed(java.awt.event.ActionEvent evt) {
JFileChooser loadFile = new JFileChooser (path);
loadFile.setFileFilter(new FileFilter() {
@Override
public boolean accept(File f) {
String name = f.getName().toLowerCase();
return name.endsWith(".jpg")
|| name.endsWith(".png")
|| name.endsWith(".gif");

}
@Override
public String getDescription() {
return "Grafikdateien";
}
});
int result = loadFile.showOpenDialog(this);
if ( result == JFileChooser.APPROVE_OPTION ) {
updateUserChange (loadFile.getSelectedFile().getAbsolutePath());
}
}

(ggf. muss man nun die Imports korrigieren. Rechte Maustaste -> Fix imports...).

Das war es auch schon. Der Customizer funktioniert nun und übergibt die Benutzereingaben an den PropertyEditor. Damit kann der Customizer mit dem Editor schon erheblich mehr, als das eigentlich Bean. Denn JImagePanel macht ja nun überhaupt nichts mit den mühevoll übertragenen Daten.

Aber auch das ist schnell erledigt. Einfach folgende Methode in JImagePanel einfügen:

    @Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
ImageIcon background = getBackgroundImage();
if ( background != null ) {
Dimension s = getSize();
int x = 0;
int y = 0;
int width = background.getIconWidth();
int height = background.getIconHeight();
switch ( getAppearance() ) {
case SCALE: {
width = s.width;
height = s.height;
break;
}
case CENTER: {
x = (s.width/2) - (width/2);
y = (s.height/2) - (height/2);
break;
}
}
g.drawImage(background.getImage(), x, y, width, height, this);
}
}

Die Methode berücksichtigt allerdings nicht die Breite eines eingefügten Border-Objektes. Das ist aber bestimmt einfach nachzuprogrammieren.

 

Mit diesem kleinen Vierteiler sollte es nun möglich sein, beliebig komplexe GUI JavaBeans in NetBeans integrieren zu können. Inklusive eigenen Property-Editoren. Über Feedback würde ich mich natürlich freuen.

Beste Grüße,
  Josch.


Teil 1 | Teil 2 | Teil 3 | -> Teil 4


http://www.netbeans-forum.de/ Da werden Sie geholfen:
Das deutsche NetBeans Forum

      NetBeans Dream Team Member

1 Antwort to “BeanDev: GUI Beans, ein Custom Property-Editor”

  1. Marian Wendt schreibt:

    Hi,

    starkes Tutorial! Würde es sehr begrüßen, wenn die erwähnte Erweiterung zur Nutzung von Grafiken aus Projekten heraus (subdir resources) bald als "Teil 5" erscheinen würde.

    Nochmals: Besten Dank.

Hinterlasse eine Nachricht

Meine Informationen merken

CAPTCHA Bild zum Spamschutz 

[Valid RSS]