5 Advanced topics

5.1 Looks

Default parameters of widgets can be specified and grouped into "looks". A look is obtained by:

L={QTk.newLook}

Default parameters for a specific widget are specified by:

{L.set widgetname(param1:value1 ... paramX:valueX)}

Example:

{L.set entry(background:white)}

specifies that the entries will have a white background by default for the look L.

All widgets support a look parameter that specifies the look to use. Except when explicitly stated, widgets that serve as container for other widgets propagate their look parameter to their contained widgets automatically. If a contained widget defines a look itself, it overrides the inherited look from its parent container.

L1={QTk.newLook}
{L1.set entry(background:white)}
{L1.set label(background:red)}
L2={QTk.newLook}
{L2.set label(background:blue)}
{{QTk.build td(look:L1
               entry(text:"Type here")
               label(text:"Inherited red background")
               label(text:"Overrided blue background" look:L2))} show}

5.2 Using a toolkit forbidding class specializations.

At this point of the documentation, the declarative approach of the construction of windows should be clear in the reader's mind. Another important point is once the window is constructed and for all its lifetime, how does QTk compare to other toolkits?

QTk is rather different in its approach than classical object-oriented user-interface toolkits. Developers using these toolkits are often used to a specialization approach where all the components of the UI of the application are objects that are specializations of the available widget classes. Well, this approach has a major drawback: the functionality of the application is strongly inter-mixed with its user-interface. This makes any major change of the UI very expensive, but also any major change of the functionality of the application can suffer from the UI implementation. Applying the separation of concern principle, the UI implementation must be as separated as possible from the functional core of the application. The specialization feature of the object-orientation approach favors the exact opposite.

The QTk toolkit is not used as an object-oriented toolkit, but as an object-based one. The specialization process is consequently impossible, which is a good property when developing applications. QTk offers a different way to create specialized widgets: QTk aliases (see below).

When the habit of specializing widgets is strong, it may be difficult to see how to achieve the same level of expressiveness with QTk. There are two keys in achieving this:

Example: a numberentry widget with save and load functionality

Using a view/controller pattern so that the functionality of this customized widget uses the handle of the numberentry widget:

Desc=lr(numberentry(handle:Handle)  
        tbbutton(text:"Save" action:Save)  
        tbbutton(text:"Load" action:Load))
 
proc{Save}
    {Pickle.save Filename {Handle get($)}}
end 
 
proc{Load}
    {Handle set({Pickle.load Filename})}
end

Using an Oz wrapper:

fun{MyNumberEntry Filename Handle}
  ... % code above
in 
  Desc
end

Using the customized widget inside a QTk window:

{{QTk.build lr(label(text:"Customized numberentry widget:")  
                {MyNumberEntry "value" _})} show}

It is strongly advised to use such technique only when relevant, that is when there is a strong reason to couple the user interface and the functionality of the application that uses it.

5.3 QTk aliases

The creation of brand new widgets is a particular case where it is strongly advised to couple the user interface and its functionality. Note that if some part of the UI of an application has little to no meaning outside the context of this application, then that part of the UI is a very poor widget candidate. On the opposite, all parts of the UI that still have a meaning on their own outside the context of the application are very good widget candidates.

QTk offers a specific support to let users expand the widget database: aliases. To avoid name clash problems, aliases cannot be defined with the default QTk builder, the application has to ask a new one:

MyBuilder={QTk.newBuilder}

Aliases can be defined in 3 different ways:

More examples: adding list support for container widgets

Source File

declare


[QTk]={Module.link ["x-oz://system/wp/QTk.ozf"]}

Builder={QTk.newBuilder}

{ForAll [td lr grid]
 proc{$ V}
    {Builder.setAlias {VirtualString.toAtom V#l}
     fun{$ M}
        Num Other
     in
        {Record.partitionInd M fun{$ I _} {Int.is I} end Num Other}
        {Record.adjoin
         Other
         {List.toTuple V
          {List.flatten {Record.toList Num}}}}
     end}
 end}

%{{Builder.build td(tdl([label(text:"A")] [label(text:"B") label(text:"C")] label(text:"D")))} show}

5.4 QTk builders

The previous chapter already showed an example of a QTk builder. A QTk builder groups together:

By default, all QTk builders include the following widgets and aliases: button, canvas, checkbutton, dropdownlistbox, entry, grid, label, listbox, lr, lrline, lrrubberframe, lrscale, lrscrollbar, lrspace, menubutton, message, numberentry, panel, placeholder, radiobutton, scrollframe, tbbutton, tbcheckbutton, tbradiobutton, td, tdline, tdrubberframe, tdscale, tdscrollbar, tdspace and text.

A QTk builder is obtained by:

MyBuilder={QTk.newBuilder}

And supports the following functions and procedures:

There exists a default builder inside QTk. QTk.build uses the build function of this default builder. However users are not allowed to modify the widget or alias database, nor the default look of this default builder. Therefore QTk.build creates always the same windows depending only on the description record used.

5.5 QTk.flush

For efficiency reasons, all commands send to the windowing system are batched together. Because of that, a widget might fail to return some information about itself, generally regarding its actual height and width. The command {QTk.flush} blocks until all pending commands of the windowing system have been processed.


Donatien Grolaux
Version 1.3.0 (20040413)