With UI standing for user interface, then by definition, ML::AbstractUI is a UI-concept independent UI-API in Standard ML currently with crossplatform classical windowing system implementations for 'WindowsNT/95/98', 'Linux/X-Windows' and other Unixes. (yes, that's a long definition... :). It is all distributed under the GNU General Public Library Licence (LGPL), which (in short) allows you to build commercial applications with it, as long as you distribute all the sourcecode and copyrightnotices for ML::AbstractUI in unmodified form along with your software.
There is also a GUI builder, called The Construct, available for ML::AbstractUI on this homepage.
It is concievable that ML::AbstractUI will
be merged with "Stuart Croy's" 'VisualML' toolkit.
This document gives a little overview of the current release
and it also outlines my current ideas about how everything should have been :).
User Interfaces in ML::AbstractUI
User Interfaces in ML::AbstractUI is represented as a directed graph. This graph is the client interface for creating and manipulating user interfaces.
Such a UI-graph can be created
and manipulated interactively using The Construct. This
is actually recommended for building user interfaces, since
The Construct will help you to
avoid mixing user interface code with application logic.
The Graph API
When building user interfaces from within an ML program you can use the DGNodes structure. It is defined in \verb'HardcoreProcessing/GraphToolkit/srcSML/DirectedGraph/DGNodes.sml' in the distribution. The most useful functions in this structure has also got convenient infix operators defined in the structure DHShorthand in the file \verb'HardcoreProcessing/GraphToolkit/srcSML/DirectedGraph/DGShorthand.sml'. You must of course call 'open DGShorthand' before you can use these operators.
All necessary nodes are created by using other parts of the implementation. Specifically, you must always begin constructing a user interface by creating a 'MasterNode'. This is done with the call \verb'uiCore.createMasterNode ()'. You create nodes for user interface controls you need by calling \verb'Reg.createA "Window"'. The argument 'Window' here is the name of the user interface control to be created. For a list of the currently available controls, you should start The Construct and look at the buttons in the 'UI Palette' window. Alternatively you might call \verb'Reg.registered ()' to get a string list with the names of available controls.
To create the actual user interface you must first connect the nodes correctly. All nodes created as above has a node connected to it of type 'Children'. To form a valid user interface, the 'Children' node of 'MasterNode' must be connected to any 'Window' controls that you need and the 'Children' nodes of these windows must in turn be connected to the other user interface controls that you want in those windows.
This means that you must be able to obtain the 'Children' node connected from another node. This can be done by a call similar to \verb'val childrenNode = DGNodes.singleConnectedToType fromNode "Children"' or more conveniently using the corresponding shorthand operator 'val childrenNode = fromNode +: "Children"'.
Now, to connect a node to a 'Children' node you can either call \verb'DGNodes.connect childrenNode toNode' or use the shorthand operator 'childrenNode ~+ toNode'. The '~+' operator returns the node given as the left operand so that you may connect multiple nodes to the same node very conveniently like this 'fromNode ~+ node1 ~+ node2 ~+ node3'.
This should allow you to build an entire user interface (however still unconfigured).
When done the actual user interface is constructed when you call
\verb'uiCore.createGUI masterNode'. Afterwards you should
just leave the application running by calling
All the code that you have written for creating the user interface, starting
with the creation of the 'MasterNode' and until the call to \verb'uiCore.runGUI app'
should be put into a function of type 'unit -> unit'. You should pass
this function (let's call it f here) as an argument to 'uiCore.runApp f'.
This will start 'Concurrent ML' correctly on the 'X-Windows/eXene'
Setting up the user interface using protocols
To produce useful user interfaces you will need to configure your graph and setup eventhandlers etc. This is done by using protocols. Each node in the graph created contains a protocol. Nodes like the 'Children' node described above is what will be referred to as a parameter node for a UI-control (or for the 'MasterNode'). Each UI-control has several different parameternodes, such as 'PosX', 'PosY', 'EditableText' etc. The 'Children' node only has a 'Null' protocol with no methods, but most other nodes have a useful protocol. For instance the position and size nodes have a protocol for reading and writing an integer and so on. To find out which parameternodes a given UI-control supports, you should start The Construct, create the UI-control in question and look at the 'Protocol accessor' window. Alternatively you can call the \verb'DGNodes.connected' function to get a list of all the nodes connected to a node.
Protocols are implemented in structures which all begins with 'P' for protocol. Examples are 'PString', 'PInt' and 'PIntDrawable'. These structures contain the methods that constitutes the protocols. Some common protocols are defined in 'HardcoreProcessing/GraphToolkit/srcSML/Protocols/DGPCommon'. Some more special protocols are defined under 'HardcoreProcessing/MLAbstractUI/srcSML/UIProtocols/'.
All methods in all protocols take the protocol itself as the first argument and any additional arguments as appropriate. For example, calling the 'write' method in the protocol 'PString' is done like this: \verb'PString.write protocol "Some string"'.
To obtain the protocol for a node you will have to typecast the node into the correct protocol type. For convenience, all protocols have a structure with the name of the protocol structure prefixed with 'DG' for directed graph. This structure has a method called p for obtaining the protocol from a node. An example of typecasting a node to it's string protocol is \verb'val stringProtocol = DGPString.p someNode'.
A more compact way of writing to a string protocol in a node would be like this:
\verb'PString.write (DGPString.p node) "Some string"', but it may be a
good thing to keep a typecasted protocol around if you are going to call
many methods in the same parameter node. This will most likely be the case
if you are using the drawing protocol or a 'PStrings' protocol
for the lines in a listbox.
The single valued protocols are 'PString', 'PInt', 'PReal' and 'PBool'. They all have a 'write' method and a 'read' method of the appropriate types for reading and writing a single value. They also have the methods 'addChangedNotifier', 'removeChangedNotifier' and 'doNotifyChanged' for adding event handlers for when the value changes. The event handlers generally have type 'DGNodes.t -> TypeOfTheValueChanged -> unit'. The 'addChangedNotifier' returns a notifier ID that you can use later for removing your notifier by calling 'removeChangedNotifier'. The method 'doNotifyChanged' just calls all registered notifiers in the node with the arguments given.
Other useful protocols include the 'PStrings' protocol used in the 'TextLines' node on the listbox. This protocol has methods like 'clear', 'count', 'add', 'remove', 'insertAt' and 'deleteAt'.
Then there are the notification protocols 'PNotify' and
'PPointerButtonNotify' which have the methods 'addNotifier',
'removeNotifier' and 'doNotify' similar to the methods in the
single valued protocols. Lastly there's the 'PIntDrawable'
which is currently an incomplete (but very useful) drawing protocol using
integer coordinates. It is currently not implemented on the
There are a few things which I will try to give a very high priority:
As for further plans, it depends on what I will need and what you
will need. I will be very happy to recieve requests for certain features
that you might need. And of course, bugreports are highly encouraged :)