Liquiface felület fejlesztése

Posted by | · · · · · · | Cégélet · Liquiface | 3 hozzászólás Liquiface felület fejlesztése című bejegyzéshez

Korábban már írtunk róla, hogy készült nálunk egy Liquiface nevű NetBeans plugin. Amellett, hogy kifejezetten hasznosnak találjuk a Liquibase nevű eszközt, aminek igyekeztünk ezzel a fejlesztéssel “arcot” adni, azért is érdekes volt a projekt, mert se NetBeans plugin fejlesztésben, se Java Swing használatában nem volt egyikünknek sem tapasztalata. Ebből aztán természetesen adódtak meglepetések, hosszas kutatások, fejvakarások és olykor diadalittas felkiáltások. Mivel én legtöbbet a felület fejlesztésével foglalkoztam, ezért most ebből a témakörből csemegézve osztanék meg veletek néhány tapasztalatot.

Saját “desktop” vs. varázslók

Először is annyit említenék meg, hogy ha az ember új terepre téved, érdemes először nagyon alaposan utána járni a platformnak. Ez ugye triviálisnak hangzik, de hát az élet néha elmos dolgokat. Jelen esetben például, ha korábban rátaláltunk volna arra, hogy a NetBeans fejlesztés milyen jól dokumentált, és hogy azon belül is mennyire jól összeszedett FAQ található, valószínűleg megkíméltük volna magunkat néhány bosszús pillanattól.

Történt ugyanis, hogy kolléga összerakta a maven alapú netbeans projektünket, én kezembe kaptam az alapértelmezetten létrehozott főkomponenst, a TopComponentet, és úgy gondoltam, hogy innen már egyszerű az élet: eredeti terveink szerint a netbeans fülön belül egy “desktop” szerű élményt szerettünk volna kialakítani. Ez pedig ugye elvileg könnyen elérhető a Swing komponensek használatával, ezért úgy gondoltam, elég nekem annak a megismerésére koncentrálni.

table_editor_es_menu

Első képernyőtervünk: ahogy az lenni szokott, erőteljesen különbözik a végső megvalósítástól

Az eredeti koncepció az volt, hogy Internal Framekkel építjük fel a kommunikációt a felhasználóval. El is készítettem az első framet, teljesen jól is működött. Amíg be nem tettem mellé a másodikat… Kiderült, hogy a TopComponent nem képes a többszintű layerek kezelésére, szóval bár megjelentek a framek, a sorrend, hogy melyik fedi melyiket, mindig fix. Hát igen, első lépések Swingben. És akkor itt kezdődött egy hosszadalmas kutatás, hogy akkor hogyan lehetne ezt megoldani. Sokat kísérleteztem a Desktop Pane használatával, mivel ugye elvileg ez az a komponens, amit arra a funkcionalitásra találtak ki, amit mi szerettünk volna, és bár voltak arra utaló nyomok, hogy ez működhet, TopComponenten belül sehogy sem sikerült működésre bírnom. Biztos vagyok benne, hogy ha több tapasztalatom lenne desktop alkalmazások fejlesztésében, előbb vagy utóbb sikerült volna összehozni, de végül még ez előtt megvilágosodtam: alapvetően rossz a koncepcióm. Hiszen mit is akarok én? Kommunikálni a felhasználóval. Ezt pedig maga a NetBeans is sokszor megteszi. Mi is történik például, ha egy új fájlt, új projektet, stb. akarok létrehozni? Kapok egy varázslót. Hát ezt kellene nekem is tenni!

Arra jöttem rá, hogy ahelyett, hogy egy NetBeansben megnyíló, gyakorlatilag különálló alkalmazásfelületet hoznék létre, sokkal jobb megoldás, ha ténylegesen integrálom a felületünket: azaz felhasználom a NetBeans Api által nyújtott lehetőségeket. A felismerés után már könnyű volt megtalálni a varázslók használatához szükséges információkat, és legnagyobb örömömre egy teljesen kényelmes, könnyen és jól használható eszközt kaptam a kezembe, aminek nem elhanyagolható előnye, hogy vizuálisan is illeszkedik a NetBeans megjelenésébe.

Swing táblában nyomógomb

Egy másik érdekes tapasztalat volt, mikor az oszlopok hozzáadását megtámogató táblázatot raktam ki a felületre. Webes fejlesztésekből érkezve, megszoktam, hogy a táblázatok cellái gyakorlatilag univerzális konténerek, és azt pakolhatok bele, ami nekem éppen jólesik. Igaz ez akár a sima HTML-re, akár PrimeFaces-zel megtámogatott Java Server Facesre, akár Vaadinra gondolok. Ezért bizony kifejezett meglepetésben volt részem, mikor először megpróbáltam törlés gombot betenni egy táblázat minden sorába, és a gomb helyett csak a gomb toStringje jelent meg.

table_with_button

