This page describes everything related to editing data, which is stored in a single file. Some parts are specific for EMF data.
Things are described top/down:
- Selecting the file
The data can be in a fixed file, or can be in any file selected by the user. - Handling the file (EMF specific)
The user should know whether there are changes in the data.
The mechanism provided by EMF is not in line with the normal ‘new’, ‘open’ and ‘save’ operations. Therefore we provide theEMFResource
class. - The actual data editor
The editor can be used to create new objects or edit existing objects.
You can only save the data if it is valid and changed. This means that all fields have to be valid and at least one field has a different value.
This is supported byObjectControl
andObjectControlGroup
.
An editor can be created by extending theObjectEditorTemplate
class - ObjectControlGroup
This groupsObjectControl
s to check on changes and validity of all fields (beingObjectControl
s) in the editor. - ObjectControl
A control providing one or more JavaFx GUI components for editing an Object.
For anObjectControl
the value is always the type of the field that is being edited. Also the object control provides information about whether something is filled in, whether it is valid and whether it is changed.
Selecting the file?
Data in a fixed file
Define the filename via the properties mechanism, which takes care that the filename is e.g. available via SomeRegistry.dataFilename.
Upon starting the application, the first step is to check whether a filename is specified. If not, show a dialog informing the user that he has to specify a filename, offering the option to open a properties editor.
Any file to be selected by the user
Let the user select the file via a FileChooser.
Handling the file
Use an EMFResource
to handle the file.
First create the EMFResource.
Next call load() to read the date from the file. A FileNotFoundException
occurs if the file doesn’t exist yet. Catch this exception and if it occurs show the user a dialog asking whether the file should be created or not.
vacationsResource = new EMFResource<>( VacationsPackage.eINSTANCE, () -> VacationsFactory.eINSTANCE.createVacations(), true); try { vacations = vacationsResource.load(VacationsRegistry.vacationsFileName); } catch (FileNotFoundException e) { LOGGER.severe("File not found: " + e.getMessage()); Alert alert = componentFactory.createYesNoConfirmationDialog( null, translationFormatter.formatText("VacationsWindow.alertVacationsFileNotFound.header", VacationsRegistry.vacationsFileName), TRANSLATIONS.getString("VacationsWindow.alertVacationsFileNotFound.content")); alert.showAndWait().ifPresent(response -> { if (response == ButtonType.YES) { LOGGER.severe("yes, create file"); vacations = vacationsResource.newEObject(); try { vacationsResource.save(VacationsRegistry.vacationsFileName); } catch (IOException e1) { e1.printStackTrace(); } } else { LOGGER.severe("no, don't create file"); } }); }
Comparison of ObjectControl and ObjectEditPanel
Interface ObjectControlStatus extends Observable
getId(), setId()
isOptional()
isFilledIn()
isValid()
isChanged()
getStatusIndicator()
getErrorText()
addListener(), removeListener() inherited from Observable
removeListeners()
ObjectConrol | ObjectEditPane | |
---|---|---|
Interface | ObjectControl extends extends Observable T getValue(), setValue(final T objectValue) getControl() getLabel(), setLabelBaseText(String labelBaseText) setErrorTextSupplier(Supplier errorTextSupplier) getValueAsFormattedText() | ObjectEditPane |
Purpose | Represent a single property object like e.g. a date, name, file. Used in editors. Mostly has one GUI control, but sometimes more, if the value is represented in different way. | A panel to which can be added to an editor, to edit an object with several properties. |
Interface | Yes ObjectControl is the interface This interface is important as it defines generic use of many elements in an editor. | No/Yes There is no interface defined, but it implements the ObjectConrol interface. |
Template | ObjectControlTemplate | ObjectEditPaneTemplate |
Functionality | ||
Structure | initialization All done in the constructors. | initialization Quite some initialization needed. A constructor of a super class cannot call methods in the sub class, as this hasn’t been initialized yet. Therefore a separate method runEditor() has to be called to perform further initialization. |
Differences | 1 internal object value is directly coupled to GUI controls | 1 internal object is only changed on call to updateObjectFromControls |
How to handle the differences:
- setObject sets the object and fills the controls in both cases.
In both cases the object is not changed until getObject (or any similar call) is called.
For the object controls this will mean that an extra internal newValue has to be used.
Object Editor requirements
An object editor should provide the following:
- For each attribute of the object:
- one or more controls to show/edit its value
- a label describing the attribute that is shown/edited by the controls
- an indication of whether a value is mandatory or optional
- for the currently entered value an indication of whether it is invalid/invalid, changed/unchanged
- At editor level:
- a button to start editing a new object (from scratch)
- an API method to start editing an existing object
- inform the user about unsaved changes
When the ‘new’ button is pressed, or upon calling the method to start editing an existing object, it shall be checked whether there are changes which haven’t been saved yet.
If so, the user shall be informed about this and he should be able to cancel the action.
Object Editor strategy
When creating an editor to edit an object you first have to think about the main strategy to use. This section describes an editor for the following strategy:
- The editor can be used to create a new object, or to edit an existing object.
- If the object being edited is null (by default), the editor is in the ‘NEW’ mode. Otherwise it is in ‘EDIT’ mode.
- All information of an object is ‘stored’ in the GUI controls and a number of lists. Together these are referred to as the GUI controls.
When a new object is set (which may be null), the information from the object is stored in the controls. If the object is null, all information is cleared.
Any user changes are only stored in the GUI controls.
Upon ‘add object’ an object is created from the controls, ‘object’ is set to this value and the ‘object ‘ is added to the database.
Upon ‘update’, the ‘object’ is updated from the controls.
Editor overview
The following diagram shows an overview of an example editor implementation; the EventEditor.
For the creation of editors the java Template Method Design Pattern is used. So the EventEditor extends the EditorTemplate class.
The editor is (extends) a JfxStage. Which is the window with a title and an icon.
An editor (like the EventEditor) provides a small interface.
Id
Mainly for debugging it’s important to be able to identify each object. Therefore the interface EditorComponent provides the methods setId() and getId().
These methods are implemented by EditorComponentAbstract.
Customization
Every GUI component is customized via a CustomizationFx object, which by my convention is the first parameter where needed. Therefore on creating an editor, this is shall be the first parameter and it is passed on to all other objects created by the editor.
Handling changes
At each level (from an editor control to an edit panel) changes are handled in the same way. If there is a change, e.g. because the user types text, the new internal value and status are dertermined, and then any InvalidationListeners are notified. Therefore the interface EditorComponent extends Observable. However only invalidation listeners are supported.
Note: we don’t use Properties for the status information, because listeners are then notified when the first property changes. If they then ask for other status information, this is then not yet updated. Also the strategy used here is more efficient; a client is notified once on a new value and status.
Interfaces
Interface EditorComponent
setId() and getId() to set and get a unique id of the object.
EventEditor
The EventEditor is used as an example of creating an editor.
- Extend EventEditorTemplate
- Creating an instance of the editor
The method newInstance(CustomizationFx customization, EventsService eventsService) is used to create a new instance of the editor.
As always, the first parameter is a CustomizationFx for the GUI customization.
The second parameter in this case is an EventService which is used in the constructor to obtain the method for adding a new EventInfo.
The method does two things:- Create an instance of the editor by calling the (private) constructor.
The constructor calls its parents constructor with the customization and the addEvent method of the EventService as arguments. - Call performInitialization() on the EditorTemplate.
- Create an instance of the editor by calling the (private) constructor.
- Implement configureEditor()
Method performInitialization() of the EditorTemplate calls this method. In this method call the methods setAddObjectTexts(), setUpdateObjectTexts() and setNewObjectTexts() to customize the texts of the buttons of the editor. - Implement createObject()
This method is called when the ‘New’ button of the editor is pressed. It just has to create a new instance of the object type being edited. In this case an EventInfo instance. - Implement getMainObjectControlComponent()
This method typically creates and returns an EditPanel, in this case an EventEditPanel.
EventEditPanel
The EventEditPanel is used as an example of creating an edit panel.
- Extend EditPanelTemplate
- Creating an instance of the edit panel
The method newInstance(CustomizationFx customization) is used to create a new instance of the edit panel.
As always, the first parameter is a CustomizationFx for the GUI customization.
The method does two things:- Create an instance of the edit panel by calling the (private) constructor.
The constructor calls its parents constructor with the customization as argument. - Call performInitialization() on the EditPanelTemplate.
- Create an instance of the edit panel by calling the (private) constructor.
ObjectEditorTemplate
This class provides a template for an object editor and it provides some common functionality. So writing an Object Editor starts by extending this class.
The object being edited is typically part of a collection of objects or it is meant to be added to this collection.
Edit mode
The editor is always in one of the modes NEW or EDIT. Upon creation the editor is in NEW mode. In this mode all edit controls are filled with their default values (usually mostly empty). Upon pressing the ‘Add’ button, an object is created, it is filled with the values from the controls, it is added to the collection and the editor switches to the EDIT mode.
Upon calling setObject() with a non null value the editor switches to the EDIT mode, otherwise it switches to the NEW mode.
Layout overview (from top to bottom):
- Title bar, shows the Editor Title.
- The main editor panel.
- A panel with the action buttons.
The items in this panel are right aligned. By default it contains:- Edit status indicator:
- ‘+’ – in NEW mode and the input is valid, so you can add the object
- ‘!’ – At least one control is invalid
- ‘=’ – The controls are valid, but none of the values has changed
- ‘≠’ – in UPDATE mode, the controls are valid and there are changes.
- Cancel button: to quit the editor without making changes. This button is always enabled.
- Add/Update button:
In NEW mode this is an ‘Add’ button, which is only enabled if all controls are valid.
In UPDATE mode this is an ‘Update’ button, which is only enabled if all controls are valid and at least one control has changed. - New button: to start editing a new object. All controls are cleared (or filled with a default value) and the editor switches to NEW mode. This button is always enabled.
- Edit status indicator:
Customization
A CustomizationFx instance is passed to the constructor as the first argument.
As ObjectEditorTemplate
extends JfxStage
, the CustomizationFx
and related ComponentFactoryFx
are available to any class extending ObjectEditorTemplate
(as customization
and componentFactory
).
Configuration
The texts and tooltip texts of the action buttons can be set.
Implementing your editor
Create a private constructor and a factory method named newInstance
. The factory method has to call performInitialization()
.
Example:
public static EventsEditor newInstance(CustomizationFx customization, Events events) {
EventsEditor eventsEditor = new EventsEditor(customization, events);
eventsEditor.performInitialization();
return eventsEditor;
}
private EventsEditor(CustomizationFx customization, Events events) {
super(customization, WINDOW_TITLE);
this.events = events;
}
The method performInitialization()
will (apart from other things) call the following methods, that your editor will normally implement/override:
- configureEditor()
Override this method to make calls to setAddObjectTexts(), setUpdateObjectTexts() and setNewObjectTexts() for setting the texts for the action buttons.
Example:
@Override
protected void configureEditor() {
setAddObjectTexts("Add event", "Add the event to the events");
setUpdateObjectTexts("Update", "Update the current event");
setNewObjectTexts("New", "Clear the controls to start entering new event data");
}
- createControls()
Create the GUI controls and add them to theobjectControlsGroup
, which is provided by the template.
Also here you can perform any other initialization.
Example: - fillControlsWithDefaultValues()
- createEditPanel(rootPane)
- handleChanges()
Examples
The following classes are implementations of ObjectEditorTemplate (in order of increasing complexity):
- goedegep.events.app.guifx.EventsEditor
- goedegep.media.mediadb.trackeditor.guifx.TrackEditor
- goedegep.media.mediadb.albumeditor.guifx.AlbumEditor
This is a complex example, as there are different sub panels for discs. - goedegep.invandprop.app.guifx.InvoiceAndPropertyEditor
This is a tricky example as it is an editor for an Invoice, a Property or both.
ObjectControlGroup
Bla
ObjectControl (Interface goedegep.jfx.objectcontrols.ObjectControl)
This interface defines a set of methods for GUI controls to show and enter object values.
Many GUI controls, like javafx.scene.control.TextField
, represent the value of an object, but the control itself is unaware of the object type. Therefore a set of controls is defined which are object type aware.
For example, if you want to show a LocalDate
as text and allow the user to edit this value:
- To show the
LocalDate
, it has to be formatted to a String.
In this interface this is taken care of by the methodsetValue()
, where the formatting is to be done by the implementing class. - The other way around is even more complicated.
To use the text from theTextField
to set aLocalDate
, you first have to parse the text to create aLocalDate
. In this interface this is taken care of by the methodgetValue()
, where the parsing is to be done by the implementing class.
But there is more, you want to know whether the user has to enter a value, has entered a value and whether the value is valid. This is discussed below.
Optional
An edit window typically has several controls. A ‘save’ button is often only enabled if all required values are filled in. The isOptional()
method can be used to check on this. An implementing class typically provides a constructor with an ‘isOptional
‘ parameter.
Filled in
Indicates that something is entered, it may be valid or not. The isFilledIn()
method can be used to check on this.
Valid
An edit window typically has several controls. A ‘save’ button is often only enabled if all required values are valid.
Valid is defined as:
- Either: the control is optional and nothing is filled in
- Or: What is filled in can be translated to an object of the type of the control.
The isValid() method can be used to check on this.
The Value
The value (of type T) represented by the control can be set via setValue()
and obtained via getValue()
.
Changes
In an editor you usually provide feedback to the user whether he has changed something or not. Therefore this interface provides support for detecting changes. When the value of the control is programmatically changed (via setValue()
) this value is stored as a reference value. Any call to isChanged()
returns true
if the current value of the control differs from this reference value, otherwise it returns false
.
The GUI controls
There is of course always at least one GUI control representing the value, e.g. a TextField
. This control can be obtained via getControl()
.
Besides this there is a status indicator control, which can be obtained via getStatusIndicator()
.
If an ObjectControl
has more GUI controls (like e.g. the ObjectControlFileSelecter
) this control will provide extra methods to obtain these controls.
Textual representation
As the control often already represents the value in a textual form, this text can also be obtained via getValueAsFormattedText().
Textual error description
In case of an error an error message can be obtained via getErrorText()
.
Id
To identify your controls, you can set an Id (just like javafx.scene.Node.setId()
). See the methods getId()
and setId()
.
Listeners
This interface extends the javafx.beans.Observable
interface. It notifies listeners upon any change in the control.
For convenience there is an extra method removeListeners()
to remove all listeners.
How to write an ObjectControl
Of course you can simply implement this interface, but in general it is advised to extend the class ObjectControlTemplate
and follow the documentation within that class.