<< Prev | - Up - | Next >> |
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}
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:
View/controller pattern: whenever the application needs to view or control the state of a widget, use a handle. The functional part of the application is a user of these handles.
Natural Oz capability to wrap entities: first class procedures, records, classes or whatever is fitted for the task.
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.
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:
Aliases as records that define shortcuts to default values:
{MyBuilder.setAlias ilabel label(bg:black fg:white)}
{{MyBuilder.build td(ilabel(text:"Inverted label")
ilabel(text:"Inverted label with overriden foreground color"
fg:red))} show}
Note how QTk looks define a single default set of parameters for each different type of widgets in a local area of the window, while aliases define several sets of parameters for possibly the same type of widget and for the entirety of the window.
Aliases as functions that are applied at the widget construction time:
{MyBuilder.setAlias tdl
fun{$ M}
Num Other
in
{Record.partitionInd M fun{$ I _} {Int.is I} end Num Other}
{Record.adjoin
Other
{List.toTuple td
{List.flatten {Record.toList Num}}}}
end}
{{MyBuilder.build td(tdl([label(text:"A")]
[label(text:"B") label(text:"C")]
label(text:"D")))} show}
When the tdl
widget is found during the window construction process, the function defined by the alias is called with the record describing the widget as parameter. The window construction process resumes by substituting the alias by the result of the function call. Note that the actual parameter given to the alias function may be different from the record in the window declaration as QTk automatically appends look
information.
Aliases as classes:
{MyBuilder.setAlias labelddlb
class $
feat label ddlb
meth labelddlb(...)=M
M.(QTk.qTkDesc)=lr(glue:we
label(handle:self.label glue:we)
dropdownlistbox(handle:self.ddlb
action:self#select
init:M.init))
thread
% waits for the widget to be built
{Wait self.ddlb}
% actions to do when the widget is first built
if M.init\=nil then
{self.label set(M.init.1)}
end
end
end
meth set(...)=M
self.ddlb,M
{self select}
end
meth get(...)=M
self.ddlb,M
end
meth select
{self.label set({self.ddlb get(firstselection:$)})}
end
end}
{{MyBuilder.build lr(labelddlb(init:[1 2 3 4]))} show}
This method is an enhanced version of the previous one. The constructor method of the class must have the name of the alias, and it is called with the actual record used in the window declaration with an added parameter: QTk.qTkDesc
. This parameter defines the substitution record to use instead of the alias. Note that using classes, the following parameters are automatically managed and must not be taken into account: handle
, feature
, glue
. handle
parameters are bound to the instance of the alias class created as if this class was a regular QTk widget class.
More examples: adding list support for container widgets
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}
The previous chapter already showed an example of a QTk builder. A QTk builder groups together:
A list of widgets
A list of aliases
A default look for widgets
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:
{MyBuilder.build Desc}
: builds the window corresponding to Desc
where Desc
is a description record, according to the widget and alias database, and to the default look defined in MyBuilder
.
{MyBuilder.register Widget}
: register a regular QTk widget. This should not be used outside QTk itself, use aliases instead.
MyBuilder.defaultLook
: defines the default look
(Section 5.1) for widgets.
{MyBuilder.setAlias AliasName Alias}
: defines an alias (see above).
{MyBuilder.unSetAlias AliasName}
: removes an alias from the alias database of MyBuilder
.
{MyBuilder.getAlias AliasName}
: returns the function applied to transform the AliasName
alias description in the window description record. Altough an alias can be specified in three different forms (record, function or class), getAlias
returns only a one parameter function that is semantically equivalent to the initial alias specification.
{MyBuilder.getWidgetList}
: returns the list of all widgets defined in MyBuilder
.
{MyBuilder.getAliasList}
: returns the list of all aliases defined in MyBuilder
.
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.
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.
<< Prev | - Up - | Next >> |