Home » Modeling » EMF » synchronize form editor with emf editing domain
|
Re: synchronize form editor with emf editing domain [message #413673 is a reply to message #413672] |
Tue, 09 October 2007 13:16 |
Eric Rizzo Messages: 3070 Registered: July 2009 |
Senior Member |
|
|
Norbert Schöpke wrote:
> Hello EMF folks
>
> I'm currently developing a form editor for my ECore model. I studied
> the source of the EMF-generated editor, but it only contains
> editors/viewers using the ContentProvider approach. What I need
> however, is a handle to the model to traverse it and build form
> sections with text fields for each property I want the user to edit
> (the structure of the model should not be editable, only the
> attribute values). How can I do this and still be able to leverage
> the editing domain for saving and resource change management? Do I
> need to crawl through the PDE editor sources or is there some easier
> way?
I've worked on two projects that implemented Forms-based editors for EMF
models. What I did was create an abstract subclass of FormEditor and in
its init() method deserilialize the model object from the IEditorInput.
To create an EditingDomain, I first get the AdapterFactoryEditingDomain
from the generated editor class; I really only want the AdapterFactory
from it, because the EditingDomain itself has stuff that is specific to
the generated editor. So I create a new AdapterFactoryEditingDomain with
the AdapterFactory that I got from the generated editor EditingDomain
(that's a mouthful, isn't it:-) and a BasicCommandStack that I create
myself. I wish there was a better way to get an instance of a valid
EditingDomain, so maybe one of the EMF developers can chime in here...
You can attach a CommandStackListener to call editorDirtyStateChanged()
if all model modifications will go through EMF commands, thus getting
editor dirty state management without having to rely on the ManagedForm
mechanism. (don't forget to notify the CommandStack when doSave() is
called).
Hope this helps,
Eric
|
|
| |
Re: synchronize form editor with emf editing domain [message #413688 is a reply to message #413673] |
Tue, 09 October 2007 17:22 |
Steve Blass Messages: 276 Registered: July 2009 |
Senior Member |
|
|
Another approach is to place form pages directly into ViewerPanes in the
generated editor so that all the generated edit/editor tooling is
available to the forms. Doing that a few times led to creation of a
plug-in defining an extension point for form page tabs, a form page tab
interface and a small patch for Editor.javajet so that EMF will generate
editors that can pick up form pages to include as editor tabs through
this extension point at run time. I need to verify permission to share
the basic implementation before I post code.
-
Steve
Eric Rizzo wrote:
> Norbert Schöpke wrote:
>> Hello EMF folks
>>
>> I'm currently developing a form editor for my ECore model. I studied
>> the source of the EMF-generated editor, but it only contains
>> editors/viewers using the ContentProvider approach. What I need
>> however, is a handle to the model to traverse it and build form
>> sections with text fields for each property I want the user to edit
>> (the structure of the model should not be editable, only the
>> attribute values). How can I do this and still be able to leverage
>> the editing domain for saving and resource change management? Do I
>> need to crawl through the PDE editor sources or is there some easier
>> way?
>
> I've worked on two projects that implemented Forms-based editors for EMF
> models. What I did was create an abstract subclass of FormEditor and in
> its init() method deserilialize the model object from the IEditorInput.
> To create an EditingDomain, I first get the AdapterFactoryEditingDomain
> from the generated editor class; I really only want the AdapterFactory
> from it, because the EditingDomain itself has stuff that is specific to
> the generated editor. So I create a new AdapterFactoryEditingDomain with
> the AdapterFactory that I got from the generated editor EditingDomain
> (that's a mouthful, isn't it:-) and a BasicCommandStack that I create
> myself. I wish there was a better way to get an instance of a valid
> EditingDomain, so maybe one of the EMF developers can chime in here...
>
> You can attach a CommandStackListener to call editorDirtyStateChanged()
> if all model modifications will go through EMF commands, thus getting
> editor dirty state management without having to rely on the ManagedForm
> mechanism. (don't forget to notify the CommandStack when doSave() is
> called).
>
> Hope this helps,
> Eric
|
|
| | | | |
Re: synchronize form editor with emf editing domain (solution) [message #413880 is a reply to message #413672] |
Wed, 17 October 2007 13:16 |
Norbert Schöpke Messages: 63 Registered: July 2009 |
Member |
|
|
Ok, here's what I did to create my Eclipse form from my .ecore model.
First of all, I studied the EMF-generated editor for my domain model and re-used all of the resource-management
boilerplate code like saving, handling resource changes and so on. I left out the SelectionProvider stuff since I didn't
need it (having no property sheet and no outline view).
I merged this code with the SimpleFormEditor from the Forms Article (found at
http://www.eclipse.org/articles/Article-Forms/article.html).
Now the real work starts: Form what I understand, one needs the following things to get this working:
ComposedAdapterFactory : this adapts all the ItemProvider stuff from the EMF-generated .edit plugin to suit your needs
AdapterFactoryEditingDomain : this is the read/write interface to your model's resource, it needs the adapter factory
and a command stack
AdapterFactoryItemDelegator : delegates from a model element (item) to the respective item provider from the .edit plugin
AdapterFactoryLabelProvider : gets text for your model elements by asking the item provider
BasicCommandStack : well...a basic command stack with some undo/redo support
Most important is the setup of these components. I put it in a method "initializeEditingDomain" which I call in the form
editor's constructor:
protected void initializeEditingDomain() {
// Create an adapter factory that yields item providers.
// For this to work this plugin needs the dependency on the
// EMF edit plugin generated from the ThetaML .ecore and .genmodel
adapterFactory = new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
adapterFactory
.addAdapterFactory(new ResourceItemProviderAdapterFactory());
adapterFactory
.addAdapterFactory(new ThetamlItemProviderAdapterFactory());
adapterFactory
.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
// command stack that will notify this editor as commands are executed
BasicCommandStack commandStack = new BasicCommandStack();
// Add a listener to set the editor dirty of commands have been executed
commandStack.addCommandStackListener(new CommandStackListener() {
public void commandStackChanged(final EventObject event) {
getContainer().getDisplay().asyncExec(new Runnable() {
public void run() {
editorDirtyStateChanged();
}
});
}
});
// Create the editing domain with our adapterFactory and command stack.
//
editingDomain = new AdapterFactoryEditingDomain(adapterFactory,
commandStack, new HashMap<Resource, Boolean>());
// These provide access to the model items, their property source and label
itemDelegator = new AdapterFactoryItemDelegator(adapterFactory);
labelProvider = new AdapterFactoryLabelProvider(adapterFactory);
}
Next you have to create your model. You have to do this by loading it's resource into the editing domain.
I call the respective code in the "init" method, which gets called shortly after the constructor.
Here I also add in the part listener and resource change listener.
public void init(IEditorSite site, IEditorInput editorInput)
throws PartInitException {
super.init(site, editorInput);
setPartName(editorInput.getName());
site.getPage().addPartListener(partListener);
createModel();
// TODO
ResourcesPlugin.getWorkspace().addResourceChangeListener(res ourceChangeListener,
IResourceChangeEvent.POST_CHANGE);
}
private void createModel() {
URI resourceURI = EditUIUtil.getURI(getEditorInput());
Resource resource = null;
try {
// Load the resource through the editing domain.
resource = editingDomain.getResourceSet().getResource(resourceURI,
true);
} catch (Exception e) {
resource = editingDomain.getResourceSet().getResource(resourceURI,
false);
}
if (resource != null) {
this.thetaml = (ThetaML) resource.getContents().get(0);
}
}
"ThetaML" is just the root of my .ecore model. Now I can use it in the pages to be added to construct widgets for any of
it's properties.
Now, of course, each form will look different, so I'll just sketch out what one has to do the build a widget for an
element in the model.
If you have the element instance (of the element class in the EMF-generated model code), you can instruct the
above-mentioned ItemDelegator to fetch so-called "property descriptors" for this element. Just iterate over this list
and for every IItemPropertyDescriptor "descriptor" you get, you call
EObject feature = (EObject) descriptor.getFeature(your_element_instance);
Now you may introspect this thingie and decide what widget you want to create for it.
The actual value you get by calling
Object value = ((EObject) editor.getItemDelegator().getEditableValue(your_element_inst ance)).eGet(your_feature_instance)
In most cases it will be a String value, so you may just create a text field for it.
Text text = toolkit.createText(client, valueString, SWT.SINGLE);
To update your model when user modifies the value in the GUI, you have to handle this
if (value instanceof String) {
text.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent event) {
for (IItemPropertyDescriptor descriptor2 : variablePropertyDescriptors) {
if (descriptor2.getId(formula).equals(descriptor.getId(formula) )) {
String newValueText = ((Text) event.widget).getText();
descriptor2.setPropertyValue(your_element_instance, newValueText);
}
}
}
});
}
(Note: the descriptor used here - our loop variable - needs to be final)
The line where setPropertyValue is called is the important one - here the magic happens. This call is delegated to the
ItemProvider for the element you used and a SetCommand is created, added to the command stack we setup for our editing
domain, and executed. All editor state management like mark dirty, save, undo/redo is handled for you by EMF.
So thats basically it. Your mileage may vary.
Norbert
Note to the dev's: I had to debug into how the EMF-generated editor and the simpleFormEditor works for several hours to
grasp this and I'm still not sure if there is a better, "canonical" way to do this. The classes and interfaces I visited
on the way were hard to understand for the average eclipse monkey (which I still consider myself to be ;)).
The generated editor is a nice starting point but more useful would be a couple of classes which consitute the basis on
which to build an editor (not necessarily a tree editor). What I mean is I want simple and well-documented functionalty
to establish an editing domain for my model (maybe this could be done with an extension point?) and get/set values for
the elements of my domain.
|
|
| | | | |
Re: synchronize form editor with emf editing domain (solution) [message #413891 is a reply to message #413880] |
Wed, 17 October 2007 17:24 |
Steve Blass Messages: 276 Registered: July 2009 |
Senior Member |
|
|
> addModifyListener(
I've run into notification cascades with ModifyListeners attached to
text widgets in forms because it updates on every character you type.
Switching to FocusListeners and updating the value when the widget loses
focus stopped some of that for me.
-
Steve
Norbert Schöpke wrote:
> Ok, here's what I did to create my Eclipse form from my .ecore model.
>
> First of all, I studied the EMF-generated editor for my domain model and re-used all of the resource-management
> boilerplate code like saving, handling resource changes and so on. I left out the SelectionProvider stuff since I didn't
> need it (having no property sheet and no outline view).
> I merged this code with the SimpleFormEditor from the Forms Article (found at
> http://www.eclipse.org/articles/Article-Forms/article.html).
>
> Now the real work starts: Form what I understand, one needs the following things to get this working:
> ComposedAdapterFactory : this adapts all the ItemProvider stuff from the EMF-generated .edit plugin to suit your needs
> AdapterFactoryEditingDomain : this is the read/write interface to your model's resource, it needs the adapter factory
> and a command stack
> AdapterFactoryItemDelegator : delegates from a model element (item) to the respective item provider from the .edit plugin
> AdapterFactoryLabelProvider : gets text for your model elements by asking the item provider
> BasicCommandStack : well...a basic command stack with some undo/redo support
>
> Most important is the setup of these components. I put it in a method "initializeEditingDomain" which I call in the form
> editor's constructor:
>
> protected void initializeEditingDomain() {
> // Create an adapter factory that yields item providers.
> // For this to work this plugin needs the dependency on the
> // EMF edit plugin generated from the ThetaML .ecore and .genmodel
> adapterFactory = new ComposedAdapterFactory(
> ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
>
> adapterFactory
> .addAdapterFactory(new ResourceItemProviderAdapterFactory());
> adapterFactory
> .addAdapterFactory(new ThetamlItemProviderAdapterFactory());
> adapterFactory
> .addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
>
> // command stack that will notify this editor as commands are executed
> BasicCommandStack commandStack = new BasicCommandStack();
>
> // Add a listener to set the editor dirty of commands have been executed
> commandStack.addCommandStackListener(new CommandStackListener() {
> public void commandStackChanged(final EventObject event) {
> getContainer().getDisplay().asyncExec(new Runnable() {
> public void run() {
> editorDirtyStateChanged();
> }
> });
> }
> });
>
> // Create the editing domain with our adapterFactory and command stack.
> //
> editingDomain = new AdapterFactoryEditingDomain(adapterFactory,
> commandStack, new HashMap<Resource, Boolean>());
>
> // These provide access to the model items, their property source and label
> itemDelegator = new AdapterFactoryItemDelegator(adapterFactory);
> labelProvider = new AdapterFactoryLabelProvider(adapterFactory);
> }
>
>
> Next you have to create your model. You have to do this by loading it's resource into the editing domain.
> I call the respective code in the "init" method, which gets called shortly after the constructor.
> Here I also add in the part listener and resource change listener.
>
> public void init(IEditorSite site, IEditorInput editorInput)
> throws PartInitException {
> super.init(site, editorInput);
> setPartName(editorInput.getName());
> site.getPage().addPartListener(partListener);
> createModel();
> // TODO
> ResourcesPlugin.getWorkspace().addResourceChangeListener(res ourceChangeListener,
> IResourceChangeEvent.POST_CHANGE);
> }
>
>
> private void createModel() {
>
> URI resourceURI = EditUIUtil.getURI(getEditorInput());
> Resource resource = null;
> try {
> // Load the resource through the editing domain.
> resource = editingDomain.getResourceSet().getResource(resourceURI,
> true);
> } catch (Exception e) {
> resource = editingDomain.getResourceSet().getResource(resourceURI,
> false);
> }
>
> if (resource != null) {
> this.thetaml = (ThetaML) resource.getContents().get(0);
> }
> }
>
> "ThetaML" is just the root of my .ecore model. Now I can use it in the pages to be added to construct widgets for any of
> it's properties.
>
>
> Now, of course, each form will look different, so I'll just sketch out what one has to do the build a widget for an
> element in the model.
>
> If you have the element instance (of the element class in the EMF-generated model code), you can instruct the
> above-mentioned ItemDelegator to fetch so-called "property descriptors" for this element. Just iterate over this list
> and for every IItemPropertyDescriptor "descriptor" you get, you call
>
> EObject feature = (EObject) descriptor.getFeature(your_element_instance);
>
> Now you may introspect this thingie and decide what widget you want to create for it.
> The actual value you get by calling
>
> Object value = ((EObject) editor.getItemDelegator().getEditableValue(your_element_inst ance)).eGet(your_feature_instance)
>
> In most cases it will be a String value, so you may just create a text field for it.
>
> Text text = toolkit.createText(client, valueString, SWT.SINGLE);
>
> To update your model when user modifies the value in the GUI, you have to handle this
>
> if (value instanceof String) {
> text.addModifyListener(new ModifyListener() {
> public void modifyText(ModifyEvent event) {
> for (IItemPropertyDescriptor descriptor2 : variablePropertyDescriptors) {
> if (descriptor2.getId(formula).equals(descriptor.getId(formula) )) {
> String newValueText = ((Text) event.widget).getText();
> descriptor2.setPropertyValue(your_element_instance, newValueText);
> }
> }
> }
> });
> }
>
>
> (Note: the descriptor used here - our loop variable - needs to be final)
>
> The line where setPropertyValue is called is the important one - here the magic happens. This call is delegated to the
> ItemProvider for the element you used and a SetCommand is created, added to the command stack we setup for our editing
> domain, and executed. All editor state management like mark dirty, save, undo/redo is handled for you by EMF.
>
> So thats basically it. Your mileage may vary.
> Norbert
>
> Note to the dev's: I had to debug into how the EMF-generated editor and the simpleFormEditor works for several hours to
> grasp this and I'm still not sure if there is a better, "canonical" way to do this. The classes and interfaces I visited
> on the way were hard to understand for the average eclipse monkey (which I still consider myself to be ;)).
> The generated editor is a nice starting point but more useful would be a couple of classes which consitute the basis on
> which to build an editor (not necessarily a tree editor). What I mean is I want simple and well-documented functionalty
> to establish an editing domain for my model (maybe this could be done with an extension point?) and get/set values for
> the elements of my domain.
|
|
| | | | | | |
Re: synchronize form editor with emf editing domain (solution) [message #413959 is a reply to message #413957] |
Thu, 18 October 2007 15:04 |
Eric Rizzo Messages: 3070 Registered: July 2009 |
Senior Member |
|
|
Norbert Schöpke wrote:
> ok, I just tried this:
> add a modifyListener to the text control. In the modifyText method you just set the editor dirty and immediately remove
> the listener, so it's only called once. The FocusListener does the work of updating the model.
>
> But, as strange as it may seem, the editor only gets dirty if I set a breakpoint to look if the listener is activated.
> If I remove the breakpoint, the debugger doesn't jump in, but there appears no asterisk indicating the editor got marked
> dirty.
> Any idea what might cause this strange behaviour?
> I'm attaching the code I just described for reference:
> thx
> Norbert
>
>
> ModifyListener dirtyNotifyer = new ModifyListener() {
> public void modifyText(ModifyEvent event) {
> editor.editorDirtyStateChanged();
> // remove listener immediately after first modification event
> ((Text) event.widget).removeModifyListener(this);
> }
> };
> FocusListener fl = new FocusAdapter() {
> public void focusLost(FocusEvent event) {
> for (IItemPropertyDescriptor pd2 : variablePropertyDescriptors) {
> if (pd2.getId(formula).equals(pd.getId(formula))) {
> String newValueText = ((Text) event.widget).getText();
> String origValueText = (String) ((EObject) editor.getItemDelegator()
> .getEditableValue(formula)).eGet(attribute);
> if (!origValueText.equals(newValueText)) {
> pd2.setPropertyValue(formula, newValueText);
> }
> }
> }
> }
> };
> text.addFocusListener(fl);
> text.addModifyListener(dirtyNotifyer);
Wouldn't you have to re-add the dirtyNotifier on every focusGained
event? Otherwise it would be removed upon first modify event and never
re-added. In fact, it seems best to always add dirtyListener in
focusGained and remove it in focusLost, right?
Another thing: how does all of this affect undo/redo, which when using
CommandStack to track dirty state handles everything gracefully. I'd be
surprised if this does no interfere somehow.
Eric
|
|
| | | | |
Goto Forum:
Current Time: Fri Sep 20 14:16:28 GMT 2024
Powered by FUDForum. Page generated in 0.04656 seconds
|