Poslední aktualizace: 1. června 2004
Tento dokument předpokládá znalost základních konceptů struktury EMF (Eclipse Modeling Framework). Podrobnější informace o EMF najdete v Přehledu struktury Eclipse Modeling Framework (EMF).
Pokud máte model EMF, pro který jste pomocí generátoru EMF vygenerovali programový kód, a teď chcete ke svému modelu a kódu přidat uživatelské rozhraní, můžete si usnadnit práci díky struktuře EMF.Edit.
EMF.Edit je struktura Eclipse, která obsahuje znovupoužitelné generické třídy pro tvorbu editorů modelů EMF. Nabízí:
Tento dokument poskytuje přehled základních konceptů struktury a generátoru EMF.Edit. Další informace naleznete v dokumentaci k jednotlivým třídám struktury, která se podrobněji zabývá jejich specifickými schopnostmi a chováním.
Struktura uživatelských rozhraní Eclipse (JFace) obsahuje sadu znovupoužitelných tříd prohlížečů (například TreeViewer, TableViewer) pro zobrazování strukturovaných modelů. Prohlížeče JFace nevyžadují, aby se objekty prohlížených modelů řídily nějakým konkrétním protokolem (tj. aby implementovaly nějaké konkrétní rozhraní), a pracují s objekty všeho druhu (tj. s libovolnými podtřídami java.lang.Object). Je to možné proto, že prohlížeče mezi objekty modelu nenavigují přímo, ale přistupují k nim prostřednictvím jakéhosi objektu adaptéru, který se nazývá poskytovatel obsahu.
Každá třída prohlížeče používá poskytovatele obsahu, který implementuje specifické poskytovatelské rozhraní. Například TreeViewer používá poskytovatele obsahu, který implementuje následující rozhraní:
public interface ITreeContentProvider ... { public Object[] getChildren(Object object); public Object getParent(Object object); ... }
Základní strukturu zachycuje následující diagram:
Pokud jde o TreeViewer, tento prohlížeč zobrazuje na obrazovce strom objektů (nazývá je položky), přičemž každý z nich s výjimkou vstupního (kořenového) objektu je zajišťován voláním getChildren() na příslušného poskytovatele obsahu.
Ostatní druhy prohlížečů pracují podobně, ale každý z nich vyžaduje, aby poskytovatel obsahu implementoval vlastní specifické rozhraní. Ačkoli se všechna rozhraní prohlížečů navzájem liší, dokáže jich poskytovatel obsahu často implementovat několik najednou, a tak umožní používat jednu třídu poskytovatele obsahu pro více druhů prohlížečů.
Struktura EMF.Edit nabízí generickou implementační třídu poskytovatele obsahu, kterou je možné použít k poskytování obsahu pro modely EMF. Třída AdapterFactoryContentProvider implementuje rozhraní poskytovatelů obsahu delegováním na adaptéry EMF, které vědí, jak prohlížečům poskytovat objekty modelů (položky). Například třída adaptéru EMF používaná pro podporu stromového prohlížeče by implementovala následující rozhraní EMF.Edit:
public interface ITreeItemProvider { public Collection getChildren(Object object); public Object getParent(Object object); ... }
Povšimněte si podobnosti tohoto rozhraní s výše popsaným rozhraním poskytovatele obsahu ITreeContentProvider. AdapterFactoryContentProvider implementuje rozhraní poskytovatele obsahu vyhledáním a následným delegováním rozhraní na adaptér (implementující rozhraní poskytovatele položek) pro příslušnou položku. Terminologický posun od objektů k položkám je záměrný: z pohledu prohlížeče jde o položky, nikoli o objekty.
Obrázek EMF vypadá asi takto:
Poznámka: Třídy ItemProviderAdapterFactory a ItemProvider pro daný model EMF můžete vygenerovat automaticky pomocí generátoru dodávaného se strukturou EMF.Edit. Podrobnější informace později.
AdapterFactoryContentProvider je vytvořen pomocí továrny na adaptéry, která stejně jako všechny ostatní továrny na adaptéry EMF slouží k vytváření a vyhledávání adaptérů specifického typu (v tomto případě ItemProviders). Poskytovatel obsahu uspokojí požadavek jako getChildren() voláním adapt(položka) na továrnu ItemProviderAdapterFactory, která vytvoří nebo vrátí ItemProvider (adaptér) pro uvedenou položku. Potom jednoduše provede delegování na metodu požadovaného rozhraní (v tomto případě ITreeItemProvider) getChildren().
Metoda getChildren() v poskytovateli AdapterFactoryContentProvider vypadá asi takto:
public Object[] getChildren(Object object) { ITreeItemContentProvider adapter = (ITreeItemContentProvider) adapterFactory.adapt(object, ITreeItemContentProvider.class); return adapter.getChildren(object).toArray(); }
Stejný způsob řešení je použit pro všechny metody poskytovatelů obsahu. Jak již bylo uvedeno, nedělá AdapterFactoryContentProvider nic víc, než že deleguje metody poskytovatelů obsahu na specifického poskytovatele položek (adaptér), který ví, jak s požadavkem naložit.
Ve výše uvedené metodě getChildren() je objekt předávaný na adapterFactory.adapt() jednoduchý java.lang.Object (nikoli org.eclipse.emf.ecore.EObject). To je významný rys struktury EMF.Edit. Tato struktura byla pečlivě navržena tak, aby dokázala pracovat i s takovými pohledy na modely EMF, které se mohou od samotného modelu lišit (tj. s pohledy, které potlačují některé objekty nebo obsahují další fiktivní objekty). Aby základní třída struktury pro továrny na adaptéry umožnila toto směšování objektů EMF s jinými objekty, nabízí implementaci metody adapt(), která funguje asi následovně:
public Object adapt(Object object, Object type) { if (object instanceof Notifier) return this.adapt((Notifier)object, type); else return object; }
Pokud se nejedná o Notifier - objekt upozorňující na změny - EMF[1] , vrací objekt sám sebe. Při použití tohoto návrhu může poskytovatel položek, jenž chce přidat do pohledu položky nepatřící do EMF, jednoduše vrátit (například prostřednictvím své metody getChildren() libovolný objekt, který nepatří do EMF. Pokud vrácený objekt implementuje rozhraní poskytovatele položek požadované prohlížečem (například ITreeItemProvider), bude s ním nakládáno stejně jako se všemi ostatními položkami EMF.
Tento aspekt našeho návrhu jasně ukazuje, proč nazýváme třídy poskytovatelů/adaptérů raději poskytovatelé položek než adaptéry. V netriviálních aplikacích bude model pohledu (tedy ten zajišťovaný poskytovatelem obsahu prohlížeče) často kombinací "skutečných" objektů modelu (EMF), jejichž poskytovatelé obsahu budou zároveň adaptéry (EMF), a "fiktivních" objektů, jejichž poskytovatelé obsahu budou samy objekty. Takže zatímco všechny adaptéry jsou zároveň poskytovatelé obsahu, ne vždy to platí i naopak.
V předchozích částech jsme popisovali, jak prohlížeče JFace používají poskytovatele obsahu k získání různých položek obsahu. Podobný přístup se používá i pro získání obrázku a textu štítku pro položky zobrazované prohlížečem. Místo aby prohlížeč žádal o štítky samotné položky, použije jiný objekt nazývaný poskytovatel štítku (podobný poskytovateli obsahu). Aby TreeViewer získal štítky položek ve stromě, provádí například delegování na objekt implementující rozhraní ILabelProvider.
Struktura EMF.Edit používá pro implementaci poskytovatelů štítku pro modely EMF stejné mechanismy jako pro poskytování obsahu. Generická implementační třída poskytovatele štítku AdapterFactoryLabelProvider (fungující úplně stejně jako AdapterFactoryContentProvider) deleguje rozhraní ILabelProvider na poskytovatele položek pro modely (na stejné poskytovatele položek, kteří zajišťují obsah). Rozšířený obrázek vypadá asi takto:
Poskytovatel obsahu a štítku může delegovat (a obvykle deleguje) funkce na stejnou továrnu na adaptéry a v důsledku toho na stejné poskytovatele položek. Stejně jako u poskytovatelů obsahu i zde jsou to poskytovatelé položek, kteří ve skutečnosti odvedou všechnu práci.
Pomocí poskytovatelských tříd EMF.Edit může uživatel následujícím způsobem vytvořit stromový prohlížeč TreeViewer pro model EMF:
myAdapterFactory = ... treeViewer = new TreeViewer(); treeViewer.setContentProvider(new AdapterFactoryContentProvider(myAdapterFactory)); treeViewer.setLabelProvider(new AdapterFactoryLabelProvider(myAdapterFactory));
Tento TreeViewer může být zobrazen například obvyklým způsobem (předepsaným standardy JFace) v okně editoru.
Možná myslíte, že toto vše vypadá dost triviálně, ale to jen proto, že jsme si zatím ukázali pouze, jak provést delegování na někoho jiného (tedy na továrnu na adaptéry). Zatím jsme žádnou z těchto metod neimplementovali, jen jsme je delegovali jinam. EMF.Edit však podporuje implementaci těchto metod a obsahuje generátor programového kódu, který za vás vygeneruje většinu kódu poskytovatele položek a továrny. Než se k tomu však dostaneme, podívejme se nejprve, jak pracují poskytovatelé položek.
Jak ukázal předcházející oddíl, skutečnou práci při poskytování obsahu pro modely EMF odvedou adaptéry poskytovatelů položek přidružené k modelu. Počet a typy adaptérů ItemProvider byly v předchozím diagramu úmyslně ponechány blíže neurčené. To proto, že struktura EMF.Edit podporuje dva různé způsoby řešení adaptérů poskytovatelů položek:
Poskytovatele položek pro daný model je možné implementovat pomocí kteréhokoli z uvedených způsobů nebo pomocí jejich kombinace.
Pokud jde o první způsob řešení, má každý objekt modelu právě jeden adaptér přidělený pouze tomuto objektu. Každý adaptér má ukazatel (nazvaný cíl) mířící na právě jeden objekt, k němuž je přidělen.
Obrázek vypadá asi takto:
Jak je vidět, zdvojnásobuje tento způsob řešení počet objektů v aplikaci, a proto má smysl pouze v aplikacích, kde je třeba, aby instance přenášely další stav. Proto jej nazýváme Stavový vzorek (statefull).
Lepší způsob, který se obejde bez většiny přebytečných objektů, je vzorek Jedináček (singleton). Zde používáme jeden adaptér poskytovatele položek pro všechny položky stejného typu. Vše vypadá asi následovně:
Na tomto obrázku mají objekty jako obvykle ukazatele na adaptéry, ale poskytovatelé položek (kteří jsou sdíleni) nemají ukazatele mířící zpět na objekty. Pokud si vzpomenete na stromové rozhraní poskytovatele položek, kterým jsme se zabývali v oddíle poskytovatelů obsahu, možná si vybavíte, že všechny metody měly navíc argument (objekt):
public interface ITreeItemProvider { public Collection getChildren(Object object); public Object getParent(Object object); ... }
Tento objektový argument byl ke každému rozhraní poskytovatele položek přidán právě proto, aby podporoval tento způsob řešení. V případě Stavového vzorku bude tento objekt vždy stejný jako cíl adaptéru.
Další otázka, kterou si možná kladete, zní: Proč nepodporujeme způsob řešení pomocí "skutečného" vzorku samostatného adaptéru - tedy právě jeden adaptér pro všechny objekty? Odpověď zní jednoduše: Ačkoli se jedná o další možný způsob řešení (kompatibilní se strukturou EMF.Edit)[2] , nedoporučuje se, protože zcela dynamická implementace je sice jednoduchá, ale později se obtížně uživatelsky přizpůsobuje (bez spousty spletitých kontrol instanceof()). Tato alternativa tříd poskytovatelů položek pro jednotlivé typy, jejichž hierarchie dědičnosti odráží hierarchii modelu, nabízí praktický odrazový můstek pro implementaci bezchybného, přehledného objektově orientovaného kódu prohlížení v rámci modelu.
Zatím jsme si pouze ukázali, jak prohlížet modely EMF pomocí poskytovatelů obsahu a štítku. Další vlastností strukturyEMF.Edit je podpora úprav modelu pomocí příkazů. Termín "úpravy" používáme ve smyslu vratných změn, na rozdíl od obyčejné "tvorby" modelu.
Rozhraní EditingDomain (editační doména) struktury EMF.Edit se používá k zajištění přístupu k modelu EMF pro účely úprav. Další implementační třída EMF.Edit AdapterFactoryEditingDomain funguje jako poskytovatelé obsahu a štítku a deleguje svou implementaci na poskytovatele položek (prostřednictvím ItemProviderAdapterFactory):
Jak je vidět, zajišťuje také přístup k zásobníku příkazů, jejichž prostřednictvím budou prováděny veškeré modifikace modelu. Editační doména zajišťuje dvě základní služby:
Asi nejvhodnější je pohlížet na editační doménu jako na poskytovatele úprav či psaní modelu, zatímco poskytovatelé obsahu a štítku jsou poskytovatelem prohlížení nebo čtení. A takto vypadá celý obrázek:
Podívejme se na jednoduchý příklad úpravy modelu.
Předpokládejme, že třída Company (Společnost) má vztah 1:n, pojmenovaný departments (oddělení), k třídě Department (Oddělení). Chceme-li z company (společnosti) odebrat department (oddělení) (abychom implementovali například akci editoru odstranění), můžeme jednoduše napsat následující kód:
Department d = ... Company c = ... c.getDepartments().remove(d);
Tento kód je sice jednoduchý, ale také nedělá nic jiného, než provedení úpravy.
Pokud místo toho použijeme k odebrání department (oddělení) příkaz remove struktury EMF.Edit (org.eclipse.emf.edit.command.RemoveCommand), napíšeme následující:
Department d = ... Company c = ... EditingDomain ed = ... RemoveCommand cmd = new RemoveCommand(ed, c, CompanyPackage.eINSTANCE.getCompany_Departments(), d); ed.getCommandStack().execute(cmd);
Odstranit department (oddělení) tímto způsobem má několik výhod:
Budeme-li tímto způsobem používat příkazy, kdekoli je to možné, budeme moci využívat nejrůznější funkce, které struktura EMF.Edit nabízí.
V předcházejícím příkladě jsme vytvořili příkaz RemoveCommand pomocí jednoduchého volání new. Fungovalo to, ale asi to příliš často nevyužijeme - tato část kódu způsobuje velice specifickou věc, odstraňuje department (oddělení) z company (společnosti). Pokud chceme místo toho napsat například znovupoužitelnou akci odstranění, schopnou odstranit jakýkoli druh objektu, můžeme to si usnadnit práci pomocí domény EditingDomain.
Rozhraní EditingDomain obsahuje (kromě jiného) metodu továrny na příkazy, createCommand(), kterou lze použít k vytváření příkazů místo new:
public interface EditingDomain { ... Command createCommand(Class commandClass, CommandParameter commandParameter); ... }
Chceme-li k vytvoření příkazu použít tuto metodu, budeme muset nejprve vytvořit objekt CommandParameter, nastavit do něj parametry příkazu, potom zavolat metodu create a předat jí požadovanou třídu příkazu (například RemoveCommand.class) a parametry.
Než bychom nutili klienty tím vším procházet, nabízíme pro každou třídu příkazů nadstavbové statické metody create(). Pomocí statické metody create() můžete vytvořit a provést příkaz RemoveCommand následujícím způsobem:
Department d = ... EditingDomain ed = ... Command cmd = RemoveCommand.create(ed, d); ed.getCommandStack().execute(cmd);
Jak sami vidíte, jde jen o drobnou změnu syntaxe (RemoveCommand.create() namísto new RemoveCommand). Přináší však zásadní rozdíly. Kromě editační domény jsme předali pouze jeden argument (konkrétně odebíraný objekt), zatímco dříve to byly argumenty tři. Všimněte si, že tuto část programového kódu je na ní možné použít k odebrání libovolného objektu. Tím, že tvorbu příkazu delegujeme na editační doménu, necháváme ji zároveň, aby doplnila chybějící argumenty.
Abychom pochopili, jak to všechno funguje, budeme pokračovat s voláním metody RemoveCommand.create(). Jak již bylo řečeno, je statická metoda create() pouze nadstavbovou metodou, která deleguje na editační doménu přibližně následující kód:
public static Command create(EditingDomain domain, Object value) { return domain.createCommand( RemoveCommand.class, new CommandParameter(null, null, Collections.singleton(value))); }
Potom AdapterFactoryEditingDomain vezme požadavek a předá ho poskytovateli položek standardním způsobem delegování (jakým již dříve AdapterFactorContentProvider delegoval getChildren()):
public Command createCommand(Class commandClass, CommandParameter commandParameter) { Object owner = ... // získat objekt vlastníka pro daný příkaz IEditingDomainItemProvider adapter = (IEditingDomainItemProvider) adapterFactory.adapt(owner, IEditingDomainItemProvider.class); return adapter.createCommand(owner, this, commandClass, commandParameter); }
Poznámka: Pokud se podíváte na skutečnou metodu createCommand(), uvidíte, že je vlastně mnohem komplikovanější. To proto, že je navržena kromě jiného, aby dokázala odstranit najednou celé kolekce objektů. V podstatě ale nedělá nic víc než toto.
Metoda createCommand() používá objekt vlastníka pro přístup k poskytovateli položek, na nějž deleguje (ve volání adapterFactory.adapt() je tedy použitý vlastník). V našem případě bude vlastníkem objekt company (společnost) (tedy nadřazený objekt odebíraného objektu department - oddělení). Editační doména určí vlastníka voláním getParent() na poskytovatele položek odstraňovaného objektu.
Důsledkem toho všeho je, že je konečně zavolána metoda createCommand() na poskytovatele položek nadřazeného prvku odebíraného objektu (kterým je v původní části kódu CompanyItemProvider pro company - společnost c). CompanyItemProvider by tedy mohl implementovat createCommand() asi následovně:
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()); } ... } }
To by splnilo svůj účel, existuje však i lepší způsob.
Každá třída poskytovatele položek (která je zároveň adaptérem EMF) je rozšířením základní nadstavbové třídy struktury EMF.Edit ItemProviderAdapter, která zajišťuje mimo jiné výchozí implementaci metody createCommand(). Implementuje createCommand() pro všechny standardní příkazy poskytované strukturou EMF.Edit voláním několika jednoduchých metod (používaných i pro jiné účely), které jsou implementovány v podtřídách poskytovatelů položek. Toto je příklad návrhu vzorku Metoda šablon.
Aby náš příklad s RemoveCommand fungoval, musí CompanyItemProvider implementovat následující metodu:
public Collection getChildrenFeatures(Object object) { return Collections.singleton(CompanyPackage.eINSTANCE.getCompany_Departments()); }
Jak je vidět, vrací tato metoda jednu nebo více vlastností (v tomto případě pouze odkaz department - oddělení), které se používají pro odkazy na podřízené prvky objektu. Po volání této metody zjistí standardní implementace metody createCommand(), kterou vlastnost použít (pokud je vráceno více vlastností), a pomocí té správné vytvoří příkaz RemoveCommand.
Další výhodou vytváření příkazů prostřednictvím editační domény je, že nám umožní využít různé podtřídy nebo zcela odlišné implementace standardních příkazů a nechat standardní editory, ať si je přeberou. Předpokládejme například, že chceme provést navíc určité extra čištění (úklid), kdykoli z objektu company (společnost) odebereme department (oddělení). Nejjednodušším způsobem, jak toho dosáhnout, by mohlo být vytvoření podtřídy příkazu RemoveCommand nazvané RemoveDepartmentCommand, asi takto:
public class RemoveDepartmentCommand extends RemoveCommand { public void execute() { super.execute(); // udělat něco dalšího ... } }
Na tom není nic těžkého.
Nyní pokud náš editor používá statickou metodu RemoveCommand.create() (která volá editingDomain.createCommand()) místo new RemoveCommand(), můžeme standardní příkaz RemoveCommand snadno nahradit svým příkazem RemoveDepartmentCommand tak, že v poskytovateli položek potlačíme createCommand(), a to asi následovně:
public class CompanyItemProvider ... { ... public Command createCommand(final Object object, ...) { if (commandClass == RemoveCommand.class) { return new RemoveDepartmentCommand(...); } return super.createCommand(...); } }
Pokud je příkaz, který chceme zúžit, jedním z předdefinovaných příkazů (jako RemoveCommand), je jeho náhrada vlastně ještě jednodušší, protože výchozí implementace metody createCommand() přenechává tvorbu jednotlivých příkazů nadstavbovým metodám pro konkrétní příkazy, asi takto:
public Command createCommand(final Object object, ... { ... if (commandClass == RemoveCommand.class) return createRemoveCommand(...); else if (commandClass == AddCommand.class) return createAddCommand(...); else ... }
Takže jsme mohli svůj příkaz RemoveDepartmentCommand vytvořit jednodušeji pouze potlačením metody createRemoveCommand(), namísto samotné metody createCommand():
protected Command createRemoveCommand(...) { return new RemoveDepartmentCommand(...); }
Abychom vše shrnuli, důležité je, že editační doména je naším záchytným bodem pro úpravu parametrů příkazů včetně samotné třídy příkazu, abych mohli snadno ovládat chování jakéhokoli příkazu úprav ve svém modelu.
Zatím jsme nemluvili o jedné věci, a tou je upozornění na změny. Jak donutíme prohlížeče, aby aktualizovaly zobrazení poté, kdy nějaký příkaz v modelu něco změní? Odpověď zní, že to funguje s použitím kombinace standardního upozornění adaptérů EMF a mechanizmu aktualizace prohlížeče zajišťovaného strukturou EMF.Edit.
Po vytvoření se AdapterFactoryContentProvider zaregistruje jako listener (tj. org.eclipse.emf.edit.provider.INotifyChangedListener) své továrny na adaptéry (která implementuje rozhraní org.eclipse.emf.edit.provider.IChangeNotifier). Továrna na adaptéry se zase předá každému poskytovateli položek, který vytvoří, aby mohla být centrálním objektem upozorňujícím na změny modelu. AdapterFactoryContentProvider také zaznamená (v metodě inputChanged()) prohlížeč, pro který zajišťuje obsah, aby mohl svůj prohlížeč aktualizovat, až obdrží upozornění na změnu.
Následující diagram ukazuje, jak změna objektu modelu EMF (například změna názvu company - společnosti) postupuje přes továrnu na adaptéry zpět do prohlížečů modelu.
Kdykoli nějaký objekt EMF změní svůj stav, volá se metoda notifyChanged() na všechny adaptéry objektu včetně poskytovatelů položek (v tomto případě CompanyItemProvider). Metoda notifyChanged() v poskytovateli položek je odpovědná za určení, zda má být upozornění na událost předáno prohlížeči, a pokud ano, jaký typ aktualizace má způsobit.
Proto zabalí zajímavá upozornění do ViewerNotification, jednoduché implementace rozhraní IViewerNotification. Toto rozhraní rozšiřuje základní rozhraní Notification následujícím způsobem:
public interface IViewerNotification extends Notification { Object getElement(); boolean isContentRefresh(); boolean isLabelUpdate(); }
Tyto metody určují, kterou položku v prohlížeči aktualizovat, zda obnovit obsah pod tímto prvkem a zda aktualizovat štítek daného prvku. Protože poskytovatel položek určuje podřízené prvky a štítek objektu, musí také určit, jak efektivně aktualizovat prohlížeč.
Metoda notifyChanged() ve třídě CompanyItemProvider vypadá asi takto:
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); }
V této implementaci má změna atributu názvu za následek aktualizaci štítku a změna odkazu department (oddělení) způsobí obnovení obsahu. Žádná jiná upozornění na změny nemají na prohlížeč žádný vliv.
Metoda fireNotifyChanged() je nadstavbovou metodou ve třídě ItemProviderAdapter (základní třídě všech adaptérů poskytovatelů položek), která jednoduše předává upozornění továrně na adaptéry[3] . Továrna na adaptéry (upozorňující na změny) odešle upozornění dál všem svým listenerům (v tomto případě pouze poskytovateli obsahu stromového prohlížeče). Nakonec poskytovatel obsahu aktualizuje prohlížeč, jak nařizuje upozornění.
Modely EMF jsou často svázány vzájemnými odkazy mezi modely. Kdykoli potřebujete vytvořit aplikaci pro úpravu nebo zobrazení objektů, která zahrnuje více modelů EMF, potřebujete továrnu na adaptéry schopnou adaptovat sjednocení dvou (či více) modelů.
Často již máte továrny na adaptéry pro jednotlivé modely a potřebujete je jen dát dohromady. Pro tento účel je možné využít další nadstavbovou třídu EMF.Edit ComposedAdapterFactory:
ComposedAdapterFactory se používá, aby zajistila společné rozhraní pro ostatní továrny na adaptéry, na které jednoduše deleguje svou implementaci.
Chcete-li založit složenou továrnu na adaptéry, napíšete přibližně toto:
model1AdapterFactory = ... model2AdapterFactory = ... ComposedAdapterFactory myAdapterFactory = new ComposedAdapterFactory(); myAdapterFactory.addAdapterFactory(model1AdapterFactory); myAdapterFActory.addAdapterFActory(model2AdapterFactory); myContentProvider = new AdapterFactoryContentProvider(myAdapterFactory); ...
Poznámka: Generování modelu EMF a editoru EMF.Edit krok za krokem naleznete na stránce Výukový program: Generování modelu EMF.
Na základě definice modelu EMF dokáže generátor kódu EMF.Edit vyprodukovat plně funkční editační nástroj, který vám umožní prohlížet instance modelu pomocí několika obvyklých prohlížečů a přidávat, odebírat, vyjmout, kopírovat a vkládat objekty modelu nebo je upravovat ve standardním listu vlastností, a to vše s plnou podporou následného zrušení akce a jejího opětovného provedení.
Generátor EMF.Edit vytváří kompletní funkční moduly plug-in, které obsahují:
Jakmile je editor vygenerován, měl by se spustit. Budete jej moci použít, možná ale nebude fungovat tak, jak jste očekávali (předvolené změny provedené generátorem mohou být pro váš model nevhodné). Mělo by však být poměrně snadné vygenerovaný kód na několika místech upravit, abyste rychle zprovoznili základní funkční editor.
Následující oddíl se podrobněji zabývá některými zajímavými generovanými třídami.
Generovaná továrna ItemProviderAdapterFactory je jednoduchou podtřídou generované třídy AdapterFactory, kterou jste získali při generování svého modelu EMF.
Poznámka: Generovaná továrna na adaptéry EMF vytváří předáním metodě create() pro daný typ adaptéry, které musí podtřídy (jako ItemProviderAdapterFactory) potlačit. Továrna na adaptéry EMF (například ABCAdapterFactory) používá pro efektivní implementaci předání jinou generovanou třídu (ABCSwitch).
Při použití Stavového vzorku (statefull) metody create továrny na adaptéry jednoduše vrací nový objekt asi následovně:
class ABCItemProviderAdapterFactory extends ABCAdapterFactoryImpl { ... public Adapter createCompanyAdapter() { return new CompanyItemProvider(this); } ... }
Pokud je místo toho použit vzorek Jedináček (singleton), sleduje továrna na adaptéry navíc samostatnou instanci a vrací ji pro každé volání:
protected DepartmentItemProvider departmentItemProvider; public Adapter createDepartmentAdapter() { if (departmentItemProvider == null) { departmentItemProvider = new DepartmentItemProvider(this); } return departmentItemProvider; }
Pro každou třídu v modelu je vygenerována odpovídající třída poskytovatele položek. Vygenerovaní poskytovatelé položek se podílejí na všech rozhraních, která jsou zapotřebí pro podporu standardních prohlížečů, příkazů a listu vlastností:
public class DepartmentItemProvider extends ... implements IEditingDomainItemProvider, IStructuredItemContentProvider, ITreeItemContentProvider, IItemLabelProvider, IItemPropertySource { ... }
Pokud je třída modelu kořenová (to znamená, že nemá žádnou explicitní základní třídu), bude vygenerovaný poskytovatel položek rozšířením základní třídy poskytovatelů položek EMF.Edit ItemProviderAdapter:
public class EmployeeItemProvider extends ItemProviderAdapter ...
Pokud namísto toho třída modelu dědí od základní třídy, bude generovaný poskytovatel položek rozšířením základního poskytovatele položek asi takto:
public class EmployeeItemProvider extends PersonItemProvider ...
U třídy dědící vlastnosti více tříd bude generovaný poskytovatel položek rozšířením poskytovatele položek první základní třídy (jako v případě dědění od jediné třídy) a bude implementovat funkci poskytovatele pro zbytek základních tříd.
Když se podíváte na vygenerované třídy poskytovatelů položek, všimnete si, že velká část jejich funkcí je ve skutečnosti implementována v základní třídě poskytovatele položek. Nejdůležitější funkce generovaných podtříd poskytovatele položek jsou:
Vygenerovaný Editor a průvodce ModelWizard ukazují, jak dát všechny ostatní generované části dohromady se standardními komponentami JFace, aby vznikl funkční editor.
Průvodce ModelWizard je možné použít k vytváření nových prostředků typu modelu. Pokud už však máte prostředek, který byl vytvořen jiným způsobem, můžete jej naimportovat do pracovního prostoru svého stolního počítače a spustit pro něj editor, a ModelWizard tak zcela obejít.
[1] Notifier (objekt upozorňující na změny) je v EMF základním rozhraním pro objekty, které mohou registrovat adaptéry a posílat jim upozornění. Je rozšiřován objektem EObject, základním rozhraním pro všechny objekty modelu.
[2] Továrny na adaptéry EMF se ve skutečnosti řídí dědičností, můžete se tedy rozhodnout použít k ošetření podtříd na libovolné úrovni modelu základní adaptér, EObject je jen extrémní případ.
[3] Kromě továrny na adaptéry, která funguje pro prohlížeče jako notifier (upozorňující objekt), může mít ItemProviderAdapter i jiné (přímé) listenery, které jsou také volané metodou ItemProviderAdapter.fireNotifyChanged().