Sw4   >   About   >   Customizing Your App

Customizing Your App

This tutorial takes you through adding custom features and code to the meta-data based auto-generated Drivers app that you wrote in the Quick Start Tutorial. If you haven't completed the quick start tutorial, you will need to do that first.

Ping Buttons

Under the Programmer Menu you will see the menu item Programmer $ping Pushbuttons. By selecting this menu item you toggle the menu checkmark. With this menu item checked a tiny pushbutton (White X in a blue circle) appears in the top left corner of each subwindow.

If you click on the button a ping message tells you information about the subwindow.

If you shift+click on the button you will end up at a breakpoint inside the subwindow. This feature is very helpful when you are debugging and customizing windows.

I use the shift+click on the blue ping button technique for:

  1. Looking at ivar values such as: iList, iSQLClassName, iWinInstID
  2. Adding a red breakpoint to the $doCmnd class methods so that I can step through the code when I click one of the toolbar buttons.
  3. Adding or modifying a window class method.
  4. Adding or modifying an $event method of an entry field.

Debug OK Messages

If a developer version of Omnis Studio is being used and an error is logged, the error handler opens a debug OK message. The OK message allows the user to break into the code at the error handler. From there, using the Stack toolbar menu you can go back up the method stack to investigate the method which logged the error, and any previous methods currently in the method stack.

Being able to break into the method stack when an error is logged is extremely useful for solving bugs in your code.

Click the Run Demo button in the StudioTips Browser window to try out the debug OK message.

The debug OK message prompt is not opened for the following errors: check data, data, minor, and user errors.

Programmer Test Method

Each main library has its own mProgrammer menu class which is instantiated by the main menu as a cascading menu by the name Programmer Menu AppName. AppName is replaced with its string table translation.

