DUIM
DUIM is the Dylan User Interface Manager, a high-level platform-independent library for interactive Graphic User Interfaces. The backend implementations of DUIM are implemented in terms of a native toolkit. The end-user programmer uses the high level cross platform part, the implementor for the particular native GUI implements the low level back end part. So DUIM provides an flexible and dynamic abstraction layer that works with native (and popular, in the case of Unix) GUI toolkits.
DUIM was implemented by
[?]Andy Armstrong and
[?]Scott McKay for
Harlequin/
Functional Objects. (
Rob Myers and
Peter Housel ported DUIM to
Gwydion Dylan.) Conceptually, DUIM is descended from
[?]CLiki:CLIM, which
[?]Scott McKay also helped design and implement.
Further Information via
DUIM FAQs
Corner Articles related to DUIM
We plan a serie of articles inspired by
The Standard Widget Toolkit (SWT) articles of the
Eclipse Corner Articles.
Currently in work are the following articles
DUIM Snippets
- Snippets are minimal stand-alone programs that demonstrate specific techniques or functionality. Often a small example is the easiest way to understand how to use a particular feature.
General UI Literatur
- User Interface Engineering provide links to material external related to the wiki. Consult it for background information and get inspired by the material to code improvements.
External Links
DUIM FAQs
Basics
What are the basics of DUIM?
DUIM is '''D'''ylan's graphical '''U'''ser '''I'''nterface '''M'''anagement framework. Central to DUIM are Sheets. A '''sheet''' defines in an abnstract way a portion of a display device to which the programm can draw.
DUIM defines generic functions for drawing graphics and text into sheets. These generic functions are generally mapped directly into the host window system's primitives.
DUIM builds up a sheet behavior framework whose components focus on these behavior:
- sheet-hierarchies-related
- input-related (acting as event listener )
- output-related (acting as fix-content presenter)
- layout-related (acting as layout configurator)
- to be repaintable (acting as updatable-content presenter)
- to present ui elments (acting as gadgets which are visual present to the user)
Additional component focus on
- general topics, e.g. color-support and attribute bundling
- text provide specific support, e.g. font support
- graphics provide support for different graphic models: figure-based or path-based.
Where can I read a short intro?
Recommended introductions are
What documentations are accessible?
DUIM is documented in:
What are the roots of the concepts beyond DUIM?
The Dylan UI Management DUIM is an updated and Dylan specific implementation of CLIM (Common Lisp Interface Manager). CLIM which was originally developed by International Lisp Associates, Symbolics, and Xerox PARC.To graps the concepts of DUIM it might be helpful to first look at some CLIM articles.
Here are a list of CLIM related links
Other UI projects that influenced the design of CLIM and DUIM are: Chalk,
First practical steps
You can create a "skeleton" DUIM program by using __make-dylan-app --duim__ in
Gwydion Dylan, or choosing the appropriate options in the __New Project__ dialog of
Open Dylan.
Overview over the code resources
What you need to understand
What are differences between sheets, panes, mediums and frames?
- Sheets and panes are actually the exact same thing. The term is used "pane" to mean "a sheet contained in some sort of layout".
- A medium is just a drawing state object, like an X GContext or a Windows DC. Not all sheets have mediums, just ones you can draw on. You should only need to use a medium when you want to draw something.
- A frame is a bit of a mongrel object, in that it is used both as the repository for application state (by subclassing it and adding slots and methods), and as the repository of the applications UI (by defining panes and layouts for those panes).
What notification does DUIM provide about frames?
There is a set of events that get sent to the frames when a
frame is created or destroyed:
- <frame-created-event>, when a frame's UI gets "attached" to the display, but before it gets layed out.
- <frame-layed-out-event>, when a frame gets initially "layed out".
- <frame-mapped-event>, whenever a frame gets mapped (made visible) on the screen.
- <frame-unmapped-event>, whenever a frame gets unmapped (made invisible) on the screen.
- <frame-exit-event> gets sent when 'frame-exit' gets called. The default handler for this exits the frame, but you can write your own 'handle-event' methods that queries the user, e.g.
- <frame-exited-event> gets sent when the frame has really exited, when the UI gets "shut down".
- <frame-destroyed-event>, the very last event a frame sees when it gets shut down, after all its DUIM-managed state is gone
For example, you might very well want to write a handler on <frame-exited-event> that releases all your resources.
How to ...
How to handle events and and overriding them?
Work in Process
How to write an event loop independently of the main event loop?
see
How to take a couple of widgets, group them together, add some behavior, and make a new widget?
see
Thread: Question about DUIM/widget on
comp.lang.dylan
To-Do: Lookup several coding examples in the Dylan repository
How to parameterize the content of a frame?
- see the ''DUIM: define frame question'' in the info-dylan-archive:
How to populate a tree-control class with children?
- see thread '' <tree-node> in DUIM'' in the info-dylan-archive:
How to deal with images, pixmaps, patterns, etc
To-Do
How to extending DUIM
What are the first steps to extend DUIM to handle new controls, etc. ?
- It is not certainly not recommend writing the full control in DUIM unless you have a (future) need for portability.
- The best first step would be to add a new DUIM control for a concrete operation system, for example win32. To do this,
- you'll need to use the win32-duim module that is (re)exported from DUIM. (Originally the module is exported from the win32-duim library, but it became reexported via the DUIM library itself to make it simpler to do this sort of thing).
- You'll also need to use the duim-internals module, which is the module intended to be used when extending DUIM.
Use the duim-xxx-internal modules
Scott McKay once wrote:
All the duim-xxx-internals modules in DUIM are for people who want to extend
DUIM, rather than be "mere clients". These interfaces are more open to change
than the non-internals modules, but DUIM is pretty mature and stable.
Don't feel too guilty about using duim-xxx-internals.
Introduction to extend Gadgets
see subsection [[#How does a DUIM gadget get tied to an actual Win32 control?]] of section [[#Gadgets]].
Sheets
What is the relationship between sheets and mirrors?
Sheet are a abstract concept, which corresponds to most systems' notation of a window. The concrete window system window or "drawable"" associated with a sheet is called a ''mirror''.
Any children of the sheet hierarchy without a direct mirror will simply redirect related operations to its parent. This process goes on until an ancestors is meet which has a direct mirror.
A so-called ''mirrored sheet'' can have a direct mirror, but does not require to have one. So its allowed to ''mirrored sheets'' to have a direct mirror, but not for one, that is not mirrored.
This clarifies other mirror related questions:
- what mirrors do
- when you need to use them to get the window handle and
- what the difference between using them and using the sheet itself is.
What kind of behavior can sheets have?
- ''Sheet-hierarchies'' are maintained via the functions of the '''sheet-genealogy''' protocol which support maintaining a sheet’s parent and children.
- ''Input-sheets''' are detail in the next subsection
- ''Output-sheets'' can do output, in which case they have a '''sheet-medium''' slot.
- ''Repaintable sheets''have methods for '''handle-repaint'''.
- ''Control sheets'' act as gadgets. Gadget are detailed in a separate section
- ''Layout sheets'' participate in controlling the layout, e.g. '''pane''' is such a sheet.
Input-sheets act as event listener
Input-sheets are sheets that receive input, in which case they have:
- a '''sheet-event-queue''' slot.
- methods for '''handle-event'''.
An event is an object representing some sort of event. The most important
events are subclasses of device-event, for example, <button-press-event>
and <key-press-event>.
What attributes are known by all-sheets?
All sheets have the following attributes:
- '''sheet-region''', expressed in the sheet’s own coordinate system.
- '''sheet-transform''', which maps the sheet’s coordinate system to the
coordinate system of its parent.
- '''sheet-parent''', which is #f if the sheet has no parent.
- '''sheet-mapped?''' flag, which tells if the sheet is visible on a display, ignoring issues of occluding windows.
The sheet’s transform is an instance of a concrete subclass of <transform>.
The sheet’s region can be an instance of any concrete subclass of <region>, but
is usually represented by the region class <bounding-box>.
Gadgets
What is a Gadget?
Gadgets are sheet objects that make up an interface: the menus, buttons, sliders, checklists, and tool/command bars. Gadget classes support three protocols, value, items, and activate.
- Objects that support ''the value protocol'' respond to a request to get-value, a value-changed-callback, and have a setter function associated with them.
- Objects that support ''the items protocol'' respond to gadget-items and have a gadget setter function associated with them.
- Objects that support ''the activate protocol'' have an activation callback
associated with them.
For example:
- A list box implements the two gadget protocols: the value protocol and the activate protocol. Changing the selection in a list box invokes the value-changed-callback, and double clicking invokes the activate-callback.
Overview of the gadget classes
Here is an overview of the gadget classes, arranged in order from most frequently used to less used and purely decorative. For each object class the protocols it supports and the Windows control it maps into internally are given.
( In the column V, I and A an X stands for protocol implemented.)
DUIM Gadget | V | I | A | Windows control
=======================================================
button | X | - | - | button
check-button | X | - | - | check box
radio-button | X | - | - | radio button
push-button | - | - | X | push button
Boxed (grouped) gadgets
button-box | X | X | - | ---
check-box | X | X | - | ---
radio-box | X | X | - | ---
push-box | X | X | - | ---
Menu-related gadgets
menu | - | X | - | menu
menu-button | X | - | - | menu item
check-menu-button | X | - | - | menu item
radio-menu-button | X | - | - | menu radio item
push-menu-button | X | - | - | menu item
menu-box | X | X | X | menu
check-menu-box | X | X | X | menu
radio-menu-box | X | X | X | menu
push-menu-box | X | X | X | menu
Text-related gadgets
text-field | X | - | X | single line edit control
text-editor | X | - | X | multi-line edit control
password-field | X | - | X | single line edit control
with ES-PASSWORD style
Page Gadgets
property-page | - | - | - | property page
tab-control-page | - | - | - | ---
Value Gadgets for collections
option-box | X | X | - | if readonly drow downlist
else combo box
spin-box | X | X | - | ---
list-box | X | X | X | list box
list-control | X | X | X | list view control
table-control | X | X | X | list view control
with LVS-REPORT style
tree-control | X | X | X | tree view control
Value Gadgets for ranges
slider | X | - | - | TRACKBAR
scroll-bar | X | - | - | ---
scrolling macro | - | - | - | ---
( = scroller )
progress-control | - | - | - | progress bar
Other value gadgets
status-bar | X | - | - | status window
tab-control | - | - | - | tab control
Gadget based on composite panes that can have multiple children
menu-bar | X | X | - | menu
tool-bar | X | X | - | toolbar
Gadget based on composite panes that can only have one child
viewport | - | - | - | ---
Static Gadget
border | - | - | - | ---
group box | - | - | - | ---
( = labelled broder to group gadgets )
label | - | - | - | static control
separator | - | - | - | ---
How does a DUIM gadget get tied to an actual Win32 control?
In a nutshell:
- DUIM uses the Abstract Factory Pattern to tied a DUIM gadget to a Win32 control.
When you have created for your own Gadget via applying the Abstract Factory Pattern you should be able to bring up a simple application displaying your Win32 window. From there you need to override a few other methods to obtain a correct behaviour
- Write a do-compose-space method for your gadget to ensure a correct sizing of your gagdet
- Write a handle-notify method which specifies how to respond to a backend control notification, which informs the parent window of the control (DUIM) that an event has occurred in the control or that the control requires some kind of information.
- A value gadget will also require to write a method of which handles a value changing.
You'll need to include additional libraries and modules. Most notably, library win32-duim and modules DUIM-internals and win32-duim. Hope that provides a bit of a start.
Details are given in the following subsections:
\list-the-subsections()
How ties a DUIM gadget via the Abstract Factory Pattern to a concrete backend control?
When DUIM wants to create the back end representation of the gadget, it
- calls the method class-for-make-pane to find the actual type of the gadget to create and
- uses the actual type of gadget to dispatch to the right make-gadget-control method. The make-gadget-control is responsible to generate a Gadget object on the backend system.
This is how Dylan generally implements the
Abstract Factory Pattern in DUIM to tie to concrete backend controls.
- For a class-for-make-pane method the arguments specifies a concrete factory and an abstract product, based on the arguments the method determine a concrete product.
- A make-gadget-control method based on the concrete products returns a window handler to DUIM.
Example: How to tie the abstract date-selection-field to the concrete Win32 drop down calendar control?
The code example shows how to make the
Win32 Date-and-Time-Picker (DTP) control available from DUIM. The DTP displays the standard Win32 date time edit field with a drop down calendar available for selecting months, days, years, etc.
The basic usage allows you to do:
make(<date-selection-field>,
value-changed-callback: on-date-changed,
value: make(<date>, year: 1990, month: 12, day: 9));
The solution ties the Win32 control via the Abstract Factory Pattern to DUIM.
- Step 1 of using the Abstract Factory Pattern: Specify a concrete factory and an abstract product to determine the concrete product.
In the date example, <date-selection-field> is an abstract class. The concrete implementation for this class is obtained by DUIM by calling:
define sealed method class-for-make-pane(
framem :: <win32-frame-manager>,
class == <date-selection-field>, #key)
=> (class :: <class>, options :: false-or(<sequence>))
values(<win32-date-selection-field>, #f);
end method class-for-make-pane;
This call to tells DUIM to create an instance of <win32-date-selection-field> if the user asks for a <date-selection-field> using the <win32-frame-manager> (the default frame manager).
To create the Win32 window object the method 'make-gadget-control' is called by DUIM. The implementation used to make the Win32 drop down calendar control available from DUIM is:
define sealed method make-gadget-control(
gadget :: <win32-date-selection-field>,
parent :: <HWND>,
options,
#key x, y, width, height)
=> (handle :: <HWND>)
let ext-style =
if(gadget.border-type == #"none")
0
else
$WS-EX-CLIENTEDGE
end;
let handle :: <HWND> =
CreateWindowEx(
ext-style,
"SysDateTimePick32",
"",
%logior(options,
$WS-GROUP,
$WS-TABSTOP,
if(gadget.date-gadget-no-date-valid?)
$DTS-SHOWNONE
else
0
end if),
x, y, width, height,
parent,
$null-hMenu,
application-instance-handle(),
$NULL-VOID);
// Set the initial date value
set-control-date(handle, gadget.gadget-value);
handle;
end method make-gadget-control;
The make-gadget-control delegates the product creation to the standard Win32
CreateWindowEx method. The Win32 CreateWindowEx returns to DUIM the window handle of the created window object.
How to obtain a correct behavior of a new gadget?
In the section before we have shown how to tie a new abstract gadget of DUIM to a specifc backend control. When the abstract gadget ties to a specific backend control you are to bring up a simple application displaying your Win32 window.
From there you need to override a few methods to obtain a correct behaviour of new gadget. To ensure a correct behavior of your new gadget, you will need to write
- a do-compose-space method for your gadget. This ensure a correct sizing of your gagdet.
define sealed method do-compose-space
(gadget :: <win32-rich-text-editor>, #key width, height)
=> (space-req :: <space-requirement>)
ignore(height);
[... work out the space requirements...]
make(<space-requirement>, ...)
end method do-compose-space;
- a handle-notify method for your gadget. Such a method specifies how to respond to a backend control notification, which informs the parent window of the control (DUIM) that an event has occurred in the control or that the control requires some kind of information. (see also this MSDN technical note about the WM_NOTIFY message.
define sealed method handle-notify
(gadget :: <win32-rich-text-editor>, mirror :: <window-mirror>,
wParam :: <wparam-type>, lParam :: <lparam-type>,
id :: <integer>, code :: <integer>)
=> (handled? :: <boolean>)
// Handle the WM_NOTIFY event
select (code)
...
end
end method notify;
- a value gadget will also require to write a method of which handles a value changing. The name of such a require method depends on the type of value the gadget represents. For example in case of a textfield the method is named note-gadget-text-changed:
define sealed method note-gadget-text-changed
(gadget :: <win32-rich-text-editor>) => ()
next-method();
[... update the text...]
end method note-gadget-text-changed;
How to to set a text-field width in terms of a particular string, not pixels?
- In DUIM Reference read the entry about the generic function text-size.
- The following code shows how to use the generic function in a specific context:
define frame <a-frame> (<simple-frame>)
pane text-pane (frame)
make(<text-field>);
pane button-pane (frame)
make(<push-button>, activate-callback: on-push);
layout (frame)
vertically()
frame.text-pane;
frame.button-pane;
end;
end frame;
define method on-push(g)
let text-gadget = g.sheet-frame.text-pane;
let _port = g.port;
let style =
get-default-text-style(_port, text-gadget);
let largest-x =
with-sheet-medium( medium = text-gadget )
text-size(medium,
"Hello World",
text-style: style);
end;
notify-user(format-to-string("%d", largest-x));
end method;
define method main () => ()
start-frame(make(<a-frame>));
end method main;
How can I intervene in the pane creation after the GDI context is available and before sizing and layout are calculated?
- Example contexts for this Q:
- You are aming for something like, for instance an input field just width enough for a date
pane my-text (frame)
make (<my-text-field>, string-size: "00/00/0000");
There are different ways for such an intervention.
- First try to specialize the generic function do-compose-space (the smartest way based on the DUIM framework).
- An other possiblity could be calculating the information as soon as the Win32 representation of your gadget is created; you would do this in initialize-gadget-mirror (a generic function in win32-duim) and proably write a do-gadget-size method.
- A further option is to handle the '''<frame-mapped-event>''' for your frame. A '''<frame-mapped-event>''' is one of the subclasses of frame-event
Using the generic function do-compose-space
Via the
generic function do-compose-space the your gadget returns information that defines how much space it will take up. When this is called, the gadget has been created and text-size, etc. will be valid. But, how to write your own text-field class with a correct sheet-behavior?
- To write a own text-field class with a correct sheet-behavior require to use proper super-classe and to write something like
define class <my-text-field> (
<win32-gadget-mixin>,
<text-field>,
<leaf-pane> )
end
- The leaf-pane class identifies the gadget as one that lives at the leaf of the sheet tree - a gaget that obeys the layout protocols. It must be the last class in the precedence list.
- a text-field class belongs to the superclass text-gadget
- <win32-gadget-mixin> is exported from library:module win32-duim:win32-duim and
gives all the required win32 backend protocols.
Note that '''do-compose-space''' is sealed on the various <text-editor> subclasses for the Win32 backend. But this is not a problem if you are writing your own control, not based on those win32 classes.
Handle the frame-mapped-event for your frame
Another option to intervene in the pane creation after the GDI context is available is to handle the <frame-mapped-event> for your frame. This has the disadvantage of being seperated from your control but it does allow you to manipulate existing gadgets.
When the <frame-mapped-event> is handled, your controls have been created and you can do whatever you like.
Issues where you might use this is
- for setting extended selection on table controls.
If you need to use the same gadget over and over you could create a frame superclass that registers all of its required controls as they are created, and then updates them all on receipt of a <frame-mapped-event>.
The way to get at the window handle from DUIM
The <frame-mapped-event> event is raised when the frame has completed creating the Windows back end portions. At this point it is safe to start doing Win32 specific stuff to the gadgets and controls if need be. This is the point we want to extend the functionality of the control.
define method handle-event(
frame :: <app-frame>,
event :: <frame-mapped-event> ) => ()
let hwnd = frame.table-pane.window-handle;
let LVS-EX-FULLROWSELECT = #x20;
let LVM-FIRST = #x1000;
let LVM-GETEXTENDEDLISTVIEWSTYLE
= LVM-FIRST + #x37;
let LVM-SETEXTENDEDLISTVIEWSTYLE
= LVM-FIRST + #x36;
let lstyle = SendMessage(
hwnd,
LVM-GETEXTENDEDLISTVIEWSTYLE, 0, 0);
lstyle := %logior(lstyle, LVS-EX-FULLROWSELECT);
SendMessage(hwnd,
LVM-SETEXTENDEDLISTVIEWSTYLE,
0,
lstyle);
end method handle-event;
- Code comment
- To understand the win32 specific code see the MSDN section User Interface
- Note OpenDylan (since r 10876) has already support for LVS_EX_FULLROWSELECT to be able to select every element of a row in a <win32-table-control> /trunk/fundev/sources/duim/win32/ (wcontrols.dylan win32-definitions.dylan).
- Note that the code requires to import '''window-handle''' and '''Send-Message''' to compile. To import these binding adapt your library and module definition similar to this:
define library a-library
// ...
use c-ffi;
use win32-duim;
use win32-user;
// ...
end library a-library;
define module a-module
// ...
use c-ffi;
use win32-duim, import: { window-handle };
use win32-user, import: { SendMessage };
// ...
end library a-module;
- The win32-duim module is originally exported from the win32-duim library. But it is also reexported from DUIM itself to make it simpler to do this sort of thing.
The way to get at the device context from DUIM
The way to get at the
device context (MS GDI) is shown in an example code, which does a bitmap drawing.
define method draw-image( m :: <drawing-pane>,
image :: <cnd-win32-bitmap>, x, y) => (record)
let m = m.sheet-medium;
let hdc = m.medium-drawable.get-dc;
let newdbc = CreateCompatibleDC(hdc);
let old = SelectObject(newdbc, image.bitmap-handle);
let (height, width) = image-dimensions( image );
BitBlt(hdc, x, y, width, height, newdbc, 0, 0, $SRCCOPY);
SelectObject(newdbc, old);
DeleteDC(newdbc);
end method draw-image;
How to change the table-control behavior?
To-Do
see
Enabling extended selection on a table-control on Chris Double's site
Events
The basic about frames and events
The set of events when a frame is created or destroyed
Note that there are a set of events that get sent to the frames when a
frame is created or destroyed:
- '''<frame-created-event>''', when a frame's UI gets "attached" to the display, but before it gets layed out.
- '''<frame-layed-out-event>''', when a frame gets initially ''layed out ''.
- '''<frame-mapped-event> ''', whenever a frame gets mapped (made visible) on the screen.
- '''<frame-unmapped-event>''', whenever a frame gets unmapped (made invisible) on the screen.
- '''<frame-exit-event>''' gets sent when 'frame-exit' gets called; the default handler for this exits the frame, but you can write your own 'handle-event' methods that queries the user, e.g.
- '''<frame-exited-event>''' gets sent when the frame has really exited, when the UI gets "shut down". You might want to write a handler on <frame-exited-event> that releases all your resources.
- '''<frame-destroyed-event>''', the very last event a frame sees when it gets shut down, after all its DUIM-managed state is gone.
How to translate mouse events in DUIM terminology?
Mouse events DUIM terminology
mouse-over pointer-enter-event pointer is entering an area
pointer-exit-event pointer is leaving an area
pointer-drag-event pointer describes a drag movement
pointer-motion-event pointer is moving
mouse button pressed pointer-button-event A button of the pointer
device is pressed.
Related macros
with-pointer-grabbed macro: redirects the event stream to the sheet that grabbed the pointer.
How to create an event loop independently of the main event loop?
An independent event loop is often required when you implement mouse features like
- autoscrolling
- drag & drop
etc.
Here are the step to create and hnadle an event loop independently of the main event loop:
- Capture the mouse using the with-pointer-grabbed macro. This redirects the event stream to the sheet that grabbed the pointer.
- Then all you need to do is handle the event stream that comes in on that sheet by writing 'handle-event' methods. Deuce does this for its auto-scrolling. Look at deuce/duim/events.dylan to see how Deuce does it.
If you think you must monitor the event queue directly (and I don't think you do), you can import the event queue bindings from the 'duim-sheets-internals' module. For the details of how it works, read duim/sheets/event-queue.dylan and events.dylan.