A Drag-and-Drop sajátos kódolása listadobozokkal

Egy bolti kiskereskedő munkájával összefüggő igen gyakori eset a megrendelések minél gyorsabb, hatékonyabb összeállításának problémája. Aki nap mint nap számtalan bizonylatot állít ki, tudja, hogy az eladás milyen feszült környezetben történik. A vevő biztosan akarja az árút, vagy hezitál? Valóban megvásárolja az adott cikket, vagy sem?

Az eladónak nincs ideje az árucikkek kódjának keresésére, mert teljesen összezavarodott és már a tételre név szerint sem emlékszik. Vagy ha már beütött egy sort, csak hosszas procedúra után tudja csak javítani.

Ebben a zavaros helyzetben jól jöhet egy gyorsan kereshető lista, amely például az eladandó árukat tartalmazhatja és amelyből egy másik, például egy számla csupán egérműveletekkel könnyen összeállítható a fogd és vidd módszerrel. Erre a való világból, az életből merített problémának egy részére produkál megoldást ez a cikk és kód. Ugyanis "tetszőleges" helyről, listadobozból "tetszőleges" helyre szállítható egy tétel, amely nemcsak tétel, hanem egy tetszőleges objektum lehet. Hiszen egy listába objektumokat lehet elhelyezni, kivenni, törölni.

A programot lefuttatva három JList-ből származtatott sajátos, görgethető  listadoboz jelenik meg, amelyekből az elemek áthúzhatók és beledobhatók egy másik vagy ugyanabba a dobozba. A drag-and-drop művelet előtt egy rövidet kell kattintani a kiválasztandó elemre. Ezután végezhető csak el a húzási művelet, majd a céldobozt kijelölve beleejthető az átvitt objektum. Ha nem kattintunk, hanem azonnal nyomva tartással húzzuk az elemet, a lista autogörgetésbe kezd, amely igen megkönnyíti a gyors lapozást. A görgetéshez használhatjuk még a középső egérgomb görgetőjét, ha a nyíllal a listadobozra mutatunk - ez a komponens beépített viselkedése. A beledobott objektum hozzáadódik a céldoboz elemeihez, azaz a lista végéhez fűződik. Az eredeti elem nem távolítódik el a forrásból.

A feladat sajátosan került megvalósításra, mivel az eredeti ..dnd.. csomagból semmit sem importál a program. A java fejlesztői a vidd és dobd technikát általánosabban fogalmazzák meg a package java.awt.dnd.*; csomagban. Azonban ott is nekünk kell az elemek hozzáadásáról és/vagy eltávolításáról gondoskodni. A "gyári" csomagból különböző interfészek implementálásával oldható meg a feladat. Végül is a saját kódban is interfészekkel valósul meg a megoldás, csak itt az egéresemények lekezelésével. Az egyéni megoldást erősíti még az is, hogy az átvitt elem vonszolás közben láthatóvá válik, így lehetőség van egy esetleges korrigálást végrehajtani, például a rossz elem közömbös területre ejtésével.

A kód a JList kiterjesztésével (mydnd.myjlists.MyJList) egy olyan mobil komponenst hoz létre, amelynek példányából akármennyi lehet az ablakban, tehát  ez az osztály kielégíti az újrafelhasználhatóság követelményét. Viszont a listadobozok tartalom paneljének(MyJPanel) létrehozásakor, amely a rátett listák esetében közös, bizonyos megkötéseket kell alkalmaznunk. Ilyen az elemek hozzáadása, a görgetőpanel (JScrollPane) vektorba gyűjtése és a vektor beállítása, a függőleges görgetősáv kötelezővé tétele. A beállítások folyamata a MyJPanel konstruktorában megtalálható. 

A MyJList implementálja az egéreseményekkel összefüggő interfészeket, amellyel megvalósítja például annak lekérdezését, hogy az egér gombjának felengedésekor listadoboz felett járunk-e. Ha igen, kikeresi a beállított vektor alapján a megfelelő komponenst, majd hozzáadja az elemet. A MyJList adatait egy adatmodellen keresztül lehet elérni, amely a Vector-hoz hasonlítható. Nem tanácsos azonban vektorral létrehozni és kezelni az adatokat, mivel ha a konstruktorban vektort adunk meg, majd a vektor adatait módosítjuk, az adatokban bekövetkezett változás nem mindig lesz látható a képernyőn. Kiküszöböléséhez használjuk a lista modelljét és ha ki akarjuk nyerni az adatokat, állítsunk össze egy olyan metódust (getVector()), amely az elemeket egy vektorban adja vissza.

Az elem jelen van az egérkurzor mellett, miközben azt vonszoljuk. Megoldása a drawImage metódussal történik, amely kirajzolja az elemet, és az eredeti blokkot visszamásolja. Vagyis kivág a grafikából egy szeletet, amelyet mozgatás után visszaír, hogy ne látszódjon a maga után húzott nyom. A kirajzolás gyorsan megtörténik a drawImage függvénnyel egy JComponent esetében, nem úgy, mint az elavult awt használatakor.