Oszlopok hozzáadásánál összegzésre és törlésre használt táblázat

Mint kiderült a probléma abban rejlik, hogy a JTable beépítetten nem támogatja JButton renderelését, hanem az egyszeri programozónak kell ezzel a funkcionalitással felvérteznie. Szerencsére nem én voltam az első, akiben ilyen igény merült fel, így elég könnyen találtam elég sok leírást, magyarázatot, példát és konkrét megvalósítást a problémára. Ami miatt végül saját implementáció mellett döntöttem, az volt, hogy a talált megoldások inkább az alapul szolgáló adatból generáltak gombot, én viszont a saját gombomat teljes viselkedésével együtt már magában a TableModelben implementálni akartam. Végül a következő megoldás született:


public class TableButtonRenderer implements TableCellRenderer {

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        return (JButton) value;
    }

}

Mivel nekem már a modell egy JButtont ad vissza, ami pedig tudja magáról, hogy hogyan kell őt renderelni, ezért elég mefelelően kasztolva visszaadni.


public class TableButtonEditor extends AbstractCellEditor implements TableCellEditor {

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int column) {
        JButton button = (JButton) value;
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireEditingStopped();
            }
        });
        return button;
    }

    @Override
    public Object getCellEditorValue() {
        return null;
    }

}

Ahhoz, hogy kattintható legyen a gombunk, szerkeszthetőnek is kell lennie. Utóbbihoz pedig szükség van egy TableCellEditor implementációra. Fontos, hogy itt a gombhoz hozzáadok egy ActionListenert, ami gombnyomás esetén jelzi, hogy befejeztük a szerkesztést. Ha ezt nem tesszük meg, törlés után a komponens kicsit összezavarodik, hogy adott sorban most akkor mégis melyik sorhoz tartozó gomb is van kirenderelve.


columnsTable.setDefaultRenderer(JButton.class, new TableButtonRenderer());
columnsTable.setDefaultEditor(JButton.class, new TableButtonEditor());

Ezután már csak meg kell adni a táblázatnak, hogy JButton típusú elem esetén a mi implementációnkat használja, és máris működik a törlés gomb.

Liquiface menüpont

Ha már van egy jó kis NetBeans pluginunk, alapvetően szeretnénk, ha ezt a felületről könnyen el is lehetne érni. Itt jön a képbe a menü és a menüpont kérdése. Mivel a szerkesztőterületen, új tabon nyílik meg az alkalmazás, ezért úgy gondoltuk, hogy a Window menüpont lesz számunkra a legjobb hely. Ikont használunk, almenü nem nyílik belőlünk, gyors gombot nem használunk, az ideális pozíció a Properties és az Output menüpontok között található. 7-es NetBeans alatt már kényelmesen, annotációval adhatjuk meg, hol szeretnénk elhelyezkedni. Az ActionReference annotáció paraméterei pedig magukért beszélnek. De tényleg. Teljesen egyértelműen. Path paraméterrel tényleg nincs is gond, nekünk ez “Menu/Window”. Pozíció már kicsit érdekesebb: javadocban bőbeszédű leírást kapunk, nevezetesen, hogy ez szolgál a pozíció megadására. Remek, egész szám, de mégis mennyi az annyi? Na, hát ez az, amire ha van is valahol válasz, nagyon elrejtették, mert én nem találtam meg. Persze nem jelent gondot próbálgatással addig cserélgetni a számunkat, amíg megfelelő pozícióba nem kerülünk, de hát azért ez nem tűnik túl profi hozzáállásnak. Sokkal szebb lenne, ha megtalálnám valahol, hogy pontosan milyen értékek tartoznak a Properties és az Output menüpontokhoz, és ez alapján állítanám be, megpróbálva olyan számot választani, hogy ha valaki más is itt szeretné elhelyezni a saját menüpontját, azért talán ne akadjunk össze.

Hát arról, hogy mik lennének ezek az alapértelmezett értékek, semmit nem találtam. Nem kevés keresgélésem annyira vezetett, hogy megtudtam, már más is kereste erre a kérdésre (eredménytelenül) a választ (sajnos nem találtam meg újra a linket), illetve hogy a NetBeans elvileg layout.xml-t generál az annotációkból, és futás közben azt használja fel. Következő kísérletem a generált layout.xml megtalálására irányult, de ez sem hozott eredményt. A legtöbb, amire jutottam, hogy mi is tudunk a saját modulunkban layout.xml-t létrehozni, és ha ilyenünk van, a NetBeans projekt fülén, a layout-on belül, a <this layer in context> menüt lenyitva láthatjuk a teljes struktúrát. A nem mi általunk definiált menüpontokról egyébként semmi új adatot nem tudunk itt kideríteni (bár törölni tudnánk őket). Gyakorlati haszna ennek számomra annyi volt, hogy ha módosítottam a position értékét az annotációban, akkor egy clean-and-build után már az alkalmazás elindítása nélkül is látszott, hogy mi a menüpontunk új pozíciója. Egy fokkal kényelmesebb, de továbbra is csak tippelés.
Még annyi hasznosat megtudtam, hogy ha menüpontunkhoz almenüt is szeretnénk létrehozni és pozicionálni, azt jelenleg tisztán annotációkkal nem fogjuk tudni megoldani.

