<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Elmars Notizen &#187; Software-Entwicklung</title>
	<atom:link href="http://www.elmar-baumann.de/blog/category/software/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.elmar-baumann.de/blog</link>
	<description>Software, Programmieren, Sonstiges</description>
	<lastBuildDate>Mon, 16 Jan 2012 18:25:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Fehlermeldungen</title>
		<link>http://www.elmar-baumann.de/blog/2012/01/09/fehlermeldungen/</link>
		<comments>http://www.elmar-baumann.de/blog/2012/01/09/fehlermeldungen/#comments</comments>
		<pubDate>Mon, 09 Jan 2012 21:15:03 +0000</pubDate>
		<dc:creator>Elmar</dc:creator>
				<category><![CDATA[Programme]]></category>
		<category><![CDATA[Programmierung]]></category>
		<category><![CDATA[Software-Entwicklung]]></category>

		<guid isPermaLink="false">http://www.elmar-baumann.de/blog/?p=1285</guid>
		<description><![CDATA[In diesem Artikel fasse ich zusammen, wie Fehlermeldungen aussehen, wie sie aussehen sollten und wie nicht. Außerdem reiße ich an, wie Programmierer mit Fehlern umgehen können. Der Artikel bezieht sich hauptsächlich auf Programme, die mit dem Benutzer interagieren. Er gilt aber auch für Programme, die ohne Aufsicht (im Hintergrund) ablaufen: Diese können in der Regel [...]]]></description>
			<content:encoded><![CDATA[<p>In diesem Artikel fasse ich zusammen, wie Fehlermeldungen aussehen, wie sie aussehen sollten und wie <em>nicht</em>. Außerdem reiße ich an, wie Programmierer mit Fehlern umgehen können. </p>
<p>Der Artikel bezieht sich hauptsächlich auf Programme, die mit dem Benutzer interagieren. Er gilt aber auch für Programme, die ohne Aufsicht (im Hintergrund) ablaufen: Diese können in der Regel zwar keine Benutzereingaben anfordern, die eine Fehlerbehebung anbieten, jedoch haben sie Fehler in eine Logdatei (Protokolldatei, <strong>Windows-Ereignisanzeige</strong>) zu schreiben <strong>in der gleichen Qualität</strong>.<br />
<span id="more-1285"></span></p>
<h2>Qualitätsabstufungen: Wie melden Computerprogramme Fehler?</h2>
<p>Die Qualität der Fehlermeldungen steigt mit jedem Aufzählungspunkt, den letzten Punkt sollten wir von Programmen einfordern, den vorletzten hat ein Programm <em>mindestens</em> einzuhalten.</p>
<ul>
<li><strong>Keine Fehlermeldung bei Fehlern</strong>: Leider gibt es noch Programme, die Fehler <em>nicht</em> melden und ich habe unter Umständen nachzuschauen, ob das Programm die Arbeit richtig erledigte. Reagierte das Programm nicht wie beabsichtigt und ich habe Glück, schreibt es eine gute Fehlermeldung in eine <strong>Logdatei</strong>. Habe ich Pech, kann ich nur durch Intuition, Versuch und Irrtum das Programm zum Funktionieren überreden.</li>
<li><strong>&quot;Es ist ein unbekannter Fehler aufgetreten&quot;</strong>: Hier weiß ich, etwas lief schief, aber nicht <em>was</em>. Vor zwei Tagen wollte ich Daten auf eine Blu-ray-Disc brennen und das Brennprogramm zeigte genau diesen Text an. Ursache war, dass im gleichen Verzeichnis der gleiche Dateiname einmal mit großem Anfangsbuchstaben stand und einmal mit kleinem (<code>text.txt</code> und <code>Text.txt</code>), was anscheinend die benutzte <a href="http://de.wikipedia.org/wiki/Universal_Disk_Format" title="Wikipedia">UDF</a>-Version nicht erlaubt (prinzipiell ist dies möglich) bzw. das Brennprogramm nicht akzeptiert. Diese Ursache musste ich selbst herausfinden und das war nicht einfach, für ein Computerprogramm hingegen ist dies kein größeres Problem, beispielsweise kann es intern alle Dateinamen eines Verzeichnisses in Kleinbuchstaben umwandeln und zählen wie häufig jeder klein geschriebene vorkommt.</li>
<li><strong>Falsche Fehlermeldung</strong>: Die Fehlermeldung hat <em>nichts</em> zu tun mit der Ursache. Durch (Web-) Recherche habe ich herauszufinden, unter welchen Umständen diese Meldung bislang auftrat und alle Lösungsvorschläge der Reihe nach so lange durchzuführen, bis das Programm richtig läuft.</li>
<li><strong>Fehlernummern</strong>: Statt einer Beschreibung des Fehlers erscheint eine Meldung in der Art: &quot;Es ist ein Fehler aufgetreten. Fehler-Code 0x47110815B2312&quot;. Durch (Web-) Recherche führt der Fehlercode zum tatsächlichen Fehler oder auch nicht. Die misslungenen automatischen Updates von Windows 7 lieferten mir bislang nur solche Fehlercodes, deren Erklärung ich dann auf dem Microsoft Website anschauen musste. Dort waren für den gleichen Code in der Regel mehrere mögliche Ursachen aufgelistet und ich durfte die Lösungsvorschläge je Ursache der Reihe nach durchprobieren. Das kostete sehr viel Zeit und führte selten zum Erfolg.</li>
<li><strong>Richtige Fehlermeldung</strong>: Nur in diesem Fall kann ich den Fehler ohne viel Stress beheben. Voraussetzung ist eine gute Formulierung, beispielsweise: &quot;Die Datei <code>abc.txt</code> kann nicht in das Verzeichnis <code>xyz</code> geschrieben werden, da Sie keine Schreibrechte im Verzeichnis <code>xyz</code> besitzen&quot;.</li>
<li><strong>Richtige Fehlermeldung inklusive Lösungsvorschlag</strong>: Um beim letzten Beispiel zu bleiben, könnte ergänzend erklärt werden, <em>wie</em> ich Schreibrechte erhalte.  Eigentlich sollte eine Schaltfläche &quot;Schreibrechte erlangen&quot; auf der Fehlermeldung existieren <strong>mit guter Usability</strong>, <em>keine</em> kruden Dialoge, bei denen ich mehrfach auf weitere Schaltflächen zu klicken habe, die erneut Dialoge öffnen, wie beispielsweise bei der Rechtevergabe über den Windows-Explorer.</li>
</ul>
<p><strong>Fazit:</strong> Kann das Programm den Fehler nicht beheben, hat es zumindest eine präzise Beschreibung der Ursache zu liefern, damit ich ohne Zeitverschwendung weiß, ob ich etwas tun kann und was ich tun sollte. Kann es den Fehler beheben, sollte die Fehlermeldung eine Schaltfläche enthalten, die dies durchführt ohne dass ich hierbei unnötig viel zu unternehmen habe (gute Usability/Gebrauchstauglichkeit), im besten Fall ist das ein (einziger) Mausklick oder Eintippen eines Buchstabens auf der Kommandozeile.</p>
<p>Wir schreiben das Jahr 2012 und noch immer halte ich diesen Artikel für nötig aufgrund täglichen Herumärgerns mit schlechten Fehlermeldungen. Weshalb sollen viele Benutzer enorme Zeit aufwenden und Fehlerursachen häufig erraten anstelle dass sich wenige Programmierer etwas mehr Zeit nehmen beim Programmieren?</p>
<h2>Programmiertechnik grob angerissen</h2>
<p>Ein Programmierer sollte es als &#8220;Ehrensache&#8221; betrachten, gute Fehlermeldungen zu liefern. Darüber freuen sich nicht nur die Benutzer, auch der Programmierer kann so Bugs schneller beheben.</p>
<p>Oft entstehen Fehler in &#8220;unteren Schichten&#8221;, in Methoden (Funktionen, Prozeduren, Subroutinen) die durch Methoden aufgerufen werden,  die durch Methoden aufgerufen werden usw. Falls die Programmiersprache Ausnahmen (<strong>Exceptions</strong>) werfen kann wie beispielsweise C++ und Java, sollte eine Methode davon bei Bedarf Gebrauch machen.</p>
<p>Ein Fehler, über den ich mich häufig an dieser Stelle ärgerte, ist, der Exception nicht alle verfügbaren Daten zu geben, die helfen können, den Fehler möglichst genau zu identifizieren und zu beheben. Das sind neben dem Wortlaut des Fehlers (Fehlernachricht, <strong>Error Message</strong>) unter anderem die Methodenparameter und die präzise Systemzeit (Millisekunden oder genauer). Ich warf in einem C++-Programm eine Exception, falls beim Öffnen einer Datei das Dateiformat ungültig war, vergaß jedoch den Dateinamen in die Meldung zu schreiben. Da die Datei nicht durch Benutzerauswahl geöffnet wurde, hatte ich bei Fehlern immer viel Arbeit, den Benutzern zu erklären, wo er sie finden kann.</p>
<p>Kann eine Methode eine Exception nicht beheben, reicht sie diese weiter oder wirft eine Exception mit dem Exception-Objekt als Ursache und mit zusätzlichen dienlichen Informationen. Das geht so weiter bis hin in die oberste Schicht, die den Fehler protokolliert und die Fehlermeldung anzeigt.</p>
<p>Die Meldung richtet sich nach der Aufgabe der obersten Schicht, sie lautet beispielsweise &quot;Die Datei <code>Blafasel.txt</code> konnte nicht geöffnet werden!&quot;. In einem Detailabschnitt stehen dann alle Meldungen vorheriger Exceptions wie beispielsweise &quot;Die Datei <code>Blafasel.txt</code> existiert nicht&quot; oder &quot;Im Verzeichnis <code>xyz</code> fehlen Leserechte&quot; oder &quot;Die Datei <code>Blafasel.xml</code> hat ein ungültiges Dateiformat: Zeile 25 hat ein schließendes Tag &lt;/blubb&gt; ohne dass vorher ein öffnendes Tag &lt;blubb&gt; existiert&quot;.</p>
<p>Falls es möglich ist, sollte in der Logdatei der gesamte Callstack stehen, so weiß der Programmierer, von wo aus es im Quellcode zum Fehler ging wo genau er auftrat und kann Bugs manchmal sekundenschnell beheben.</p>
<p>Ich integriere in Fehlermeldungen eine Schaltfläche zum Schicken einer E-Mail, das Benutzen eines Bugtracking-Systems führt garantiert zu <em>viel</em> weniger Feedback, da es wissenden Programmbenutzern eine Menge Arbeit aufbürdet und die unwissenden ausschließt.</p>
<p>Hat eines meiner Programme Bugs, stehe <em>ich</em> in der &#8220;Schuld&#8221;, diese zu beheben und ich finde es unschön, falls der Benutzer neben dem negativen Erlebnis auch noch viel Arbeit hat, Fehler zu melden. In der Regel werden Benutzer diese Arbeit unterlassen, sofern sie für das Programm nicht Geld bezahlten und statt dessen ein anderes benutzen, falls es Alternativen gibt.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elmar-baumann.de/blog/2012/01/09/fehlermeldungen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Das NetBeans-Lookup für Nicht-RCP-Projekte benutzen</title>
		<link>http://www.elmar-baumann.de/blog/2011/09/26/das-netbeans-lookup-fur-nicht-rcp-projekte-benutzen/</link>
		<comments>http://www.elmar-baumann.de/blog/2011/09/26/das-netbeans-lookup-fur-nicht-rcp-projekte-benutzen/#comments</comments>
		<pubDate>Mon, 26 Sep 2011 17:35:53 +0000</pubDate>
		<dc:creator>Elmar</dc:creator>
				<category><![CDATA[Programmierung]]></category>
		<category><![CDATA[Software-Entwicklung]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Netbeans]]></category>

		<guid isPermaLink="false">http://www.elmar-baumann.de/blog/?p=1128</guid>
		<description><![CDATA[Im Artikel Java-Aktionen richtig benutzen überlegte ich, wie Java-Actions ihre Daten erhalten und sich aktivieren oder deaktivieren abhängig davon, ob es relevante Daten gibt. Ich wollte nicht Swing-Components nach Daten befragen, die Actions dort als Listener registrieren und auf Statusänderungen des GUI reagieren. Als Lösung schlug ich Lookups vor &#8211; Container mit Elementen, die Beobachter [...]]]></description>
			<content:encoded><![CDATA[<p>Im Artikel <a href="http://www.elmar-baumann.de/blog/2010/09/18/java-aktionen-richtig-benutzen/" title="Artikel">Java-Aktionen richtig benutzen</a> überlegte ich, wie Java-<code>Action</code>s ihre Daten erhalten und sich aktivieren oder deaktivieren abhängig davon, ob es relevante Daten gibt. Ich wollte <em>nicht</em> Swing-<code>Component</code>s nach Daten befragen, die Actions dort als Listener registrieren und auf Statusänderungen des GUI reagieren. Als Lösung schlug ich <strong>Lookups</strong> vor &ndash; Container mit Elementen, die Beobachter benachrichtigen, falls sich ihr Inhalt ändert. </p>
<p>Ein solches Lookup benutzen <a href="http://netbeans.org/features/platform/">NetBeans Platform</a>-Anwendungen, es kann in <em>Nicht</em>-Platform-Anwendungen eingesetzt werden. Die Projekte integrieren dazu das JAR <code>org-openide-util-lookup.jar</code>, es ist unterhalb des NetBeans-Installationsverzeichnisses im Verzeichnis <code>platform/lib</code>.<br />
<span id="more-1128"></span></p>
<h2>Zugrunde liegende Idee</h2>
<ul>
<li>Ein Anwendungsmodul hat einen oder mehrere Lookups</li>
<li>Wird im GUI etwas ausgewählt oder abgewählt, gelangen relevante Daten, die das GUI-Element repräsentiert, in eines der Lookups</li>
<li>Die Actions beobachten das Lookup: Ist es leer, sind sie deaktiviert, enthält es Elemente, sind sie aktiviert. In <code>actionPerformed()</code> benutzen Actions die Elemente des Lookups.</li>
</ul>
<p>Das NetBeans-Lookup benachrichtigt Beobachter nur, falls Daten eine bestimmten Typs im Lookup sind: Eine Action, die Personen in eine Datenbank speichert, will in der Regel nicht wissen, ob und wieviele Bücher im Lookup sind.</p>
<h2>Beispiels-Implementierung</h2>
<p>Ich habe ein Beispiels-Projekt implementiert und in ein <a href="/blog/wp-content/uploads/LookupTest.zip">ZIP-Archiv</a> gepackt. Nach dem Entpacken kann es mit NetBeans geöffnet werden. Das Kontextemenü des <strong>Projects</strong>-Window bietet <strong>Run</strong> oder <strong>Debug</strong> an (auf die Klasse <code>Main</code> mit der rechten Maustaste klicken).</p>
<p><em>(Nur) Der Übersichtlichkeit wegen verzichtete ich bei den Beispielen auf Parameterüberprüfung, in &#8220;Produktions-Code&#8221; sollten die Parameter überprüft werden, beispielsweise eine NullPointerException geworfen, falls null überreicht wird anstelle einer erwarteten Objektreferenz.</em></p>
<h3>LookupAction</h3>
<p>Die <code>LookupAction</code> habe ich kopiert aus dem Projekt <a href="http://jphototagger.org">JPhotoTagger</a>. <code>LookupAction</code>s beobachten einen Lookup. Bei Aufruf von <code>actionPerformed()</code> ruft die <code>LookupAction</code> bei den spezialisierten Actions die abstrakte Methode <code>actionPerformed(Collection&lt;? extends T&gt; lookupContent)</code> auf, mit <code>isEnabled(Collection&lt;? extends T&gt; lookupContent)</code> können sie entscheiden, ob sie aktiviert sein sollen, in der Regel werden sie <code>true</code> liefern, falls der Lookup-Content Elemente enthält.</p>
<pre>
public abstract class LookupAction&lt;T&gt; extends AbstractAction implements LookupListener {

    private final Class&lt;? extends T&gt; lookupResultClass;
    private final Lookup lookup;
    private Lookup.Result&lt;? extends T&gt; lookupResult;

    protected LookupAction(Class&lt;? extends T&gt; lookupResultClass, Lookup lookup) {
        this.lookupResultClass = lookupResultClass;
        this.lookup = lookup;
        setLookupResult();
    }

    protected abstract boolean isEnabled(Collection&lt;? extends T&gt; lookupContent);

    protected abstract void actionPerformed(Collection&lt;? extends T&gt; lookupContent);

    private void setLookupResult() {
        if (lookupResult == null) {
            lookupResult = lookup.lookupResult(lookupResultClass);
            lookupResult.addLookupListener(this);
            resultChanged(null);
        }
    }

    @Override
    public void resultChanged(LookupEvent evt) {
        setEnabledInDispatchThread();
    }

    private void setEnabledInDispatchThread() {
        final boolean isEnabled = isEnabled(lookupResult.allInstances());

        if (EventQueue.isDispatchThread()) {
            setEnabled(isEnabled);
        } else {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    setEnabled(isEnabled);
                }
            });
        }
    }

    protected Collection&lt;? extends T&gt; getLookupContent() {
        return (lookupResult == null)
                ? Collections.&lt;T&gt;emptyList()
                : lookupResult.allInstances();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        setLookupResult();
        actionPerformed(lookupResult.allInstances());
    }
}
</pre>
<h3>Eigenes Lookup</h3>
<p>Die Klasse <code>ModifiableLookup</code> liefert ein Lookup und bietet Methoden an, dessen Inhalt zu modifizieren.</p>
<pre>
public final class ModifiableLookup implements Lookup.Provider {

    private final InstanceContent content = new InstanceContent();
    private final Lookup lookup = new AbstractLookup(content);

    @Override
    public Lookup getLookup() {
        return lookup;
    }

    public void add(Object content) {
        this.content.add(content);
    }

    public void remove(Object content) {
        this.content.remove(content);
    }

    public void set(Collection&lt;?&gt; content) {
        this.content.set(content, null);
    }
}
</pre>
<h3>Beispiel: Ausgewählte Listenelemente in das Lookup einfügen</h3>
<p>Will ich ausgewählte Items einer <code>JList</code> in das Lookup einfügen, kann ich folgende Klasse benutzen: </p>
<pre>
public final class LookupListSelectionListener implements ListSelectionListener {

    private final ModifiableLookup lookup;

    public LookupListSelectionListener(ModifiableLookup lookup) {
        this.lookup = lookup;
    }

    @Override
    public void valueChanged(ListSelectionEvent e) {
        if (!e.getValueIsAdjusting()) {
            JList list = (JList) e.getSource();
            List&lt;Object&gt; selectedValues = Arrays.asList(list.getSelectedValues());
            lookup.set(selectedValues);
        }
    }
}
</pre>
<h3>Beispielsklassen, die in das Lookup sollen</h3>
<pre>
public final class Human {

    private final String name;

    public Human(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}
</pre>
<pre>
public class Dog {

    private final String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name + " (Wuff)";
    }
}
</pre>
<pre>
public final class CreatureListModel extends DefaultListModel {

    private static final long serialVersionUID = 1L;

    public CreatureListModel() {
        addElement(new Human("Anton"));
        addElement(new Human("Berta"));
        addElement(new Human("Cäsar"));
        addElement(new Human("Dora"));
        addElement(new Human("Emil"));
        addElement(new Dog("Fiffi"));
        addElement(new Dog("Waldi"));
    }
}
</pre>
<h3>Kommunikation über ein zentrales Lookup</h3>
<p>Ein Anwendungsmodul kann Lookups beispielsweise so bereitstellen:</p>
<pre>
public final class Lookups {

    public static final Lookups INSTANCE = new Lookups();
    private final ModifiableLookup humansLookup = new ModifiableLookup();

    public ModifiableLookup getHumansLookup() {
        return humansLookup;
    }

    private Lookups() {
    }
}
</pre>
<h3>Erweiterte LookupAction</h3>
<p>Die erweiterte <code>LookupAction</code> interessiert sich (nur) für Instanzen von <code>Human</code></p>
<pre>
public final class ShowHumansInLookupAction extends LookupAction&lt;Human&gt; {

    private static final long serialVersionUID = 1L;

    public ShowHumansInLookupAction() {
        super(Human.class, Lookups.INSTANCE.getHumansLookup().getLookup());
        putValue(Action.NAME, "Zeige ausgewählte Menschen");
    }

    @Override
    protected boolean isEnabled(Collection&lt;? extends Human&gt; humans) {
        return !humans.isEmpty();
    }

    @Override
    protected void actionPerformed(final Collection&lt;? extends Human&gt; humans) {
        JOptionPane.showMessageDialog(null, humans);
    }
}
</pre>
<h3>In einem GUI benutzen</h3>
<p>Die entscheidenden Zeilen in einem GUI, die Action ist einem Button zugeordnet, Model und ListSelectionListener der Liste:</p>
<pre>
    list.setModel(new CreatureListModel());
    list.addListSelectionListener(new LookupListSelectionListener(Lookups.INSTANCE.getHumansLookup()));
    button.setAction(new ShowHumansInLookupAction());
</pre>
<h3>Worauf achten beim Ausführen der Beispielsanwendung</h3>
<ul>
<li>Ist nicht ausgewählt, setzt sich die Action automatisch auf deaktiviert, der Button mit der Action ist deaktiviert (&#8220;ausgegraut&#8221;)</li>
<li>Werden ein oder mehrere Menschen ausgewählt, ist die Action aktiviert, der Button reagiert auf Klicks (Betätigung)</li>
<li>Sind nur Hunde ausgewählt, ist der Button deaktiviert</li>
<li>Die Action erhält nur Menschen: Sind Hunde und Menschen ausgewählt, zeigt bei Klick auf den Button die Action den Lookup-Content an &ndash; es sind nur die erwarteten Objekte darin</li>
</ul>
<h2>Vorteile</h2>
<ul>
<li>Die Action ist <em>nicht</em> an ein GUI gebunden (sie benötigt nur ein Lookup). Sie ist nicht anzupassen, falls später beispielsweise ein <code>JTree</code> die Geschöpfe anzeigt (getrennt nach Mensch und Tier). Es könnte auch ein Dialog zum Ändern der Daten eines Menschen benutzt werden: Der &#8220;Mensch im Dialog&#8221; gelangt in das Lookup, das die Action benutzt.</li>
<li>Die Action <strong>fragt nicht nach den benötigten Daten</strong>, sie erhält sie</li>
<li>Die Action aktiviert und deaktiviert sich automatisch</li>
<li>Das Lookup könnte über Modulgrenzen hinweg benutzt werden, beispielsweise über das Java-<strong>Service Provider Interface</strong> (SPI) oder ein globales Lookup, wie es die NetBeans-Platform anbietet. Die Actions anderer Module sind nur mit dem passenden Lookup zu verknüpfen, sie müssen nicht wissen, woher die Daten kommen (wie sie an diese gelangen).</li>
<li>Actions sind nur ein Beispiel, es können beliebige Klassen <code>LookupListener</code> sein</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.elmar-baumann.de/blog/2011/09/26/das-netbeans-lookup-fur-nicht-rcp-projekte-benutzen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Java: Laufzeitausnahmen anzeigen &#8211; Variante</title>
		<link>http://www.elmar-baumann.de/blog/2011/09/21/java-laufzeitausnahmen-anzeigen-variante/</link>
		<comments>http://www.elmar-baumann.de/blog/2011/09/21/java-laufzeitausnahmen-anzeigen-variante/#comments</comments>
		<pubDate>Wed, 21 Sep 2011 19:47:04 +0000</pubDate>
		<dc:creator>Elmar</dc:creator>
				<category><![CDATA[Programmierung]]></category>
		<category><![CDATA[Software-Entwicklung]]></category>
		<category><![CDATA[Exceptions]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://www.elmar-baumann.de/blog/?p=1098</guid>
		<description><![CDATA[Im Artikel &#8220;Java: Laufzeitausnahmen anzeigen&#8221; steht, wie ich im AWT-EventDispatchThread nicht gefangene Ausnahmen mit einem Dialog anzeige. Inzwischen verfolge ich eine weniger &#8220;aufdringliche&#8221; Variante, die nicht von der Arbeit ablenkt oder das Schließen des Programmfensters verhindert, falls im EventDispatchThread laufend neue Ausnahmen geworfen und nicht gefangen werden: Die Ersatz-EventQueue benutzt das Logging-System und loggt die [...]]]></description>
			<content:encoded><![CDATA[<p>Im Artikel &#8220;<a href="/blog/2010/04/02/java-laufzeitausnahmen-anzeigen/" title="">Java: Laufzeitausnahmen anzeigen</a>&#8221; steht, wie ich im <strong>AWT-EventDispatchThread</strong> nicht gefangene Ausnahmen mit einem Dialog anzeige. Inzwischen verfolge ich eine weniger &#8220;aufdringliche&#8221; Variante, die nicht von der Arbeit ablenkt oder das Schließen des Programmfensters verhindert, falls im <code>EventDispatchThread</code> laufend neue Ausnahmen geworfen und nicht gefangen werden:<br />
<span id="more-1098"></span></p>
<ul>
<li>Die Ersatz-<code>EventQueue</code> benutzt das Logging-System und loggt die Ausnahme mit dem Level <code>SEVERE</code></li>
<li>Ein <code>Handler</code> &ndash; ein erweiterter <code>java.util.logging.Handler</code> &ndash; reagiert auf bestimmte Loglevel, zeigt beispielsweise einen &#8220;Fehlerindikator&#8221; an, bei Klick darauf erhält der Benutzer nähere Informationen über den Fehler</li>
</ul>
<h2>Modifizierte EventQueue</h2>
<pre>
public final class AppEventQueue extends java.awt.EventQueue {

    @Override
    protected void dispatchEvent(AWTEvent event) {
        try {
            super.dispatchEvent(event);
        } catch (Throwable t) {
            Logger.getLogger(AppEventQueue.class.getName()).log(Level.SEVERE, null, t);
        }
    }
}
</pre>
<h2>Handler</h2>
<pre>
public final class ErrorLogHandler extends Handler {

    private static final int MIN_LOG_LEVEL_VALUE = Level.WARNING.intValue();

    public ErrorLogHandler() {
        Logger.getLogger("").addHandler(this);
    }

    @Override
    public void publish(LogRecord record) {
        int recordLevelValue = record.getLevel().intValue();
        boolean isError = recordLevelValue &gt;= MIN_LOG_LEVEL_VALUE;

        if (isError) {
            // Hier den Fehler visualisieren
        }
    }

    @Override
    public void flush() {
        // ignorieren
    }

    @Override
    public void close() throws SecurityException {
        // ignorieren
    }
}
</pre>
<p>Der Handler fügt sich dem <strong>root Logger</strong> hinzu, der <strong>root Logger</strong> ruft die Handler-Methode <code>publish()</code> auf. Jeden Loglevel ab <code>WARNING</code> betrachtet der Handler als Fehler (<code>isError</code>). Bei Fehlern kann er beispielsweise über die <code>setIcon()</code>-Methode eines Labels ein Fehler-Icon setzen und bei Klick auf das Label die Fehlerinformationen anzeigen, beispielsweise anhand Fehler-<code>LogRecord</code>s, die er temporär speichert.</p>
<h2>EventQueue ersetzen</h2>
<p>Die <code>EventQueue</code> ersetze ich &#8220;rechtzeitig&#8221; durch folgende Codezeile (im Projekt <a href="http://jphototagger.org/">JPhotoTagger</a> unmittelbar nach Erzeugen des Programmfenster-Frames):</p>
<pre>
Toolkit.getDefaultToolkit().getSystemEventQueue().push(new AppEventQueue());
</pre>
<p>Die folgenden Abbildungen zeigen die Auswirkungen: In <a href="http://netbeans.org">NetBeans</a> setzte ich einen Haltepunkt in die Zeile 127 der Klasse <code>AppInit</code>, Methode <code>setJptEventQueue()</code>, das ist die Zeile mit dem <code>push()</code>-Afuruf. Gelb unterlegt ist der <strong>Thread AWT-EventQueue-0</strong>. Nach Aufruf ist zu sehen, dass nun <strong>Thread AWT-EventQueue-1</strong> läuft, die Namen lassen sich nicht ändern, es wird nur die Nummer hochgezählt.</p>
<p><a href="http://www.elmar-baumann.de/blog/wp-content/uploads/jpt-eventqueue-01.png"><img src="http://www.elmar-baumann.de/blog/wp-content/uploads/jpt-eventqueue-01.png" alt="" width="312" height="224" class="alignnone size-full wp-image-1112" /></a><br />JPhotoTagger: AWT-EventQueue ersetzen, Debugger-Ansicht vor Ersetzen</p>
<p><a href="http://www.elmar-baumann.de/blog/wp-content/uploads/jpt-eventqueue-02.png"><img src="http://www.elmar-baumann.de/blog/wp-content/uploads/jpt-eventqueue-02.png" alt="" width="312" height="133" class="alignnone size-full wp-image-1113" /></a><br />JPhotoTagger: AWT-EventQueue ersetzen, Debugger-Ansicht nach Ersetzen.</p>
<p><a href="http://www.elmar-baumann.de/blog/wp-content/uploads/jpt-error-handler.png"><img src="http://www.elmar-baumann.de/blog/wp-content/uploads/jpt-error-handler.png" alt="" width="318" height="68" class="alignnone size-full wp-image-1118" /></a><br />Fehleranzeige durch einen Log-Handler in JPhotoTagger am unteren Programmfensterrand.</p>
<h2>Außerhalb der AWT-EventQueue</h2>
<p>In anderen Threads kann ich nicht gefangene Ausnahmen loggen mit <code>java.lang.Thread#setDefaultUncaughtExceptionHandler()</code> &ndash; der Handler wird <em>nicht</em> aufgerufen im <strong>AWT-Event Dispatch Thread</strong>. Beispiel:</p>
<pre>
public final class UncaughtExceptionLogger implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
         Logger.getLogger(UncaughtExceptionLogger.class.getName()).log(Level.SEVERE, null, e);
    }
}
</pre>
<p>Benutzung:</p>
<pre>
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger());
</pre>
<h2>Zusammenfassung (mit Erweiterung um FileHandler)</h2>
<ul>
<li>Nicht gefangene Ausnahmen sollen dem Benutzer unaufdringlich angezeigt werden und in einer Logdatei stehen</li>
<li>Ein erweiterter <code>java.util.logging.Handler</code> <strong>zeigt dem Benutzer Probleme an</strong> ab dem Loglevel <code>WARNING</code>, ein <code>java.util.logging.FileHandler</code> <strong>schreibt die LogRecords in eine Logdatei</strong>. Diese Handler füge ich dem <strong>root Logger</strong> hinzu.</li>
<li>Je ein Logger <strong>loggt ungefangene Ausnahmen</strong> mit dem Level <code>SEVERE</code>
<ul>
<li>im <strong>AWT-EventDispatchThread</strong> in einer eigenen <code>EventQueue</code>, die die &#8220;Standard&#8221;-<code>EventQueue</code> ersetzt</li>
<li>in (allen) <strong>anderen Threads</strong> in einem <code>java.lang.Thread.UncaughtExceptionHandler</code>, den ich setze mit <code>Thread#setDefaultUncaughtExceptionHandler()</code></li>
</ul>
</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.elmar-baumann.de/blog/2011/09/21/java-laufzeitausnahmen-anzeigen-variante/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Modulare Java-Programmierung via Services</title>
		<link>http://www.elmar-baumann.de/blog/2011/07/25/modulare-java-programmierung-via-services/</link>
		<comments>http://www.elmar-baumann.de/blog/2011/07/25/modulare-java-programmierung-via-services/#comments</comments>
		<pubDate>Mon, 25 Jul 2011 17:15:54 +0000</pubDate>
		<dc:creator>Elmar</dc:creator>
				<category><![CDATA[Software-Entwicklung]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://www.elmar-baumann.de/blog/?p=1031</guid>
		<description><![CDATA[Eine umfangreichere Anwendung ist unübersichtlich, schwierig zu &#8220;pflegen&#8221; und erweitern, falls ich sie in einem einzigen JAR unterbringe. Teile ich sie auf in mehrere Projekte und jedes Projekt ergibt ein JAR, ist dies besser. Die Abhängigkeiten der Projekte untereinander sollten minimal sein (eine Klasse des Projekts X sollte nicht nach einer Klasse des Projekts Y [...]]]></description>
			<content:encoded><![CDATA[<p>Eine umfangreichere Anwendung ist unübersichtlich, schwierig zu &#8220;pflegen&#8221; und erweitern, falls ich sie in einem einzigen <a href="http://download.oracle.com/javase/1.5.0/docs/guide/jar/jar.html">JAR</a> unterbringe. Teile ich sie auf in mehrere Projekte und jedes Projekt ergibt ein JAR, ist dies besser. Die Abhängigkeiten der Projekte untereinander sollten minimal sein (eine Klasse des Projekts X sollte nicht nach einer Klasse des Projekts Y verlangen).<br />
<span id="more-1031"></span><br />
Bevor ich eine größere Anwendung programmiere, sollte ich mich einarbeiten in ein Framework, das zu diesem Zweck entwickelt wurde, beispielsweise der <a href="http://netbeans.org/kb/trails/platform.html">NetBeans Platform</a> und dieses benutzen. Die Einarbeitung kostet Zeit, die Benutzung spart diese jedoch auf lange Sicht und schont die Nerven.</p>
<p>Falls ich mich nicht einarbeiten will, ist der <a href="http://download.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html">ServiceLoader</a> nach meinen aktuellen Kenntnissen am geeignetsten, dieser lässt sich auch in einem Framework benutzen.</p>
<p>Dazu definiere ich in einem oder mehreren Projekten <strong>Interfaces</strong> (<strong>Services</strong>). Diese implementiere ich in <em>anderen</em> Projekten.</p>
<p>Beispielsweise könnte ich entscheiden, das Programmfenster hat drei Bereiche, in die andere Module (Projekte) &#8220;Fenster&#8221; einfügen können. Die Interface-Definition sähe z.B. so aus:</p>
<pre>
public <span style="color:#0000ff;font-weight:bold">interface WindowSystem</span> {
    void dockIntoSelectView(Component component);
    void dockIntoEditView(Component component);
    void dockIntoPropertiesView(Component component);
}
</pre>
<p>In irgend einem Projekt implementiere ich dies und publiziere diese Implementierung gemäß der JavaDoc des <code>ServiceLoader</code> im <code>META-INF/services</code>-Verzeichnis:</p>
<pre>
public final class ApplicationFrame <span style="color:#0000ff;font-weight:bold">implements WindowSystem</span> {

    <span style="color:#0000ff;font-weight:bold">@Override</span>
    public void dockIntoSelectView(Component component) {
        tabbedSelectViewPane.add(component);
    }

    <span style="color:#0000ff;font-weight:bold">@Override</span>
    public void dockIntoEditView(Component component) {
        tabbedPaneEditView.add(component);
    }

    <span style="color:#0000ff;font-weight:bold">@Override</span>
    public void dockIntoPropertiesView(Component component) {
        tabbedPanePropertiesView.add(component);
    }
}
</pre>
<p>Will ich in einem anderen Projekt der Select-View des Anwendungsfensters etwas hinzufügen, benutze ich den <code>ServiceLoader</code>. Ich vereinfache mir die Arbeit mit der Klasse <code>ServiceLookup</code> eines Projekts, das alle anderen Projekte nutzen dürfen und das keine Abhängigkeit zu einem anderen Projekt hat, siehe Implementierung weiter unten:</p>
<pre>
public final class ImageFileBrowser <span style="color:#0000ff;font-weight:bold">implements Module</span> {

    private JTree fileSystemTree;

    <span style="color:#0000ff;font-weight:bold">@Override</span>
    public void initModule() {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                createFileSystemTree();
                addFileSystemTree();
            }
        );
    }

    private void createFileSystemTree() {
        FileFilter directoryFilter = FileSystemUtil.getDirectoriesOnlyFileFilter();
        TreeModel fileSystemTreeModel = FileSystemUtil.createFileSystemTreeModel(directoryFilter);

        fileSystemTree = new JTree(fileSystemTreeModel);
        fileSystemTree.setCellRenderer(new FileSystemTreeCellRenderer());
        fileSystemTree.addTreeSelectionListener(new ImageDisplayTreeSelectionListener());
    }

    private void addFileSystemTree() {
        <span style="color:#0000ff;font-weight:bold">WindowSystem windowSystem = ServiceLookup.lookup(WindowSystem.class);</span>

        <span style="color:#0000ff;font-weight:bold">windowSystem.dockIntoSelectView(fileSystemTree);</span>
    }
}
</pre>
<p>Klasse <code>ServiceLookup</code> eines &#8220;Utility&#8221;-Projekts, das keine Abhängigkeiten zu anderen Projekten hat und das alle anderen Projekte benutzen dürfen:</p>
<pre>
public final class ServiceLookup {

    public static &lt;T&gt; T lookup(Class&lt;T&gt; serviceClass) {
        if (serviceClass == null) {
            throw new NullPointerException("serviceClass == null");
        }

        ServiceLoader&lt;T&gt; serviceLoader = ServiceLoader.load(serviceClass);
        Iterator&lt;T&gt; serviceImplementations = serviceLoader.iterator();

        return serviceImplementations.hasNext()
                ? serviceImplementations.next()
                : null;
    }

    public static &lt;T&gt; Collection&lt;? extends T&gt; lookupAll(Class&lt;T&gt; serviceClass) {
        if (serviceClass == null) {
            throw new NullPointerException("serviceClass == null");
        }

        Collection&lt;T&gt; serviceImplementations = new ArrayList&lt;T&gt;();
        ServiceLoader&lt;T&gt; serviceLoader = ServiceLoader.load(serviceClass);

        for (T service : serviceLoader) {
            serviceImplementations.add(service);
        }

        return serviceImplementations;
    }

    private ServiceLookup() {}
}
</pre>
<p>Prinzip einer modularen Anwendung mit Hilfe des <code>ServiceLoader</code>:</p>
<ul>
<li>Die Anwendung besteht aus unterschiedlichen Projekten, jedes hat eine bestimmte Aufgabe (&#8220;Zuständigkeit&#8221;)</li>
<li>Einige Projekte deklarieren verschiedene <strong>Services</strong> als <strong>Interface</strong></li>
<li>Andere Projekte implementieren diese Services und veröffentlichen die Implementierungen im <code>META-INF/services</code>-Verzeichnis</li>
<li>Projekte benutzen die Services und fügen so der Anwendung weitere Fähigkeiten hinzu</li>
<li>Ein &#8220;Sammelprojekt&#8221; enthält die JARs aller Teilprojekte und den &#8220;Startcode&#8221;, der eine von allen Teilprojekten zu implementierende &#8220;init&#8221;-Methode aufruft (im Beispiel oben die Methode <code>initModule()</code> des Services <code>Module</code>, &#8220;Abholen&#8221; der Implementierungen mit <code>ServiceLookup.lookupAll()</code>)</li>
</ul>
<p>Will ich die Anwendung erweitern, eröffne ich ein neues Projekt, benutze die Services und füge das Projekt-JAR dem Sammelprojekt hinzu. Ich brauche nicht die bisherigen Projekte anzupassen.</p>
<p>Das Beispiel oben könnte ich erweitern: Ein neues Projekt implementiert analog zu <code>ImageFileBrowser</code> über die <code>Module</code>-Schnittstelle beispielsweise eine Klasse <code>Mp3FileBrowser</code>, ein weiteres <code>VideoFileBrowser</code>. Diese Projekte müssen nichts voneinander wissen ebensowenig wie das Fenstersystem wissen muss, welche Fenster mit welcher Funktion ihm hinzugefügt werden.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elmar-baumann.de/blog/2011/07/25/modulare-java-programmierung-via-services/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Java-Aktionen richtig benutzen</title>
		<link>http://www.elmar-baumann.de/blog/2010/09/18/java-aktionen-richtig-benutzen/</link>
		<comments>http://www.elmar-baumann.de/blog/2010/09/18/java-aktionen-richtig-benutzen/#comments</comments>
		<pubDate>Sat, 18 Sep 2010 21:11:40 +0000</pubDate>
		<dc:creator>Elmar</dc:creator>
				<category><![CDATA[Programmierung]]></category>
		<category><![CDATA[Software-Entwicklung]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://www.elmar-baumann.de/blog/?p=422</guid>
		<description><![CDATA[Hier fasse ich zusammen, wie Swing-GUI-Programme auf Benutzereingaben reagieren können und wie nach meinem aktuellen Wissen am besten vorzugehen ist (es gibt mehrere Möglichkeiten). Der bevorzugte Weg ist, den JComponents Aktionen zuzweisen und mitzuteilen, durch welche Tasten diese ausgelöst werden. Eine Aktion ist Code, der beim Eintreten bestimmter Ereignisse ausgeführt wird. Die Basisklasse ist Action, [...]]]></description>
			<content:encoded><![CDATA[<p>Hier fasse ich zusammen, wie <strong>Swing-</strong>GUI-Programme auf Benutzereingaben reagieren können und wie nach meinem aktuellen Wissen am besten vorzugehen ist (es gibt mehrere Möglichkeiten).<br />
<span id="more-422"></span><br />
Der <strong>bevorzugte</strong> Weg ist, den <code>JComponent</code>s Aktionen zuzweisen und mitzuteilen, durch welche Tasten diese ausgelöst werden.</p>
<p>Eine <strong>Aktion</strong> ist Code, der beim Eintreten bestimmter Ereignisse ausgeführt wird. Die Basisklasse ist <code>Action</code>, in der Regel wird eine Klasse abgeleitet von <code>AbstractAction</code>. Der Code wird in <code>actionPerformed()</code> ausgeführt.</p>
<h3>1. Aktion auslösen anhand Tastatureingabe</h3>
<p>In <em>früheren</em> Java-Versionen (vor 1.3) war die Aktion ein <code>KeyListener</code> bei einer <code>Component</code>. <strong>Aktuelles</strong> Vorgehen:</p>
<ol>
<li>String-Schlüssel definieren, der beschreibt, <strong>was</strong> geschieht, z.B. &#8220;Kopieren in die Zwischenablage&#8221; (der Schlüsselname wird dann vielleicht <code>COPY_TO_CLIPBOARD</code> heißen). Dieser Schlüssel ist <em>nicht</em> Attribut (Field) der Aktion: Die Aktionen sollen ausgetauscht werden können (z.B. spezielles Kopieren in die Zwischenablage über eine andere Aktion), ebenso die Auslöser (z.B. unter Macintosh-Rechnern die Command-Taste anstelle der Strg-Taste oder frei definierbar durch die Benutzer) oder die Aktion soll entfernt werden können (keine Reaktion mehr auf diese Tastatureingabe).</li>
<li>Tastatureingabe, die Aktion auslösen soll, diesem Schlüssel zuweisen über die <code>InputMap</code> der <code>JComponent</code></li>
<li>Aktion diesem Schlüssel zuweisen über die <code>ActionMap</code> der <code>JComponent</code></li>
</ol>
<p><strong>Anmerkung:</strong> Eine <code>JComponent</code> hat 3 <code>InputMap</code>s, die in folgender Reihenfolge so lange abgearbeitet werden, bis für die Tastatureingabe ein Eintrag gefunden wurde: 1. Eine, sofern die <code>JComponent</code> fokussiert ist, 2. eine, falls die <code>JComponent </code>Vorgänger (Elterncontainer) einer fokussierten ist und 3. eine, falls das <code>Window</code> der <code>JComponent</code> fokussiert ist. 2./3. werden benutzt, falls eine Aktion ausgelöst werden soll unabhängig von der gerade fokussierten Komponente.</p>
<p>Weitere Informationen: <a href="http://download.oracle.com/javase/tutorial/uiswing/misc/keybinding.html">How to Use Key Bindings</a>.</p>
<h3>2. Aktion Buttons und Menüs zuweisen</h3>
<p>Die gleichen Aktionen können z.B. <code>JMenuItem</code>s und <code>JButton</code>s über deren Konstruktoren zugewiesen werden (z.B. aus der <code>ActionMap</code> über deren Schlüssel holen).</p>
<h3>3. Wie wird die Aktion aktiviert und deaktiviert?</h3>
<p>Ist die Aktion von <code>AbstractAction</code> abgeleitet, ruft man <code>setEnabled()</code> auf. <code>JButton</code>s und <code>JMenuItem</code>s sind <code>PropertyChangeListener</code> bei der Aktion, werden vom Aktivieren/Deaktivieren benachrichtigt und sind anschließend (wieder) aktiv oder nicht (mehr aktiv; &#8220;schwarz&#8221; oder &#8220;ausgegraut&#8221;).</p>
<h3>4. Offene Probleme</h3>
<h4>4.1 Wie gelangt die Aktion an die zu verarbeitenden Daten?</h4>
<p>Ich gehe davon aus, die Aktion soll nicht lediglich z.B. einen Dialog anzeigen, sondern ausgewählte Daten verarbeiten, z.B. eine ausgewählte Datei löschen.</p>
<p>Manchmal erhält man die Daten &#8211; z.B. visualisiert als Knoten eines <code>JTree</code>s &#8211; über die <strong>Quelle</strong> der Aktion (Eigenschaft <code>source</code> des <code>ActionEvent</code>s in <code>actionPerformed()</code>, im speziellen Fall des <code>JTree</code>) . Bei Menüaufrufen nutzt das nichts, da die Aktionsquelle das <code>JMenuItem</code> ist. Gleiches trifft zu, falls die Aktion an eine Vorfahren-Komponente gebunden ist, die nicht fokussiert ist, Aktionsquelle ist dann die Vorfahren-Komponente, nicht die fokussierte.</p>
<p>Wird von einem GUI-Builder eine Aktion erzeugt, ist diese oft eine anonyme innere Klasse eines GUIs, die Zugriff hat auf alle Attribute (Fields) des GUIs. Das ist nicht wiederverwendbar.</p>
<p>Man könnte alle Daten in der Aktion als Attribute speichern, was ich weniger gut finde (die vielseitigen Gründe sind hier nicht beschrieben, der vermutlich wichtigste: Die Aktion kann nicht &#8220;universell&#8221; eingesetzt werden, sondern ist fest gekoppelt an eine spezialisierte <code>JComponent</code>).</p>
<p>In den oben aufgeführten Fällen wird man wiederholt Code schreiben, der die Daten mehr oder weniger aufwändig ermittelt, z.B. alle ausgewählten Knoten eines Baums herausfindet und anhand dieser die Daten, beispielsweise Dateisystemordner oder in einer Datenbank gespeicherte Werte.</p>
<h4>4.2 Wie werden die Bedingungen festgestellt, unter denen die Aktion aktiviert/deaktiviert wird?</h4>
<p>Ist in einem Baum <em>kein</em> Knoten selektiert, soll die <kbd>Entf</kbd>-Taste <em>nichts</em> löschen und das Kontextmenü soll keinen oder einen deaktivierten &#8220;Löschen&#8221;-Menüpunkt anzeigen.</p>
<p>Es sollte <em>nicht</em> Aufgabe der Aktion sein, jedes mal zu prüfen, ob sie ausgeführt werden soll.</p>
<h4>4.3 Lösungsansatz</h4>
<p>Aktuell favorisiere ich den Ansatz der <span class="product">NetBeans-Platform</span> (der in dieser Form selbst zu implementierende spezialisierte Aktionen erfordert): Es gibt Container mit Objekten, sogenannte <strong>Lookups</strong>. Beobachter melden sich bei den Lookups an und ändert sich der Inhalt der Lookups, werden die Beobachter benachrichtigt (nur falls Klassen betroffen sind, die den Beobachter interessieren). Es gibt einen (&#8220;globalen&#8221;) Lookup, der den Inhalt der gerade ausgewählten spezialisierten <code>JComponent </code>enthält.</p>
<p>Die Aktion ist ein Beobachter des (globalen) Lookups: Ist &#8220;interessanter&#8221; Inhalt im Lookup, aktiviert sie sich und sie deaktiviert sich, ist das beobachtete Lookup leer. Wird die Aktion ausgeführt, benutzt sie den Inhalt des Lookups.</p>
<p>Aktionen interessieren sich nur für bestimmte Objekte: Eine, die Dateien umbenennt, benötigt nur die <code>File</code>-Objekte der umzubenennenden Dateien. Wäre sie an einen <code>JTree</code>, einer <code>JList</code> oder anderen <code>JComponent</code>s gebunden, müsste sie jedesmal mehr oder weniger umständlich die Dateien daraus ermitteln und wäre begrenzt auf die spezialisierte  <code>JComponent</code>.</p>
<p>Das Lookup löst beide Probleme (4.1 und 4.2): Die Aktionen sind nur aktiv, falls sinnvoll und sie haben die Daten, mit denen sie arbeiten wollen.</p>
<p>&#8220;Außerhalb&#8221; der NetBeans-Platform sind folgende Probleme zu lösen (wie, zeigt der Artikel nicht auf):</p>
<ul>
<li>Ein &#8220;globales&#8221; Lookup definieren, bei dem die Aktionen als Beobachter angemeldet werden</li>
<li>In dieses Lookup sind bei Selektion die selektierten Objekte einzufügen; das Lookup enthält nichts (nichts ausgewählt) oder alle ausgewählten Objekte (<em>nicht</em> die &#8220;Low-Level&#8221;-Objekte wie <code>TreeNode</code>s, sondern die Daten, mit denen ein Programm arbeiten will) der <strong>aktiven</strong> (fokussierten) <code>JComponent</code></li>
</ul>
<h3>5. Temporäre Selektionen</h3>
<p>Auch hierfür bietet dieser Artikel keine Lösung (und auch nicht die NetBeans-Platform, diese ändert einfach die Auswahl): Wird mit der rechten Maustaste geklickt, während der Mauszeiger über einem &#8220;Item&#8221; einer <code>JComponent</code> ist, z.B. einem Tree- oder List-Item, das <em>nicht ausgewählt</em> ist, erwartet der Benutzer:</p>
<ul>
<li>Dieses Item wird <em>nicht</em> ausgewählt (Ist das Item z.B. ein Verzeichnis, werden <em>nicht</em> dessen Dateien angezeigt)</li>
<li>Dieses Item wird hervorgehoben</li>
<li>Die aktuelle Auswahl geht <em>nicht</em> verloren</li>
<li>Ein Popupmenü erscheint an der Item-Position</li>
<li>Alle Aktionen des Menüs beziehen sich auf das Item</li>
<li>Wird das Menü geschlossen, wird die Hervorhebung zurückgenommen</li>
</ul>
<p>Eine Lösung wäre ein globales Lookup für temporäre Auswahlen, das mit dem globalen Lookup für Auswahlen verbunden ist und <strong>temporär,</strong> solange das Popupmenü geöffnet ist, dessen Inhalt ersetzt.</p>
<p>Siehe hierzu folgenden <a href="/blog/2011/09/26/den-netbeans-lookup-fur-nicht-rcp-projekte-benutzen/" title="">neueren Artikel</a>, dieser beschreibt die Benutzung eines Lookups und enthält zum Download ein Beispielsprojekt.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elmar-baumann.de/blog/2010/09/18/java-aktionen-richtig-benutzen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bücher über Software-Entwicklung und Programmierung</title>
		<link>http://www.elmar-baumann.de/blog/2009/01/16/bucher-uber-software-entwicklung-und-programmierung/</link>
		<comments>http://www.elmar-baumann.de/blog/2009/01/16/bucher-uber-software-entwicklung-und-programmierung/#comments</comments>
		<pubDate>Fri, 16 Jan 2009 18:09:26 +0000</pubDate>
		<dc:creator>Elmar</dc:creator>
				<category><![CDATA[Bücher]]></category>
		<category><![CDATA[Software-Entwicklung]]></category>

		<guid isPermaLink="false">http://www.elmar-baumann.de/blog/?p=92</guid>
		<description><![CDATA[Folgend liste ich die Bücher über Software-Entwicklung und Programmierung, die ich am nützlichsten finde von jenen, die ich gelesen habe. C Kernighan/Ritchie: Programmieren in C. Mit dem C-Reference Manual in deutscher Sprache. Zweite Ausgabe. ANSI C. Carl Hanser Verlag. C++ Bjarne Stroustrup: Die C++ Programmiersprache. Addison-Wesley-Longman. Scott Meyers: Effektiv C++ programmieren. 50 Wege zur Verbesserung [...]]]></description>
			<content:encoded><![CDATA[<p>Folgend liste ich die Bücher über Software-Entwicklung und Programmierung, die ich am nützlichsten finde von jenen, die ich gelesen habe.<br />
<span id="more-92"></span></p>
<h3>C</h3>
<ul>
<li><em>Kernighan/Ritchie</em>: <strong>Programmieren in C.</strong> Mit dem C-Reference Manual in deutscher Sprache. Zweite Ausgabe. ANSI C. Carl Hanser Verlag.</li>
</ul>
<h3>C++</h3>
<ul>
<li><em>Bjarne Stroustrup</em>: <strong>Die C++ Programmiersprache.</strong> Addison-Wesley-Longman.</li>
<li><em>Scott Meyers</em>: <strong>Effektiv C++ programmieren. 50 Wege zur Verbesserung Ihrer Programme und Entwürfe.</strong> Addison-Wesley-Longman.</li>
</ul>
<h3>Java</h3>
<ul>
<li><em>Joshua Bloch</em>: <strong>Effective Java</strong>. Second Edition. Addison-Wesley.</li>
</ul>
<h3>Perl</h3>
<ul>
<li><em>Damian Conway</em>: <strong>Perl Best Practices</strong>. Standards für guten Perl-Code. O&#8217;Reilly.</li>
</ul>
<h3>Entwurf</h3>
<ul>
<li><em>Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides</em>: <strong>Entwurfsmuster. Elemente wiederverwendbarer objektorientierter Software.</strong> Addison-Wesley.</li>
</ul>
<h3>Techniken</h3>
<ul>
<li><em>Martin Fowler</em>: <strong>Refactoring. Improving the Design of existing code.</strong> Addison Wesley Longman.</li>
<li><em>Robert C. Martin</em>: <strong>Clean Code. Refactoring, Patterns, Testen und Techniken für sauberen Code.</strong> 1. Auflage. mitp.</li>
<li><em>Andrew Hunt, David Thomas</em>: <strong>The Pragmatic Programmer. From journeyman to master.</strong> Addison-Wesley.</li>
<li><em>Bernd Oestereich</em>: <strong>Objektorientierte Softwareentwicklung. Analyse und Design mit der Unified Modeling Language.</strong> R. Oldenbourg.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.elmar-baumann.de/blog/2009/01/16/bucher-uber-software-entwicklung-und-programmierung/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Fotos drucken mit Lightroom</title>
		<link>http://www.elmar-baumann.de/blog/2008/12/03/fotos-drucken-mit-lightroom-und-dry/</link>
		<comments>http://www.elmar-baumann.de/blog/2008/12/03/fotos-drucken-mit-lightroom-und-dry/#comments</comments>
		<pubDate>Wed, 03 Dec 2008 02:14:30 +0000</pubDate>
		<dc:creator>Elmar</dc:creator>
				<category><![CDATA[Fotografie]]></category>
		<category><![CDATA[Software-Entwicklung]]></category>
		<category><![CDATA[Bildbearbeitung]]></category>
		<category><![CDATA[drucken]]></category>
		<category><![CDATA[DRY]]></category>
		<category><![CDATA[Lightroom]]></category>

		<guid isPermaLink="false">http://www.elmar-baumann.de/blog/?p=13</guid>
		<description><![CDATA[Ein Prinzip beim Schreiben von Computerprogrammen lautet: Don&#8217;t repeat Yourself, als Akronym: DRY. Bei guten Programmen gilt dieses Prinzip auch für die Bedienung: Der Benutzer soll nicht wieder und wieder die gleichen stupiden Schritte unternehmen zum Erledigen einer Aufgabe. Adobe Photoshop Lightroom berücksichtigt das bei vielen Aufgaben, als Beispiel beschreibe ich kurz das Ausdrucken von [...]]]></description>
			<content:encoded><![CDATA[<p>Ein Prinzip beim Schreiben von Computerprogrammen lautet: <strong>Don&#8217;t repeat Yourself</strong>, als Akronym: <strong>DRY</strong>. Bei guten Programmen gilt dieses Prinzip auch für die <strong>Bedienung</strong>: Der Benutzer soll <em>nicht</em> wieder und wieder die gleichen stupiden Schritte unternehmen zum Erledigen einer Aufgabe.<br />
<span id="more-13"></span><br />
<span class="product">Adobe Photoshop Lightroom</span> berücksichtigt das bei vielen Aufgaben, als Beispiel beschreibe ich kurz das Ausdrucken von Bildern. Früher benutzte ich dafür Photoshop (die Bildbearbeitung, <em>nicht</em> Lightroom), was mich nach <strong>jedem</strong> Neustart oder Wechseln der Papiersorte viel (unnötige) Arbeit kostete:</p>
<ul>
<li>Druckertreiber einstellen: Treiber-Dialog aufrufen, dort mehrere Mausklicks</li>
<li>Überprüfen, ob das Farbmanagement in Photoshop richtig eingestellt ist (Übernahme dieses, ICC-Profil, Rendering Intents &#8211; siehe Artikel <a href="http://www.elmar-baumann.de/fotografie/ebv/farbmanagement-06.html">Farben umrechnen zwischen Farbräumen</a>), korrigieren, wenn nicht</li>
<li>Größe des Ausdrucks einstellen (Druckauflösung und damit Bildskalierung)</li>
<li>Bild für den Ausdruck schärfen anhand der Druckauflösung</li>
</ul>
<p>Im Treiberdialog lassen sich Einstellungen speichern. Dort (Treiber für Epson R2400) darf der Bezeichner nur etwa 12 Buchstaben enthalten, was zu kryptischen Namen führt und mir so <em>nicht</em> die Kontrolle aller Einstellungen erspart.</p>
<p>In Lightroom ist das so gelöst: <strong>Alle</strong> Einstellungen kann ich als Vorlage speichern unter einem mir bedeutungsvollen Namen:</p>
<ul>
<li>Sämtliche Druckertreiber-Einstellungen, insbesondere: Papiersorte, Papiergröße, Druckqualität, Farbmanagement deaktiviert</li>
<li>Farbmanagement in Lightroom: ICC-Profil für den Ausdruck und Rendering Intents<a href="http://www.elmar-baumann.de/fotografie/ebv/farbmanagement-06.html"></a></li>
<li>Auflösung für den Ausdruck (Skalierung des Bilds)</li>
<li>Größe des Rands</li>
<li>Größe der längsten Bildseite</li>
<li>Schärfung</li>
</ul>
<p>Ich wähle die Vorlage aus (bei Wechsel von Papiersorte und -Größe), das ist ein Mausklick und klicke der Reihe nach alle Bilder an, die ich ausdrucken will. Hoch- und Querformat werden automatisch ermittelt, ebenso die Größe der Bilder. Was noch fehlt, ist <a href="http://www.elmar-baumann.de/fotografie/ebv/farbmanagement-11-05.html">Softproofing</a> &#8211; hoffentlich hat das die nächste Version von Lightroom.</p>
<div class="wp-caption alignnone" style="width: 295px"><img src="/blog/wp-content/uploads/lightroom-druckvorloage-01.gif" alt="Einige meiner Druckvorlagen in Lightroom." width="285" height="162" /><p class="wp-caption-text">Ausschnitt: Einige meiner Druckvorlagen in Lightroom.</p></div>
<p>Eine weitere schöne Eigenschaft von Lightroom: Die Bilder (Originale) werden für den Ausdruck nicht modifiziert. Wollte ich in Photoshop die Druckeinstellungen nicht verlieren, müsste ich für jede unterschiedliche Druckgröße eine extra Datei (-version/-variante) speichern  (Schärfung und Druckauflösung).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.elmar-baumann.de/blog/2008/12/03/fotos-drucken-mit-lightroom-und-dry/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

