System for creating and displaying forms in a graphical user interface.
This invention relates to a system for creating and displaying forms in a graphical user interface (commonly abbreviated to "GUI") .
Many client applications that operate in a GUI environment require a flexible front-end display of information for the user to browse and edit. Traditional applications provide a fixed user interface that, by aiming to suit every user's needs, fails to suit many users well. Systems have been developed in the past to provide flexible user interfaces via encoding window or form information in a text document format. Contemporary systems generally use XML.
Existing systems typically take one of two approaches. One is to encode all or most aspects of the GUI programming syntax in the document for maximum flexibility. This approach is essentially equivalent to writing the fully-featured GUI design language (for instance, Java Swing) in XML. The alternative is to provide a simplistic syntax, with is easier to learn and understand, but has sharply limited constraints.
Systems that follow the first approach suffer from the weakness that the user needs to be a programmer, or have a similar level of skill to a programmer, to write a screen effectively. In addition, the user typically loses syntax, type and other checks that are performed by the compiler during development with "real" program code. Systems that follow the second approach generally suffer from severe limitations and inflexibility, limiting their applicability to simplistic user interfaces.
The view of the present inventors is that neither of these solutions are satisfactory. Most third-party toolkits deal with the goal of expressing Swing fully as XML. However, program code is easier to manage and debug as Java source than XML . XML is a good mark-up language, but it is not well-suited for use as a programming language.
An aim of this invention is to provide a flexible but simple syntax that abstracts from Swing
programming, but allows the full power of Swing to be used when desired.
From a first aspect, this invention provides a method of creating form on a GUI display comprising:
• creating a representation of the content of the form in a language that is independent of the technical specifications of the GUI display system; and
• invoking one or more renderers upon the representation, the renderers being configured to interface with the specific GUI display system and thereby generate a display that is described by the representation.
One effect of this method is to abstract the representation that is created by a developer from the GUI system that will eventually display the form. The developer can therefore concentrate upon creation of the content and function of the from, rather than being driven by the capabilities of a particular GUI display system. It also creates code that is inherently system-independent.
In typical embodiments of the invention, the representation is encoded within an XML document. This is convenient in many respects. An XML document has a hierarchical structure that has good correspondence with a typical GUI form, and it can
be serialised to a text document in a standard, platform-independent manner.
Very advantageously, the structure of the representation is hierarchical such that a first form (as displayed on the GUI) can be incorporated into a second form by incorporation of a representation of the first form (or components of a form) into a representation of the second form. This allows easy re-use of form components. incorporation is achieved by interpretation of the content of the representation. For example, this may take the form of an "include" tag within the description.
From another aspect, this embodiment provides a system for rendering a form on a graphical user interface, the system comprising:
• a parser for receiving as input a representation of a form to be displayed; and
• a plurality of renderers that can be invoked by the parser to render elements of the form on the display.
A common problem encountered in implementation of GUI forms is ensuring a common and consistent layout of graphical objects as the form is rendered across multiple platforms. GUI systems typically all implement a layout manager that has specific characteristics and properties. Therefore, it is
particularly advantageous for embodiments of this invention to implement a layout manager to extract this aspect of design of the form from the GUI system upon which the form will be rendered.
It is particularly advantageous to permit layouts to be nested. That is to say, amongst the objects that can be managed by a layout manager are further layouts. This helps a developer to produce a form of arbitrary complexity without having to resort to creating highly complex layouts.
Advantageously, a method embodying the invention further includes creation of a resource property file that contains, amongst other things, text that will be displayed on the form. Separation of the design of the form from its text content significantly simplifies internationalisation and localisation of the form. Therefore, the method may include a step of creating multiple resource property files containing text suitable for multiple locales.
In a typical embodiment of the invention, the renderers serve to wrap calls to the API of a graphical user interface. For instance, the API may be the Java Swing API. The renderers are typically capable of rendering a plurality of standard controls. These need not have a direct correspondence with controls provided by the underlying API. The renderers may also be capable of rendering widgets that are composed from a
plurality of components. For each of the above described controls and widgets there may be a corresponding entry within the representation. For instance, where the representation is an XML document, each control or widget may be specified in a single tag.
An embodiment of the invention will now be described in detail, by way of example, and with reference to the accompanying drawings, in which:
Figure 1 illustrates diagrammatically components of a display system embodying the invention and a form displayed by the embodiment.
A system embodying the invention includes two main component groups: a parser group 10 and a rendering group 12. The function of the parser is to read a representation of a form and to decide which visual objects must be rendered in order to display the form. The output of the parser is a document object model that is . a description of all graphical components of the form. The parser is independent of the particular GUI system upon which the form is to be displayed. The rendering group comprises one or more renderers, each of which is capable of displaying a graphical object on the target GUI system. The renderers are specific to the GUI system upon which the form will be rendered and must, therefore, be written specifically for each such system. To render a form, the parser analyses
the representation and makes appropriate calls to the renderers .
Listing 1 presents a form definition of a product form in a description language of a first embodiment of the invention. The elements present in this listing will be described. It is important to note that elements of the syntax used in the description language do not necessarily correspond directly to GUI objects.
Central to any definition of a form in a GUI is the means by which components can be laid out on a form; a matter typically handled by a layout manager. This embodiment implements a layout manager that is based upon the Java Swing layout manager, although other embodiments may use a different underlying layout manager or implement a layout manager that is entirely system-independent . The layout manager is called FormLayout. This layout manager is based upon the TableLayout class that is provided by the developers of the Swing system for use in association with the Java Swing libraries, and has a syntax that is consistent with that class. In use, FormLayout is normally the only layout normally needed and only its usage will be described; FormLayout can be used to express most other layout managers but with a simple consistent API. However, individual controls can use other layout managers if required.
FormLayout implements a cellular grid with justification rules. Cells can have a fixed size, a
percentage of screen size, a preferred size, or fill the available size. A component may occupy more than one cell and can be justified within a cell or across multiple cells. Layouts can be nested such that any cell can contain a layout.
The grid is specified in two sets of strings that define rows and columns . Each set of strings contains one of four size types: an integer to specify a fixed number of pixels, a decimal number between 0 and 1 to specify the percentage of total width or height of the grid, the character ' p' to specify the maximum preferred width or height of all components in the row or column, or the character 'f to fill with a proportionate fraction of the remaining space. Space within the grid is allocated in the order described above: first, the fixed pixel sizes are allocated, then the percentage spaces, then the preferred sizes, and finally what is left is shared out amongst the fill columns, which may be of zero size.
Various layouts are defined in listing 1 in <layout ... /> tags .
Components are placed within the grid using <control ... /> tags. A tag can specify the x and y co-ordinate of a cell within the grid having the form <conrol ... constrain"=l, 0"/> . Where multiple fields are to be specified, co-ordinates xi, yi of the top-left cell and x2, y2 of the bottom right co-ordinate are given, as in <control ... constrain="0, 0, 1, 0"/>, specifies a horizontal
band of two cells and <control... constrain"=l, 0, 2, l"/> specifies a 2x2 block of cells. A single character specifies justification 'r' and ' 1' for horizontal right and left justification, 't' and 'b' for vertical top and bottom alignment, and ' c' and 'f for centre and fill. For example, <control ... constrain="l, 0, r, c"/>. Various examples are presented in Listing 1, with the final example being as complex as is normally required. Where an arrangement of greater complexity is required, it is preferable to make use of nested layouts.
All text to be displayed on the form may be contained within a resource property file, a fragment of which is shown in Listing 2. All form descriptions use the key with a prefix (to identify which resource file to use) , this being specified in a tag of the following form: <group name="pdt.categories" constrain="0, 2, 1, 2">. This tag instructs the renderers to look in the "pdt" resource bundle and use the value of the "categories" key. Alternatively, text may simply be quoted within the form definition.
As has been discussed, a particular advantage of this method is that it can be used to promote code re-use by incorporation of one description file into another. This can be done manually through use of a text editor, or it can be done by the file include mechanism. The <include .. /> tag is used to implement this latter mechanism, examples of use of this tag being shown in the first part of Listing 1. This mechanism can be used to give consistent content to
multiple forms in an application, for example, by defining standard menus, stylesheets, toolbars and so forth in included files. These elements can then be changed consistently throughout the application by changing only the included file.
Listing 3 shows the content of an included file that defines menus and toolbars. Swing Actions are a decoupling mechanism built into Swing that allows menus and toolbars to operate from common shared underlying code.
It is important to note that this file does not describe all aspects of the menus. Within this embodiment, the first step is to define a Swing action with a name and optional icon description, and optional additional entries to specify menu item or toolbar button mnemonics and accelerator keys, plus "tooltip" information. The resource files are scanned for a standard suffix to pick up these details .
Central to any form system are the controls that will appear on the form with which a user can interact. A problem with many existing APIs, including to some minds Swing, is that the controls have been developed piecemeal over some years. Components have a conflicting multitude of APIs that have grown up over time. Existing components also suffer from a poor use of Java interfaces - too few interfaces and too many methods on each one. Rather than try to write a forms framework that deals with
-li¬
the resulting mess, this invention takes the approach of wrapping each Swing component behind a higher-level control interface. This makes the resulting form building code much simpler as it can depend on a standard API. Therefore, to create a new control for use within the embodiment of the invention, after creating a Java Swing component, a wrapper is then provided.
This embodiment of the invention provides a forms toolkit that includes a standard control interface and set of standard controls (widgets) . The aim is to create a standard, simplified interface that hides the underlying implementation in a manner that allows arbitrary complexity.
Controls are referenced by the Form XML, with a tag such as :
<control name="pdt .props__name_c" type="Nu berControl" format="####0.00" .../>
Standard controls are automatically imported, otherwise the XML code must use the fully qualified class name.
Controls can be given an arbitrarily long list of name-value pairs to process for their configuration. These names and values can have any desired structure. Controls may also contain nested tag information (i.e. data models) as well as attributes .
It is important to note that writing a good reusable Swing control is not easy . The developer must be very familiar with Swing ' s design goals and philosophy. The control must be separated out into its model , view and controller constituent parts . The view parts must conform correctly to the Swing "look and feel" standards . The invention adds the need to then wrap this control in Keystone ' s interfaces . However , the benefit of a good re- usable control is that it can be used reliably throughout applications to give a consistent user experience .
This embodiment provides a set of standard controls to minimise the need for a developer to create new controls . A minimal set of controls is as follows :
TextControl - text input field BooleanControl - checkbox ComboControl - combobox DateControl - date picker IconControl - Icon browser LabelControl - simple, uneditable label NumberControl - formatted text input field for numeric values TextAreaControl - text area input field TableControl - tabular /grid control DualListControl - select list from list control
A more comprehensive embodiment might also provide more complex controls such as tree views , file selection dialogs and so forth .
Listing 1
<?xml version="1.0"?>
<formxml xmlns:xsi="http:// ww.w3.org/2001/XM Schema-instance" xsi:noNamespaceSchemaLocation="aobxml-l .1.xsd"> <sheet name="main_sheet" icon="uk/co/idbs/product/icons/product.png" title="pdt.product_title" appClass="uk.co.idbs .product. Product"> <te plate persister="u . co . idbs .product . CategoryProductTemplatePersister" /> <navigator> <model type="uk . co . idbs . keystone . gui .model . BasicNavigatorSortModel"> <property name="name"/> </model> </navigator> <query> <column property="picture" label="Picture"> <editorRenderer type="u . co . idbs . gui . renderers . BloblmageCellRenderer"/> </column> <column property="discountDay" label="DiscountDay"> <editorRenderer type="uk . co . idbs .product . rendering . DayOfWeekColumn" /> </column> </query> <include node="main_sheet" childrenOnly="true" xml="uk/co/idbs/product/forms/StandardProduct.form.xml"/> <panel bounds="centre_bounds"> <layout cols="£" ro s="f, f, f"> <tabbedpane constrain="0, 0, 0, 0"> <tab title="pdt.props"> <layout cols="f" ro s="f"> < ! — Main panel and layout — > <group constrain="0, 0 , 0 , 0" title="pdt .props"> <layout name="properties_layout" cols="5, p, 12 , f, 5" ro s="f, 5, f, 5, f, 5 , f, 5"> <bounds name="a" constrain="l, 0, 1, c"/> <bounds name="b" constrain="3 , 0, f, c" /> <bounds name="c" constrain="l, 2, 1, c"/> <bounds name="d" constrain="3 , 2 , 1, c" /> <bounds name="e" constrain="l, , 1, c"/> <bounds name="f" constrain="3 , 4 , f, c" /> <bounds name="g" constrain="l, 6, 1, c"/> <bounds name="h" constrain="3, 6, 1, c" /> </layout> </group> </layout> </tab> <tab title="pdt .moreprops"> <layout cols="f" rows="f"> <group constrain="0, 0, 0, 0" ιrops"> <layout cols="5, p, 12, f, 5" rows= ="f , 5 , f, <bounds name="i" constrain="l, 0, 1, c"/> <bounds name="j" constrain="3, 0, f, c"/> <bounds name="k" constrain="l, 2, 1, c"/> <bounds name="l" constrain="3, 2, f, C"/> <bounds name="m" constrain="l, 4, 1, c"/> <bounds name="n" constrain="3, 4, 1, c"/> <bounds name="o" constrain="l, 6, 1, c'7> <bounds name="p" constrain="3, 6, 1, c"/> </layout> </group> </layout> </tab> </tabbedpane> <group constrain="0, 1, 0, 1" title="pdt. reviews "> <layout cols="5, f, 5" rows="f, 5"> <bounds name="q" constrain="l, 0, f, f"/>
</layout> </group> <group constram="0, 2, 0, 2" tιtle="pdt. categories'^ <layout cols="5, f, 5" rows="f, 5"> <bounds name="r' constram="l, 0, f, f"/> </layout> </group> </layout> </panel> < ' — Product Properties — > <control bounds="a" label="pdt .props_name" type="u . co . idbs . gui . components . LabelControl " /> <control bounds="b" property="name" type="uk . co .ιdbs . gui . components . pmnable . TextPinnableControl" wιdth="20" valιdator="uk . co . idbs . gui . validator . NotEmptyStrmgValidator " /> <control bounds="c" label="pdt .props_pπce" type="uk . co . idbs . gui . components . LabelControl" /> <control bounds="d" property="pπce" wιdth="10" type="uk . co . idbs . gui . components . pmnable . DoublePmnableControl" f ormat="####0.00" mmValue="0.0" valιdator="uk . co . idbs . keystone . gui . validator . BusmessRuleValιdator"/> <control bounds="e" label="pdt .props_bestb4 " type="uk. co . idbs . gui . components . LabelControl "/> - control bounds="f" property="bestBefore" type="uk . co . idbs . gui . components . pmnable . DatePmnableControl " wιdth=" 10 " valιdator="uk . co . idbs . keystone . gui . validator . BusxnessRuleValidator "/> <control bounds="g" label="pdt .props_stockc" type="u . co . idbs . gui . components . LabelControl" /> <control bounds="h " property="stockCount" type="uk . co . idbs . gui . components .pmnable . Integer PmnableControl" f ormat="#" mιnValue="0" wιdth="10" valιdator="uk. co . idbs . eystone . gui .validator .BusmessRuleValidato "/> <' — More Product Properties —> <control bounds=" " label="pdt .props_group" type="uk. co .idbs . gui . components . abelControl"/> <control bounds="
3" property="group" type="ιdbs .impl. keystone. exe .workbench. swing.ObpectComboControl" valιdator="uk.co.ιdbs .keystone . gui. alidato .BusmessRuleValidato "> eystone.gui .model .QueryRowModel" .product .Group"/>
•ccontrol bounds="k" label="pdt.props_dιscount" type="uk.co.ιdbs . gui. components .LabelControl"/> <control bounds="l" property="dιscountDay" type="uk. co. idbs .gu . components .pmnable . ComboP nableControl" renderer="uk. co. idbs .product . rendering . DayOfWeekColumn"> <model type="uk.co.ιdbs . eystone .gui.model .Enu erationRowModel" property="dιscountDay" class="uk. co. idbs .product . DayOfWeek"/> </control> •control bounds="rα" label="pdt.props_sale"
<ι — Product Reviews —> <control bounds="q" property="revιews" rowHeιght="20" type="uk.co .idbs . gui . components .TableControl"> <model type="u . co. idbs . keystone . gui .model.BasιcTableDataModel"> <rows type="u . co.idbs . keystone . gui .model .CollectionRowModel" property="revιews" appClass="uk. co. idbs.product .Revιew"> <wrapper parent="product" appClass="uk.co. idbs.product.Revιew"/> </rows>
<columns type="uk . co. ιdbs . keystone . gui . model . AppOb
3ectColumnModel" appClass="uk . co . idbs .product . evιew"> <column property="revιewer" label="pdt . revιews_revιewer"> <control property="revιewer" type="u . co . idbs . gui . components . TextControl" valιdator="uk . co . idbs . gui . validator . NotEmptyStrmgValidator" /> </column> ■ccolumn property=" score" label="pdt . revιews_ratmg"> <edιtorRenderer type="uk . co . idbs . roduct . rendering . Star atmgColumn" /> </column> •ccolumn property="buyAgam" label="pdt . revιews_buyagam"> <control property="buyAgam" type="uk . co . idbs . gui . components . BooleanControl " /> </column> <column property="comment" label="pdt. revιews_comment"> <control property="comment" type="u . co . idbs . gui . components . TextControl" valιdator="uk . co . idbs . gui .validato . NotEmptyStr gValidator"/> </column> </columns> </model> </control> <ι — Product Categories —> <control bounds="r" property="categoπes" type="uk . co . idbs . gui . components . DualListControl"> •cmodel type="uk. co . idbs . keystone . gui .model . BasicDualL stModel"> <column label="pdt .categorιes_source" name="source"> <renderer type="uk . co . idbs . product . rendering . CategoryCategoryProductColumn" /> </column> <column label="pdt .categorιes_target" name="target"> <renderer type="uk . co . idbs . product . rendering. CategoryCategoryProductColumn"/> </column> <source type="uk . co . idbs . keystone . gui .model . QueryRowModel" appClass="uk.co.ιdbs .product .Category"> <wrapper parent="ιd. roduct" source="ιd. category" appClass="uk . co . idbs . product . CategoryProduct" /> </source> <target type="uk.co.ιdbs .keystone. gui. odel. CollectionRowModel" property="categories"
</control> </sheet> </formxml>
Listing 2
# Prefix key to select on resources_prefιx = pdt
# Product form product_tιtle = Product Form categories = Categories
Listing 3
<?xml version="1.0"?>
<fonnxml xmlns:xsi="http://www. w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="aobxml- 1.1.xsd" name="standard_actions"> <action name="close_window" action="uk.co.idbs.keystone.gui.action.CloseWindowAction"/> <action name="new_record" label="aob.new_record" short="aob.new_record_short" icon="idbs/impl/keystone/exe/workbench/swing/newicons/new_24x24.gif mnem="aob.new_record_mnem" accel- 'aob.new_record_acc" action="uk.co.idbs.keystone.gui.action.New ecordAction"/> <action name="open" label="aob.open_record" short="aob.open_record_short" icon="idbs/impl/keystone/exe workbench swing/newicons/open_24x24.gif' mnem="aob.open_record_mnem" accel=''aob.open_record_acc" action- 'uk.co.idbs.keystone.gui.action.OpenAction'7> <action name="close" label="aob.close_record" short="aob.close_record_short" icon="idbs/impl keystone/exe/workbench swing/newicons/close_24x24.gif' mnem="aob.close_record_mnem" accel="aob.close_record_acc" action="uk.co.idbs.keystone.gui.action.CloseAction"/> <action name="copy_record" label="aob.copy_record" shorl^"aob.copy_record_short" icon="idbs/impl/keystone/exe workbench swing/newicons/copy_record_24x24.gif mnem="aob.copy_record_mnem" accel="aob.copy_record_acc" action="uk.co.idbs.keystone.gui.action.CopyAction"/> <action name="save" label="aob.save_record" short^"aob.save_record_short" icon="idbs/impl/keystone/exe/workbench/swing/newicons/save_24x24.gif' mnem="aob.save_record_mnem" accel="aob.save_record_acc" action- 'uk.co.idbs.keystone.gui.action.SaveAction"/> <action name="save_wb" label="aob.save_wb" short="aob.save_wb_short" icon="idbs/impl keystone/exe/workbench/swing/icons/workbench.png" mnem- 'aob.save_wb_mnem" accel="aob.save_wb_acc" action="uk.co.idbs.keystone.gui.action.SaveToWorkbenchAction"/> <action name="delete" label="aob.delete_record" short="aob.delete_record_short" action="uk.co.idbs.keystone.gui.action.DeleteAction"/> <action name="perfonn_query" label="aob.perform_query" short="aob.perform_query_short" icon=''idbs/impl/keystone/exe/workbench/swing newicons/search_24x24.gif' rnnem=''aob.perforrn_query_mnem" accel="aob.perform_query_acc" action="uk.co.idbs.keystone.gui.action.PerformQueryAction"/> <action name="undo" label="aob.undo" short^"aob.undo_short" icon="idbs/impl keystone/exe/workbench swing/newicons/undo_24x24.gif mnem="aob.undo_mnem" accel="aob.undo_acc"/> <action name="cut" label- 'aob.cut" short="aob.cut_short" icon- 'idbs/impl/keystone/exe/workbench/swing/newicons/cut -4x24.gif' mnem="aob.cut_mnem" accel="aob.cut_acc"/> <action name="copy" label="aob.copy" short^"aob.copy_short" icon="idbs/impl/keystone/exe/workbench/swing/newicons/copy_24x24.gif' mnem="aob.copy_mnem" accel="aob.copy_acc"/> <action name="paste" label="aob.paste" short="aob.paste_short" icon="idbs/impl/keystone/exe/workbench swing/newicons/paste_24x24.gif' mnem="aob.paste_mnem" accel="aob.paste_acc"/> <action name="first" label="aob.first_record" short="aob.first_record_short" icon=''idbs/impl/keystone/exe/workbench swing/newicons/first_24x24.gif' mnem=''aob.first_record_mnem'' action="uk.co.idbs.keystone.gui.action.FirstNavigationAction"/> <action name="previous" label="aob.ρrevious_record" short^"aob.previous_record_short" icon="idbs/impl keystone/exe/workbench/swing/newicons/next_24x24.gif mnem="aob.previous_record_mnem" action="uk.co.idbs.keystone.gui.action.PreviousNavigationAction"/> <action name="next" label="aob.next_record" shortr="aob.next_record_short" icon="idbs/imρl/keystone/exe workbench swing/newicons/previous_24x24.gif mnem="aob.next_record_mnem" action="uk.co.idbs.keystone.gui.action.NextNavigationAction"/> <action name="last" label="aob.last_record" short="aob.last_record_short" icon="idbs/impl keystone/exe/workbench/swing/newicons/last_24x24.gif'mnem="aob.last_record_mnem" action="uk.co.idbs.keystone.gui.action.LastNavigationAction"/> <action name="view_toolbar" label="aob.view_toolbar" short="aob.view_toolbar_short" action="uk.co.idbs.keystone.gui.action.ViewComponentAction" component="standard_toolbar"/> <action name="view_auditbar" label="aob.view_auditbar" shoι ="aob.view_auditbar_short" action="uk.co.idbs.keystone.gui.action.ViewComponentAction" component="auditbar" > <action name="view_statusbar" label="aob.view_statusbar" short?="aob.view_statusbar_short" action- 'uk.co.idbs.keystone.gui.action.ViewComponentAction'' component="statusbar"/> <action name="view_popin" label="aob.view_popin" short="aob.view_popin_short" action="uk.co.idbs.keystone.gui.action.PopAction"/> <!— Hidden actions — > <action name="title_change" action- 'uk.co.idbs.keystone.gui.action.TitleChangeAction'7> <action name="nav_show" action="uk.co.idbs.keystone.gui.action.NavigationDisplayAction"/> <action name="closed_edit" action="uk.co.idbs.keystone.gui.action.ClosedEditAction"/> <action name="event_manager" action="uk.co.idbs.keystone.gui.action.EventManagerAction"/> <action na e="audit_change" action="uk.co.idbs.keystone.gui.action.AuditBarAction"/> <action name="status_change" action="uk.co.idbs.keystone.gui.action.StatusBaιAction"/>
</formxml>