[prev] [up] [next]

Application Notes on Widget Programming

Contents

Introduction

This document collects various application notes and hints.
It is not meant as an introductionary text, but instead is a collection of frequently asked questions and answers.

Dialogs

Opening modal dialogs from a button

If you open a modal dialog in a buttons pressAction block, it may seem to stay pressed after the dialog is closed. It redraws itself correctly, after some mouse movement in and out of the buttons area.

The reason for this behavior is that the button may not get a buttonRelease notification, because at the time you release the mouse button, it is covered by the dialog (which gets the release event, but does not care). Thus, the button ``thinks'' that the mouse button is still pressed after the dialog is closed - until you move the mouse.
This problem only appears if you have the button trigger-on-press or use the pressAction (in contrast to the default behavior, where the action is triggered in release).

The following example illustrates that behavior:

    |top b dialog|

    top := HorizontalPanelView new.
    top extent:300@300.
    top horizontalLayout:#center; verticalLayout:#center.

    dialog := WarningBox label:'some dialog'.

    b := Button label:'press me' in:top.
    b pressAction:[dialog open].

    top open
a solution to this problem is to either let the button trigger on release (which is its default) or to manually turn the button back to the off-position within the action:
    |top b dialog|

    top := HorizontalPanelView new.
    top extent:300@300.
    top horizontalLayout:#center; verticalLayout:#center.

    dialog := WarningBox label:'some dialog'.

    b := Button label:'press me' in:top.
    b pressAction:[dialog open. b turnOff. ].

    top open
Notice:
It is not a good idea to setup a button to perform its action on press - the default behavior (i.e. action-on-release) allows the user of your program to change her mind, by moving the mouse-pointer out of the button before releasing the mouse-button. Action-on-press should only be used for scrollBar-like positioning buttons.

Arranging widgets row-wise

If a bunch of widgets (typically: buttons) is to be arranged row-wise or column-wise, the easiest way to acomplish this is to use nested panelViews.
For example, to arrange for 16 buttons to be arranged in a topLeft-to-bottomRight grid, use a vertical panel, add four horizontal panels, and place the buttons into them.
(of course, you can also create all buttons in the top view and give each of them a relative origin/extent or layout - however, this will be very hard to maintain, and require a rewrite if additional buttons are to be added later ...)

A calculator-like arrangement is acomplished with:

    |top vPanel hPanels buttons|

    buttons := (1 to:16) collect:[:i | Button label:i printString].

    top := StandardSystemView extent:300@300.
    vPanel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.
    hPanels := (1 to:4) collect:[:i | HorizontalPanelView in:vPanel].

    1 to:4 do:[:hPanelNr |
	1 to:4 do:[:buttNr |
	    |butt|

	    butt := buttons at:((hPanelNr - 1 * 4) + buttNr).
	    (hPanels at:hPanelNr) addSubView:butt.
	].
    ].

    top open
well, almost - we need to specify a useful layout (by default, the panels layout is #center).
Adding corresponding layout messages fixes this:
    |top vPanel hPanels buttons|

    buttons := (1 to:16) collect:[:i | Button label:i printString].

    top := StandardSystemView extent:300@300.
    vPanel := VerticalPanelView origin:0.0@0.0 corner:1.0@1.0 in:top.
    vPanel horizontalLayout:#fit.
    vPanel verticalLayout:#fitSpace.
    hPanels := (1 to:4) collect:[:i |
				    |panel|

				    panel := HorizontalPanelView in:vPanel.
				    panel horizontalLayout:#fitSpace.
				    panel verticalLayout:#fit.
				].

    1 to:4 do:[:hPanelNr |
	1 to:4 do:[:buttNr |
	    |butt|

	    butt := buttons at:((hPanelNr - 1 * 4) + buttNr).
	    (hPanels at:hPanelNr) addSubView:butt.
	].
    ].

    top open
The same setup can (of course) be generated if you use the GUI painter. However, you have to manually add those buttons (use copy-paste ... to create all those buttons).

Constructing a menu

Most menus are now built using the MenuEditor tool; however, in certain situations, it may be required to dynamically construct a menu programatically.
A typical situation is when some history information is to be presented as a menu.
To do so, write a method which returns a Menu instance; for example:
    constructMyDynamicMenu
	|myMenu item|

	myMenu := Menu new.

	item := MenuItem label:'action1'.
	item value:#theActionSelector1.
	myMenu addItem:item.

	item := MenuItem label:'action2'.
	item value:#theActionSelector2.
	myMenu addItem:item.

	^ myMenu
and an aspect method, which returns a block to construct the menu whenever invoked:
    myDynamicMenu
	^ [ self constructMyDynamicMenu]
In the GUI-painter, set the menu-aspect to #myDynamicMenu.

The above menu will invoke the methods named theActionSelector1 and theActionSelector2 in the application.
Sometimes, it is useful to invoke the same method, but pass different arguments.
This can be done by giving each menuItem the same selector, and an additional argument, as in:

    constructMyDynamicMenu
	|myMenu item|

	myMenu := Menu new.

	item := MenuItem label:'action1'.
	item value:#'theActionSelector:'.
	item argument:'Argument 1'.
	myMenu addItem:item.

	item := MenuItem label:'action2'.
	item value:#'theActionSelector:'.
	item argument:'Argument 2'.
	myMenu addItem:item.

	^ myMenu
FInally, it may be useful to combine some fix menu (as constructed in the MenuEditor) with a variable part (as constructed above).
To do this, define the menu as usual (in the MenuEditor), and save it. Here, we assume that the menu was saved under the name baseMenu.
On the instance side, define a method as above, which should look like:
    constructMyDynamicMenu
	|myMenu item|

	myMenu := self class baseMenu decodeAsLiteralArray.

	item := MenuItem label:'-'.     "/ separator
	myMenu addItem:item.

	item := MenuItem label:'action1'.
	item value:#'theActionSelector:'.
	item argument:'Argument 1'.
	myMenu addItem:item.

	item := MenuItem label:'action2'.
	item value:#'theActionSelector:'.
	item argument:'Argument 2'.
	myMenu addItem:item.

	^ myMenu
Notice the decodeAsLiteralArray; this is needed since the menuSpec methods as generated by the MenuEditor return a literal-encoded specification (i.e. an encoded Array).
In order to append more items, a real Menu instance is needed.


Copyright © 1995 Claus Gittinger, all rights reserved

<cg at exept.de>

Doc $Revision: 1.23 $ $Date: 2021/03/13 18:24:52 $