Végül a következő megoldást választottam pozíciónk egzakt megadására: új Action létrehozásakor a NetBeans varázslója rákérdez, hová szeretném azt elhelyezni. Itt ki tudom választani a Window menüt, azon belül a Position értékének pedig a következőt: “Properties – HERE – Output”. Ezt elvégezve megkapom, hogy az általam kívánt érték szép kerek, konkrétan 1000. Úgy döntöttem, hogy a NetBeans biztos tudja, mit csinál, így maradtam ennél az értéknél. Ha pedig más is ide pozicionálja a saját pluginját, akkor meg majdcsak eldől valahogy a sorrend, mondjuk  az aktuális napfolttevékenységek függvényében…

menu

A Liquiface menüpont elhelyezkedése a NetBeansben

EventBus

Egy másik projekt kapcsán, egymásról nem tudó felületi komponensek közötti kommunikáció lehetőségét vizsgálgattam, amikor először találkoztam az EventBus megoldással. Abban a projektben végül nem használtuk fel, de megmaradt bennem a késztetés, hogy ezt ki kellene próbálni, és a Liquiface jó alapot nyújtott erre. Az EventBus alapvetően a publish-subscribe minta egy megvalósítása. Lényege tömören, hogy az egymással kommunikáló feleknek valójában nem kell ismerniük egymást, a kommunikáció nem közvetlenül történik: az EventBus szolgál közvetítő közegként. A fogadó fél feliratkozik az EventBusra, és közben megmondja, milyen konkrét eseményekre kíváncsi. Az adó fél pedig saját “adását”, azaz az általa kiváltott eseményt az EventBusnak adja át, ami eljuttatja minden, az adott eseményre feliratkozott fogadónak. Ez esetünkben azt jelentette, hogy nem volt szükség sok felületi komponensük referenciájának átadogatására a rendszerben, elég volt őket a buszra feliratkoztatni.

Az elhatározás után, hogy EventBust akarunk használni, már csak azt kellett eldönteni, hogy konkrétan melyik megvalósítást. Három implementáció volt, ami felkeltette az érdeklődésem: az EventBus, a SimpleEventBus és a Guava Eventbus projektek. Végül a Guava megoldása mellett döntöttem, mert könnyűsúlyú, egyszerűen használható, jól dokumentált és jó leírásokat találni hozzá. Az is mellette szólt, hogy rendszeresen frissül, míg a másik két projekthez már egy ideje nem érkezett új verzió. Ellenérvként azt lehetne felhozni, hogy strong referenciákat tárol. Aki egyébként bővebb összehasonlításra vágyik, itt talál egy leírást.

Összességében azt kell mondjam, meg voltunk elégedve a választással: a Guava EventBus jól működik nálunk. Persze ennek a módszernek a legnagyobb előnye egyben a legnagyobb hátránya is: mivel a komponensek nem tudnak egymásról, ezért nagyon nagy és komplex rendszernél körülményes lehet annak visszakövetése, hogy ki, mikor, miért és mire reagál. Egyelőre azonban mi ilyen gondokkal még nem szembesültünk, viszont az előnyöket élvezzük: tisztább, szebb kódot kaptunk végeredményben. Szóval aki még nem találkozott vele, annak mindenképpen ajánlani tudom, hogy nézzen utána!

Zárszó

A fent leírtak olyan ember tollából (azaz inkább klaviatúrájából) származnak, aki bevallottan most találkozott először közelről Swing komponensekkel és NetBeans plugin fejlesztéssel, így előfordulhat, hogy a leírt megoldásoknál van jobb, szebb megközelítés. Ebből kifolyólag örömmel fogadok / fogadunk minden észrevételt, meglátást, megjegyzést, kritikát, ami segíti a fejlődésünket vagy csak úgy kikívánkozik belőletek!

Share on FacebookShare on Google+Email this to someoneTweet about this on TwitterShare on RedditShare on LinkedIn

3 hozzászólás

Webstar Blog | Liquiface használati útmutató says:

2013. július 25. at 16:11

[…] korábbi bejegyzésben már említettük, hogy készítettünk egy Liquiface névre hallgató NetBeans plugint, mely a […]

Reply

hron84 says:

2013. szeptember 7. at 21:03

Valami gond van ezzel a cikkel, a bejegyzesek listajaban a “Tovabbolvasom” gomb nem kulon sorban jelenik meg itt, hanem az utolso sor vegen.

Reply

Fenyó says:

2013. szeptember 10. at 11:00

Köszi, javítottuk.

Reply

Leave a comment