Diese FAQ hat Reinhold (Autor von Jollina) aus einer Reihe von ausgetauschten Mails zusammengestellt.
Name | Wert |
---|---|
Project | jameica |
Main-Class | de.willuhn.jameica.Main |
Program-Arguments | „-f /home/<username>/.jameica.test -p test“. Der Parameter „-f“ gibt ein vom Standard abweichendes Plugin-Verzeichnis vor, „-p“ übergibt das Master-Passwort direkt, sodass es beim Starten im Eclipse-Degbugger nicht immer manuell eingegeben werden muss |
VM-Arguments | „-Djava.library.path=lib/swt/linux“ bzw. „-Djava.library.path=lib/swt/win32“ (abhängig vom verwendeten Betriebssystem) |
Classpath | Default-Classpath des Projekts „jameica“ |
[...] jameica.plugin.dir.0=plugins jameica.plugin.dir.1=../jameica_exampleplugin [...]
Dieser Fehlercode tritt auf, wenn zwei Plugin's gegenseitig auf die Daten des jeweils anderen Plugin zugreifen. Folgende Einstellungen in den Project/Properties von eclipse sind zu treffen:
Eclipse compiliert nun alle Projekte neu - fertig .
SWT ist single-threaded. Greift man nicht aus dem GUI-Thread heraus auf SWT-Komponenten zu, erscheint eine „SWTException: Invalid Thread Access“. Das kannst man umgehen, indem die Aktion über die SWT-Job-Queue ausgeführt wird.
public void run() { GUI.getDisplay().syncExec(new Runnable() { public void run() { // Hier Programm-Code ausführen } }); }
Ja. In einer View hast du Zugriff auf das gegehörige SWT-Composite und kannst dieses beliebig erweitern:
public class Test extends AbstractView { public void bind() throws Exception { GUI.getView().setTitle("Blubb"); Composite comp = this.getParent(); // Das Composite kannst man nun beliebig weiterverwenden // und da drauf weitere Sachen malen. Falls du z.Bsp. unter // der Tabelle Buttons haben willst, koenntest du das so machen: TablePart table = ....; table.paint(comp); ButtonArea buttons = new ButtonArea(comp,1); buttons.addButton(i18n.tr("Zurück"),new Back()); // Man kann auch direkt SWT-Widgets verwenden. Z.Bsp.: Label l = new Label(comp,SWT.BORDER); l.setText("laber"); } }
Falls Elemente nicht angezeigt werden, liegt das meist daran, dass die Angabe des Layout oder LayoutData fehlt. Beispiel für GridLayout:
Composite comp = new Composite(getParent(),SWT.NONE); GridData gridData = new GridData(GridData.FILL_BOTH); comp.setLayoutData(gridData); // Zweispaltiges Layout, wobei die Spalten unterschiedlich breit sein koennen GridLayout layout = new GridLayout(2,false); comp.setLayout(layout);
Hinweis: Das Composite, welches man von AbstractView via getParent() erhaelt, besitzt bereits ein 1-spaltiges GridLayout.
Wenn sie als Bestandteil einer View angelegt wurden, müssen sie nicht manuell disposed werden. Das übernimmt Jameica automatisch rekursiv, sobald eine View verlassen wird.
Mit „setCheckable()“ legt man fest, ob eine Tabelle ueberhaupt Checkboxen anzeigen soll. Da hier noch nicht klar ist, ob die Checkboxen aktiviert oder deaktiviert sein sollen, sind per Default erstmal alle aktiviert. Ob nun fuer ein konkretes Objekt die Checkbox aktiv sein soll oder nicht, kann man entweder in einem Formatter machen. Oder *nachdem* die Tabelle gezeichnet wurde (also irgendwo das paint() der Tabelle direkt oder indirekt aufgerufen wurde) auch ausserhalb eines Formatters.
Mit folgendem Code z.Bsp. wird in jeder dritten Zeile die Checkbox aktiviert:
[...] c.addPart(table); [...] List<Object> objects = table.getItems(); for (int i=0;i<objects.size();++i) { table.setChecked(objects.get(i),i % 3 == 0); }
Ja, wie folgt:
Manifest mf = Application.getPluginLoader().getManifest(<Pluginklasse>.class); // Entweder: Ermitteln des Navigationsbaumes (links in Jameica) NavigationItem item = mf.getNavigation(); // Oder: Menu (oben in Jameica) MenuItem item = mf.getMenu(); // Der erste Parameter legt fest, ob das Element aktiv oder inaktiv sein soll. // Mit dem zweiten Parameter kann die Einstellung rekursiv für alle ggf. vorhandenen Kind-Elemente übernommen werden item.setEnabled(false,true);
Ja, du kannst entweder die DOM/SAX-API von Java verwenden oder den bei Jameica ohnehin mitgelieferten minimalistischen XML-Parser NanoXML verwenden. Jameica verwendet den intern, um z.Bsp. die plugin.xml zu lesen.
Beispiel:
<?xml version="1.0" ?> <elster> <steuer typ="Lohn"> <betrag>100.00</betrag> <waehrung>EUR</waehrung> </steuer> </elster>
// Parser erzeugen XMLParser parser = XMLParserFactory.createDefaultXMLParser(); parser.setReader(new StdXMLReader(new FileInputStream("test.xml"))); // Root-Element "elster" ermitteln IXMLElement root = (IXMLElement) parser.parse(); // Element "steuer" holen IXMLElement steuer = root.getFirstChildNamed("steuer"); // Testen, ob der Typ "Lohn" ist if (!steuer.getAttribute("typ","").equals("Lohn")) return; Enumeration e = steuer.enumerateChildren(); while (e.haseMoreElements()) { IXMLElement element = (IXMLElement) e.nextElement(); System.out.println(element.getName() + ": " + element.getContent()); }
// Liste der Plugin-Verzeichnisse File[] dirs = Application.getConfig().getPluginDirs(); // Benutzer-Verzeichnis // Default: // Linux: /home/<username>/.jameica // Windows: C:\Dokumente und Einstellungen\<username>\.jameica String dir = Application.getConfig().getWorkDir(); // Config-Verzeichnis des Benutzers // Default: // Linux: /home/<username>/.jameica/cfg // Windows: C:\Dokumente und Einstellungen\<username>\.jameica\cfg String dir = Application.getConfig().getConfigDir(); // Work-Verzeichnis eines konkreten Plugins String dir = Application.getPluginLoader().getPlugin(<PluginClass>.class).getResources().getWorkPath();
Über die Plugin-Ressourcen erhälst du ein vorkonfiguriertes Settings-Objekt mit Gettern und Settern. Ein explizites Speichern ist nicht nötig.
Settings s = Application.getPluginLoader().getPlugin(<PluginClass>.class).getResources().getSettings(); boolean test = s.getBoolean("foo.blubb",false); s.setAttribut("foo.bar","test");
Möglichkeit 1: Direkt auf die Datenservices des anderen Plugins zugreifen
Verwende hierzu die Service-Factory und schau in der plugin.xml des anderen Plugins nach den benötigten Servive-Namen.
DBService db = Application.getServiceFactory().lookup(HBCI.class,"database"); SammelUeberweisung s = (SammelUeberweisung)db.createObject(SammelUeberweisung.class,null); s.setFoo(...); [...] s.store();
Möglichkeit 2: Zugriff auf GUI-Elemente des anderen Plugins mittels Extension-System
Siehe Javadoc
Möglichkeit 3: Senden und Empfangen von Messages
Siehe Javadoc
// Definieren eines Nachrichtentyps public class MyMessage implements Message { public String getText() { return "Hallo"; } } // Abonnieren dieses Nachrichten-Typs public class MyConsumer implements MessageConsumer { /** * @see de.willuhn.jameica.messaging.MessageConsumer#handleMessage(de.willuhn.jameica.messaging.Message) */ public void handleMessage(Message message) throws Exception { MyMessage m = (MyMessage) message; System.out.println(m.getText); } /** * @see de.willuhn.jameica.messaging.MessageConsumer#autoRegister() */ public boolean autoRegister() { // Ist der MessageConsumer NICHT in einer "Inner-Class" definiert, wird // er beim Start von Jameica automatisch erkannt und kann ggf. auch automatisch // registriert werden, wenn diese Methode hier true zurueckliefert. // Andernfalls kann der Consumer auch manuell mit folgendem Code // registriert werden: // Application.getMessagingFactory().registerMessageConsumer(new MyConsumer()); return true; } /** * @see de.willuhn.jameica.messaging.MessageConsumer#getExpectedMessageTypes() */ public Class[] getExpectedMessageTypes() { // Legt fest, welche Arten von Nachrichten der Consumer erhalten will. return new Class[]{MyMessage.class}; } } // Asynchrones Senden einer Nachricht: // Sie werden dann in einem separaten Worker-Thread an alle Abonnenten verteilt. Application.getMessagingFactory().sendMessage(new MyMessage()); // Synchrones Senden Application.getMessagingFactory().sendSyncMessage(new MyMessage()); // Das Senden und Zustellen der Nachrichten erfolgt plugin-übergreifend
TablePart t = new TablePart(...); t.addSelectionListener(new Listener() { public void handleEvent(Event event) { Object o = event.data; // In event.data befindet sich dann direkt das markierte Fachobjekt. Falls mehrere markiert sind, ist o ein Array. } });
final TextInput eingabeFeld1 = new TextInput("foobar"); eingabeFeld1.addListener(new org.eclipse.swt.widgets.Listener() { public void handleEvent(Event event) { // Das Event wird immer dann ausgeloest, wenn sich der Focus // des Eingabe-Felds aendert. Also sowohl beim Aktivieren // als auch beim Deaktivieren. String value = (String) eingabeFeld1.getValue(); } });
public class MyView extends AbstractView { public void bind() { GUI.getView().setTitle("Test"); TabFolder folder = new org.eclipse.swt.widgets.TabFolder(getParent(), SWT.NONE); folder.setLayoutData(new GridData(GridData.FILL_BOTH)); folder.setBackground(Color.BACKGROUND.getSWTColor()); TabGroup tab1 = new TabGroup(folder,"Tab1"); pers.addLabelPair("Vorname",new TextInput("Max")); pers.addLabelPair("Nachname",new TextInput("Mustermann")); TabGroup tab2 = new TabGroup(folder,"Tab2"); pers.addLabelPair("Strasse",new TextInput("Musterstrasse")); pers.addLabelPair("Ort",new TextInput("Musterhausen")); [...] ButtonArea colorButtons = new ButtonArea(getParent(),2); colorButtons.addButton(i18n.tr("Zurück"), new Back()); colorButtons.addButton(i18n.tr("Speichern"), new Action() { public void handleAction(Object context) throws ApplicationException { [...] } }); } }
<navigation> [...] <item name="Adressen" icon-close="page.gif" icon-open="page.gif" action="dein packagename.gui.action.AddressList" /> [...] </navigation>
public class AddressList implements Action { public void handleAction(Object context) throws ApplicationException { GUI.startView(....gui.view.AddressList.class,null); } }
CREATE TABLE foo ( id NUMERIC DEFAULT UNIQUEKEY('foo'), name VARCHAR(20) NOT NULL, [...] PRIMARY KEY (id) );
Die Tabelle und die Datenbank muss man manuell anlegen. Im Beispiel-Plugin stehen die Statements in create.sql. Desweiteren muss eine Basis-Klasse des Plugins existieren, welche von AbstractPlugin abgeleitet und in plugin.xml registriert ist. Im Beispiel-Plugin heisst die Klasse „ExamplePlugin“. Sie besitzt 4 relevante Funktionen, welche von Jameica beim Initialisieren automatisch aufgerufen werden:
public void install() throws ApplicationException { // Wenn wir im Netzwerk-Betrieb arbeiten und Jameica nicht mit // lokalen Daten sondern mit einem Jameica-Server arbeiten soll, // dann erstellen wir keine Datenbank. if (Application.inClientMode()) return; PluginResources res = Application.getPluginLoader().getPlugin(ExamplePlugin.class).getResources(); EmbeddedDatabase db = new EmbeddedDatabase(res.getWorkPath() + "/db","exampleuser","examplepassword"); // Wenn wir bis hierher gekommen sind, wurde eine leere Datenbank // erfolgreich installiert. Wir koennen nun die Tabellen erstellen // Dazu erzeugen wir ein File-Objekt, welches auf die SQL-Datei zeigt. // res.getPath() liefert hierbei das Installations-Verzeichnis, in dem // sich das Plugin befindet. java.io.File file = new File(res.getPath() + "/sql/create.sql"); // und fuehren es nun auf der Datenbank aus. db.executeSQLScript(file); }
Wenn nicht alles erfolgreich durchlief, werfen wir eine ApplicationException. Damit geben wir Jameica die Information, dass die Installation fehlschlug. Es wird daraufhin auf der Jameica-Startseite einen Hinweis anzeigen, dass die Installation nicht funktionierte. Jameica wird die install-Methode beim nächsten Start automtatisch wieder aufrufen. Und das geschieht solange bei jedem Start, bis die install-Funktion endlich fehlerfrei durchläuft.
Ja, hier kann das Statement „ALTER CREATE TABLE“ genutzt werden. Hierbei können sowohl Spalten hinzugefügt als auch existierende geändert werden. Existiert die Tabelle noch nicht, wird sie automatisch angelegt. Allerdings dürfen die neuen Spalten nicht „NOT NULL“ sein.
ALTER CREATE TABLE testtabelle ( id NUMERIC DEFAULT UNIQUEKEY('testtabelle'), beschreibung VARCHAR(20) NOT NULL, kommentar text, zahl DOUBLE, dasistneu charchar(40) NULL, UNIQUE (id), PRIMARY KEY (id) );
Package | Beschreibung |
---|---|
.rmi | Interfaces der Fachobjekte |
.server | Implementierungen der Fachobjekt-Interfaces. |
.gui | Grafische Benutzeroberfläche |
.gui.views | Die Views mit den Eingabefeldern, Tabellen usw. |
.gui.controller | Controller (Kopplung Fachobjekt-View |
.gui.dialogs | Modale Dialoge |
.gui.parts | Vorgefertigte grafische Komponenten (z.Bsp. fertig konfigurierte Umsatz-Tabelle |
.gui.menus | Context-Menus |
.gui.action | Klassen, die das Interface de.willuhn.jameica.gui.Action implementieren und die Aufgaben kapseln, die bei Benutzer-Interaktivität ausgelöst werden können |