Sw4   >   About   >   Customizing Your App
Under the you will see the menu item . 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:
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 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 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.Each main library has its own mProgrammer menu class which is instantiated by the main menu as a cascading menu by the name . 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 to try out different things. If I have a batch of records I need to fix in the database, I'll often use the to write and run the code.
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.One of the keys to using the StudioWorks framework (and Omnis Studio ) is learning to use the . I wrote code using Omnis Studio for about 2 years without using the . Today, I'd be lost without the .
The 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 .
If you aren't familar with the and how to use it to write code, let's go through some exercises.
For these exercises you will be writing code in the > . 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.

Do not use # hash variables for your real app code.
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.

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.

Do not use # hash variables for your real app code.
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 .

From the method editor you can look at the of the class you are in using the IDE toolbar button.
From the you can look at the 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:
To add a toolbar button to the contacts list window:
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:
Do inherited Returns List
Do List.$add('HelloWorld')
Quit method List
There are two ways to remove a toolbar button.
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.
; 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
; 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

In your Omnis Studio > 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.
If you want to write code that is easy to maintain you must be diligent about writing good error handling into your code.

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.
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 button in the window to see a demonstration.

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.
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.
Do method setDefaultsAndPrefs Return FlagOK
Quit Method FlagOK
Do inherited Return FlagOK
If FlagOK
; Do the additional stuff you wanted to do
End if
Quit method FlagOK

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.
Only prompt the user from public visual class methods.
In the context of the StudioWorks framework visual classes are:
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:
You have several choices for prompting the user:
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:
Do List.$doworkBatch() Return FlagOK
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 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 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 button and see the very same behavior.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 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.