Een groot aantal mogelijke acties voor de workbench is onder de loep genomen, maar er is geen aandacht besteed aan de methode run() van een actie. De methoden mogen dan per actie verschillen, door de code te structureren als bewerking die ongedaan kan worden gemaakt, kan de actie worden opgenomen in de uitgevoerde en opnieuw uitvoerbare acties van het platform.
Het platform bevat een framework voor bewerkingen die ongedaan kunnen worden gemaakt in het pakket org.eclipse.core.commands.operations. Als u de code in een run()-methode implementeert om een IUndoableOperation te maken, kunt u de bewerking beschikbaar maken voor ongedaan maken en opnieuw uitvoeren. Het converteren van een actie naar ondersteuning voor bewerkingen is eenvoudig, behalve het toepassen van het gedragspatroon voor ongedaan maken en opnieuw uitvoeren.
Eerst volgt een eenvoudig voorbeeld. We gebruiken de gedelegeerde ViewActionDelegate uit de readme-voorbeeldplugin. Zodra de gedelegeerde wordt opgeroepen, wordt door de actie simpelweg een dialoogvenster gestart dat de uitvoering ervan aangeeft.
public void run(org.eclipse.jface.action.IAction action) { MessageDialog.openInformation(view.getSite().getShell(), MessageUtil.getString("Readme_Editor"), MessageUtil.getString("View_Action_executed")); }De methode run zorgt ervoor dat een bewerking wordt gemaakt waarmee de taken worden uitgevoerd die eerst door de methode zelf werden uitgevoerd. Bovendien wordt de bewerking toegevoegd aan de bewerkingenhistorie zodat deze ongedaan kan worden gemaakt of opnieuw kan worden uitgevoerd.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation( view.getSite().getShell()); ... operationHistory.execute(operation, null, null); }De bewerking omvat het gedragspatroon van de methode run en kan bovendien ongedaan worden gemaakt of opnieuw worden uitgevoerd.
class ReadmeOperation extends AbstractOperation { Shell shell; public ReadmeOperation(Shell shell) { super("Readme Operation"); this.shell = shell; } public IStatus execute(IProgressMonitor monitor, IAdaptable info) { MessageDialog.openInformation(shell, MessageUtil.getString("Readme_Editor"), MessageUtil.getString("View_Action_executed")); return Status.OK_STATUS; } public IStatus undo(IProgressMonitor monitor, IAdaptable info) { MessageDialog.openInformation(shell, MessageUtil.getString("Readme_Editor"), "Viewactie ongedaan maken"); return Status.OK_STATUS; } public IStatus redo(IProgressMonitor monitor, IAdaptable info) { MessageDialog.openInformation(shell, MessageUtil.getString("Readme_Editor"), "Viewactie opnieuw uitvoeren"); return Status.OK_STATUS; } }
Bij eenvoudige acties kan het mogelijk zijn de hoofdtaken naar de bewerkingsklasse te verplaatsen. In dit geval kan het nuttig zijn de vroegere actieklassen samen te voegen in één geparametriseerde actieklasse. De bewerking wordt door de actie uitgevoerd zodra erom wordt gevraagd. Dit is grotendeels een aspect van toepassingsontwerp.
Als een actie door een wizard wordt gestart, wordt de bewerking doorgaans gemaakt als onderdeel van de methode performFinish() van de wizard of de methode finish() van een pagina van de wizard. Het converteren van de finish-methode voor ondersteuning van bewerkingen is vergelijkbaar met het converteren van een run-methode. Door de methode wordt een bewerking gemaakt en uitgevoerd voor verwerking van de taken die eerst regel voor regel werden verwerkt.
De bewerkingenhistorie wordt nu verder uitgelegd. Hieronder volgt nogmaals de code voor het maken van onze voorbeeldbewerking.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation( view.getSite().getShell()); ... operationHistory.execute(operation, null, null); }Wat houdt een bewerkingenhistorie in? Het object dat alle bewerkingen bijhoudt die ongedaan kunnen worden gemaakt, wordt gedefinieerd door IOperationHistory. Bewerkingen worden eerst door de bewerkingenhistorie verwerkt en vervolgens aan de historie toegevoegd. Bewerkingen kunnen ongedaan worden gemaakt of opnieuw worden uitgevoerd met het protocol IOperationHistory.
De bewerkingenhistorie van een toepassing kan op diverse manieren worden opgehaald. De eenvoudigste manier is het gebruik van OperationHistoryFactory.
IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();
Ook kunt u de workbench gebruiken om de bewerkingenhistorie op te halen. Door de workbench worden een standaardbewerkingenhistorie en een toegangsprotocol tot de historie geconfigureerd. In het volgende fragment ziet u hoe u vanuit de workbench toegang krijgt tot de bewerkingenhistorie:
IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench(); IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();Nadat u een bewerkingenhistorie hebt opgehaald, kunt u query's uitvoeren op de lijst met beschikbare bewerkingen voor ongedaan maken of opnieuw uitvoeren, de eerstvolgende bewerking opvragen die ongedaan kan worden gemaakt of opnieuw kan worden uitgevoerd en bepaalde bewerkingen ongedaan maken of opnieuw uitvoeren. Op clients kan een listener worden toegevoegd via IOperationHistoryListener, zodat kan worden gereageerd op wijzigingen in de historie. Een ander protocol stelt clients in staat limieten in te stellen voor de historie- of wijzigingslisteners met betrekking tot wijzigingen aan bepaalde bewerkingen. Voordat het protocol aan bod komt, krijgt u nadere informatie over de context voor ongedaan maken.
Zodra een bewerking wordt gemaakt, wordt er een context voor ongedaan maken aan toegewezen waarmee de gebruikerscontext wordt aangeduid waarin de bewerking oorspronkelijk is uitgevoerd. De context voor ongedaan maken hangt meestal af van de view of de editor waarin de bewerking is uitgevoerd. Zo zijn in een editor gemaakte wijzigingen alleen van toepassing op de editor. Voor de editor moet dan een afzonderlijke context worden gedefinieerd, die wordt toegewezen aan de bewerkingen in de editorhistorie. Zo zijn alle bewerkingen in de editor lokaal en maar gedeeltelijk toegankelijk buiten de editor. In editors of views van een gedeeld model wordt vaak een context voor ongedaan maken gehanteerd die gerelateerd is aan het gemanipuleerde model. Als een algemenere context wordt gebruikt, kunnen de in één view of editor uitgevoerde bewerkingen mogelijk ongedaan worden gemaakt of opnieuw worden uitgevoerd in andere views of editors die volgens hetzelfde model werken.
Het gedragspatroon van contexten voor ongedaan maken is betrekkelijk eenvoudig. Het protocol voor IUndoContext is redelijk minimaal. De belangrijkste functie van een context is het toewijzen van een specifiek kenmerk aan een bewerking, zodat kan worden bepaald welke bewerkingen in welke contexten zijn gemaakt. Zo wordt in de bewerkingenhistorie een overzicht van alle bewerkingen uit de globale historie bijhouden die ongedaan kunnen worden gemaakt of opnieuw kunnen worden uitgevoerd, en kunnen views en editors de historie filteren op bewerkingen die overeenkomen met de context voor ongedaan maken.
U kunt een context maken met de plugin waarmee de bewerkingen worden gemaakt, of waartoe u via API toegang kunt krijgen. De workbench stelt bijvoorbeeld een context beschikbaar die in de hele workbench toegankelijk is. Ongeacht de methode van ophalen moet een context voor ongedaan maken worden toegewezen bij het maken van een bewerking. In het volgende fragment ziet u hoe de gedelegeerde ViewActionDelegate uit de readme-plugin een context aan de bewerkingen kan toewijzen die in de hele workbench toegankelijk is.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation( view.getSite().getShell()); IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench(); IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); IUndoContext undoContext = workbench.getOperationSupport().getUndoContext(); operation.addContext(undoContext); operationHistory.execute(operation, null, null); }
Wat is het nut van een context voor ongedaan maken? Waarom zou u niet voor iedere view en editor een afzonderlijke bewerkingshistorie bijhouden? Als u afzonderlijke bewerkingshistories bijhoudt, wordt ervan uitgegaan dat de histories door de views en editors zelf worden bijgehouden en dat ongedaan maken niet globaal wordt gebruikt in de toepassing. In bepaalde toepassingen kan dit wenselijk zijn en is het een goed idee als elke view en editor zelf een context voor ongedaan maakt creëert. In andere toepassingen is het handiger een globale voorziening voor ongedaan maken te implementeren die op alle bewerkingen van de gebruiker van toepassing is, ongeacht de view of editor waarin de bewerkingen zijn uitgevoerd. In een dergelijk geval moet de workbenchcontext worden toegewezen door alle plugins waarmee bewerkingen aan de historie worden toegevoegd.
In complexere toepassingen is ongedaan maken niet per se lokaal of globaal, maar overlappen de contexten elkaar. U kunt dan meerdere contexten aan een bewerking toewijzen. Een workbenchview van IDE kan bijvoorbeeld het hele werkgebied manipuleren en het werkgebied als context beschouwen. Een editor waarin een bepaalde resource uit het werkgebied is geopend, beschouwt de bewerkingen mogelijk voornamelijk lokaal. Bewerkingen die in de editor zijn uitgevoerd, zijn mogelijk echter van invloed op zowel de resource als het werkgebied. (De JDT-ondersteuning voor herstructureringen is hier een goed voorbeeld van, omdat structurele wijzigingen aan Java-elementen kunnen worden doorgevoerd tijdens het bewerken van een bronbestand.) In dergelijke gevallen is het nuttig beide contexten aan de bewerking toe te wijzen, zodat de bewerking beschikbaar is in zowel de editor zelf als de views waarmee het werkgebied wordt gemanipuleerd.
Nu wordt nader ingegaan op het protocol voor IOperationHistory. In het volgende fragment ziet u hoe een bewerking ongedaan wordt gemaakt met de context myContext:
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); try { IStatus status = operationHistory.undo(myContext, progressMonitor, someInfo); } catch (ExecutionException e) { // uitzondering afhandelen }De historie haalt de laatst uitgevoerde bewerking met de opgegeven context op en maakt deze ongedaan. U kunt een ander protocol gebruiken om de hele historie voor ongedaan maken of opnieuw uitvoeren op te halen voor een context of om de bewerking op te sporen die ongedaan wordt gemaakt of opnieuw wordt uitgevoerd in een bepaalde context. In het volgende fragment wordt het label opgehaald van de bewerking die ongedaan wordt gemaakt in een bepaalde context:
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); String label = history.getUndoOperation(myContext).getLabel();
U kunt de globale context, IOperationHistory.GLOBAL_UNDO_CONTEXT, inzetten om naar de globale historie te verwijzen (dat wil zeggen: alle bewerkingen in de historie, ongeacht de context). In het volgende fragment wordt de globale historie voor ongedaan maken opgehaald:
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); IUndoableOperation [] undoHistory = operationHistory.getUndoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT);
Bij het uitvoeren, ongedaan maken of opnieuw uitvoeren van een bewerking met het protocol van de bewerkingenhistorie, kan een client een voortgangsmonitor en eventuele aanvullende gebruikersinterfacegegevens verstrekken die nodig zijn voor het uitvoeren van de bewerking. Deze gegevens worden aan de bewerking zelf doorgegeven. In het oorspronkelijke voorbeeld is een bewerking geconstrueerd met een shell-parameter waarmee het dialoogvenster kan worden geopend. De shell wordt niet in de bewerking bewaard, maar in plaats daarvan worden parameters doorgegeven aan de methoden voor uitvoeren, ongedaan maken of opnieuw uitvoeren die gebruikersinterfacegegevens bevatten voor het uitvoeren van de bewerking. Deze parameters worden aan de bewerking zelf doorgegeven.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation(); ... operationHistory.execute(operation, null, infoAdapter); }De infoAdapter is een IAdaptable waarmee minimaal de Shell kan worden geleverd voor het starten van dialoogvensters. In de voorbeeldbewerking moet de parameter als volgt worden gebruikt:
public IStatus execute(IProgressMonitor monitor, IAdaptable info) { if (info != null) { Shell shell = (Shell)info.getAdapter(Shell.class); if (shell != null) { MessageDialog.openInformation(shell, MessageUtil.getString("Readme_Editor"), MessageUtil.getString("View_Action_executed")); return Status.OK_STATUS; } } // iets anders doen... }
Het platform bevat standaard herbruikbare actie-afhandelingsroutines die door views en editors kunnen worden geconfigureerd om ondersteuning voor ongedaan maken en opnieuw uitvoeren te bieden voor de specifieke contexten. Bij het maken van een actie-afhandelingsroutine wordt er een context aan toegewezen, zodat de bewerkingenhistorie wordt gefilterd op een manier die voor de view geschikt is. De actie-afhandelingsroutines zorgen voor het bijwerken van de labels voor ongedaan maken en opnieuw uitvoeren om de huidige bewerking aan te duiden, het verschaffen van de juiste voortgangsmonitor en gebruikersinterfacegegevens aan de bewerkingenhistorie en het optioneel opschonen van de historie zodra de huidige bewerking ongeldig is. Er is een actiegroep beschikbaar waarmee de actie-afhandelingsroutines worden gemaakt en worden toegewezen aan de globale acties voor ongedaan maken en opnieuw uitvoeren.
new UndoRedoActionGroup(this.getSite(), undoContext, true);De laatste parameter is booleaans en geeft aan of de histories voor de opgegeven context moeten worden gewist als de bewerking die beschikbaar is voor ongedaan maken of opnieuw uitvoeren niet geldig is. De instelling van deze parameter hangt samen met de opgegeven context en de validatiestrategie die door bewerkingen met die context wordt gehanteerd.
Eerder hebt u al gezien hoe u een context kunt gebruiken om verschillende soorten toepassingsmodellen voor ongedaan maken te implementeren. Doordat een of meer contexten aan een bewerking kunnen worden toegewezen, kunnen toepassingen strategieën voor ongedaan maken implementeren die uitsluitend lokaal zijn per view of editor, uitsluitend globaal zijn voor alle plugins of een combinatie van beide zijn. Een ander ontwerpaspect is of een bewerking te allen tijde ongedaan kan worden gemaakt of opnieuw kan worden uitgevoerd, of dat alleen de meest recente bewerking beschikbaar is voor ongedaan maken of opnieuw uitvoeren (lineair model).
Met IOperationHistory wordt een protocol gedefinieerd waarmee u flexibele modellen kunt maken en de implementaties zelf kunt laten bepalen wat er toegestaan is. In het protocol dat we eerder hebben besproken, wordt ervan uitgegaan dat in een bepaalde context slechts één geïmpliceerde bewerking beschikbaar is voor ongedaan maken of opnieuw uitvoeren. Er is een aanvullend protocol beschikbaar waarmee clients een specifieke bewerking kunnen uitvoeren, ongeacht de positie in de historie. De bewerkingenhistorie kan zodanig worden geconfigureerd dat het model kan worden geïmplementeerd dat geschikt is voor de toepassing. U kunt hiervoor een interface gebruiken waarmee aanvragen voor ongedaan maken of opnieuw uitvoeren kunnen worden goedgekeurd of afgekeurd voordat ze daadwerkelijk worden uitgevoerd.
IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // alleen de meest recente bewerking toestaan. history.addOperationApprover(new LinearUndoEnforcer());
In dit voorbeeld wordt een goedkeuringsmechanisme van het framework, LinearUndoEnforcer, op de historie toegepast, zodat alleen de laatst ongedaan gemaakte of uitgevoerde bewerking wordt toegestaan.
Een ander goedkeuringsmechanisme, LinearUndoViolationUserApprover, herkent ook de laatst ongedaan gemaakte of uitgevoerde bewerking, maar vraagt de gebruiker om toestemming. Dit mechanisme kan in een bepaald workbenchgedeelte worden geïnstalleerd.
IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // de gebruiker om toestemming vragen als een bewerking niet de meest recente bewerking is. IOperationApprover approver = new LinearUndoViolationUserApprover(myUndoContext, myWorkbenchPart); history.addOperationApprover(approver);
Ontwikkelaars van plugins kunnen zelf goedkeuringsmechanismen maken voor het implementeren van specifieke modellen voor ongedaan maken en goedkeuringsstrategieën in toepassingen. In uw plugin kan het wenselijk zijn om goedkeuring te laten vragen voordat de bewerking in eerste instantie wordt uitgevoerd, alsmede om de oorspronkelijke bewerking ongedaan te maken of opnieuw uit te voeren. In dat geval moet het goedkeuringsmechanisme ook IOperationApprover2 implementeren, zodat het uitvoeren van de bewerking wordt goedgekeurd. Als het platform een aanvraag voor een bewerking uit te voeren ontvangt, verzoekt de platformbewerkingshistorie om goedkeuring aan een goedkeuringsmechanisme dat deze interface implementeert.
U hebt fragmenten gezien waarin het workbenchprotocol wordt gebruikt voor het benaderen van de bewerkingenhistorie en de workbenchcontext voor ongedaan maken. Hiervoor hebt u IWorkbenchOperationSupport nodig (beschikbaar vanuit de workbench). Het concept van een context die in de hele workbench toegankelijk is, is tamelijk algemeen. De workbenchtoepassing bepaalt het specifieke bereik dat door de workbenchcontext wordt geïmpliceerd en welke views en editors op de workbenchcontext gebaseerd zijn.
In het geval van de Eclipse IDE-workbench moet de context worden toegewezen aan alle bewerkingen die op het IDE-werkgebied zelf van invloed zijn. Deze context wordt gebruikt door views voor manipuleren van het werkgebied (zoals de Resourcenavigator). De IDE-workbench installeert een adapter in het werkgebied voor IUndoContext waarmee de workbenchcontext voor ongedaan maken wordt opgehaald. Deze modelregistratie maakt het voor plugins die het werkgebied manipuleren mogelijk de context voor ongedaan maken zelfs op te halen als naar geen enkele workbenchklasse wordt verwezen.
// bewerkingenhistorie ophalen IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // de juiste context voor ongedaan maken ophalen voor mijn model IUndoContext workspaceContext = (IUndoContext)ResourcesPlugin.getWorkspace().getAdapter(IUndoContext.class); if (workspaceContext != null) { // bewerking maken en de context eraan toewijzen }Het is voor andere plugins raadzaam dezelfde techniek te hanteren voor het registreren van modelcontexten.