A Dnd, MyJFrame és MyWindowAdapter osztályokról nem szólnék részletesen, mivel alapvető funkciókat látnak el: Program indítása, keret létrehozása és az ablak bezárását biztosító Adapter.

A mydnd.MyJPanel osztály konstruktorának feladata a komponensek elhelyezése, és adatainak beállítása, amelyet az alábbi módon ajánlott elvégezni.

public MyJPanel() {

      MyJList l1 = new MyJList(this);

      l1.addItem("alma"); // objektum hozzáadása a lista elemei közé

      JScrollPane s1 = new JScrollPane(l1);

/* görgethető panel létrehozása, amely tartalmazza a listát. Ezáltal a lista görgethető lesz. */

      Vector sps = new Vector();

      sps.add(s1);

/* a görgetőpaneleket egy vektorba gyűjti, amely alapján állapítható majd meg, hogy az egérkurzor melyik lista felett jár */

      setLayout(new BorderLayout(1,1)); // a panel elrendezés menedzsere

      add(s1);

/* listákat tartalmazó panelek hozzáadása a fő panelhez, amelyen az összes komponens van! */

      MyJList.setVerticalScrollBars(sps);

// a görgetőpanelek vertikális sávjának kötelezővé tétele

      l1.setVector(sps);

l2.setVector(sps);

      sps.remove(s2);

l3.setVector(sps);

}

/* a listákhoz a görgetőpanelek vektorának beállítása, mivel így tudja az adott lista melyik másik lista felett tartózkodik a felügyelete alá tartozó drag-and-drop művelet kurzora. Látható, hogy a harmadik lista vektorából eltávolítottuk a második elemet, ezért a harmadik lista nem veszi figyelembe, ha a második listába elemet dobunk. Így tehát szabályozható egy adott korrekt dobási művelet érvényessége. */

A vonszolt elem a forrásba is beleejthető. Azonban ez az eset nem kerülhető el úgy, hogy a setVector metódus által beállított vektorból kihagyjuk az aktuális görgetőpanelt, de le lehet vizsgálni azt, hogy a jl változó referenciája (mouseReleased metódus, lásd alább...) megegyezik-e az sp1 változó referenciájával. Mert ha igen, az elemet nem kell hozzáadni önmagához.

A mydnd.myjlists.MyJList sajátosan megkomponált osztály néhány fontosabb metódusa a következőképpen fest:

public void mouseClicked(MouseEvent e) { // Klikkelés

Robot r = null;

/* a Robot osztály segíti az adott koordinátájú és területű rect téglalap által meghatározott képernyőterület képpé alakítását, amelyet az img változó tárol */

try { r = new Robot(); } catch( AWTException e2) {;}

img = r.createScreenCapture(rect);

}

Mivel csak görgetőpaneleket tárolunk, az elem hozzáadásához szükséges kinyerni belőle a listadobozt, amely a következő procedúra:

public void mouseReleased(MouseEvent e) { // Az egér gombjának felengedése

if (getOnScreen(obj).contains(new Point(cx+px+1,cy+py+1))) {

      JViewport jv = (JViewport)obj.getComponentAt(5,5);

      MyJList jl = (MyJList)jv.getView();

      jl.addItem(getItem(selectedItem));

// elem kivétele a forrásból és hozzáadása a céldobozhoz

}

public void mouseDragged(MouseEvent e) { // A kurzor vonszolása

if ((img!=null) && (selectedItem == getSelectedIndex())) {

/* Meg kell vizsgálni, hogy a kiválasztott elem valóban megfelelő a vonszoláskor elindított folyamat esetén is. */

if (isPainted==false) {

/* kirajzoljuk a kiválasztott elemet még egyszer, hogy látszódjon honnan mozdult el. */

isPainted=true;

int x = (int)moveRect.getX()-px;

int y = (int)moveRect.getY()-py;

if (imagedraw) {g.drawImage(img,x,y,this); }

}

}

A programot két számítógépen teszteltem. Az egyik geForce2 videokártyával ellátott, 2Ghz sebességű volt. A másik 333Mhz frekvenciájú, S3 2MB kártyával felszerelt gép volt. Az előbbinél jó sebességgel, kifogástalanul működött, az utóbbinál a képek rajzolása közben nyom keletkezett a mozgatás során. Ugyanaz a program, ugyanazzal a java verzióval eltérő eredményt produkált. Ha a kirajzolás (drawImage) nem megfelelő, a java valami miatt, például hardware vagy driver miatt nem támogatja, akkor a vontatott elem megjelenítése ki is kapcsolható az imagedraw változó hamisra állításával. Ilyenkor a fogd és vidd művelet működni fog, csak az elem nem lesz majd látható a kurzor mellett. Az alkalmazáshoz 800x600-as felbontás használata ajánlott, és lehetőleg ne SVGA monitor, illetve kártya legyen.

Dunás-Varga Zsolt - dproxy@freemail.hu