Martha H. Weller
The
easiest kind of book to create in ToolBook consists of simple pages, all
displayed in the default main window.
(See weller1.tbk)
The
single-window type of book was all that was available until version 3 of
ToolBook came out. I remember working
on a project in the early 90s (using version 1.53) that had me jumping through
hoops. I ended up with pages and
backgrounds “piled high” with objects that I would hide or show as needed. This approach worked, but was challenging to
maintain. Another approach I used was
to show pages from a separate book by running a second instance of
ToolBook. Again, this worked – but had
a heavy memory overhead.
The
addition of viewers to the ToolBook environment was a critical advance. Viewers allow the designer to display
multiple windows on the screen simultaneously and to use OpenScript to send
messages freely between and among objects in different viewers. We will look at some specific examples of
this powerful design feature in a little while.
So,
what are viewers then, exactly?
Viewers are windows that you can create in ToolBook to let you provide multiple-window functionality in your applications. Viewers can display any page of any book on your system without opening separate instances of ToolBook.
Creating Viewers
Viewers can be created using the ToolBook interface, at author level. Select the Object menu, then Viewers. The Viewers dialog window appears. Click the New button.

The New Viewer dialog window then appears. The New Viewer dialog window lets you select some initial settings for your viewer. The screen capture below shows the default configuration. If you click OK at this point, you will create a new viewer that will not be based on one of the predetermined styles (Dialog Box, Palette, ToolBar, Status Bar, Read-Only Popup, or Thick Frame). Instead, it will resemble (more or less) the sample viewer displayed – it will have a normal caption bar and a thick frame border style. It will have a system menu (the colorful icon shown in the sample’s top left corner) Although not shown in the Sample, it will also have minimize and maximize buttons, along with the close button in the sample’s top right corner. The window will be movable (thanks to the presence of a caption bar) and resizable (since the borderstyle is set to thick Frame). If the viewer is resized to be smaller than the size of the background of the page being displayed in it, scrollbars will appear (since scrolling is included as part of the viewer’s style). .

Notice that the Create New Page and Background option is also checked by default. This option automatically creates a new page and background for each new viewer you create. If you deselect the option, ToolBook assumes that you wish to use the page currently displayed in the target window as the defaultPage for your new viewer This brings us to a very important point about viewers:
A viewer cannot exist without a page to display. When you first open a viewer, ToolBook will need to locate the page it is to display. The page it looks for is determined by the defaultPage property of the viewer. This property is set when you initially create the viewer (but it can be modified via the interface or by OpenScript). As the screen capture below indicates, the Default page setting is located on the Draw tab of the Properties for Viewer dialog window. ToolBook uses the id number to identify the page. Whenever possible, it is preferable to work with the names of objects (rather than their id numbers). I recommend naming the page that will be a viewer’s default page and then changing the defaultPage property of the viewer to refer to the page by name. Why bother? It may happen that you accidentally delete the default page for a given viewer. In this case, if you try to open the viewer (or show it), you will be presented with an error message telling you that the page is missing. (ToolBook will then display the first page of the book in which the viewer is located. ) It is easier to understand which page is missing if the error message refers to it by name.