This method is helpful to you for experimenting and testing code which you write in StudioWorks. If I have a chunk of code that doesn't work and I want to experiment with variations of the code, I'll often go the the Programmer Test Method to try out different things. If I have a batch of records I need to fix in the database, I'll often use the Programmer Test Method to write and run the code.

  1. Select the main menu > Programmer Menu AppName > Programmer Test Method
  2. This takes you to a Breakpoint command followed by a Quit method command.
  3. Below the Quit method you can write any code you like. The code in this method can access any of the task variable object methods so long as you are in the method stack. (i.e. You haven't cleared the method task after coming into the Programmer Test Method.)
  4. Enter the following code below the Quit method.

    Do stb.$getText('Mn','AppName',#S1) Return FlagOK
  5. Assuming you are still in the method stack, double-click on the line of code you just entered.
  6. Click the Step In IDE toolbar button. You should have stepped into the $getText method of the oStringTable object class.
  7. Click the Step Out button of the IDE toolbar. You should be back to the mProgrammer method.
  8. Hover over the #S1 variable in your line of code. The value should be the name of the current app.
The StudioWorks code only works from the main library task instance. You can't just open an object class from the IDE and double-click on a line of code in a method and hit the Go toolbar button. Wherever you run StudioWorks code you must be within the task instance of the main library's Startup_Task. If you don't understand the last few sentence don't worry about it for now. Just trust me, that if you want to test some code in StudioWorks you either catch it with a red breakpoint when the method is run, or you write it in the Progammer Test Method, select Programmer Menu > Programmer Test Method and then double-click below the Quit method and start stepping through your code.

Method Calls or Messages?

One of the things that was confusing to me when I got into reading the object-oriented programming books was this thing about sending messages.

In Omnis I was used to the the term method call. If I was explaining code to someone I would say that MethodA does a method call to MethodB even if MethodB was in another class.

Instead of using the words making a method call to another class, the object-oriented programming world uses the word sending a message, which once you get your head adjusted to it, it really is a better word picture of what the code is doing.

So, when we talk to each other about our code (or if we're talking to ourselves), let's talk about class instances sending messages to each other rather than using the term of making method calls.

You can still use the term method call when MethodA and MethodB are in the same class.

So how does this word picture help the developer? Let's use a the real world which all of use can easily relate to.

Do you see how this relates to classes and public methods in Omnis Studio? Let's use a StudioWorks framework example to tell a similar story.

Once you get the picture of instances sending messages to each other, a lot of things gain clarity in Omnis Studio.

When a user clicks on a line in a headed list:

When you open a library, Omnis Studio looks for a task class named Startup_Task. If found Omnis Studio opens an instance of the Startup_Task class and names the instance the same name as the library name. Omnis Studio then sends the task instance a $construct message. If the $construct recipient method exists, the code in the method can open instances of menu classes, toolbar classes, and window classes. All of these instances that are opened are contained within the startup task instance.

Hopefully the above note help you to see more clearly the picture of class instances sending messages to each other. The StudioWorks framework depends heavily on the concept of sending messages between source and target class instances, so it will be good if you think and talk in these terms.

Interface Manager

One of the keys to using the StudioWorks framework (and Omnis Studio ) is learning to use the Interface Manager. I wrote code using Omnis Studio for about 2 years without using the Interface Manager. Today, I'd be lost without the Interface Manager.

The Interface Manager shows you the public interface to a class, that is, all the class methods which begin with the $ character.

Well built object-oriented programs have a good clean public interface. You write your code using the public interface. The Omnis Studio IDE works very well with the Interface Manager.

If you aren't familar with the Interface Manager and how to use it to write code, let's go through some exercises.

For these exercises you will be writing code in the Programmer Menu AppName > Programmer Test Method. The instructions below assume that you will write the code below the Quit method in the $event method of the Programmer Test Method menu line of the mProgrammer class method.

Get and set the $:AppName property in the oConstants object class instance.

  1. In the method, type the word Do and tab to the Calculation line.
  2. In the Variable pane, click the Task tab, and find the task variable cn.
  3. Right-click on cn > select .
  4. Scroll down the list and find the $:AppName method.
  5. Drag the $:AppName method from the into the Calculation field of your Do command.
  6. Press tab and enter #S1 in the return field.

    Warning

    Do not use # hash variables for your real app code.

  7. Programmer Menu > Programmer Test Method > double-click your line of code and click the Step Over button in the IDE toolbar.
  8. Hover your mouse over the #S1 variable in your line of code. Is the value Drivers or whatever you named your app?
  9. Start a new Do command line.
  10. Drag the $:AppName.$assign method from the into the Calculation field. Omnis Studio very kindly includes the parameters in parenthesis for us when we drag in the method from the .
  11. Replace pAppName with 'Crazy Drivers' including the single quotes.
  12. Double-click your line of code and click the Step Over button in the IDE toolbar.
  13. Now double-click the first Do command and Step Over that line of code. The value returned to #S1 should now be Crazy Drivers.

Your finished code should look like this:

Do cn.$:AppName() Returns #S1

Do cn.$:AppName.$assign('Crazy Drivers')

Now we'll use the

to look at the table class methods. List and row variables do not know which table class they are supposed to point to until after you bind them to a table class. In StudioWorks you bind a list or row variable by sending a $retDefinedList to the lsts instance of the oSQLList object class.

  1. Enter a Do command.
  2. Right-click the task variable lsts > select .
  3. Drag the $retDefinedList method into the Calculation field of your Do command.
  4. Replace the pSQLClassName parameter with 'sCountry' including the single quotes.
  5. Remove the other parameters, they are optional.
  6. Enter #L1 in the Returns field.
  7. Programmer Menu > Programmer Test Method. Double-click your line of code and click the Step Over button in the IDE toolbar.
  8. Right-click the #L1 variable > select Variable #L1... This opens the variable window which should display to you an empty list with the column names. Close the variable window.
  9. Enter another Do command in the next line.
  10. Right-click #L1 in your method code > select .

    Note

    The list or row variable

    is only available after the list or row has been bound to a table class while you are stepping through the code. If you were to clear the method stack and right-click on the list variable the menu line does not appear in the context menu.

  11. Drag the $:BaseTable method into the Calculation field of your new Do command.
  12. Enter #S1 in the Returns field.
  13. Double-click your line of code and click the Step Over button in the IDE toolbar.
  14. Hover the mouse of the #S1 variable, that value should be Country.
  15. Enter another Do command in the next line.
  16. Right-click #L1 in your method code > select .
  17. Select the $getAllRecords method in the Interface Manager.
  18. Click the Description tab in the bottom part of the . Note the method description.

    If I've done my job, you should see a short description about what the method does and what it returns. Well written classes with good public methods should be like a black box. You should never need to look inside the box to see what the method does. The Interface Manager tells you the method name, the parameters, the method description, and the return value you can expect.
  19. Drag the $getAllRecords method into the Calculation field of your Do command.
  20. Enter #F in the Returns field.

    Warning

    Do not use # hash variables for your real app code.

  21. Double-click your line of code and click the Step Over button in the IDE toolbar.
  22. Right-click #L1 in your Do command > select Variable #L1...

    All going well the variable window will show a list of country records that were fetched from the database.

Your finished code should look like this:

Do lsts.$retDefinedList('sCountry') Returns #L1

Do #L1.$:BaseTable Returns #S1

Do #L1.$getAllRecords() Returns #F

The

is an important tool for you to use when writing code in Omnis Studio. Documenting all of the classes and methods in the StudioWorks framework would take longer than writing the code... and would end up being a repetition of the information you can find yourself by using the .

Tip

From the method editor you can look at the Interface Manager of the class you are in using the View IDE toolbar button.

From the F2 Browser you can look at the Interface Manager of any class by right-clicking on the class and selecting

.

Take some time and study the public interface of the Startup_Task task variables. These are the key object classes of the StudioWorks framework. Before you spend time writing a custom method, look at the object classes in the StudioWorks core libraries. There are object classes for exporting and importing data, encrypting and decrypting files, opening the client's web browser to a specific URL, preparing an email in the client's email program, etc. If you are unsure, ask a question on the StudioWorks list. Other StudioWorks members might know where to look. One of the benefits of the StudioWorks community is that we can create reuseable object classes which we can share, because we are using a common framework.

If you know the task variable name and have an idea of the public method name you don't have to use the

.

Try the following:

  1. Set your Omnis > Preferences > General tab > $notationhelptimer to 50 milliseconds.
  2. In the method editor type Do
  3. Press the Tab key to go to the Calculation field.
  4. Type the characters err. The task variable errhndler should show up in the notation helper.
  5. Press the Tab key and type the characters $pr. The method $promptonceLastError should show up in the notation helper.
  6. Press the Tab key.
  7. Enter the opening and closing parenthesis () to finish the calculation.
I find the notation helper to be a huge help when writing code in Omnis Studio. It helps you enter method names, variable names, and even functions. You can play with the notation timer speed. I like it around 50 so that I don't have to pause and wait for the helper. You can use the up and down arrow keys or your mouse to select something in the notation helper list. If you don't select an item in the notation helper list the first matching item to what you have typed so far in the Calculation field will be used when you press the Tab key.

Adding a Toolbar Button

To add a toolbar button to the contacts list window:

  1. Main Window > Contacts > Shift+click the blue ping button.
  2. Find the $:ToolbarCmndsCSV method in the class methods.
  3. Right+click the method and select Override Method.
  4. Uncomment the method lines.
  5. Add ,HelloWorld to the end of the toolbar buttons string being returned by the Quit method.

    Quit method 'Edit,New,NewCopy,Delete,SPACER,Find,Print,Export,HelloWorld'
  6. Right+Click the class methods > select Insert New Method.
  7. Name the new method $HelloWorld and put a breakpoint in the method.
  8. Close the IDE window.
  9. Close and reopen the main window and select the Contacts node.

The HelloWorld button should appear in the toolbar, but you will notice that the text is not translated, it is not enabled, and does not have an icon. To solve these problems:

  1. Add HelloWorld to the sMn_stb schema class in the module. Enter Hello World in the description.
  2. Add HelloWorld to the sIcons schema class in the module. Enter 1653 in the description.
  3. Programmer Menu > Reload String Tables. (This also rebuilds the icons list)
  4. Main Window > Contacts > Shift+click the blue ping button.
  5. Find the $_retActiveCmndsList method in the class methods, override the method, and add the following code to the method:

    Do inherited Returns List
    Do List.$add('HelloWorld')
    Quit method List

  6. Close and reopen the main window and select the Contacts node. All going well the button will have the correct text, icon, and be enabled.
  7. Click the Hello World icon. All going well you will end up at the breakpoint in the $HelloWorld method.

Removing a Toolbar Button

There are two ways to remove a toolbar button.

  1. Override the $:ToolbarCmndsCSV method and remove the button from the CSV string being returned by the method.
  2. Send a $removeCmnd message to the window instance.
A situation where you might use the $removeCmnd technique is where you are reusing an edit window inside a container window and you want to remove the Save and Close button from the edit subwindow. After constructing the edit subwindow, the container would send a $removeCmnd('SaveAndClose') message to the edit subwindow. The subwindow would remove the button from its toolbar.

Writing Methods

How you write your code is up to you. The following are a list of guidelines which I follow when writing code. These guidelines have evolved over the years.

  1. Always return something. Either FlagOK (true or false) or a return value.
  2. Only exit at the end of the method. (This guideline was added in mid 2005. Code written by me prior to mid 2005 has lots of early exit points.)
  3. If a method is returning a value calculate the value to a local variable and return the variable. If an error occurs in the method, set the variable to #NULL. The sender can check the return value with the isnull() function.
  4. Preset the return local variable to the negative condition in the init value in the variables pane. FlagOK to kFalse. Return value to #NULL.
  5. Always test the return flag or value from a method before proceeding further in the method. Sample code as follows:

    ; Test FlagOK
    Do List.$getAllRecords() Returns FlagOK
    If FlagOK
       ; Continue
    End If
    Quit method FlagOK


    ; Test return list. Lists and rows you can not test with isnull()
    Do lsts.$retDefinedList('sContact') Returns List
    If List.$colcount=0
       Calculate FlagOK as kFalse
    Else
       ; Continue
    End If
    Quit method FlagOK


    ; Test return string
    Do fn.$retCSVStringFromList(List) Returns String
    If isnull(String)
       Calculate FlagOK as kFalse
    Else
       ; Continue
    End If
    Quit method FlagOK

  6. The method where an error occurs must immediately log the error to the error handler. Always declare and set both error message variables, Mssg and Dtls, even if you set Dtls to empty.
  7. Only public methods of visual classes prompt the user with errors. (windows, toolbars, menus, menu obsever objects classes) Wait till the end of the method before prompting the user.

    ; Pretend this is a public method of a window class.
    Do lsts.$retDefinedList('sContact') Returns List
    If List.$colcount=0
       Calculate FlagOK as kFalse
    Else
       Do List.$getAllRecords() Returns FlagOK
       If FlagOK
          Do List.$smartlist.$assign(kTrue)
          Do List.$sendall($ref.Active.$assign(kTrue))
          Do List.$doworkBatch() Returns FlagOK
       End If
    End If

    ; Wait till the end of the method. Test the flag. Prompt the last error if the flag is false.
    If not(FlagOK)
       Do errhndlr.$promptonceLastError()
    End If



    You can avoid a lot of $promptonceLastError message calls by structuring your code so that the public methods of your visual classes call lots of private or protected methods which do the real work. The non-public methods only have to worry about returning the flag or value since the public visual class methods are responsible for taking care of the $promptonceLastError.
  8. Only public methods of visual classes issue redraws. Following this technique makes it easy for non-public methods and non-visual class methods... they never have to think about redrawing the window, that is the responsibility of the visual class public methods.
Tip

In your Omnis Studio Preferences > General tab set the $notationhelpertimer property to 50 milliseconds. The notation helper will come up a lot faster and save you loads of keystrokes and typing errors.

Error Handling

If you want to write code that is easy to maintain you must be diligent about writing good error handling into your code.

  1. Anticipate where errors might happen.
  2. Log every error to the error handler.
  3. Write clearly understandable error message text. (The Mssg and Dtls variables)

    Note

    The error handler takes care of reporting the library/class/method which reported the error. You simply need to report additional helpful error message and details information.

  4. Check the return flag or value from every method which you call and bail out if the flag or value is communicating an error.
  5. End every visual class public methods with an if error test which sends a $promptonceLastError message to the error handler if an error was detected by the visual class public method code.

The StudioWorks error handler has specific log error methods which make it easier for you to log certain types of errors. Study the different $log...Error methods in the oErrorHandler object class to become familiar with them. By using the correct method you can save yourself some time, and your user will get an error message with with the appropriate icon and error message information.

In some cases the error is not a program error. For example the user enters an incorrect password. A non-visual class method can send a $logMinorError message to the error handler and return false to the sender. The visual class method then sends a $promptonceLastError message to the error handler and the user is prompted with a non-threatening error message.

Click the Run Demo button in the StudioTips Browser window to see a demonstration.

Warning

Do not use OK message commands in your non-visual class methods. If you ever try to run your StudioWorks app as web server app the OK message commands will halt your application on the web server preventing it from responding to any further requests. Someone will have to go to the server and click the OK message to allow it to continue. Not a good thing.

Overriding a Method

When overriding a superclass method in StudioWorks, to avoid duplication, you should normally first Do inherited and then add your subclass specific method code.

; Run the superclass code
Do inherited Returns FlagOK
If FlagOK
   ; Do the subclass additional code.
End If

Using the 4GL Do inherited command you can get away with not declaring the parameters in the subclass, but if you put a breakpoint on the Do inherited and step up into the superclass method the parameters in the superclass method will be empty. For the superclass code to work while stepping through a Do inherited, the correct parameters must also be declared in the subclass method. If you need any of the parameters in the subclass method, you will need to declare the parameters in the subclass. Sometimes is it easier to copy the entire superclass method, which also copies the parameters to the subclass, and then delete the code from the sublclass method and enter Do inherited at the top of the subclass method. See the steps below for the proper way to copy a superclass method to a subclass.

Running the superclass method isn't always possible. In some cases you will have to copy the superclass method to the subclass and then modify the copied method in the subclass.

Use the following steps to copy a superclass method to the subclass.

  1. Select the superclass method from the subclass. The superclass method will be in blue text.
  2. Double-click anywhere on the superclass code. This takes you up to the superclass method.
  3. Right-click on the superclass method > select Copy. This copies the entire method to the clipboard.
  4. Close the superclass methods IDE window.
  5. Right-Click on the superclass method in the subclass > select Override Method.
  6. Right-Click on the overridden superclass method in the subclass > select Paste. This copies the entire method from the clipboard to the subclass.
If you only copy the method text from the superclass to the subclass you run the risk of having the superclass parameters being copied into the subclass method in the wrong order, which very quickly breaks your code and can lead to hours of debugging. I know this from first hand experience.

Calling a Superclass Private Method

You may run into a situation where there is a private in an StudioWorks superclass which you want to call from your subclass. Rather than copying the private method to your subclass you can add a protected method in the superclass which calls the private superclass method and then post a request to the StudioWorks list to add the protected method the superclass in the StudioWorks framework. This way you can immediately using the superclass private method and the next release will work without modification as long as the superclass method you requested is added to the framework. Here's an example.
  1. You subclass oStartupTaskDefaultMethods to your main library.
  2. You want to add to the setDefaultsAndPrefs method, but it is a private method called from the $constructMethod method.
Here's what you can do.
  1. Find all occurrences of Do method setDefaultsAndPrefs in oStartupTaskDefaultMethods and change them to Do $cinst.$_setDefaultsAndPrefs 2. Add a $_setDefaultsAndPrefs method to the superclass which has the following code:

    Do method setDefaultsAndPrefs Return FlagOK
    Quit Method FlagOK

  2. Override the method in your subclass and enter the following code:

    Do inherited Return FlagOK
    If FlagOK
    ; Do the additional stuff you wanted to do
    End if
    Quit method FlagOK

  3. Send a request to the StudioWorks members list to add $_setDefaultsAndPrefs to oStartTaskDefaultMethods for the next release. When the next release comes out you don't need to change any code. If I decide to rename or refactor the private method in the superclass your code won't get busted. If I add or fix code in the superclass private method, your subclass will use it. If I decide to move the private method code to the protected method and delete the private method nothing is broken.
    Note

    Background - Private methods give me much more flexibility because they are inside the box where I can still play with them. As soon as a method name has a $ character prefix, I can't mess with the name, the purpose, the parameters, etc. If you are coding from my end, you soon realize how restrictive that is... especially in the early stage of developing an object when you need to refactor the methods and code a few times as it progresses and gains clarity over the course of several releases. -- Doug K.

    Prompting the User

    Only prompt the user from public visual class methods.

    In the context of the StudioWorks framework visual classes are:

    1. Window classes
    2. Menu classes
    3. Toolbar classes
    4. Menu observer object classes (oReportsMenuObserver, oSpecialReportsMenuObserver)
    5. Startup_Task $construct and $destruct methods.

    All other classes should be considered to be non-visual classes and therefore should never prompt the user.

    In the context of the StudioWorks framework public methods are:

    1. Methods which begin with the $ character, excluding those which begin with $_ character.

    You have several choices for prompting the user:

    1. Omnis Studio 4GL commands: OK, Yes/No, No/Yes, Prompt for input These are modal prompts (they halt code execution), and they have a 255 character limit.
    2. StudioWorks oPrompts object methods which you access via the prmpts task variable. These are modal prompts which use one font size for the message text and a small font size for the additional details text. The StudioWorks prompts do not have a limit on the number of characters. The StudioWorks prompts do string table translations on any text strings that are in the format stbname.stbid.
    3. StudioWorks oModelessPrompts object methods which you access via the modelessprmpt task variable. These are modeless prompts which use one font size for the message text and a small font size for the additional details text. The StudioWorks prompts do not have a limit on the number of characters. The StudioWorks prompts do string table translations on any text strings that are in the format stbname.stbid.

      The StudioWorks modeless prompts let you build custom prompts or input prompts which can be a combination of radio buttons, checkboxes, etc. See StudioWorks > Prompts documentation for more information.
    Click the Run Demo button in the StudioTips Browser window for a demo of the different prompts.

    Database and SQL

    If flag trueThe StudioWorks framework uses the table class tBase for all selects, fetches, inserts, updates, and deletes with the database.

    The StudioWorks framework makes extensive use of the Omnis Studio $smartlist functions. When you set a list to be a smartlist, Omnis Studio makes a hidden history list. The history list keeps track of lines added, lines changed, and lines deleted from the original list at the time is was set to be a smartlist. The smartlist eliminates all the code you would have to write to keep track of which lines in a list need to be inserted into the database, which lines need to update records in the database, and which lines need to delete records in the database. You simply issue a $dowork and Omnis Studio does the database work for you. (StudioWorks uses a modified version of $dowork which it has named $doworkBatch)

    Without the StudioWorks framework you would need to write at least the following code to update a batch of contact records in the database.

    Calculate HostName as 'PathToTheOmnisDataFile'
    Calculate UserName as 'TheUserName'
    Calculate Password as 'ThePassword'
    Calculate SessionName as 'TheSessionName'
    Do SessionObj.$logon(HostName,UserName,Password,SessionName) Returns FlagOK
    If not(FlagOK)
       ; Log the $logon error here.
    Else
       Do List.$definefromsqlclass('myAppModule.sContacts')
       Calculate List.$sessionobject as SessionObj
       Do List.$select() Returns FlagOK
       If not(FlagOK)
          ; Log the SQL error here.
       Else
          Do List.$fetch(kFetchAll) Returns FetchStatus
          If FetchStatus=kFetchError
             ; Log the SQL error here.
             Calculate FlagOK as kFalse
          Else
             Do List.$smartlist.$assign(kTrue)
             For List.$line from 1 to List.$linecount step 1
                Calculate List.ColName as 'SomeValue'
                Calculate List.ModBy as UserName
                Calculate List.ModDateTime as #D
                Calculate List.EditNum as List.EditNum+1
             End For
             Do List.$dowork() Returns FlagOK
          End If
       End If
    End If
    Quit method FlagOK

    With the StudioWorks framework the following code accomplishes the same thing.

    ; Ask the oSQLLists object to return a defined list bound to the correct session object.
    ; If there is an error oSQLLists will log the error for you.
    Do lsts.$retDefinedList('sContact') Returns List
    If List.$colcount
       
       ; Ask the tBase table class to select and get all the records.
       ; If there is an error tBase will log the error for you.
       Do List.$getAllRecords() Returns FlagOK
       If FlagOK
          
          Do List.$smartlist.$assign(kTrue)
          
          For List.$line from 1 to List.$linecount step 1
             Calculate List.ColName as 'SomeValue'
             ; Setting the admin columns (EditNum, InsBy, InsDateTime, ModBy, ModDateTime) is handled by tBase.
          End For
          
          ; A custom dowork method in tBase, $doworkBatch, updates all the admin columns for you.
          ; If there is an error tBase will log the error for you.
          Do List.$doworkBatch() Returns FlagOK
       End If
    End If
    Quit method FlagOK

    You can go a long ways with just the above snip of code. The code breaks down to 5 steps:

    1. Define the SQL list variable. Always to this by sending a $retDefinedList message to the oSQLLists object.

      Do lsts.$retDefinedList('SchemaOrQueryClassName') Return List
    2. Fetch the record(s) you want update. Always use one of the $get... methods of the tBase table class.

      Do List.$getWhere(WhereRowOrWhereText) Return FlagOK
    3. Set the data list variable to be a smartlist.

      Do List.$smartlist.$assign(kTrue)
    4. Make your changes to the smarlist.
      1. To add lines use Do List.$addNewLine()
      2. To update lines make sure the line you want to update is the current line, and use Calculate List.ColName as Value
      3. To delete lines use Do List.$remove(LineNum) or Do List.$remove(kListDeleteSelected) or Do List.$remove(kListKeepSelected)

    5. When you are ready to update the database issue a $doworkBatch

      Do List.$doworkBatch() Return FlagOK

    There is a lot more that the StudioWorks framework tBase table class can do for you. Study the public methods of tBase to see what is available to you. See StudioWorks > SQL documentation for more info.

    SubWindows

    Every window and subwindow that you instantiate in StudioWorks must have a unique window instance ID. We refer to the unique window instance as the WinInstID.

    The WinInstID, CountryList, would be a headed list window used for displaying a list of Countries in the Country table.

    The WinInstID, CountryEdit would be a window with entry fields used for editing new or existing Country table records.

    Default WinInstIDs are automatically generated by StudioWorks for every server table based schema class in each module. The WinInstIDs use the naming convention TablenameList and TablenameEdit, where Tablename is the cap() value of the server table name in the database.

    You can declare your own custom WinInstIDs in the $addCustomWinInstIDs method oWindowList in each module.

    Information about each WinInstID is gathered from the oWindowsList object in each module. The information is stored in the oWindows object where it can be accessed through the task variable wn. To look at the WinInstID information you would issue:

    Do wn.$retWinListRow('CountryList') Return Row

    Virtually every WinInstID is instantiated as a subwindow inside of a parent shell window.

    The Main Window is a subclass of wShell with a navigation treelist subwindow added to it.

    The wShell window class is designed so that it can instantiate an infinite number of subwindows. The wShell window class keeps track of the subwindows it instantiates in its own subwindows list and subwindows stack.

    When you click a node in the navigation treelist a message is sent from the treelist subwindow to the main window shell asking it to prepare and open a specific WinInstID. The main window checks to see if the WinInstID has already been instantiated. If not, it adds a subwindow field, and based on the information in the windows list, it sets the $classname property of the new subwindow field. This immediately instantiates the window class inside the subwindow field. The subwindow is brought to the front by hiding the previous subwindow and making the new subwindow visible.

    Each subwindow in StudioWorks is designed to be self-contained. It can have its own title, toolbar, and pushbuttons. The subwindow has very little dependence on the shell window which instantiates it. For example, the CountryList subwindow constructs its own headed list, toolbar, and search bar. When a user presses New on the toolbar a $new message is sent to the subwindow. The subwindow finds out from the meta-data that the WinInstID, CountryEdit, is used for creating new records and it sends a message to its shell asking the shell to prepare a CountryEdit WinInstID. The shell prepares the WinInstID as a subwindow and returns a reference to the $new method which then sends a $newRecord message to CountryEdit. CountryList also attaches itself as an observer to CountryEdit asking to be notified if the database is changed. CountryEdit is self-contained, so it knows what to do.

    By making each subwindow as independent as possible, we can more easily use a window class in different situations. If you shift+click on a node in the navigation treelist the same subwindow will be instantiated in a separate shell; where you can click the New button and see the very same behavior.

    Container Windows

    The next level in using subwindows is to combine several subwindows inside a container window. Instead of instantiating just a ContactsList window, you might want to have the ContactList subwindow in the top half of the window and the ContactEdit subwindow in the bottom half of the window. Clicking on a contact in the list subwindow triggers displaying the contact details in the edit subwindow. A window which instantiates more than one subwindow is called a container window.

    The Town/City Lookups window is a container window. It has a tab pane interface with each tab being a separate WinInstID.

    The StudioWorks framework ships with a number of container window superclasses and templates which you can copy and play around with. These container windows have not been extensively developed or documented so you have to be prepared to do some investigative learning.

    The container window attempts to take typical window layouts and use generic code to reduce the amount of code you have to write in order to make them work. If you double-click on the subwindow field, you will find a $:InitialWinInstID property method. Enter the WinInstID you want that subwindow to intially be instantiated as.

    Good luck. Feel free to create you own custom container windows. The container windows gets into the realm of your domain because this is where things get very application and developer specific.

    From the object-oriented point of view, the shell window sends messages to the container window. The container window is fully responsible for its subwindows. A subwindow should not communicate directly to another subwindow, rather it should send the message to its container, and the container passes the message to the other subwindow.

    For example, in the contacts list/edit container window we talked about at the beginning, when the user clicks on a contact in the list, the container window should be sent a message notifying it of this event. The container then sends an $editRecord message to the edit subwindow. Try to avoid having subwindows talk directly to each other. Like a family, the parents usually know best because they see the bigger picture, so it's best to work through the parents.

    Writing Reports

    See StudioWorks > Reports documentation. Let Doug know if you need more documentation or examples.