The Source for ToolBook and VBTrain.Net News
|...||From Platte Canyon Multimedia Software
ToolBook Tips and News
More News and Information
We have been creating VBTrain.Net products left
and right the past few months here at Platte Canyon.
We're quite proud of each of them and hope you'll
take time to read the news pieces below and browse
the vbtrain.net web site. I think that this set of
products brings creating training with .NET within
the reach of anyone who wants to give it a try.
TBCON 2003 Just Around the Corner!
Can you believe that this will be the fifth
incarnation of TBCON? We've got a dynamite program lined
up for the 2003 edition of The ToolBook & VBTrain.Net
User's Conference. We can't say it any better than Robin
McDermott's recent posting to the ToolBook List:
The conference will take place at Colorado College here in Colorado Springs from July 28 – 30, 2003. The conference includes all meals and, if desired, lodging on the campus. Attendees last year were very impressed with the new apartments. Here is the pricing if you sign up before June 1.
We have an awesome line-up of preconference training
for Saturday, July 26 and Sunday, July 27:
Preconference pricing varies by the number of sessions you attend: 1 ($150), 2 ($285), 3 ($420), or 4 ($555).
Hope to see many of you there!
New Question Object Works with ASP.NET and Windows Forms
The new VBTrain.Net Question allows you to easily
create multiple choice, true/false, or fill-in-the-blank
question objects in either ASP.NET or Windows
applications. Load content from a database, generate
SCORM runtime information, provide multiple levels of
feedback, and more!
Here's the scoop: ToolBook does not do an optimal job
when exporting DHTML content that needs to be Section
508 compliant. (Section 508 is a part of the Americans
with Disabilities Act that deals with accessibility of
Price is $695,
but we are offering it for $625 if you order by May 15,
Web Player and Graphical Button Products Released
The new VBTrain.Net Web Player™ ($145) allows you to
dynamically embed a Windows Media® Player, Flash™
Player, RealPlayer®, or just plain text into your
ASP.NET applications. Set the URL to the desired media
from a database or allow the user to turn off the media
and display "closed captions" instead. You can specify
the exact player and version to be used or you can tell
the Web Player to automatically adjust to the extension
assigned to the MediaUrl property. So the exact same
ASP.NET page can be WMP, Flash, RealPlayer, or even just
text based on a single property. Store your media file
names in a database with no problem. Allow the user to
choose the media type or turn off media completely (and
in that case show "close captions" instead). Control
your media with server-side properties or code (VB, C#,
etc.) rather than editing HTML tags and writing
Shape and Graphical Text Now Include ASP.NET Versions
The path-breaking Shape™ and Graphical Text™ objects ($145 each) now include corresponding ASP.NET objects. They will dynamically create image files on your web server or even stream the image directly to the browser! Both products now have awesome Designers as well. We've added intuitive Designers to both of these products as well as their Windows-only training versions: Training Shape™ and Training Text™.
Books of Note
Never been able to figure out how to use the Actions
Timer, or Time Markers of the Universal Media Player?
What about the assortment of Question Object types in
the "Various" category? Denny's new book "ToolBook
Catalog Objects - Fully Explained" will remove the
mystery from all of the objects in the catalog, letting
you know what they are really for. It offers detailed
descriptions of the various settings and options for
each object. Download a free peek at the book today.
Want to learn more about SCORM, AICC, ADL, and many
other acronyms related to online learning. Then check
out "E-Learning Standards: A Guide to Purchasing,
Developing and Deploying Standards-Conformant
Instructor 8.6 Preview
Instructor 8.6 is scheduled to be released any day now. We've tested it out and it rocks. Note that this can easily be called a service pack rather than a true upgrade, but there are new things as well. Here is a list of the key new features:
We're very happy to see this new version. We’ve tested our ToolBook product line with the new release and all was smooth. We think you’ll find the same for your applications. Nice job Click2learn!
ToolBook Tips and News
Plug-In Pro Tool Spotlight: Comments
We have previously talked about Sticky Notes as a
great way for developers to communicate information back
and forth as they share a book. But what if they aren't
sharing a book? Rather, one person always has the
"master" version but others are reviewing it. What you
need then is a nice way to make notes about a page. At
one point, we used Notepad or Word for this task, but we
would often neglect to write down the page name or
number, making it difficult for the developer to match
up the comment with a page. Even if comments had page
numbers, there is no guarantee that the number will be
the same by the time the developer is ready to make the
by Jeff Rhodes
to handle cbtSave -- do something here end cbtSave to handle cbtPrint -- do something here end cbtPrint -- etc.But this is not very efficient when you have a 50+ different menu items. In this case, it is much better to handle the menuItemSelected message.
to handle menuItemSelected string mMenuName, string mAlias local string bookPath, tarWinName -- anything that gets here is not part of -- the scenario tarWinName = name of targetWindow conditions when tarWinName = "letMeTry" when tarWinName = "cbtToolbar" when tarWinName = "cbtCatalog" when tarWinName = "cbtSystemDefaultProperties" when tarWinName = "cbtRightClickMenu" when tarWinName = "cbtScriptEditor" when tarWinName = "cbtBookProperties" when tarWinName = "cbtViewerProperties" when tarWinName = "cbtFieldProperties" when tarWinName = "cbtButtonProperties" when tarWinName = "cbtStageProperties" when tarWinName = "cbtDisplay" when tarWinName = "cbtSharedActions" conditions when mAlias = "cbtExit" close targetWindow when mAlias = "clear" forward else in viewer ID 0 of self send letMeTrySelection mMenuName, mAlias to this page end in end conditions else forward end conditions end menuItemSelectedSince the menuItemSelected message is sent by ANY menu (including author level menus), we first check to see if one of our simulation windows is open. If so, we in general send a message to the main training page telling it what was selected. If this is the right answer, the page will handle it by showing a viewer, displaying the "Congratulations" box, displaying the next dialog in the sequence, giving more feedback, etc. If not, the message passes back up to the book script, where the default answer is that the choice is "not part of your task." Notice also the use of multiple "when" statements to make an OR statement. This is much easier to follow (and code in the first place) than using OR.
We've been working on a custom ToolBook application that involves collecting information and generating electronic forms that can be both printed and uploaded to a web server. As part of this, Cindy created the following command window scripts. The first one is used after the special "forms" book has all the bitmaps of the forms imported as resources (using Plug-In Pro of course). Now we needed each form to have a corresponding background with the resource set as the backdrop. We send the newBackground message to system to avoid the "New Background" dialog box. You can skip this dialog from .ini settings as well, but we only wanted to disable for this one script.
-- create backgrounds with backdrops from resources r = resourceList("bitmap", this book) while r <> null pop r into rid rName = name of rid send newBackground to system pid = this page bid = this background name of pid = rName name of bid = rName backdrop of bid = rid backdropStyle of bid = "stretch" rInfo = resourceInfo of rid hSize = (item 1 of sysPageUnitsPerPixel) * (item 2 of rInfo - 1) vSize = (item 2 of sysPageUnitsPerPixel) * (item 3 of rInfo - 1) size of bid = hSize,vSize end whileIn testing, Cindy discovered that there were display problems when the page and background was shown in a viewer and scrolled. Switching from the backdrop to a button on the background fixed the problem, so she now needed a script to do this conversion.
-- change from backdrop to graphic button b = backgrounds of this book while b <> null pop b into bid go to page 1 of bid send background rid = backdrop of bid if rid <> null draw button from 0,0 to 6000,6000 borderStyle of selection = "none" fillColor of selection = white excludeTab of selection = true enabled of selection = false highlight of selection = false transparent of selection = true drawDirect of selection = false caption of selection = null name of selection = null normalGraphic of selection = rid position of selection = 0,0 rInfo = resourceInfo of rid hSize = (item 1 of sysPageUnitsPerPixel) * (item 2 of rInfo - 1) vSize = (item 2 of sysPageUnitsPerPixel) * (item 3 of rInfo - 1) size of selection = hSize,vSize sharedScript of selection = sharedScript "lockedImageButton" clear backdrop of bid end if end whileThese scripts saved a ton of time as you can imagine!
Broadcast Functions - Coping With Non-Dynamic Object
Download the source ToolBook application at:
I recently created an Actions Editor version of the game
Mastermind to gather fodder for this article. It was a great
project since it required sending a lot of messages to a lot
of objects, and as many of us know this meant dealing with
the issue of non-dynamic object references.
******* -- Shared Actions "BroadcastToColors" ------------------------------------------ Send User event to Button "color 1" of Page "Mastermind" with value: functionString Send User event to Button "color 2" of Page "Mastermind" with value: functionString Send User event to Button "color 3" of Page "Mastermind" with value: functionString Send User event to Button "color 4" of Page "Mastermind" with value: functionString Send User event to Button "color 5" of Page "Mastermind" with value: functionString Send User event to Button "color 6" of Page "Mastermind" with value: functionString Send User event to Button "color 7" of Page "Mastermind" with value: functionString Send User event to Button "color 8" of Page "Mastermind" with value: functionString *******Next, I wrote one shared action, "ColorFunctions," that operated on "target." This shared action performed any functions I needed on the color buttons, but operated only on target. It accepted a single parameter, "functionString." Sorry to muddy the waters, but functionString can be a comma-delimited list of values. The first value is always the name of the function, the other values are parameters of the function. So the first thing I do is split the functionString parameter into an array of values (via another shared action, "ListToArray"). Then the first value of the array (g_tempArray) is the "function name."
******* -- Shared Actions "ColorFunctions" --------------------------------------------- Set colorNum to characters 7 to charCount(name of target) of name of target Execute Shared Actions "ListToArray"; store return value in returnValue If returnValue > 0 Set functionName to lowercase(g_tempArray) If functionName = "show" If g_tempArray = "true" and colorNum <= g_numColors Set visible of Target to true Set enabled of Target to true Else Set visible of Target to false End if Else if functionName = "disable" Set enabled of Target to false Else if functionName = "cleartext" Set caption of Target to "" End if End if *******One more trick. My color buttons are grouped, so I put the execute command for ColorFunctions in the User Event of the group. This meant the call only needed to be in one place, and since User Event is forwarded, the target went through fine (it remained at the button, not the group).
******* Actions for Group "colorGroup" of Page "Mastermind" -------------------------------------------------------------------------------- -- On User event... ------------------------------------------------------------ Execute Shared Actions "ColorFunctions" *******That's it! So what did this accomplish? It allowed me to eliminate specific object references when showing, disabling, clearing text, etc. When I wanted to clear the text of all the buttons, I just executed shared script "BroadcastToColors" with a "clearText" parameter. When I wanted to hide all buttons I executed "BroadcastToColors" with a "show,false" parameter, etc. It gave me a single location (ColorFunctions) where I could add any functions I needed to perform on these objects. And I never needed to list object references beyond the one "BroadcastToColors" script. Maybe not a lot in this case, but I think it has potential!
It always amazes me how many ToolBook developers I
meet who do not subscribe to the ToolBook Listserv. When
I ask why they don't, they often say they have just let
it go by accident and that they'll be back on there
soon. I also hear that getting all that email every day
is a pain to deal with even if their mail client has the
ability to automatically sort messages into folders
based on rules. I'm here to say that it *IS* worth it!
And more than that, there are lots of easy ways to
participate in the listserv without getting all the
This list of the sessions directly related to ToolBook will make you wish the conference were tomorrow!
Visit http://www.tbcon.com/tbconsessions.aspx for a complete list and session descriptions.
VBTrain.Net Tips and Tricks
What does "server-side" code really mean? One example is the scoring page for the Question object (http://www.tbcon.com/demos/StartExam.aspx and then take and score an exam). After the user selects the exam to be scored, she clicks "Score" button. That "posts" the page and allows the server-side Click handler written in Visual Basic to run::
Private Sub ScoreExamBtn_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles ScoreExamBtn.Click Dim QCollectionId As Questions = Me.QuestionCollectionReference If IsNothing(QCollectionId) = False Then UpdateQuestionDataGrid() ' Update First in case resetting test Dim scoringFormatSetting As Score_FormatOverrideEnum = _ CType(System.Enum.Parse(GetType(Score_FormatOverrideEnum), _ Score_FormatOverrideBox.SelectedItem.Value), Score_FormatOverrideEnum) Dim roundingDigits As Integer = CInt(RoundingDigitsBox.Text) Dim testScoreArray() As Decimal = QCollectionId.ScoreTest(scoringFormatSetting, _ roundingDigits, LockTestBox.Checked, ResetTestBox.Checked, _ TestIdBox.SelectedItem.Value) Dim testMessage As String = String.Concat("Normalized test score = ", _ (testScoreArray(0) * 100).ToString, "%. Raw test score = ", _ testScoreArray(1).ToString, " out of a maximum score of ", _ testScoreArray(3).ToString, ". The minimum possible score was ",_ testScoreArray(2).ToString, ".") StatusLabel.Text = testMessage End If End Sub
The QCollectionId variable is a reference to the entire "collection" of questions that have been visited. This is stored in the Session object. We then call our own UpdateQuestionDataGrid (see below) handler to show the questions, answers, scores, etc. We then figure out if want to override the scoring format (e.g., count partial scores as completely right or completely wrong) and how many digits to round the score. We when call the collection's "ScoreTest" function with this data as well as whether to lock or reset the exam after scoring it). This returns an array that gives us the normalized, raw, min, and max score that we then display in a Label control.
Private Sub UpdateQuestionDataGrid() Dim tableId As New DataTable("Results") Dim rowId As DataRow Dim columnId As DataColumn Dim testId As String = TestIdBox.SelectedItem.Value Dim entryId As DictionaryEntry Dim questionId As Question Dim QCollectionId As Questions = Me.QuestionCollectionReference With tableId.Columns .Add("QuestionText", GetType(String)) .Add("Score", GetType(Decimal)) .Add("Min Score", GetType(Decimal)) .Add("Max Score", GetType(Decimal)) .Add("Selected Answer(s)", GetType(String)) .Add("Correct Answer(s)", GetType(String)) .Add("Time Spent (seconds)", GetType(Integer)) .Add("Number of Tries", GetType(Integer)) End With For Each entryId In QCollectionId questionId = CType(entryId.Value, Question) If questionId.Test_Id.Contains(testId) = True Then rowId = tableId.NewRow With questionId rowId("QuestionText") = .QuestionText rowId("Score") = .Score_Current rowId("Min Score") = .Score_Min rowId("Max Score") = .Score_Max rowId("Selected Answer(s)") = CreateResponseList(.Responses) rowId("Correct Answer(s)") = CreateCorrectAnswerList(.Answers,
.Answers_Weight) rowId("Time Spent (seconds)") = .Time_Current rowId("Number of Tries") = .Tries_Number End With tableId.Rows.Add(rowId) End If Next ' Now bind to DataGrid With ResultsGrid .DataSource = tableId .DataBind() End With End SubHere's the UpdateQuestionDataGrid method:
Private Sub UpdateQuestionDataGrid() Dim tableId As New DataTable("Results") Dim rowId As DataRow Dim columnId As DataColumn Dim testId As String = TestIdBox.SelectedItem.Value Dim entryId As DictionaryEntry Dim questionId As Question Dim QCollectionId As Questions = Me.QuestionCollectionReference With tableId.Columns .Add("QuestionText", GetType(String)) .Add("Score", GetType(Decimal)) .Add("Min Score", GetType(Decimal)) .Add("Max Score", GetType(Decimal)) .Add("Selected Answer(s)", GetType(String)) .Add("Correct Answer(s)", GetType(String)) .Add("Time Spent (seconds)", GetType(Integer)) .Add("Number of Tries", GetType(Integer)) End With For Each entryId In QCollectionId questionId = CType(entryId.Value, Question) If questionId.Test_Id.Contains(testId) = True Then rowId = tableId.NewRow With questionId rowId("QuestionText") = .QuestionText rowId("Score") = .Score_Current rowId("Min Score") = .Score_Min rowId("Max Score") = .Score_Max rowId("Selected Answer(s)") = CreateResponseList(.Responses) rowId("Correct Answer(s)") = CreateCorrectAnswerList_ (.Answers, .Answers_Weight) rowId("Time Spent (seconds)") = .Time_Current rowId("Number of Tries") = .Tries_Number End With tableId.Rows.Add(rowId) End If Next ' Now bind to DataGrid With ResultsGrid .DataSource = tableId .DataBind() End With End SubWe have quite a lot happening here in very little code. We create our own database table in the first line and declare some other variables. We add columns for each piece of data we want to display (QuestionText, Score, etc.). We then loop through all the questions in our selected test and add a row for each one. We then populate the appropriate columns with the corresponding properties of the question. Finally, we "databind" the table to a special DataGrid object for display. It automatically takes the space it needs to display the all the questions. Note that the code is almost identical for the Windows version of this page.
Visit http://www.tbcon.com/tbconsessions.aspx for a complete list and session descriptions.
MORE NEWS AND INFORMATION
First up on the agenda is Tracker.Net 1.5. We'll be adding supervisory levels and possibly other enhancements. We're also planning a massive update to our Learning & Mastering ToolBook series in support of the next release of ToolBook.
If you are a PhotoShop user and you're not using
Actions, boy do I have some good news for you! Actions
were first introduced in PhotoShop 5 but the
fully-realized capabilities of the version 7 actions are
amazing. And the beauty is that they are quite easy to
use. It is just a matter of recording keyboard and mouse
actions and then tweaking it as necessary.
The EnterPage is distributed four times a year, with occasional special issues. Individuals who have expressed interest in Platte Canyon Multimedia Software Corporation or its products receive The EnterPage. If you do not wish to receive future issues, please send an email message to firstname.lastname@example.org with the word "unsubscribe" in the subject line. New subscriptions are available by sending an email message to email@example.com with the word "subscribe" in the subject line and the person's name and company in the text of the message. Suggestions for articles or proposals for article submissions are welcome. Send information to EP@plattecanyon.com. Back issues of the EnterPage are available at http://www.plattecanyon.com/enterpage.aspx.
All content Copyright Platte Canyon Multimedia Software Corporation, 2003.