You can also create a viewer totally from OpenScript. A simple button to create a new viewer might have this script:
to handle buttonClick
new viewer
end
When you create a new object using OpenScript, it is critical to realize that a unique reference to the newly created viewer is stored in the special local variable it. The it variable is non-persistent and the value it holds changes based on the various statements being executed. For this reason, you need to process the value of it as soon as the value is set (before something else can change the value). So, if I wanted to not only create a new viewer using OpenScript, but also name the viewer for easier manipulation later, I’d change the handler as follows:
to handle buttonClick
new viewer
name of it = “testViewer”
end
The properties of a viewer created using OpenScript will be the same as if you had selected the Custom setting in the New Viewer dialog window. BUT, no new page/background will be created. Instead, ToolBook automatically sets the page currently being displayed in the target window as the default page for the newly created viewer.
Why do I say “target
window” instead of the “main window”?
Most of the time, when you create a new viewer, the target window and the main window will be the same. But they don’t have to be. The targetWindow is a ToolBook system property that indicates where ToolBook will execute commands and search for objects. You cannot set the targetWindow property directly (you can only read it). As we will discuss later, you generally control the targetWindow property by using the in viewer control structure. So, it is possible to imagine having a “New Viewer” button on a page that is currently being displayed in another viewer, not in the main window.
Assigning a page from one
book as the defaultPage of a viewer in another book
You can do this without difficulty so long as both books are located in the same directory. If they are not, ToolBook will have to store the complete path to the book that contains the viewer’s default page. This will cause you problems down the road, if you distribute your books. As we will discuss elsewhere, it is very common to first open a viewer, set its currentPage property to a given page, and then show the viewer. Unfortunately, the instant the book is opened (but not yet displayed), ToolBook must be able to resolve the location of the viewer’s default page. If the page is not in the same, explicitly defined location, an error will result and you’ll never have the opportunity to set either the defaultPage or the currentPage property of the viewer to a valid page. Unless you have a very compelling reason to do otherwise, I would recommend keeping the default page of a viewer and the viewer in the same book and, if you must split them across books, to make sure they are in the same directory. (Note: you can set the defaultPage of a viewer before you open it. This would allow you to get around the explicit path problem.)
Viewer Properties
Viewers, like all ToolBook
objects, have properties that control their appearance and behavior. (See
weller2.tbk)
Before you actually create a new viewer for your ToolBook application, you should have a clear idea of the purpose the viewer will serve and the advantages a viewer offers over simply placing ToolBook objects either on the page or background. Here’s one scenario where a viewer offers a distinct advantage:
If the objects you wish
to make available to the user will appear on multiple backgrounds and/or in
multiple books, in the same relative location and serving the same function,
use a viewer.
Another:
If you wish to show the user variable information (e.g., definitions, examples), a viewer can allow you to provide more flexible access to the changing information. (See weller2.tbk, page 2 for an illustration of this use.)
Once you know the purpose of the viewer you will create, many of the property choices fall into place. Let’s consider some of these key properties.
Child or Popup Behavior
A
viewer can be set to display either as a popup window, or as a child
window. A popup window floats on top of
other windows and can move outside of its parent window.
A
child window is contained within its parent window. A child window can be “clipped” by the parent window. This means that you can position a child
window in such a way that a portion of the child window is hidden by its
parent.
When
to Use Child or Popup?
As we will see in a moment, only popup viewers can be opened as modal windows (meaning you have to dismiss them before you can do anything else). Consequently, a major use for popup viewers is to create dialog windows – when you need to ask the user a question, or require the user to make a choice. Also, since popup viewers can be moved around the screen, they are useful for providing supplemental information or controls to the user.
Since child viewers are contained within their parent viewer, it is less likely that you will allow the user to reposition them (although you could). I tend to use child viewers to add features that are either always available to the user or are used extensively throughout the application.
A Word about Positioning
Viewers
Child viewers are easy to position, especially if you don’t let the end user change the position. You can set the pixel coordinates of the viewer when you create it. These coordinates are also easy to visualize, since they are based on the top left corner of the parent viewer. You also don’t have to worry about changes in video adapter resolution (e.g., 640 x 480 vs. 800 x 600 vs. 1024 x 768, etc.), so long as the parent window fits within the lowest resolution you require for your application.
If you do need to control the position of a child viewer when it is opened, the code might look like this (imagine a button that opens the child viewer):
to handle buttonClick
open viewer “childDemo”
position of viewer “childDemo” = 100,100 --these are pixels
show viewer “childDemo”
end
Positioning popup viewers is a bit trickier. In fact, most of the time you’ll probably not try to position the popup viewer explicitly. Instead, when you create the viewer, you’ll simply choose to have the viewer appear centered on the desktop. This is an excellent choice for popup viewers that you use to require information or decisions from your users. Sometimes, however, you would like to set the initial location of a popup viewer –even if you plan to let the user move the viewer around the screen. You may wish to do this for aesthetic reasons or to make sure that the initial location of the popup viewer doesn’t obscure information that the user may need to see while the popup is open. You should also consider the possibility that your user may not wish to move the popup viewer (or may not even realize that moving the viewer is an option).
To get a feel for the primary difference in positioning child and popup viewers, try this experiment on your own. On page 2 of the weller2.tbk sample, switch to Author level and open the command window (shift + F3). Return to Reader level and select the Behavior (Child or Popup) option from the combo box. Click the Child and Popup buttons to open both viewers. Now, type the following OpenScript statements into the command window, pressing Enter after each. Observe the positions of the two viewers.
position of viewer "childdemo" = 0,0
position of viewer "popupdemo" = 0,0
Even though we appear to be typing in the same coordinates (“0,0”), the results are quite different. Both positions are being expressed in pixels, but the popup viewer’s position is relative to the entire desktop, while the child viewer is constrained by the bounds of its parent window. If your video adapter is set to 1024 x 768, you could set the position of the popup viewer to 1000,700 and just see the top left corner of the viewer in the bottom right corner of your desktop. The visible range for the child viewer is determined by the dimensions of the parent window. For my sample books, I used the standard 9000 x 6000 page unit background size. This equates to 600 x 400 in pixels. (If you aren’t familiar with the Unit Conversion Tool provided under the Tools menu, take a look.) If I set the position of my child window to 580, 380 – I can just barely see the top of the viewer in the bottom right corner of the main window. (To close the viewers, just select another option from the combo box.)
Position a Popup Viewer
Precisely, Regardless of Video Resolution
Given that popup viewers are positioned relative to the entire desktop, you might wonder how you can specify a set of coordinates that will work with all possible user video resolutions. If you are designing your application while working at a screen resolution of 800 x 600 and pick a set of coordinates for a popup viewer based on what looks good (i.e., how close the popup viewer is to something else in your application), you will have a surprise when you try the application at 1024 x 768.
The solution is this: position your popup viewer based on the position of some other object within your ToolBook application. This will allow you to have your popup always appear in the same relative location, regardless of the video adapter’s resolution.
The weller2.tbk sample shows you two examples using the pageUnitsToScreen( )[2] function to achieve this relative positioning.
The first example (click button Position1) opens the popup viewer and positions it relative to the combo box in the main window. Here’s the code from that button:
to handle buttonClick
--keep in mind that we are
altering the position of the viewer
--in a non-persistent
way. The next time the viewer is opened
--(assuming this handler
isn't run), its position will revert to its
--saved position
in mainwindow
if not isOpen of viewer "popupDemo"
open viewer
"popupDemo"
end if
position
of viewer "popupDemo" = pageUnitsToScreen (position of comboBox
"choices", mainWindow)
if not visible of viewer "popupDemo"
show viewer
"popupDemo"
end if
end in
end
Notice that I am checking to see if the viewer is already open and later, already visible to determine whether I issue the open and show commands. I do this because I give you two buttons, allowing you to change the position of the viewer. Once the viewer is open (or visible), I don’t need to open it (or show it) again.
The second example (click button Position 2) illustrates how you can position a popup viewer relative to a child viewer. The relevant line of code is:
position of viewer "popupDemo" = pageUnitsToScreen("0,0", viewer "childDemo")
Notice that I used a set of coordinates (“0,0”) here to refer to the top left corner of the childDemo viewer. If I had wanted to align my popup viewer with an object displayed within the child viewer, I could have used either the object’s coordinates or an expression that referred to the object’s position . For example, to open the viewer relative to the position of the text field:
position of viewer "popupDemo" = pageUnitsToScreen (position of field "text" of page "childdemo", viewer "childDemo")
Notice that I had to provide a full reference to the location of the text field by including the name of the page. (I could have used the in viewer structure and wouldn’t have needed to use the name of the page – more about this later.)
There are several additional conversion functions that can be useful when positioning child and popup viewers relative to one another. The two defined below would let you change the position of child viewers or objects in a viewer to match the position of a popup viewer. These might come in handy for designing game play. (You will have to experiment with them on your own.)
ScreenToClient
(<coordinates>, viewer reference)
Converts pixels
relative to the screen into pixels relative to the origin of the client window
of the specified viewer. Use this function to align a viewer that is a child of
the viewer's client window with a popup window. For example, a non-tiled child
window is a child of a client window.
screenToPageUnits(<coordinates>,
<viewer reference>)
Converts pixels
relative to the screen to page units for the specified viewer. Use this
function to align an object on the page displayed in the viewer with a popup
window.
By
default, whenever you show a viewer, the viewer is displayed in “normal”
mode. Normal means that the most
recently opened viewer is active and you can interact freely with other
ToolBook viewers and objects. To show a
viewer in normal mode you use this OpenScript statement:
show viewer <foo>
(Here’s
that famouse <foo> , which simply means that you can substitute the name
of any valid viewer here.) Notice that
you don’t use any parameters to indicate normal mode.
There
may be times when you will wish to show a viewer in other than the normal
mode. The most obvious situation is
when you wish to ask the user a question and require an answer before anything
else happens. In this case, you will
need to show the viewer in “modal” mode.
The OpenScript statement would be:
show viewer <foo> as modal
The
end user must perform whatever action is required to close the viewer before
any other script will be executed.
Finally,
it is possible to open a viewer so that it is not active when it is opened. The
OpenScript statement would be:
show viewer <foo> as notActive
It
is a bit harder to see when and why you might wish to do this. In my weller2.tbk sample, I give you three
ways to open the popup viewer and I’ve included a text field that doesn’t have
its scripts activated (so you could type into it). Try out each button and attempt to type immediately into the
field. When the viewer is shown as
modal, the viewer is active and has the focus – so you can immediately type
into the field. When the viewer is
shown as notactive, you cannot type immediately and using the tab key has no
impact. You would have to click the
viewer first to make it active. When
the viewer is shown normally, the field doesn’t have the focus, but pressing
the tab key places the cursor into the field and you could begin typing.
A
viewer’s style has to do with its overall appearance. Do we see a caption bar?
Is there a border around the viewer and how thick is it? Is there a system menu? Maximize/minimize controls? Does the viewer have its own menu bar?
The
weller2.tbk sample lets you examine how various style settings interact. You can examine the code in the update
button of the Style Controls page (which is displayed in its own viewer) to see
how these style properties can be controlled using OpenScript.
There
are other things that can be examined concerning viewer properties. Of particular interest are the various
options available to you on the Behavior tab of the viewer properties dialog
window. (The one I use most often is
the ‘close on buttonclick’ option. This
is automatically set when you create a viewer using the ‘Read-only Popup’
template.)
A
major challenge in using viewers is knowing how to make sure the commands you
send (the messages) get sent to the correct target object.
In
the sample book, I provide a button that is intended to change the fillcolor of
the combobox that can be seen on the page in the main window. Here’s the handler:
to
handle buttonClick
fillColor of comboBox “choices” = lightGray
end
When
clicked, the following error message appears:

To be able to change the object being displayed in one viewer in response to an action occurring in another viewer (like clicking on a button), you have two choices:
Using
the in viewer control structure is particularly helpful when you have multiple
messages to send, involving several objects.
(Remember: I could have referred
to the mainwindow as viewer id 0, or I can actually rename the mainwindow and
say viewer “main”, for example.)
For
the past few years, I’ve worked on a project originally designed and developed
by Gerald Nelson (Associate Professor, Agricultural and Consumer Economics,
University of Illinois). Dr. Nelson has
created computer-based courseware (DiscoverEcon) that is intended to accompany
specific economics textbooks published by McGraw-Hill. The courseware is
intended to supplement the textbook (not replace it) and provides many
interactive exercises for the student.
The design of the software has been evolutionary. Its original format (long before I became
involved with the project) was in HyperCard.
Later, it was converted to ToolBook (version 1.5). When I began working on the project, we
undertook a major overhaul of the design.
This new design has, in fact, undergone a couple of subsequent major
revisions. One of the major thrusts has
been to modify the structure of the books and develop programming strategies
that would make it easier to adapt the existing courseware for additional
textbooks.
The
most recent version of the courseware was delivered to the publisher only a few
weeks ago. McGraw-Hill publishes three
“flavors” of this particular economics textbook by Schiller: The Economy Today, The Macro
Economy Today, and The Micro Economy Today. The “Macro” and the “Micro” versions are
actually subsets of the full text.
McGraw-Hill will include a single “flavor” cd-rom with the corresponding
textbook.
The
full set version of the courseware includes individual ToolBook books as
follows:
37
chapters, plus 2 appendices and one introductory book
10
system books
an
additional 37 books that hold data for a multiple choice exercise for each
chapter
numerous
graphics and an assortment of audio and video files.
Our
challenge, over the years, has been to look for ways to modularize aspects of
the interface, so we could reuse components both to deal with the need for
multiple sets for a given textbook and to adapt the courseware to new textbooks.
Since
I can’t provide access to the entire program here, I’ve taken some screen
captures and placed them in another sample book (weller3.tbk), along with some
comments. Also, since the topic is
viewers, I’ve taken the opportunity in weller3.tbk to demonstrate some
additional uses of viewers. You should
look at the sample book in conjunction with reading the remainder of this
paper.
The
first viewer I present in the weller3.tbk sample is what we refer to as the
“tab” viewer. In one of our earlier
versions, you could actually see the tab design (adapted from the tab widget
included still in the library.tbk book that comes with ToolBook). Since we have three slightly different
versions of the courseware to deal with, we created three tab pages. We keep all three versions in a single
book. The full version of the text,
with all 37+ chapters, requires a slightly larger background for the tabs than
do the other two versions. We take
advantage of two very important viewer features here:
We
learned early on to avoid placing scripts in objects. Imagine correcting code buried in 37 separate tab buttons! Instead, we use the shared script approach –
attaching a single script to the multiple buttons.
The
control panel consists of several buttons and fields that allow the user to
move within each chapter and to access some special features (glossary,
results, help). It also provides areas
in which we show special information such as specific pages in the
corresponding textbook and the user’s location in the courseware.
For
the most part, the buttons in the control panel contain only very brief scripts
– to send a message to longer handlers that reside in a system book. Originally, we had only one system book that
kept getting bigger and bigger. For the
most recent revision, we decided to divide the contents of our single system
book among several. We have tried to
divide the handlers up in a systematic way, grouping routines that deal only
with exercises in a single system book.
We hope that this may facilitate updates and help us track the code
better.
It’s
hard to know where you should put your handlers. We have not followed a strict rule on this, as some do. My instincts tell me to avoid placing long,
complex handlers inside objects. I
prefer to place them at a higher level.
Since the interactive exercises are such an important part of the DiscoverEcon courseware, we provide easy access to them by including an Exercises menu item. This opens a viewer containing a listing of all of the exercises for the textbook. As with the tab viewer, we have three pages (one for each version of the textbook). We use system variables, set when the program executable is launched, to determine which textbook version is in use and this determines the page that is displayed in the viewer.
As
another menu option, the user can access a viewer that allows him to set
certain preferences related to visual effects and audio. We also have an ftp feature that students
can test and they can reset the settings that indicate their web browser
preference (so they can specify a different web browser to launch to access
various web sites linked in the books).
As with the control panel viewer, the Preferences viewer and the page it
displays exists in only one location and functions seamlessly regardless of the
version of the courseware launched.
There
are several additional viewers (Help, About, Jokes, Calculator, Results,
Glossary) that all work in pretty much the same way – the user specifically
requests the feature by clicking a button.
I
want to go into more detail concerning two viewers that work together: the Graph Viewer and a separate viewer that
provides controls related to graphing.

The image above shows the graph viewer as it appears in the graphing system book. It exists only as a single page, single background. The image below shows the control panel for the graphing viewer, with all of the controls now visible.
![]()
Because we might get lost in the details of the scripts, I’m going to walk you through some comments to explain how we work back and forth among the main window and the graph and graphing controls viewers.
|
User
Action |
Message
/ where received |
Result |
|
User
enters a page on which a graph needs to be displayed. |
Enterpage
handler at the page level. In
addition to setting some system variables used by the graphing routines, this
handler calls another handler:
setGraph. The setGraph handler
is located in the graphing system book’s book script. Example code: send setgraph 0,0,25,6, "Quantity of typing","Price of typing" |
Graphs vary page by page, depending on the content being taught. We have a number of system variables that let us set the captions for each axis. We also pass some variable data as parameters when we call a handler that sets up the graph scale. (Note: we could do everything by passing parameters, or use system variables, user properties, etc. The important thing is that we have generalized our handlers that deal with graphing so that we display all of our graphs by feeding data to objects on a single page (displayed in the graph viewer). |
|
|
SetGraph The
setgraph handler uses the system variables set in the enterpage handler, does
some checking and then calls some additional handlers. Example: send graphinit to background "graphback" of book grafBook Notice
that we are sending the message to a specific location (grafBook is a system
variable that stores the dynamic path of the graphing system book). |
The graph is modified as needed for the exercise or example. Note: Handlers that are located in the book scripts of active system books (i.e., included in your sysbooks) are directly “callable”. Unfortunately, your book script can fill up pretty quickly. The solution is to use multiple system books and/or to divide the work to be done so you can place some of the code in additional handlers, located either in a page or background script (for example). The handlers that do the bulk of the work to set up the graph are located in the background script of the graphing page. |
|
User
clicks the “Graph” button on the graphing controls viewer. |
Here’s
the entire script of the graph button: to
handle buttonClick system DEcurrentPage syscursor = 4 in mainWindow send drawGraph to DEcurrentPage end in sysCursor = 1 end
DecurrentPage is a system variable that we update to provide a complete reference to the page being displayed in the main window. Just as the information we need to set the graphing environment up initially varies page by page, so does the specific graphing routine to be drawn. The drawGraph handler in the page script obtains various values required to draw the graph (from information contained in the handler, or stored in hidden fields on the page) and then calls higher level routines located in the graphing system book to actually do the work. |
|
Our final example from the DiscoverEcon software is the matching exercise at the end of each content chapter. Each chapter contains a page as shown below. The user can specify between 5 and 15 questions to try and then clicks the “Take Quiz” button. The script of this button performs some checks and increments an array counter that tracks how many times the user has tried the quiz. The script ends with this statement: send startMCQuiz.

The
startMCQuiz handler is located in the system book devoted to exercises and
reads as follows:
to
handle startMCQuiz
system exerBook
send startMCQuiz to page
"MCQuiz" of book exerBook
end
The
system variable exerBook contains the dynamic path to the exercise system
book. We are using the handler in the
book script to redirect the message to the handler in the page script of a page
in the book. (This was done primarily
to make sure we didn’t run out of room in our book script.)
The
exercises system book contains 5 pages that we use to present the multiple
choice questions. (We’ve allowed for 5
pages, but in practice only 2 or 3 are ever needed.) When the startMCQuiz message is sent to page MCQuiz, a series of
events occur:
1.
Initialize
several variables.
2.
Check
that the number of items entered by the user is between 5 and 15.
3.
Determine
the chapter number (relative to the full set version of the textbook) of the
current book; pad that number with an extra 0 (if necessary) and create a
string that points dynamically to the toolbook file that contains the multiple
choice questions for the current chapter.
4.
Make
sure the data file is present; give a warning if it isn’t and exit the handler
gracefully.
5.
Hide
certain objects on the page in the main window; show a message indicating that
we are preparing the quiz.
6.
Clear
out any previous quiz items that have been placed on the quiz pages.
7.
Obtain
a randomly selected list of numbers to use to draw corresponding quiz items
from the data book. Use these items to
actually set up the quiz. During the setup process, the screen is locked so the
end user doesn’t see what is going on. Once all of the quiz items have been
placed on the question page(s), the MCQViewer is shown.
8.
Show controls and field objects on the background
of the page in the main window.
The
MCQViewer is displayed in the screen capture below. It is a scrolling viewer and its style is shadowed. Each time a user selects a number of items
to include in the quiz, the program creates a randomly generated set of numbers
(ex: 3,1,7,9,23,46,2,8,4,10,50,33,13,6,15). These numbers are then used to determine the
page number in the data book from which the item data are read. For the first item displayed, for example,
the program reads the information located on page 3 of the data book that
corresponds to this chapter (sch08003.tbk).

Figuring
out a way to populate our quiz pages was the biggest programming challenge in
this multiple-choice exercise. Some of
the question items have graphics associated with them, many do not. Since we are building our quizzes
dynamically each time, we have no way to predict where a graphic needs to be
presented.
The
solution to our problem lies in the NviewLib dynamic link library (dll)
graciously provided free by K. Nishita.
The web site listed for this is:
http://freeware.freeservers.com/ (but this actually redirects you here: http://home.att.net/~knishita/). This dll lets you import and display file
formats (such as gif or jpg) at runtime.
Note: if you’re creating a
commercial product, stay with the jpg format rather than gif – to avoid need to
license the gif format.
I’ve
included the MCQuiz page script as an Appendix A. As I looked through it, trying to “summarize” how it works, I
realized what a complex piece of coding it really is. The part of the script that actually places the question items on
the page is the setupQuiz handler.
Without
trying to explain all of the little details that make this work, here’s the
general process:
Viewers
are an enormously empowering programming feature, once you get the hang of
working with them.
Key
things to remember:
Appendix A:
MCQuiz Page Script
to handle startMCquiz
system
MCItemNum,currChap,clipCounter,exerbook,dbFilesPath,bookshortName,pathholder
dbFilesPath
= pathholder & "dbfiles\"
in
mainwindow
the
scoreflag of this page = 0
clipCounter
= 0 -- we have to have a separate dummy clip for each bitmap displayed on same
page
sysCursor
= 4
MCItemNum
= text of recordfield "Question Number" of this page
if
MCItemNum is null or \
MCItemNum < 5 or \
MCItemNum > 15
request
"Please enter a number between 5 and 15 in the yellow box above!"
syscursor
= 1
break
to system
end
if
--all
of the data files are numbered according to the big book numbers
in
mainWindow
currChap
= the bbchnum of this book
end
in
while
charCount(currChap) < 3
currChap
= "0" & currChap
end
whichDataFile
= dbFilesPath & bookshortname & currchap & ".tbk"
get
fileExists(whichDataFile)
if
it < 1
clear
text of recordfield "question number" of this page
request
"Not available."
syscursor
= 1
break
end
if
hide
group "setupObjs" of this background
quizPageCount
of this book = 0
show
field "processing" of this background
the
totalCorrect of this book = 0
the
totalIncorrect of this book = 0
the
totalTries of this book = 0
send
updateScores to background "showMCQ" of book exerbook
send
clearOldQuiz
send
getMCItems (MCItemNum)
clear
text of recordfield "question number" of this page
show
group "quizControls" of this background
end
in
sysCursor
= 1
end
to handle clearOldQuiz
system
settingUp,exerbook
settingUp
= true
syslockscreen
= true
if
isOpen of viewer "mcqView" of book exerbook is false
open
viewer "mcqView" of book exerbook
end
if
in
viewer mcqView of book exerbook
step
i from 1 to 5
currPage
= "MCQuestions" & i
go
to page (currPage) of book exerbook
syssuspend
= false
select
objects of page (currPage) of book exerbook
if
selection <> null
send
cut
end
if
end
step
end
in
settingUp
= false
end
to handle getMCItems howMany
system
exerBook
--
this routine will first generate an appropriate set of random
--
numbers drawn from the total number of questions in the database
--
for the given chapter
--=========================================
system
currchap,dbFilesPath,bookshortname-- will determine which set of chapter
correspondences are in effect
local
randomSet[]
whichDataFile
= dbFilesPath & bookshortname & currchap & ".tbk"
sysPasswords
= upasswords of book (exerBook)
questionCt
= pageCount of book (whichDataFile) - 1
--modified after adding viewer to test jpg display
--we
need to first check the data tbk file and find out how many items it has in it
randomSet=
randomSet(questionCt,howMany)
in
mainwindow
text
of field "quizNum" of this background = howMany
end
in
--now,
we take the randomSet and pass it to the setup routine
send
setupQuiz (randomSet)
end
to get randomSet questionCt,
howMany
local
qset,randomSet[]
--set
up an array of question numbers 1 to questionCt
step
i from 1 to questionCt
item
i of qset = i
end
step
--howMany
is how many MC items will be presented
--we
will now draw a random number between 1 and questionCt
--use
that number to draw an item from the qset list
--put
the question number into the randomSet arry
--clear
the item from the qset list and reduce the questionCt by 1
step
k from 1 to howMany
whichItem
= random(questionCt)
randomSet[k]
= item whichItem of qset
--request
item whichItem of qset
clear
item whichItem of qset
decrement
questionCt
end
step
return
randomSet
end
to handle setupQuiz randomSet[]
system
exerbook,dbFilesPath,bookShortName,MCItemNum, dynamic
qArray[][],currChap,clipCounter,settingUp
settingUp
= true
syssuspend
= false
--the
randomSet array contains the questionNumbers to be retrieved
--from
the database
--this
is a test to generate an appropriate number of test items, based on
--
a. the number of items selected by the
user
--
b. the nature of each item (graph or
not)
--
c. the 'running space': is there room to present another item on the
current
-- page, or does the current quiz have to
spill over onto a second page
MCItemNum
= dimensions(randomSet) -- this tells us how many items are in the quiz
syslockscreen
= true
in
viewer "mcqview" of book exerbook
go
to page "MCQuestions1" of book exerbook
whichDataFile
= dbFilesPath & bookshortname & currchap & ".tbk"
sysPasswords
= upasswords of this book
step
i from 1 to MCItemNum
whichRecord
= randomSet[i]
qArray[i][1]
= text of recordfield "ID" of page (whichrecord) of book
(whichDataFile)
qArray[i][2]
= text of recordfield "question" of page (whichrecord) of book
(whichDataFile)
qArray[i][3]
= text of recordfield "choiceA" of page (whichrecord) of book
(whichDataFile)
qArray[i][4]
= text of recordfield "choiceB" of page (whichrecord) of book
(whichDataFile)
qArray[i][5]
= text of recordfield "choiceC" of page (whichrecord) of book
(whichDataFile)
qArray[i][6]
= text of recordfield "choiceD" of page (whichrecord) of book
(whichDataFile)
qArray[i][7]
= text of recordfield "CorrectAns" of page (whichrecord) of book
(whichDataFile)
qArray[i][8]
= text of recordfield "graphRef" of page (whichrecord) of book
(whichDataFile)
qArray[i][9]
= text of recordfield "tableRef" of page (whichrecord) of book
(whichDataFile)
qArray[i][10]
= text of recordfield "pages" of page (whichrecord) of book
(whichDataFile)
--FOR
TESTING ONLY
qarray[i][11]
= whichRecord -- this will let me check problems in the database
end
clear
syspasswords
groupPos = "60,100"
--this starts the positioning for the MC groups
lastGroup
= "" -- this will contain the name of the last group pasted onto the
page
-- to let us check if we have
room to paste another group, or need to
-- setup an additional testing page
Qspacer
= 60 -- distance between question bottom and top of first choice
Cspacer
= 40 -- distance between choice bottom and next choice top
GrpSpacer
= 300 -- distance between question groups
cPos
= 1000
buttonxPos
= 400
fldOffset
= 60 -- to make the choice fields line up better with the choice buttons
in
mainwindow
quizPageCount
of this book = 1 -- default
tquizPageCount
= quizPageCount of this book
end
in
step
i from 1 to MCItemNum
--when
the data is stored in the array, index #8 and #9 will screen for graph/no graph
grafHeight
= 0 -- if there is a graph, these 2
will be changed & will be subtracted
grafSpacer
= 0 -- out at the end of the step
if
qArray[i][8] is "" and qArray[i][9] is "" -- no graph or
table
currType
= "mctype1"
else
-- there is a graph or a table
currType
= "mctype3"
end
if
go
to page "MCTemplate" of book exerbook
select
group (currType) of page "MCTemplate" of book exerbook
currHeight
= item 2 of the size of the selection
send
copy
backPage
= "MCquestions" & tquizPageCount
go
to page (backPage) of book exerbook
if
lastGroup <> "" -- we've pasted in at least one group already
--
need to get its bottom coordinate
bottomPoint
= item 4 of bounds of (lastgroup)
if
currType is "mctype3"
--allow
a 6000 unit extra space for graphic
tempHeight
= currHeight + 6000
else
tempHeight
= currHeight
end
if
if
bottomPoint + tempHeight > item 2 of size of this background
in
mainwindow
increment
the quizPageCount of this book
increment
tquizPageCount
end
in
nextPage
= "Mcquestions" & tquizpagecount
go
to page (nextPage) of book exerbook
groupPos
= "60,100" -- start the positioning over again from the top
end
if
end
if
focuswindow
= viewer "mcqview" of book exerbook
send
paste
name
of selection = (currType) & "_" & i
lastGroup
= "group " & name of the selection
position
of selection = groupPos --this is starting point for top left corner of group
if
currType is "mctype3"
oldSelection
= the selection
oldGroupName
= the name of the selection
tempGraph
= qArray[i][8]
tempTable
= qArray[i][9]
if
tempGraph <> ""
whichGraph
= dbFilesPath & "graphs\" & tempGraph
else
whichGraph
= dbFilesPath & "tables\" & tempTable
end
if
get
ImportMyGraphic(whichGraph)
focuswindow
= viewer "mcqview" of book exerbook
lineStyle
of the selection = none
grafHeight
= item 2 of size of the selection + 100
position
of the selection = position of (oldSelection)
tempSelection
= the selection
select
(oldSelection)
increment
item 2 of the position of the selection by grafHeight
send
ungroup
extend
select tempSelection
send
group
name
of selection = oldGroupName
item
2 of groupPos = item 2 of groupPos + grafHeight
end
if
whichField
= field "itemNum" of selection
position
of (whichField) = 60,(item 2 of groupPos)
text
of (whichField) = i & "."
whichField
= field "qStem" of selection
position
of (whichField) = 400,(item 2 of groupPos)
tempYPos
= item 2 of groupPos
text
of (whichField) = qArray[i][2]
if
char 1 of text of whichField is CR
clear
char 1 of text of whichField
end
if
if
char 1 of text of whichField is LF
clear
char 1 of text of whichField
end
if
send
fixHeight(whichField)
position
of (whichField) = 400,tempYpos
nextPos
= cPos,(item 4 of bounds of (whichfield) + QSpacer + fldOffset)
whichField
= field "choice1" of selection
position
of (whichField) = nextPos
position
of button "choice1" of selection = buttonxPos,(item 2 of nextPos -
fldOffset)
text
of (whichField) = qArray[i][3]
send
fixHeight(whichField)
nextPos
= cPos,(item 4 of bounds of (whichfield) + cSpacer + fldOffset)
whichField
= field "choice2" of selection
position
of (whichField ) = nextPos
position
of button "choice2" of selection = buttonXPos,(item 2 of nextPos -
fldOffset)
text
of (whichField) = qArray[i][4]
send
fixHeight(whichField)
nextPos
= cPos,(item 4 of bounds of (whichfield) + cSpacer + fldOffset)
whichField
= field "choice3" of selection
position
of (whichField) = nextPos
position
of button "choice3" of selection = buttonXPos,(item 2 of nextPos -
fldOffset)
text
of (whichField) = qArray[i][5]
send
fixHeight(whichField)
nextPos
= cPos,(item 4 of bounds of (whichfield) + cSpacer + fldOffset)
whichField
= field "choice4" of selection
position
of (whichField) = nextPos
position
of button "choice4" of selection = buttonXPos,(item 2 of nextPos -
fldOffset)
text
of (whichField) = qArray[i][6]
send fixHeight(whichField)
whichGroup
= name of the selection
show
field "feedback" of the selection
groupObjs
= objects of the selection
send
ungroup
selection
= null
sysSuspend
= false
select
item 1 of groupObjs
step
f from 2 to itemCount(groupObjs)
extend
select (item f of groupObjs)
end
step
send
group
hide
field "feedback" of the selection
name
of the selection = whichgroup
correctAns
of selection = qArray[i][7] -- stores correct answer as a user property of the
group
bkpages
of selection = qArray[i][10] -- stores page reference for question
syssuspend
= false
tempHeight
= item 2 of size of selection -- this is after adjustments have been made
item
2 of groupPos = (item 2 of groupPos + tempHeight + grpSpacer - grafHeight -
grafSpacer)
end
step
clear
settingUp
end
in
in
mainwindow
send
updateNav to this background
end
in
show
viewer "MCQView" of book exerbook
in
viewer "mcqview" of book exerbook
go
to page "mcquestions1" of book exerbook
send
checkState to this background
end
in
end
to handle fixHeight whichField
--we'll
change only the height of the field, the width remains constant
conditions
when
the textOverflow of (whichField) > 0 -- field isn't big enough
do
increment
item 2 of size of (whichField) by 10
until
textoverflow of (whichField) = 0
when
the textOverFlow of (whichField) = 0 -- field is larger than needed
do
decrement
item 2 of size of (whichField) by 10
until
textoverflow of (whichField) > 0
increment
item 2 of size of (whichField) by 10
end
conditions
end
to get ImportMyGraphic
GraphicNameAndPath
-- Get the "nviewLib.dll" at
http://freeware.freeservers.com/
-- Make sure this DLL is placed in the ToolBook Directory
-- The file will be placed on your current page as a paint object
named MyPic.
-- Call this function with the following syntax:
-- get ImportMyGraphic(c:\myfiles\image.jpg)
system exerbook
linkDll "user"
int openClipBoard(INT)
int closeClipBoard()
int setclipBoardData(INT,INT)
int emptyClipBoard()
end
get sysOperatingSystem
currsystem = it
if"Windows 3" is not in currsystem
linkDLL "TBFILE32.DLL"
STRING
getShortFileName32(STRING, WORD)
end
GraphicNameAndPath =
getShortFileName32(GraphicNameAndPath,1)
end if
get fileExists(GraphicNameAndPath)
if it <> 1
request "You
seem to be missing files. Please report
this problem."
in mainwindow
hide
field "processing" of this background
show
group "setupobjs" of this background
syscursor
= 1
end in
if isOpen of viewer
"mcqview" of book exerBook is true
close
viewer "mcqview" of book exerBook
end
if
break to system
end if
linkDLL "Nviewl16.dll"
int NViewLibLoad(STRING,INT)
end
local int handle
sysLockscreen = true
-- Note: if GraphicNameAndPath is not valid (the file is not
there) an error
-- will occur. Check for the existence of the file first, using
OpenScript.
handle = NViewLibLoad(GraphicNameAndPath,0)
get openClipBoard(sysWindowHandle)
get emptyClipBoard()
get closeClipBoard()
if handle is not null
focuswindow = viewer "mcqview" of book exerbook
get openClipBoard(sysWindowHandle)
get setClipBoardData(2,handle)
get closeClipBoard()
send paste
-- must empty the clipboard
get openClipBoard(sysWindowHandle)
get emptyClipBoard()
get closeClipBoard()
end
sysLockscreen = false
unLinkDll "nViewL16.dll"
return TRUE
end
to handle updateNav
system
exerbook
in
mainwindow
tquizPageCount
= the quizPagecount of this book
end
in
if
isOpen of viewer "MCQView" of book exerbook is true
in
viewer "MCQView" of book exerbook
whichPage
= 0
temp
= last char of name of this page
whichPage
= temp
if
whichPage < tquizPageCount
my
enabled = true
else
my
enabled = false
end
if
end
in
end
if
end
to handle nextMCPage
system
exerbook
if
isOpen of viewer "MCQView" of book exerbook is true
in
viewer "MCQView" of book exerbook
temp
= last char of name of this page
nextPage
= "Mcquestions" & (temp + 1)
go
to page (nextpage)
end
in
end
if
send
updateNav
end
to handle prevMCPage
system
exerbook
if
isOpen of viewer "MCQView" of book exerbook is true
in
viewer "MCQView" of book exerbook
temp
= last char of name of this page
prevPage
= "Mcquestions" & (temp - 1)
go
to page (prevpage)
end
in
end
if
send
updateNav
end
[1] This paper and the accompanying sample ToolBook files may be distributed without additional permission from me within an instructional environment, for non-commercial purposes.
[2]
pageUnitsToScreen(<coordinates>,
<viewer reference>
Converts
page units for a specified viewer into pixels that are relative to the origin
of the screen. Use this function to align a popup window with an object on a
page displayed in the viewer.