Utolsó frissítés: 2004. június 1.
A dokumentum i, hogy ismeri az EMF (Eclipse Modeling Framework) alapelveit. További EMF információkat az EMF áttekintése tartalmaz.
Bevezetés
EMF objektumok megjelenítése JFace megjelenítőkben
Elemszolgáltató megvalósítási osztályok
EMF modellek módosítása parancsokkal
EMF.Edit kódelőállító használata
Ha rendelkezik EMF alapú modellel, melynek az EMF kódelőállítóval előállította a kódját, és most készen áll arra, hogy felhasználói felületeket vegyen fel a modellbe és a kódba, akkor az EMF.Edit keretrendszer megkönnyíti ezt a munkát.
Az EMF.Edit olyan Eclipse keretrendszer, amely általános, újra felhasználható osztályokat tartalmaz a szerkesztők összeépítéséhez az EMF modellekhez. A következőket biztosítja:
A dokumentum áttekintést nyújt az EMF.Edit keretrendszerről és előállító eszközről. Részletesebb információkért nézze meg a keretrendszer osztályok dokumentációját, amely részletesen tárgyalja a specifikus viselkedéseket és képességeket.
Az Eclipse felhasználói felület keretrendszer (JFace) újra felhasználható megjelenítő osztályok készletét (például: TreeViewer, TableViewer) tartalmazza a strukturált modellek megjelenítéséhez. A JFace megjelenítők nem követelik meg, hogy a modellobjektumoknak valamilyen megadott protokollhoz igazodjanak (vagyis megvalósítsanak valamilyen felülettípust), helyette bármilyen fajta objektumokkal (vagyis bármely java.lang.Object alosztállyal) működnek. Ez azért lehetséges, mert a megjelenítők nem közvetlenül navigálnak a modellobjektumok között, hanem egy illesztő objektumon, úgynevezett tartalomszolgáltatón keresztül férnek hozzá a modellobjektumokhoz.
Minden egyes megjelenítő osztály olyan tartalomszolgáltatót használ, amely egy adott szolgáltató felületet valósít meg. Például a TreeViewer olyan tartalomszolgáltatót használ, ami az alábbi felületet valósítja meg:
public interface ITreeContentProvider ...
{
public Object[] getChildren(Object object);
public Object getParent(Object object);
...
}
Az alapvető szerkezet az alábbi ábrán látható:
A TreeViewer egy objektumokból (elemekből) álló fát jelenít meg a képernyőn. A bemeneti (gyökér) objektum kivételével az összes objektum úgy áll a rendelkezésére, hogy meghívja a getChildren() metódust a tartalomszolgáltatójára.
Más fajta megjelenítők kezelése is hasonló, bár minden egyes megjelenítő megköveteli, hogy a tartalomszolgáltató megvalósítsa az adott felületet. Habár az összes megjelenítő felület különböző, egy tartalomszolgáltató gyakran többet is megvalósíthat egyszerre, lehetővé téve ugyanannak a tartalomszolgáltató osztálynak a használatát többféle megjelenítőhöz.
Az EMF.Edit keretrendszer olyan általános tartalomszolgáltató megvalósítási osztályt nyújt, amellyel tartalmat biztosíthat az EMF modellek számára. Az AdapterFactoryContentProvider osztály úgy valósítja meg a tartalomszolgáltató felületeket, hogy olyan EMF illesztőket bíz meg, amelyek ismerik a modellobjektumok (elemek) navigációját a megjelenítőkhöz. Például a fa megjelenítőt támogató EMF illesztőosztály az alábbi EMF.Edit felületet valósítja meg:
public interface ITreeItemProvider
{
public Collection getChildren(Object object);
public Object getParent(Object object);
...
}
Ez a felület hasonló a fent bemutatott ITreeContentProvider tartalomszolgáltató felülethez. Az AdapterFactoryContentProvider úgy valósítja meg a tartalomszolgáltató felületet, hogy megkeresi, majd megbízza a kívánt elemhez tartozó illesztőt (ami megvalósítja az elemszolgáltató felületet). A terminológia eltolódása az objektumokról az elemekre szándékos: a megjelenítő szempontjából ezek elemek, nem objektumok.
Az EMF képe így néz ki:
Megjegyzés: Lehetősége van ItemProviderAdapterFactory és ItemProvider osztály automatikus előállítására egy adott EMF modellhez az EMF.Edit keretrendszerrel biztosított előállító használatával. Erről később ejtünk még szót.
Az AdapterFactoryContentProvider egy illesztőgyárral van összeállítva, ami - más EMF illesztőgyárakhoz hasonlóan - adott típusú illesztők (jelen esetben ItemProviders) létrehozására és keresésére szolgál. A tartalomszolgáltató úgy szolgál ki egy kérést, például getChildren() kérést, hogy meghívja az adapt(item) metódust az ItemProviderAdapterFactory osztályra. Ez létrehozza vagy visszaadja a megadott elemhez tartozó ItemProvider (adapter) értéket. Ezután egyszerűen megbízza a kívánt felület (jelen esetben ITreeItemProvider) getChildren() metódusát.
Az AdapterFactoryContentProvider getChildren() metódusa valahogy így néz ki:
public Object[] getChildren(Object object)
{
ITreeItemContentProvider adapter =
(ITreeItemContentProvider)
adapterFactory.adapt(object, ITreeItemContentProvider.class);
return adapter.getChildren(object).toArray();
}
Ugyanez a minta használatos az összes tartalomszolgáltató metódushoz. Ahogy korábban már kijelentettük, az AdapterFactoryContentProvider nem csinál mást, mint egyszerűen olyan tartalomszolgáltató metódusokat bíz meg az adott elemszolgáltatókhoz (illesztőkhöz), amelyek tudják, hogy hogyan kell kiszolgálni a kérést.
A fenti getChildren() metódusban az adapterFactory.adapt() számára átadott objektum egy egyszerű java.lang.Object (nem org.eclipse.emf.ecore.EObject). Ez az EMF.Edit keretrendszer fontos jellemzője. A keretrendszert úgy tervezték, hogy illeszkedjen az EMF modellek nézeteihez, amelyek eltérhetnek magától a modelltől (vagyis olyan nézetek, amelyek elnyomnak objektumokat, illetve további, fantom objektumokat tartalmaznak). Az EMF és nem EMF objektumok ezen keveréke úgy lehetséges, hogy a keretrendszer alap osztálya az illesztőgyárakhoz biztosít egy adapt() megvalósítást, ami valahogy így működik:
public Object adapt(Object object, Object type)
{
if (object instanceof Notifier)
return this.adapt((Notifier)object, type);
else
return object;
}
Ha a megadott objektum nem EMF értesítő[1] , akkor magát az objektumot adja vissza. Így azok az elemszolgáltatók, amelyek nem EMF elemeket akarnak adni egy nézethez, egyszerűen visszaadhatnak tetszőleges nem EMF objektumokat (például a getChildren() metódusból). Amíg a visszaadott objektum megvalósítja a megjelenítő szükséges elemszolgáltató csatolóét (például: ITreeItemProvider), ugyanúgy tekinti azt a rendszer, mint az összes többi EMF elemet.
E terv ezen nézőpontja kiemeli, hogy miért hívjuk inkább elemszolgáltatóknak a szolgáltató/illesztő osztályokat, nem illesztőknek. Nem triviális alkalmazásokban a nézet modellje (vagyis a megjelenítő tartalomszolgáltatója által biztosított modell) gyakran "valós" (EMF) modellobjektumok (amelyek elemszolgáltatói történetesen (EMF) illesztők) és "fantom" objektumok (amelyek elemszolgáltatói maguk az objektumok) keveréke. Így, míg az összes illesztő elemszolgáltató is egyben, ez fordítva már nem feltétlenül igaz.
Az előző szakaszokban leírtuk, hogy a JFace megjelenítők hogyan használják a tartalomszolgáltatókat a tartalomelemek lekéréséhez. Hasonló megközelítés használatos a címkeképek és -szövegek lekéréséhez a megjelenítő által mutatott elemekhez. Ahelyett, hogy magukból az elemekből kérdezné le a címkéjüket, a megjelenítő egy másik objektumot, úgynevezett címkeszolgáltatót használ (ami hasonló a tartalomszolgáltatóhoz). A TreeViewer például megbíz egy objektumot az ILabelProvider felület megvalósításával, hogy lekérje a fában található elemek címkéjét.
Az EMF.Edit keretrendszer ugyanazt a mechanizmust használja a címkeszolgáltatók megvalósításához az EMF modellekhez, mint amit a tartalom biztosításához is használ. Az általános címkeszolgáltató megvalósítási osztály, az AdapterFactoryLabelProvider (ami pontosan ugyanúgy működik, mint az AdapterFactoryContentProvider) megbízza az ILabelProvider felületet a modell elemszolgáltatóinak (ugyanazokat az elemszolgáltatókat, amelyek a tartalmat is biztosítják). A kibővített kép így néz ki:
A tartalom- és címkeszolgáltató képes (általában fogja is) ugyanazt az illesztőgyárat, és ennek következtében ugyanazokat az elemszolgáltatókat megbízni. A tartalomszolgáltatóhoz hasonlóan az elemszolgáltatók is azok, ahol a munka valójában elvégzésre kerül.
Az EMF.Edit szolgáltató osztály használatával a felhasználó például az alábbiak szerint állíthatja összes egy EMF modell TreeViewer megjelenítőjét:
myAdapterFactory = ... treeViewer = new TreeViewer(); treeViewer.setContentProvider(new AdapterFactoryContentProvider(myAdapterFactory)); treeViewer.setLabelProvider(new AdapterFactoryLabelProvider(myAdapterFactory));
Ez a TreeViewer aztán a szokásos módon megjeleníthető például egy szerkesztőablakban (JFace előírás).
Ez idáig elég egyszerűnek tűnhet, de csak azért, mert csak azt mutattuk meg, hogy hogyan lehet megbízni valaki mást (az illesztőgyárat). Nem valósítottunk még meg egyetlen metódust sem, csak megbíztuk őket. Az EMF.Edit azonban támogatja a metódusmegvalósítást is. Tartalmaz kódelőállítót, ami előállítja az elemszolgáltató és a gyár kód legnagyobb részét Ön helyett. Mielőtt belemennénk ebbe, először nézzük meg, hogy az elemszolgáltatók hogyan végzik a feladatukat.
Ahogyan az előző szakaszban láttuk, az EMF modellek tartalmának biztosítását valójában a modellhez csatlakoztatott elemszolgáltató illesztők végzik. Az előző diagramon szándékosan nincs pontosítva az ItemProvider illesztők száma és típusa. Ennek oka, hogy az EMF.Edit keretrendszer két különböző mintát támogat az elemszolgáltató illesztőkhöz:
Egy adott modell elemszolgáltatói megvalósíthatók a két minta bármelyikével vagy akár ezek keverékével is.
Az első mintánál a modellben található minden objektum egy-egy viszonyban van az illesztőjével. Minden egyes illesztő rendelkezik egy mutatóval (úgynevezett céllal) arra az egy és csakis egy objektumra, amelyet illeszt.
A kép így néz ki:
Mint látható, ez a minta megduplázza az objektumok számát az alkalmazásban, ezért csak az olyan alkalmazásokban van értelme, ahol a példányoknak további állapotot kell hordozniuk. Ezért nevezzük ezt Állapotmegőrző mintának.
Jobb megközelítés, ami elkerüli a többlet objektumok legnagyobb részét, az Egyke minta. Ennél a mintánál egyetlen elemszolgáltató illesztőt használunk az összes azonos típusú elemhez. A következőképpen néz ki:
A képen az objektumok rendelkeznek illesztő mutatókkal - szokás szerint -, de az (osztott) elemszolgáltatók nem rendelkeznek mutatókkal vissza az objektumokra. Ha felidézi a korábban, a tartalomszolgáltató szakaszban bemutatott fa elemszolgáltató felületet, akkor feltűnhetett, hogy ott minden metódusnak volt egy extra argumentuma (egy objektum):
public interface ITreeItemProvider
{
public Collection getChildren(Object object);
public Object getParent(Object object);
...
}
Az objektum argumentum kifejezetten a minta támogatása érdekében lett hozzáadva minden egyes elemszolgáltató felülethez. Az Állapotmegőrző esetben ez az objektum mindig ugyanaz, mint az illesztő célja.
Másik felmerülő kérdés, hogy miért nem támogatjuk az "igazi" egyke illesztő mintát - vagyis pontosan egy illesztőt az összes objektumhoz? A válasz egyszerűen annyi, hogy bár ez is egy lehetséges minta (kompatibilis az EMF.Edit keretrendszerrel)[2] , nem javasoljuk, mivel a teljesen dinamikus megvalósítás, bár egyszerű, nehezen testreszabható (sok nehezen követhető instanceof() ellenőrzés nélkül). Az alternatíva a típusokba sorolt elemszolgáltató osztályok használata, melyek öröklési hierarchiája tükrözi a modellét, kényelmes kiindulópontot biztosít a tiszta, objektumorientált nézet kód megvalósításához a modellhez.
Eddig még csak azt mutattuk be, hogy hogyan jelenítheti meg az EMF modelleket tartalom- és címkeszolgáltatók használatával. Az EMF.Edit keretrendszer másik jellemzője, hogy támogatja a modell parancsalapú szerkesztését. A "szerkesztés" a modell egyszerű "írásával" ellentétben a modell nem visszavonható módosítását jelenti.
Az EditingDomain EMF.Edit felület szerkesztési hozzáférést biztosít az EMF modellhez. Másik EMF.Edit megvalósítási osztály, az AdapterFactoryEditingDomain a tartalom- és címkeszolgáltatókhoz hasonlóan működik, és a megvalósítást az elemszolgáltatókra bízza (az ItemProviderAdapterFactory útján):
Mint látható, hozzáférést biztosít egy parancsveremhez is, amelyen keresztül végrehajtható a modell összes módosítása. A szerkesztés tartomány két alapvető szolgáltatást nyújt:
A szerkesztés tartomány leginkább úgy fogható fel, mint a modell módosítási vagy írási szolgáltatója, míg a tartalom- és címkeszolgáltatók a megjelenítési vagy olvasási szolgáltatók. Íme a nagyobb kép:
Vegyünk egy egyszerű példát a modell módosítására.
Tegyük fel, hogy a Company osztály egy-sok viszonyban van a Department osztállyal. A viszony neve: departments (részlegek). Ha el akar távolítani egy részleget a cégből (meg akar valósítani például egy szerkesztői törlés műveletet), akkor egyszerűen megírhatja az alábbi kódot:
Department d = ... Company c = ... c.getDepartments().remove(d);
Habár egyszerű, ez a kód nem csinál mást, mint egyszerűen elvégzi a módosítást.
Ha ehelyett az EMF.Edit eltávolítás parancsát (org.eclipse.emf.edit.command.RemoveCommand) használjuk a részleg eltávolításához, akkor a következőt írjuk:
Department d = ...
Company c = ...
EditingDomain ed = ...
RemoveCommand cmd =
new RemoveCommand(ed, c, CompanyPackage.eINSTANCE.getCompany_Departments(), d);
ed.getCommandStack().execute(cmd);
Ha így töröl egy részleget, annak számos előnye van:
Ha következetesen így használjuk a parancsokat, azzal lehetővé válik az EMF.Edit keretrendszer által biztosítható mindenféle funkció használata
Az előző példában egy egyszerű új hívással hoztunk létre RemoveCommand parancsot. Ez remekül működött, de nem igazán újra felhasználható; a kódtöredék egy nagyon specifikus dolgot csinál, eltávolít egy részleget a cégből. Ha ehelyett olyan újra felhasználható törlés műveletet akarunk írni, ami bármilyen fajta objektum törlésére képes, akkor az EditingDomain használata segíthet ebben.
Az EditingDomain cmd.canExecute() tartalmaz (többek között) egy parancsgyár metódust, a createCommand() metódust, mennyel parancsokat hozhat létre:
public interface EditingDomain
{
...
Command createCommand(Class commandClass, CommandParameter commandParameter);
...
}
Ha ezzel a metódussal akar parancsot létrehozni, akkor először hozzon létre egy CommandParameter objektumot, állítsa be bele a parancsparamétereket, majd hívja meg a create metódust és adja át neki a kívánt parancsosztályt (például RemoveCommand.class) és a paramétereket.
Ahelyett, hogy az ügyfeleknek kellene mindezeken végigmenni, kényelmes statikus create() metódusokat biztosítunk mindegyik parancsosztályban. A statikus create() metódus használatával az alábbiak szerint lehetséges a RemoveCommand létrehozása és futtatása:
Department d = ... EditingDomain ed = ... Command cmd = RemoveCommand.create(ed, d); ed.getCommandStack().execute(cmd);
Mint látható, ez csak kis szintaktikai változás (RemoveCommand.create() a new RemoveCommand helyett). De alapvető különbségek vannak. A korábbi három argumentum helyett csak egy argumentumot adtunk át (az eltávolított objektumot), a szerkesztés tartományt nem számítva. Figyelje meg, hogy ez a kódrész most már tetszőleges fajta objektum eltávolítására használható. Azzal, hogy a szerkesztés tartományra bíztuk a parancs létrehozását, hagyjuk, hogy az töltse ki a hiányzó argumentumokat.
Működésének megértéséhez kövessük végig a RemoveCommand.create() hívást. Mint korábban említettük, a create() metódus csak egy kényelmi metódus, ami a következőhöz hasonlóval bízza meg a szerkesztési tartományt:
public static Command create(EditingDomain domain, Object value)
{
return domain.createCommand(
RemoveCommand.class,
new CommandParameter(null, null, Collections.singleton(value)));
}
Az AdapterFactoryEditingDomain ezután veszi a kérést és átadja egy elemszolgáltatónak a szabvány delegálási minta használatával (ahogyan korábban az AdapterFactorContentProvider a getChildren() metódust bízta meg):
public Command createCommand(Class commandClass, CommandParameter commandParameter)
{
Object owner = ... // a parancshoz tartozó tulajdonos objektum beszerzése
IEditingDomainItemProvider adapter =
(IEditingDomainItemProvider)
adapterFactory.adapt(owner, IEditingDomainItemProvider.class);
return adapter.createCommand(owner, this, commandClass, commandParameter);
}
Megjegyzés: ha megnézi a tényleges createCommand() metódust, akkor láthatja, hogy az valójában némileg bonyolultabb. Ennek oka, hogy úgy tervezték, hogy többek között az objektumok kollekcióinak törlését is kezelje. Azonban amit valójában összességében csinál.
A createCommand() metódus egy tulajdonos objektum használatával fér hozzá a megbízott elemszolgáltatóhoz (vagyis a tulajdonos kerül felhasználásra az adapterFactory.adapt() hívásban). Példánkban a tulajdonos a company objektum (vagyis az eltávolított részleg szülője). A szerkesztési tartomány úgy állapítja meg a tulajdonost, hogy meghívja a getParent() metódust a törölt objektum elemszolgáltatójára.
Mindennek hatására a createCommand() az eltávolítani kívánt objektum szülőjének elemszolgáltatójára kerül meghívásra (vagyis a company c CompanyItemProvider szolgáltatójára az eredeti kódtöredékben). Így a CompanyItemProvider az alábbiak végrehajtásával valósíthatja meg a createCommand() metódust:
public class CompanyItemProvider ...
{
...
public Command createCommand(final Object object, ..., Class commandClass, ...)
{
if (commandClass == RemoveCommand.class)
{
return new RemoveCommand(object,
CompanyPackage.eINSTANCE.getCompany_Departments(),
commandParameter.getCollection());
}
...
}
}
Ez is elvégezné a feladatot, de van jobb módszer is.
Minden elemszolgáltató osztály (ami EMF illesztő is) egy EMF.Edit kényelmi alap osztály, az ItemProviderAdapter kiterjesztése, ami alap createCommand() megvalósítást biztosít (többek között). Úgy valósítja meg a createCommand() metódust az EMF.Edit keretrendszer által biztosított összes szabvány parancshoz, hogy hívásokat intéz az elemszolgáltató alosztályokban megvalósított néhány egyszerű metódushoz (amelyek nem csak erre a célra használatosak). Ez a Sablon metódus tervezési mintára példa.
A RemoveCommandpélda működéséhez mindössze annyi kell, hogy a CompanyItemProvider megvalósítsa az alábbi metódust:
public Collection getChildrenFeatures(Object object)
{
return Collections.singleton(CompanyPackage.eINSTANCE.getCompany_Departments());
}
Mint látható, a metódus visszadja az objektum utódainak hivatkozásához használt egy vagy több jellemzőt (jelen esetben csak a deparments hivatkozást). A metódus meghívása után a createCommand() alapértelmezett megvalósítása kiválasztja, hogy melyik jellemzőt használja (ha a metódus többet is visszaad), és a megfelelővel létrehozza a RemoveCommand elemet.
Ha szerkesztési tartományon keresztül hoz létre parancsokat, annak másik előnye, hogy az lehetővé teszi más alosztályok vagy standard parancsok teljesen más megvalósításának csatlakoztatását, és a standard szerkesztők felveszik őket. Például tételezzük fel, hogy némi extra takarítást akarunk végezni, amikor eltávolítunk egy részleget egy cégből. Ennek legegyszerűbb módja, ha létrehozzuk a RemoveCommand RemoveDepartmentCommand nevű alosztályát az alábbiak szerint:
public class RemoveDepartmentCommand extends RemoveCommand
{
public void execute()
{
super.execute();
// extra teendők végtehajtása ...
}
}
Ez a rész elég egyszerű.
Most ha a szerkesztő a statikus RemoveCommand.create() metódust használja (ami a editingDomain.createCommand() metódust hívja) az új RemoveCommand() helyett, akkor a RemoveCommand könnyedén lecserélhető a RemoveDepartmentCommand-ra úgy, hogy újradefiniáljuk a createCommand() metódust az elemszolgáltatóban az alábbiak szerint:
public class CompanyItemProvider ...
{
...
public Command createCommand(final Object object, ...)
{
if (commandClass == RemoveCommand.class)
{
return new RemoveDepartmentCommand(...);
}
return super.createCommand(...);
}
}
Valójában ha a specializálni kívánt parancs az előre meghatározott parancsok egyike (például a RemoveCommand), akkor a csere még egyszerűbb, mivel a createCommand() alapértelmezett megvalósítása a parancsra jellemző kényelmi metódusokkal végzi az egyes parancsok létrehozását. Például:
public Command createCommand(final Object object, ...
{
...
if (commandClass == RemoveCommand.class)
return createRemoveCommand(...);
else if (commandClass == AddCommand.class)
return createAddCommand(...);
else ...
}
Így a RemoveDepartmentCommand létrehozása egyszerűbben is megoldható úgy, hogy egyszerűen újradefiniáljuk a createRemoveCommand() metódust, és nem magát a createCommand() metódust:
protected Command createRemoveCommand(...)
{
return new RemoveDepartmentCommand(...);
}
Összefoglalva: a szerkesztési tartomány az a pont, ahol beállíthatjuk a parancsok paramétereit, beleértve magát a parancsosztályt, így könnyen vezérelhetjük a modell bármelyik szerkesztési parancsának viselkedését.
Még egy dolog, amiről eddig nem beszéltünk, a változási értesítések kérdése. Hogyan vesszük rá a megjelenítőket a frissítésre, miután egy parancs megváltoztat valamit a modellben? A válasz, hogy ez a standard EMF illesztő értesítés és az EMF.Edit által biztosított megjelenítő frissítési mechanizmus kombinációjának használatával működik.
Összeállításakor az AdapterFactoryContentProvider regisztrálja magát a hozzá tartozó illesztőgyár (ami megvalósítja az org.eclipse.emf.edit.provider.IChangeNotifier felületet) figyelőjeként (org.eclipse.emf.edit.provider.INotifyChangedListener). Az illesztőgyár cserébe átadja magát minden általa létrehozott elemszolgáltatónak, így ez a modell központi változásértesítője. Az AdapterFactoryContentProvider rögzíti (az inputChanged() metódusban), hogy mely megjelenítőnek biztosít tartalmat, így frissíteni tudja a megjelenítőjét, amikor változásértesítést kap.
Az alábbi diagram bemutatja, hogy az EMF modell objektumában bekövetkezett változás (például a cégnév módosítása) hogyan jut el az illesztőgyáron keresztül a modell megjelenítőiig.
Valahányszor megváltozik egy EMF objektum állapota, meghívásra kerül a notifyChanged() metódus az objektum összes illesztőjére, beleértve az elemszolgáltatókat is (ez jelen esetben a CompanyItemProvider). Az elemszolgáltató notifyChanged() metódusa felelős annak megállapításárt, hogy át kell-e adni az egyes eseményértesítéseket a megjelenítőnek, és ha igen, akkor ennek milyen típusú frissítést kell eredményeznie.
Ehhez összeszedi az érdeklődésre számot tartóértesítéseket a ViewerNotification elembe, ami az IViewerNotification felület egyszerű megvalósítása. A felület így terjeszti ki az alapvető Notification felületet:
public interface IViewerNotification extends Notification
{
Object getElement();
boolean isContentRefresh();
boolean isLabelUpdate();
}
Ezek a metódusok meghatározzák, hogy mely elemet kell frissíteni a megjelenítőben, hogy kell-e frissíteni az adott elem alatti tartalmat, valamint hogy kell-e frissíteni az adott elem címkéjét. Mivel az elemszolgáltató határozza meg az objektumok utódait és címkéjét, azt is meg kell határoznia, hogy hogyan frissítse hatékonyan a megjelenítőt.
A CompanyItemProvider osztály notifyChanged() metódusa így néz ki:
public void notifyChanged(Notification notification)
{
...
switch (notification.getFeatureID(Company.class))
{
case CompanyPackage.COMPANY__NAME:
fireNotifyChanged(new ViewerNotification(notification, ..., false, true));
return;
case CompanyPackage.COMPANY__DEPARTMENT:
fireNotifyChanged(new ViewerNotification(notification, ..., true, false));
return;
}
super.notifyChanged(notification);
}
Ebben a megvalósításban a name attribútum módosítása címkefrissítést eredményez, a department hivatkozás módosítása pedig tartalomfrissítést okoz. Az összes többi változásértesítés hatástalan a megjelenítőre.
A fireNotifyChanged() metódus olyan kényelmi metódus az ItemProviderAdapter osztályban (az összes elemszolgáltató illesztő alap osztálya), ami egyszerűen továbbítja az értesítést az illesztőgyárnak[3] . Az illesztőgyár (változásértesítő) továbbítja az értesítést az összes figyelőjének (jelen példában csak a fa megjelenítő tartalomszolgáltatójának). Végül a tartalomszolgáltató frissíti a megjelenítőt az értesítés utasításai szerint.
Az EMF modellek gyakran össze vannak kötve modellközi hivatkozásokkal. Valahányszor olyan alkalmazást kell építenie, ami több EMF modellre is kiterjedő objektumokat jelenít meg vagy szerkeszt, szüksége van egy olyan illesztőgyárra, amely képes adaptálni a két (vagy több) modell uniójából származó objektumokat.
Gyakran már vannak illesztőgyárak az egyedi modellekhez, és mindössze annyi a dolga, hogy összefogja őket. Egy másik EMF.Edit kényelmi osztály, a ComposedAdapterFactory használható erre a célra:
A ComposedAdapterFactory közös felületet biztosít más illesztőgyárakhoz, amelyeket egyszerűen megbíz a megvalósításával.
Összetett illesztőgyár beállításához a következő hasonló kódot kell írnia:
model1AdapterFactory = ... model2AdapterFactory = ... ComposedAdapterFactory myAdapterFactory = new ComposedAdapterFactory(); myAdapterFactory.addAdapterFactory(model1AdapterFactory); myAdapterFActory.addAdapterFActory(model2AdapterFactory); myContentProvider = new AdapterFactoryContentProvider(myAdapterFactory); ...
Megjegyzés: az EMF modell és az EMF.Edit szerkesztő előállításának részletes ismertetője itt található: Ismertető: EMF modell előállítása.
Az EMF modell meghatározásából az EMF.Edit kódelőállító teljes funkcionalitással rendelkező szerkesztőeszközt tud előállítani, amely lehetővé teszi a modell példányainak megjelenítését számos általános megjelenítőben, a modell objektumainak hozzáadását, eltávolítását, kivágását, másolását és beillesztését, valamint az objektumok módosítását egy standard tulajdonságlapon; mindezt tejes visszavonás/újra támogatással.
Az EMF.Edit előállító teljes, működő bedolgozókat állít elő, melyek tartalmazzák a következőket:
Az előállítás után futnia kell a szerkesztőnek. Elindul, de vagy a várakozásnak megfelelően működik, vagy nem (vagyis az előállító alapértelmezett választásai nem feltétlenül jók az adott modellhez). Viszont meglehetősen egyszerű alakítani néhány helyen az előállított kódon, hogy egy egyszerű, működő szerkesztőhöz jusson, ami fut.
Az alábbiakban jobban szemügyre vesszük az előállított osztályok közül az érdekesebbeket.
Az előállított ItemProviderAdapterFactory az előállított AdapterFactory osztály egyszerű alosztálya, amelyet az EMF modell előállításakor kapott.
Megjegyzés: az előállított EMF illesztőgyár úgy hoz létre illesztőket, hogy típusspecifikus create() metódust indít, amelyet az alosztályoknak (mint az ItemProviderAdapterFactory) újra kell definiálniuk. Az EMF illesztőgyár (például az ABCAdapterFactory) egy másik előállított osztály (ABCSwitch) használatával végzi hatékonyan ezt az indítást.
Az állapotmegőrző minta használatakor az illesztőgyár create metódusai egyszerűen visszadnak egy új objektumot, például:
class ABCItemProviderAdapterFactory extends ABCAdapterFactoryImpl
{
...
public Adapter createCompanyAdapter()
{
return new CompanyItemProvider(this);
}
...
}
Ha az egyke mintát használja inkább, akkor az illesztőgyár követi is az egyke példányt és visszaadja azt minden egyes híváskor:
protected DepartmentItemProvider departmentItemProvider;
public Adapter createDepartmentAdapter()
{
if (departmentItemProvider == null)
{
departmentItemProvider = new DepartmentItemProvider(this);
}
return departmentItemProvider;
}
A modell minden egyes osztályához előállításra kerül egy megfelelő elemszolgáltató osztály. Az előállított elemszolgáltatók tartalmazzák az összes olyan felületet, ami szükséges a standard megjelenítők, parancsok és a tulajdonságlap támogatásához:
public class DepartmentItemProvider extends ...
implements
IEditingDomainItemProvider,
IStructuredItemContentProvider,
ITreeItemContentProvider,
IItemLabelProvider,
IItemPropertySource
{
...
}
Ha egy modellosztály gyökér (vagyis nincs kifejezett alaposztálya), akkor az előállított elemszolgáltató az EMF.Edit elemszolgáltató alaposztályból, az ItemProviderAdapter osztályból terjeszti ki:
public class EmployeeItemProvider extends ItemProviderAdapter ...
Ha a modellosztály ehelyett egy alaposztálytól örököl, akkor az előállított elemszolgáltató így lesz az alap elemszolgáltató kiterjesztése:
public class EmployeeItemProvider extends PersonItemProvider ...
Többszörös öröklő osztálynál az előállított elemszolgáltató az első alaposztály elemszolgáltatójának lesz a kiterjesztése (ugyanúgy, mint az egyszeres öröklésnél), és ez valósítja meg a szolgáltató funkciót a többi alaposztályhoz is.
Ha megnézi az előállított elemszolgáltató osztályokat, akkor láthatja, hogy funkciójuk nagy része valójában az elemszolgáltató alaposztályban van megvalósítva. Az előállított elemszolgáltató alosztályok legfontosabb funkciói:
Az előállított Editor és ModelWizard bemutatja, hogy hogyan kell összerakni az összes többi előállított darabot a standard JFace összetevőkkel egy működő szerkesztő előállításához.
A ModelWizard használatával a modell típusába tartozó új erőforrásokat hozhat létre. Ha ehelyett már rendelkezik egy más módon létrehozott erőforrással, akkor importálhatja azt a munkaasztal munkaterületre és elindíthatja rajta a szerkesztőt, a ModelWizard teljes kihagyásával.
[1] A Notifier az alap felület az EMF-ben az olyan objektumoknál, amelyek képesek illesztőket regisztrálni és értesítéseket küldeni nekik. Az EObject terjeszti ki, ami minden modellobjektum alap felülete.
[2] Valójában az EMF illesztőgyárak öröklésirányítottak, ezért választhatja azt, hogy alap illesztőt használ az alosztályok kezeléséhez a modell bármely szintjén; az EObject a szélsőséges eset.
[3] Az illesztőgyáron túl, ami a változásértesítő a megjelenítőkhöz, az ItemProviderAdapter más (közvetlen) figyelőkkel is rendelkezhet, melyeket az ItemProviderAdapter.fireNotifyChanged() metódusban hív meg.