Here are the steps to creating a sample GUI application.

This application is going to show all of the Top level window captions (that are not blank) running in Windows. Concepts introduced are: Creating a Window with the GUI tool, adding an event, calling an API and passing a context block, creating an executable.

Creating the Window

  1. Load STMT
  2. Start the GUI Builder  from the Transcript Tools menu.
  3. From the File menu select New... and then Application Window
  4. Drop a Listbox into the window. Expand the Listbox to the size of the window.
  5. In the Layout menu select Framing... and in the Default placement select the second item in the list (i.e. Scales with the parent). Press select and then OK. Now the Listbox sizes with the window. (Note to get the framing menu, the Listbox must be selected)
  6. Open Properties for this new window (right click on the window frame).
  7. On the Application tab set the Smalltalk class name to: WindowViewer and change the Superclass to FrameWindow. On the Window tab, set the Caption to Window Viewer.
  8. Save this new class with the File Save menu.
  9. Browse this class with the File menu Browse Classes.
  10. Close the GUI builder

At this point we have created a FrameWindow subclass called WindowViewer with 5 methods. To test this window, right click on the class and select Register window (this only needs to be done once to register this window class with Windows). Then right click on the class and select Test Window. At this point your window should open empty. It has all of the basic framewindow functionality like sizing, moving, minimize, maximize etc. Close this window and let's proceed to give it some contents.

Creating the Project

First we will create a project to save this class in case of accident. Bring up the Project Browser from Browse Projects icon on the ToolBar (3rd Icon along). Select File New. Now we need to add our class to the project. You can either drag and drop the class from the CHB, or select Classes in the project, right click and select Add Class. You can Find... your class (WindowViewer). Tick the Definition check box to include everything about the class then click Add. The class should now show up in the project.

To save the project, use the File Save As... menu. Create a new directory for this Project called WindowViewer. Save the project into this new directory and call it WindowViewer.sp (include the sp extension, as projects can be saved in different formats).

Giving the window some contents

  1. Open the GUI builder by right clicking on the class WindowViewer and selecting Edit GUI...
  2. Open the window properties. Select the Events tab. Select IDC_LIST1. Choose the event WM_CONTEXTMENU and press the Link button. Accept the default method name of onContextmenuList1. Close the properties box, save the changes (File Save) and browse the class. Notice that two items have been added. An initialize method on the class side linking the event to the method. And a method called onContextmenuList1 on the instance side.
  3. Now we can add some code to the event method to populate the ListBox. Add the following code (copy and paste) to onContextmenuList1:
    (self childAt: IDC_LIST1) deleteAllItems. "Clear out the listbox"

    WINAPI EnumWindows: [ :hwnd :lParam | |caption|
        (caption := (Window basicNew handle: hwnd) getWindowText) isEmpty ifFalse: [
            (self childAt: IDC_LIST1) addItem: caption.
        ].
        TRUE
    ]
    with: 0.
    ^NULL
  4. Now test the window by right clicking on the class WindowViewer and selecting Test window. In your application window, right click in the Listbox and the box should fill with the top level window captions.
  5. Save your project (save icon 3rd on the toolbar).

Creating an executable

Notice on project browser that the Build icon is disabled on the toolbar (8th item). This is because we need to provide one more piece which is the WINMAIN.sm file. This file contains the script to start the executable.

  1. In a workspace(Ctrl-N) enter the following (copy and paste):
    !ApplicationProcess * methods!
    winMain: hModule with: hPrevInstance with: cmdLineArgs with: nCmdShow
    "
    Public - Calls initialization function.
    "
    " register window classes used by the app "
    Window registerClass.
    WindowViewer registerClass: hModule.
    " instantiate application, create main window and run message loop "
    WinApplication new run: [WindowViewer new open]
    ! !
  2. Save the workspace into the project directory (WindowViewer) as WINMAIN.SM
  3. Reselect the project in the project brower and notice that the Build icon is now enabled. Build the project and exit Smalltalk when prompted.
  4. The WINDOWVIEWER.EXE will be created in the Smalltalk MT directory. This size should be approx 92K.

To deploy this application you will need the WINDOWVIEWER.EXE and the support DLL strtdll30.dll from the system subdirectory of Smalltalk MT (26 Kb).

Comments

How did we know that the Listbox was IDC_LIST1? You could have looked at the properties of the ListBox in the GUI builder, or look at the instance method WindowViewer>>initChildWindows which the GUI builder generated. We are using the default assignments for this tutorial.

Note that the results in the Listbox are sorted. This is because Sort is by default selected for a ListBox.

In traditional Smalltalk, Windows callbacks are difficult to manage. But in STMT we can pass a context block directly to the Windows API. The Windows API EnumWindows: call takes two parameters which are a callback procedure and a user defined value. We don't need a user defined value so we pass 0.

We can treat the WINAPI EnumWindows: as though it was a STMT <external agent> do: [    ].

When the callback executes, the first parameter is a window handle given to us by Windows. The second parameter is 0. We create a  Smalltalk MT Window which gets sent handle: with the handle given to us in the first parameter. Since we now have a valid Smalltalk Window instance, we can send it any Window message, in this case getWindowText (which you can find on the instance side of class Window).

The TRUE at the end of the callback returns a Windows 1 (non-zero value) which indicates that the callback function succeeded.

I used a WINAPI call here for simplicity. Some may argue that this is to low level but as I have demonstrated this callback is just like any other STMT block.

Of course the WINAPI call could have been abstracted by making a class method in the Window class in category Enumerating e.g.

Window class Enumerating methods
 enumerateTopLevelWindows

|windowCollection|
windowCollection := OrderedCollection new.
WINAPI EnumWindows: [ :hwnd :lParam | |caption|
    (caption := (Window basicNew handle: hwnd) getWindowText) isEmpty ifFalse: [
        windowCollection add: caption.
    ].
    TRUE
]
with: 0.
^windowCollection

To include this method in your project, just drag the method from the Class Hierarchy Browser to the Project in the Project Browser.

Then onContextmenuList1 could have written:

(self childAt: IDC_LIST1) deleteAllItems. "Clear out the listbox"
Window enumerateTopLevelWindows do: [:each| (self childAt: IDC_LIST1) addItem: each].
^NULL

This abstraction allows the resuse of enumerateTopLevelWindows but at the expense of an OrderedCollection that is used as a go between the API call and filling in the list. This OrderedCollection must be allocated and garbage collected.