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

General UI Literatur

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:

Additional component focus on

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?

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:

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?

How to populate a tree-control class with children?

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. ?

  1. It is not certainly not recommend writing the full control in DUIM unless you have a (future) need for portability.
  2. The best first step would be to add a new DUIM control for a concrete operation system, for example win32. To do this,
    1. 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).
    2. 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 kind of behavior can sheets have?

Input-sheets act as event listener
Input-sheets are sheets that receive input, in which case they have: 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: coordinate system of its parent. 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. associated with them. For example:
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: 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 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
  1. calls the method class-for-make-pane to find the actual type of the gadget to create and
  2. 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.

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. 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-f­ield>, #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

 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;
 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; 
  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?

 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?

  pane my-text (frame)
    make (<my-text-field>, string-size: "00/00/0000");
There are different ways for such an intervention.
  1. First try to specialize the generic function do-compose-space (the smartest way based on the DUIM framework).
  2. 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.
  3. 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?
 define class <my-text-field> (
   <win32-gadget-mixin>,
   <text-field>,
   <leaf-pane> )
 end
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

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;
 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 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:

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 etc. Here are the step to create and hnadle an event loop independently of the main event loop: 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.