<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>StudioWorks - Customizing Your App</title> <meta name="keywords" content="omnis studio, raining data, studioworks documentation" /> <link rel="stylesheet" type="text/css" href="http://www.studiotips.net/css/codedoc.css" /> <link rel="stylesheet" type="text/css" href="http://www.studioworks-dev.net/css/codedoc.css" /> </head> <body> <div id="container"> <p><a href = ../index.html > Sw4 </a> &nbsp &gt; &nbsp <a href = index.html > About </a> &nbsp &gt; &nbsp Customizing Your App</p> <a name="customizingyourapp" /> <h2>Customizing Your App</h2> This tutorial takes you through adding custom features and code to the meta-data based auto-generated <span class="nav">Drivers</span> app that you wrote in the <span class="nav">Quick Start Tutorial</span>. If you haven't completed the quick start tutorial, you will need to do that first. <a name="pingbuttons" /> <h3>Ping Buttons</h3> <p>Under the <span class="nav">Programmer Menu</span> you will see the menu item <span class="nav">Programmer $ping Pushbuttons</span>. 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.</p> <p>If you click on the button a ping message tells you information about the subwindow.</p> <p>If you shift+click on the button you will end up at a breakpoint inside the subwindow. This feature is <strong>very</strong> helpful when you are debugging and customizing windows.</p> <p>I use the shift+click on the blue ping button technique for:</p> <ol> <li>Looking at ivar values such as: <span class="code">iList</span>, <span class="code">iSQLClassName</span>, <span class="code">iWinInstID</span></li> <li>Adding a red breakpoint to the <span class="code">$doCmnd</span> class methods so that I can step through the code when I click one of the toolbar buttons.</li> <li>Adding or modifying a window class method.</li> <li>Adding or modifying an <span class="code">$event</span> method of an entry field.</li> </ol> <a name="debugokmessages" /> <h3>Debug OK Messages</h3> <p>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 <span class="nav">Stack</span> 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.</p> <p>Being able to break into the method stack when an error is logged is extremely useful for solving bugs in your code.</p> <p>Click the <span class="code">Run Demo</span> button in the <span class="nav">StudioTips Browser</span> window to try out the debug OK message.</p> The debug OK message prompt is not opened for the following errors: check data, data, minor, and user errors. <a name="programmertestmethod" /> <h3>Programmer Test Method</h3> <p>Each main library has its own <span class="code">mProgrammer</span> menu class which is instantiated by the main menu as a cascading menu by the name <span class="nav">Programmer Menu AppName</span>. <em>AppName</em> is replaced with its string table translation.</p> <p>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 <span class="nav">Programmer Test Method</span> to try out different things. If I have a batch of records I need to fix in the database, I'll often use the <span class="nav">Programmer Test Method</span> to write and run the code.</p> <ol> <li>Select the main menu > <span class="nav">Programmer Menu AppName</span> > <span class="nav">Programmer Test Method</span></li> <li>This takes you to a <span class="code">Breakpoint</span> command followed by a <span class="code">Quit method</span> command.</li> <li>Below the <span class="code">Quit method</span> 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 <em>in the method stack</em>. (i.e. You haven't cleared the method task after coming into the Programmer Test Method.)</li> <li>Enter the following code <strong>below</strong> the <span class="code">Quit method</span>.<br /> <br /> <span class="code">Do stb.$getText('Mn','AppName',#S1) Return FlagOK</span></li> <li>Assuming you are still in the method stack, double-click on the line of code you just entered.</li> <li>Click the <span class="nav">Step In</span> IDE toolbar button. You should have stepped into the <span class="code">$getText</span> method of the <span class="code">oStringTable</span> object class.</li> <li>Click the <span class="nav">Step Out</span> button of the IDE toolbar. You should be back to the mProgrammer method.</li> <li>Hover over the <span class="code">#S1</span> variable in your line of code. The value should be the name of the current app.</li> </ol>The StudioWorks code only works from the <em>main library task instance</em>. 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 <span class="nav">Go</span> toolbar button. Wherever you run StudioWorks code you must be within the task instance of the main library's <span class="code">Startup_Task</span>. 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 <span class="nav">Progammer Test Method</span>, select <span class="nav">Programmer Menu</span> > <span class="nav">Programmer Test Method</span> and then double-click below the <span class="code">Quit method</span> and start stepping through your code. <a name="methodcallsormessages" /> <h3>Method Calls or Messages?</h3> <p>One of the things that was confusing to me when I got into reading the object-oriented programming books was this thing about <em>sending messages</em>.</p> <p>In Omnis I was used to the the term <em>method call</em>. If I was explaining code to someone I would say that <span class="code">MethodA</span> does a method call to <span class="code">MethodB</span> even if <span class="code">MethodB</span> was in another class.</p> <p>Instead of using the words <em>making a method call to another class</em>, the object-oriented programming world uses the word <em>sending a message</em>, which once you get your head adjusted to it, it really is a better word picture of what the code is doing.</p> <p>So, when we talk to each other about our code (or if we're talking to ourselves), let's talk about class instances <em>sending messages</em> to each other rather than using the term of making method calls.</p> <p>You can still use the term <em>method call</em> when <span class="code">MethodA</span> and <span class="code">MethodB</span> are in the same class.</p> <p>So how does this word picture help the developer? Let's use a the real world which all of use can easily relate to.</p> <ul> <li>In this world there is a class called <span class="code">Human</span></li> <li><span class="code">Doug</span> is an <span class="code">instance</span> of the class <span class="code">Human</span>.</li> <li><span class="code">Andy</span> is another <span class="code">instance</span> of the class <span class="code">Human</span>.</li> <li>The instance <span class="code">Doug</span> sends an <span class="code">$email</span> message to the instance <span class="code">Andy</span>. Doug is the <em>sender</em>. Andy is the <em>target</em>.</li> <li>In order for <span class="code">Andy</span> to receive the <span class="code">$email</span> message, Andy needs to have an <span class="code">$email</span> recipient method.</li> <li>The <span class="code">$email</span> message may contain parameters: <span class="code">pFrom, pTo, pReplyTo, bBody,</span> which must map correctly to the $email recipient method's parameters.</li> </ul> <p>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.</p> <ul> <li>In StudioWorks there is a class called <span class="code">oStringTable</span></li> <li>The <span class="code">stb</span> startup task variable is an <span class="code">instance</span> of the class <span class="code">oStringTable</span>.</li> <li><span class="nav">Programmer Menu</span> is an instance of the class <span class="code">mProgrammer</span></li> <li>The instance <span class="nav">Programmer Menu</span> sends a <span class="code">$retText</span> message to the instance <span class="code">stb</span> and includes the parameters <span class="code">('Mn','AppName')</span> with the message.</li> <li>The <span class="code">stb</span> instance receives the <span class="code">$retText</span> message with the enclosed parameters, processes the message, and returns the value, <span class="code">Drivers</span>, to the sender.</li> </ul> <p>Once you get the picture of instances sending messages to each other, a lot of things gain clarity in Omnis Studio.</p> <p>When a user clicks on a line in a headed list:</p> <ul> <li>Omnis Studio sends an <span class="code">$event</span> message to the headed list object instance. <br /> <br /> In this special case of event handling instead sending parameters to the <span class="code">$event</span> method, Omnis Studio sets the task scope variables <span class="code">pEventCode</span> and <span class="code">pRow</span> so that any instance within the task can access the event parameters.</li> <li>If the headed list object has an <span class="code">$event</span> recipient method it can receive and act on the message.</li> <li>The <span class="code">$event</span> method of the headed list object instance can then pass the message to the <span class="code">$control</span> method of the window class.</li> <li>If the window class has a <span class="code">$control</span> method it can receive the message and process it.</li> </ul> <p>When you open a library, Omnis Studio looks for a task class named <span class="code">Startup_Task</span>. If found Omnis Studio opens an <span class="code">instance</span> of the <span class="code">Startup_Task</span> class and names the instance the same name as the library name. Omnis Studio then sends the task instance a <span class="code">$construct</span> message. If the <span class="code">$construct</span> recipient method exists, the code in the method can open <em>instances</em> of menu classes, toolbar classes, and window classes. All of these instances that are opened are contained within the startup task instance.</p> 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 <em>sending messages</em> between <em>source</em> and <em>target</em> <em>class instances</em>, so it will be good if you think and talk in these terms. <a name="interfacemanager" /> <h3>Interface Manager</h3> <p>One of the keys to using the StudioWorks framework (and Omnis Studio ) is learning to use the <span class="nav">Interface Manager</span>. I wrote code using Omnis Studio for about 2 years without using the <span class="nav">Interface Manager</span>. Today, I'd be lost without the <span class="nav">Interface Manager</span>.</p> <p>The <span class="nav">Interface Manager</span> shows you the public interface to a class, that is, all the class methods which begin with the <span class="code">$</span> character.</p> <p>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 <span class="nav">Interface Manager</span>.</p> <p>If you aren't familar with the <span class="nav">Interface Manager</span> and how to use it to write code, let's go through some exercises.</p> <p>For these exercises you will be writing code in the <span class="nav">Programmer Menu AppName</span> > <span class="nav">Programmer Test Method</span>. The instructions below assume that you will write the code below the <span class="code">Quit method</span> in the <span class="code">$event</span> method of the <span class="code">Programmer Test Method</span> menu line of the <span class="code">mProgrammer</span> class method.</p> <p>Get and set the <span class="code">$:AppName</span> property in the <span class="code">oConstants</span> object class instance.</p> <ol> <li>In the method, type the word <kbd>Do</kbd> and tab to the <span class="nav">Calculation</span> line.</li> <li>In the <span class="nav">Variable</span> pane, click the <span class="nav">Task</span> tab, and find the task variable <span class="code">cn</span>.</li> <li>Right-click on <span class="code">cn</span> > select <span class="nav"><nav>Interface Manager</span></nav>.</li> <li>Scroll down the list and find the <span class="code">$:AppName</span> method.</li> <li>Drag the <span class="code">$:AppName</span> method from the <span class="nav"><nav>Interface Manager</span></nav> into the <span class="nav">Calculation</span> field of your <span class="code">Do</span> command.</li> <li>Press tab and enter <span class="code">#S1</span> in the return field. <br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/warning.gif" alt="Warning" /><p>Do not use # hash variables for your real app code.</p> </div></li> <li><span class="nav">Programmer Menu</span> > <span class="nav">Programmer Test Method</span> > double-click your line of code and click the <span class="nav">Step Over</span> button in the IDE toolbar.</li> <li>Hover your mouse over the <span class="code">#S1</span> variable in your line of code. Is the value <span class="code">Drivers</span> or whatever you named your app?</li> <li>Start a new <kbd>Do</kbd> command line.</li> <li>Drag the <span class="code">$:AppName.$assign</span> method from the <span class="nav"><nav>Interface Manager</span></nav> into the <span class="nav">Calculation</span> field. Omnis Studio very kindly includes the parameters in parenthesis for us when we drag in the method from the <span class="nav"><nav>Interface Manager</span></nav>.</li> <li>Replace <span class="code">pAppName</span> with <kbd>'Crazy Drivers'</kbd> including the single quotes.</li> <li>Double-click your line of code and click the <span class="nav">Step Over</span> button in the IDE toolbar.</li> <li>Now double-click the first <span class="code">Do</span> command and <span class="nav">Step Over</span> that line of code. The value returned to <span class="code">#S1</span> should now be <span class="code">Crazy Drivers</span>.</li> </ol> <p>Your finished code should look like this:</p> <p class="code">Do cn.$:AppName() Returns #S1<br /> <br /> Do cn.$:AppName.$assign('Crazy Drivers')</p> <p>Now we'll use the <span class="nav"><nav>Interface Manager</span></nav> 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 <em>bind</em> them to a table class. In StudioWorks you <em>bind</em> a list or row variable by sending a <span class="code">$retDefinedList</span> to the <span class="code">lsts</span> instance of the <span class="code">oSQLList</span> object class.</p> <ol> <li>Enter a <kbd>Do</kbd> command.</li> <li>Right-click the task variable <span class="code">lsts</span> > select <span class="nav"><nav>Interface Manager</span></nav>.</li> <li>Drag the <span class="code">$retDefinedList</span> method into the <span class="nav">Calculation</span> field of your <span class="code">Do</span> command.</li> <li>Replace the <span class="code">pSQLClassName</span> parameter with <kbd>'sCountry'</kbd> including the single quotes.</li> <li>Remove the other parameters, they are optional.</li> <li>Enter <kbd>#L1</kbd> in the <span class="nav">Returns</span> field.</li> <li><span class="nav">Programmer Menu</span> > <span class="nav">Programmer Test Method</span>. Double-click your line of code and click the <span class="nav">Step Over</span> button in the IDE toolbar.</li> <li>Right-click the <span class="code">#L1</span> variable > select <span class="nav">Variable #L1...</span> This opens the variable window which should display to you an empty list with the column names. Close the variable window.</li> <li>Enter another <kbd>Do</kbd> command in the next line.</li> <li>Right-click <span class="code">#L1</span> in your method code > select <span class="nav"><nav>Interface Manager</span></nav>.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>The list or row variable <span class="nav"><nav>Interface Manager</span></nav> is only available <strong>after</strong> the list or row has been <em>bound</em> 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 <span class="nav"><nav>Interface Manager</span></nav> menu line does not appear in the context menu.</p> </div></li> <li>Drag the <span class="code">$:BaseTable</span> method into the <span class="nav">Calculation</span> field of your new <span class="code">Do</span> command.</li> <li>Enter <kbd>#S1</kbd> in the <span class="nav">Returns</span> field.</li> <li>Double-click your line of code and click the <span class="nav">Step Over</span> button in the IDE toolbar.</li> <li>Hover the mouse of the <span class="code">#S1</span> variable, that value should be <span class="code">Country</span>.</li> <li>Enter another <kbd>Do</kbd> command in the next line.</li> <li>Right-click <span class="code">#L1</span> in your method code > select <span class="nav"><nav>Interface Manager</span></nav>.</li> <li>Select the $getAllRecords method in the <span class="nav">Interface Manager</span>.</li> <li>Click the <span class="nav">Description</span> tab in the bottom part of the <span class="nav"><nav>Interface Manager</span></nav>. Note the method description. <br /> <br /> 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 <span class="code"><span class="nav">Interface Manager</span></span> tells you the method name, the parameters, the method description, and the return value you can expect.</li> <li>Drag the <span class="code">$getAllRecords</span> method into the <span class="nav">Calculation</span> field of your <span class="code">Do</span> command.</li> <li>Enter <span class="code">#F</span> in the <span class="nav">Returns</span> field.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/warning.gif" alt="Warning" /><p>Do not use # hash variables for your real app code.</p> </div></li> <li>Double-click your line of code and click the <span class="nav">Step Over</span> button in the IDE toolbar.</li> <li>Right-click <span class="code">#L1</span> in your <span class="code">Do</span> command > select <span class="nav">Variable #L1...</span> <br /> <br /> All going well the variable window will show a list of country records that were fetched from the database.</li> </ol> <p>Your finished code should look like this:</p> <p class="code">Do lsts.$retDefinedList('sCountry') Returns #L1<br /> <br /> Do #L1.$:BaseTable Returns #S1<br /> <br /> Do #L1.$getAllRecords() Returns #F</p> <p>The <span class="nav"><nav>Interface Manager</span></nav> 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 <span class="nav"><nav>Interface Manager</span></nav>.</p> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/tip.gif" alt="Tip" /><p>From the method editor you can look at the <span class="code"><span class="nav">Interface Manager</span></span> of the class you are in using the <span class="nav">View</span> IDE toolbar button. <br /> <br /> From the <span class="nav">F2 Browser</span> you can look at the <span class="code"><span class="nav">Interface Manager</span></span> of any class by right-clicking on the class and selecting <span class="nav"><nav>Interface Manager</span></nav>.</p> </div><p>Take some time and study the public interface of the <span class="code">Startup_Task</span> task variables. These are the <strong>key</strong> 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.</p> <p>If you know the task variable name and have an idea of the public method name you don't have to use the <span class="nav"><nav>Interface Manager</span></nav>.</p> <p>Try the following:</p> <ol> <li>Set your <span class="nav">Omnis</span> > <span class="nav">Preferences</span> > <span class="nav">General</span> tab > <span class="code">$notationhelptimer</span> to <kbd>50</kbd> milliseconds.</li> <li>In the method editor type <span class="code">Do</span></li> <li>Press the <span class="nav">Tab</span> key to go to the <span class="nav">Calculation</span> field.</li> <li>Type the characters <kbd>err</kbd>. The task variable errhndler should show up in the notation helper. </li> <li>Press the <kbd>Tab</kbd> key and type the characters <kbd>$pr</kbd>. The method <span class="code">$promptonceLastError</span> should show up in the notation helper. </li> <li>Press the <kbd>Tab</kbd> key.</li> <li>Enter the opening and closing parenthesis <kbd>()</kbd> to finish the calculation.</li> </ol>I find the notation helper to be a <strong>huge</strong> 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 <kbd>up</kbd> and <kbd>down</kbd> 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 <span class="nav">Calculation</span> field will be used when you press the <kbd>Tab</kbd> key. <a name="addingatoolbarbutton" /> <h3>Adding a Toolbar Button</h3> <p>To add a toolbar button to the contacts list window:</p> <ol> <li><span class="nav">Main Window</span> > <span class="nav">Contacts</span> > Shift+click the blue ping button.</li> <li>Find the <span class="nav">$:ToolbarCmndsCSV</span> method in the class methods.</li> <li>Right+click the method and select <span class="nav">Override Method</span>.</li> <li>Uncomment the method lines.</li> <li>Add <kbd>,HelloWorld</kbd> to the end of the toolbar buttons string being returned by the <span class="code">Quit method</span>.<br /> <br /> <span class="code">Quit method 'Edit,New,NewCopy,Delete,SPACER,Find,Print,Export,HelloWorld'</span></li> <li>Right+Click the class methods > select <span class="nav">Insert New Method</span>.</li> <li>Name the new method <kbd>$HelloWorld</kbd> and put a breakpoint in the method.</li> <li>Close the IDE window.</li> <li>Close and reopen the main window and select the <span class="nav">Contacts</span> node.</li> </ol> <p>The <span class="code">HelloWorld</span> 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:</p> <ol> <li>Add <kbd>HelloWorld</kbd> to the <span class="code">sMn_stb</span> schema class in the module. Enter <kbd>Hello World</kbd> in the description.</li> <li>Add <kbd>HelloWorld</kbd> to the <span class="code">sIcons</span> schema class in the module. Enter <kbd>1653</kbd> in the description.</li> <li><span class="nav">Programmer Menu</span> > <span class="nav">Reload String Tables</span>. (This also rebuilds the icons list)</li> <li><span class="nav">Main Window</span> > <span class="nav">Contacts</span> > Shift+click the blue ping button.</li> <li>Find the <span class="nav">$_retActiveCmndsList</span> method in the class methods, override the method, and add the following code to the method:<br /> <br /> <p class="code">Do inherited Returns List<br /> Do List.$add('HelloWorld')<br /> Quit method List</p> </li> <li>Close and reopen the main window and select the <span class="nav">Contacts</span> node. All going well the button will have the correct text, icon, and be enabled.</li> <li>Click the <span class="nav">Hello World</span> icon. All going well you will end up at the breakpoint in the <span class="code">$HelloWorld</span> method.</li> </ol> <a name="removingatoolbarbutton" /> <h3>Removing a Toolbar Button</h3> <p>There are two ways to remove a toolbar button.</p> <ol> <li>Override the <span class="code">$:ToolbarCmndsCSV</span> method and remove the button from the CSV string being returned by the method.</li> <li>Send a <span class="code">$removeCmnd</span> message to the window instance.</li> </ol>A situation where you might use the <span class="code">$removeCmnd</span> 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 <span class="code">$removeCmnd('SaveAndClose')</span> message to the edit subwindow. The subwindow would remove the button from its toolbar. <a name="writingmethods" /> <h3>Writing Methods</h3> <p>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.</p> <ol> <li>Always <strong>return something</strong>. Either <span class="code">FlagOK</span> (true or false) or a return value.</li> <li>Only exit at the <strong>end</strong> of the method. (This guideline was added in mid 2005. Code written by me prior to mid 2005 has lots of early exit points.)</li> <li>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 <span class="code">#NULL</span>. The sender can check the return value with the <span class="code">isnull()</span> function.</li> <li>Preset the return local variable to the <strong>negative</strong> condition in the init value in the variables pane. <span class="code">FlagOK</span> to <span class="code">kFalse</span>. Return value to <span class="code">#NULL</span>.</li> <li>Always test the return flag or value from a method before proceeding further in the method. Sample code as follows:<br /> <br /> <p class="code"><span class="omcomment">; Test FlagOK</span><br /> Do List.$getAllRecords() Returns FlagOK<br /> If FlagOK<br /> &nbsp;&nbsp;&nbsp;<span class="omcomment">; Continue</span><br /> End If<br /> Quit method FlagOK</p> <br /> <p class="code"><span class="omcomment">; Test return list. Lists and rows you can not test with isnull()</span><br /> Do lsts.$retDefinedList('sContact') Returns List<br /> If List.$colcount=0<br /> &nbsp;&nbsp;&nbsp;Calculate FlagOK as kFalse<br /> Else<br /> &nbsp;&nbsp;&nbsp;<span class="omcomment">; Continue</span><br /> End If<br /> Quit method FlagOK</p> <br /> <p class="code"><span class="omcomment">; Test return string</span><br /> Do fn.$retCSVStringFromList(List) Returns String<br /> If isnull(String)<br /> &nbsp;&nbsp;&nbsp;Calculate FlagOK as kFalse<br /> Else<br /> &nbsp;&nbsp;&nbsp;<span class="omcomment">; Continue</span><br /> End If<br /> Quit method FlagOK</p> </li> <li>The method where an error occurs must immediately log the error to the error handler. Always declare and set both error message variables, <span class="code">Mssg</span> and <span class="code">Dtls</span>, even if you set <span class="code">Dtls</span> to empty.</li> <li>Only <strong>public</strong> methods of <span class="code">visual</span> classes prompt the user with errors. (windows, toolbars, menus, menu obsever objects classes) Wait till the <span class="code">end</span> of the method before prompting the user.<br /> <br /> <p class="code"><span class="omcomment">; Pretend this is a public method of a window class.</span><br /> Do lsts.$retDefinedList('sContact') Returns List<br /> If List.$colcount=0<br /> &nbsp;&nbsp;&nbsp;Calculate FlagOK as kFalse<br /> Else<br /> &nbsp;&nbsp;&nbsp;Do List.$getAllRecords() Returns FlagOK<br /> &nbsp;&nbsp;&nbsp;If FlagOK<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$smartlist.$assign(kTrue)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$sendall($ref.Active.$assign(kTrue))<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$doworkBatch() Returns FlagOK<br /> &nbsp;&nbsp;&nbsp;End If<br /> End If<br /> <br /> <span class="omcomment">; Wait till the end of the method. Test the flag. Prompt the last error if the flag is false.</span><br /> If not(FlagOK)<br /> &nbsp;&nbsp;&nbsp;Do errhndlr.$promptonceLastError()<br /> End If</p> <br /> <br /> You can avoid a lot of <span class="code">$promptonceLastError</span> 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 <span class="code">$promptonceLastError</span>.</li> <li>Only <strong>public</strong> methods of <span class="code">visual</span> 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.</li> </ol><div class="image_sideline"><img src="http://www.studiotips.net/css/images/tip.gif" alt="Tip" /><p>In your Omnis Studio <span class="nav">Preferences</span> > <span class="nav">General</span> tab set the <span class="code">$notationhelpertimer</span> property to <kbd>50</kbd> milliseconds. The notation helper will come up a <strong>lot</strong> faster and save you loads of keystrokes and typing errors.</p> </div> <a name="errorhandling" /> <h3>Error Handling</h3> <p>If you want to write code that is easy to maintain you must be diligent about writing good error handling into your code.</p> <ol> <li>Anticipate where errors might happen.</li> <li>Log every error to the error handler. </li> <li>Write clearly understandable error message text. (The <span class="code">Mssg</span> and <span class="code">Dtls</span> variables)<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>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.</p> </div></li> <li>Check the return flag or value from <strong>every</strong> method which you call and bail out if the flag or value is communicating an error.</li> <li>End every visual class public methods with an if error test which sends a <span class="code">$promptonceLastError</span> message to the error handler if an error was detected by the visual class public method code.</li> </ol> <p>The StudioWorks error handler has specific log error methods which make it easier for you to log certain types of errors. Study the different <span class="code">$log...Error</span> methods in the <span class="code">oErrorHandler</span> 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.</p> <p>In some cases the error is not a <em>program</em> error. For example the user enters an incorrect password. A non-visual class method can send a <span class="code">$logMinorError</span> message to the error handler and return false to the sender. The visual class method then sends a <span class="code">$promptonceLastError</span> message to the error handler and the user is prompted with a non-threatening error message.</p> <p>Click the <span class="nav">Run Demo</span> button in the <span class="nav">StudioTips Browser</span> window to see a demonstration.</p> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/warning.gif" alt="Warning" /><p>Do not use <span class="code">OK message</span> commands in your non-visual class methods. If you ever try to run your StudioWorks app as web server app the <span class="code">OK message</span> 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.</p> </div> <a name="overridingamethod" /> <h3>Overriding a Method</h3> <p>When overriding a superclass method in StudioWorks, to avoid duplication, you should <em>normally</em> first <span class="code">Do inherited</span> and then add your subclass specific method code.</p> <p class="code"><span class="omcomment">; Run the superclass code</span><br /> Do inherited Returns FlagOK<br /> If FlagOK<br /> &nbsp;&nbsp;&nbsp;<span class="omcomment">; Do the subclass additional code.</span><br /> End If</p> <p>Using the 4GL <span class="code">Do inherited</span> command you can get away with not declaring the parameters in the subclass, but if you put a breakpoint on the <span class="code">Do inherited</span> 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 <span class="code">Do inherited</span>, 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 <span class="code">Do inherited</span> at the top of the subclass method. See the steps below for the proper way to copy a superclass method to a subclass.</p> <p>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.</p> <p>Use the following steps to copy a superclass method to the subclass.</p> <ol> <li>Select the superclass method from the subclass. The superclass method will be in blue text.</li> <li>Double-click anywhere on the superclass code. This takes you up to the superclass method.</li> <li>Right-click on the superclass method > select <span class="nav">Copy</span>. This copies the entire method to the clipboard.</li> <li>Close the superclass methods IDE window.</li> <li>Right-Click on the superclass method in the subclass > select <span class="nav">Override Method</span>.</li> <li>Right-Click on the overridden superclass method in the subclass > select <span class="nav">Paste</span>. This copies the entire method from the clipboard to the subclass.</li> </ol>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 <strong>wrong</strong> order, which very quickly breaks your code and can lead to hours of debugging. I know this from first hand experience. <a name="callingasuperclassprivatemethod" /> <h3>Calling a Superclass Private Method</h3> 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. <ol> <li> You subclass <span class="code">oStartupTaskDefaultMethods</span> to your main library. <li> You want to add to the <span class="code">setDefaultsAndPrefs</span> method, but it is a private method called from the <span class="code">$constructMethod</span> method. </ol> Here's what you can do. <ol> <li> Find all occurrences of <span class="code">Do method setDefaultsAndPrefs</span> in <span class="code">oStartupTaskDefaultMethods</span> and change them to <span class="code">Do $cinst.$_setDefaultsAndPrefs</span> 2. Add a <span class="code">$_setDefaultsAndPrefs</span> method to the superclass which has the following code: <p class="code"> Do method setDefaultsAndPrefs Return FlagOK<br /> Quit Method FlagOK</p> <li> Override the method in your subclass and enter the following code: <p class="code"> Do inherited Return FlagOK<br /> If FlagOK<br /> <span class="omcomment">; Do the additional stuff you wanted to do</span><br /> End if<br /> Quit method FlagOK</p> <li>Send a request to the StudioWorks members list to add <span class="code">$_setDefaultsAndPrefs</span> to <span class="code">oStartTaskDefaultMethods</span> 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. <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p><strong>Background</strong> - Private methods give me much more flexibility because they are <em>inside the box</em> where I can still play with them. As soon as a method name has a <span class="code">$</span> 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.</p></div> <a name="promptingtheuser" /> <h3>Prompting the User</h3> <p>Only prompt the user from <em>public visual class methods</em>.</p> <p>In the context of the StudioWorks framework <em>visual</em> classes are:</p> <ol> <li>Window classes</li> <li>Menu classes</li> <li>Toolbar classes</li> <li>Menu observer object classes (oReportsMenuObserver, oSpecialReportsMenuObserver)</li> <li>Startup_Task <span class="code">$construct</span> and <span class="code">$destruct</span> methods.</li> </ol> <p>All other classes should be considered to be <em>non-visual</em> classes and therefore should <strong>never</strong> prompt the user.</p> <p>In the context of the StudioWorks framework <em>public methods</em> are:</p> <ol> <li>Methods which begin with the <span class="code">$</span> character, excluding those which begin with <span class="code">$_</span> character.</li> </ol> <p>You have several choices for prompting the user:</p> <ol> <li>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.</li> <li>StudioWorks <span class="code">oPrompts</span> object methods which you access via the <span class="code">prmpts</span> 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 <span class="code">stbname.stbid</span>.</li> <li>StudioWorks <span class="code">oModelessPrompts</span> object methods which you access via the <span class="code">modelessprmpt</span> 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 <span class="code">stbname.stbid</span>. <br /> <br /> 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.</li> </ol>Click the <span class="nav">Run Demo</span> button in the <span class="nav">StudioTips Browser</span> window for a demo of the different prompts. <a name="databaseandsql" /> <h3>Database and SQL</h3> <p>If flag trueThe StudioWorks framework uses the table class <span class="code">tBase</span> for all selects, fetches, inserts, updates, and deletes with the database.</p> <p>The StudioWorks framework makes extensive use of the Omnis Studio <span class="code">$smartlist</span> 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 <span class="code">$dowork</span> and Omnis Studio does the database work for you. (StudioWorks uses a modified version of <span class="code">$dowork</span> which it has named <span class="code">$doworkBatch</span>)</p> <p>Without the StudioWorks framework you would need to write at least the following code to update a batch of contact records in the database.</p> <p class="code">Calculate HostName as 'PathToTheOmnisDataFile'<br /> Calculate UserName as 'TheUserName'<br /> Calculate Password as 'ThePassword'<br /> Calculate SessionName as 'TheSessionName'<br /> Do SessionObj.$logon(HostName,UserName,Password,SessionName) Returns FlagOK<br /> If not(FlagOK)<br /> &nbsp;&nbsp;&nbsp;<span class="omcomment">; Log the $logon error here.</span><br /> Else<br /> &nbsp;&nbsp;&nbsp;Do List.$definefromsqlclass('myAppModule.sContacts')<br /> &nbsp;&nbsp;&nbsp;Calculate List.$sessionobject as SessionObj<br /> &nbsp;&nbsp;&nbsp;Do List.$select() Returns FlagOK<br /> &nbsp;&nbsp;&nbsp;If not(FlagOK)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="omcomment">; Log the SQL error here.</span><br /> &nbsp;&nbsp;&nbsp;Else<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$fetch(kFetchAll) Returns FetchStatus<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;If FetchStatus=kFetchError<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="omcomment">; Log the SQL error here.</span><br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate FlagOK as kFalse<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Else<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$smartlist.$assign(kTrue)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;For List.$line from 1 to List.$linecount step 1<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate List.ColName as 'SomeValue'<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate List.ModBy as UserName<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate List.ModDateTime as #D<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate List.EditNum as List.EditNum+1<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;End For<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$dowork() Returns FlagOK<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;End If<br /> &nbsp;&nbsp;&nbsp;End If<br /> End If<br /> Quit method FlagOK</p> <p>With the StudioWorks framework the following code accomplishes the same thing.</p> <p class="code"><span class="omcomment">; Ask the oSQLLists object to return a defined list bound to the correct session object.</span><br /> <span class="omcomment">; If there is an error oSQLLists will log the error for you.</span><br /> Do lsts.$retDefinedList('sContact') Returns List<br /> If List.$colcount<br /> &nbsp;&nbsp;&nbsp;<br /> &nbsp;&nbsp;&nbsp;<span class="omcomment">; Ask the tBase table class to select and get all the records.</span><br /> &nbsp;&nbsp;&nbsp;<span class="omcomment">; If there is an error tBase will log the error for you.</span><br /> &nbsp;&nbsp;&nbsp;Do List.$getAllRecords() Returns FlagOK<br /> &nbsp;&nbsp;&nbsp;If FlagOK<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$smartlist.$assign(kTrue)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;For List.$line from 1 to List.$linecount step 1<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate List.ColName as 'SomeValue'<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="omcomment">; Setting the admin columns (EditNum, InsBy, InsDateTime, ModBy, ModDateTime) is handled by tBase.</span><br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;End For<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="omcomment">; A custom dowork method in tBase, $doworkBatch, updates all the admin columns for you.</span><br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="omcomment">; If there is an error tBase will log the error for you.</span><br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Do List.$doworkBatch() Returns FlagOK<br /> &nbsp;&nbsp;&nbsp;End If<br /> End If<br /> Quit method FlagOK</p> <p>You can go a long ways with just the above snip of code. The code breaks down to 5 steps:</p> <ol> <li><span class="code">Define the SQL list variable</span>. Always to this by sending a <span class="code">$retDefinedList</span> message to the <span class="code">oSQLLists</span> object.<br /> <br /> <span class="code">Do lsts.$retDefinedList('SchemaOrQueryClassName') Return List</span></li> <li>Fetch the record(s) you want update. Always use one of the <span class="code">$get...</span> methods of the tBase table class.<br /> <br /> <span class="code">Do List.$getWhere(WhereRowOrWhereText) Return FlagOK</span></li> <li>Set the data list variable to be a smartlist.<br /> <br /> <span class="code">Do List.$smartlist.$assign(kTrue)</span></li> <li>Make your changes to the smarlist. </li><ol> <li>To add lines use <span class="code">Do List.$addNewLine()</span></li> <li>To update lines make sure the line you want to update is the current line, and use <span class="code">Calculate List.ColName as Value</span></li> <li>To delete lines use <span class="code">Do List.$remove(LineNum)</span> or <span class="code">Do List.$remove(kListDeleteSelected</span>) or <span class="code">Do List.$remove(kListKeepSelected)</span></li> </ol> <p> <li> When you are ready to update the database issue a <span class="code">$doworkBatch</span></p> <p><span class="code">Do List.$doworkBatch() Return FlagOK</span><br /> </ol></p> There is a lot more that the StudioWorks framework <span class="code">tBase</span> table class can do for you. Study the public methods of <span class="code">tBase</span> to see what is available to you. See StudioWorks > SQL documentation for more info. <a name="subwindows" /> <h3>SubWindows</h3> <p>Every window and subwindow that you instantiate in StudioWorks must have a <em>unique window instance ID</em>. We refer to the unique window instance as the <span class="code">WinInstID</span>.</p> <p>The WinInstID, <span class="code">CountryList</span>, would be a headed list window used for displaying a list of Countries in the Country table.</p> <p>The WinInstID, <span class="code">CountryEdit</span> would be a window with entry fields used for editing new or existing Country table records.</p> <p>Default WinInstIDs are automatically generated by StudioWorks for every server table based schema class in each module. The WinInstIDs use the naming convention <span class="code">TablenameList</span> and <span class="code">TablenameEdit</span>, where Tablename is the <span class="code">cap()</span> value of the server table name in the database.</p> <p>You can declare your own <em>custom</em> WinInstIDs in the <span class="code">$addCustomWinInstIDs</span> method <span class="code">oWindowList</span> in each module.</p> <p>Information about each WinInstID is gathered from the <span class="code">oWindowsList</span> object in each module. The information is stored in the <span class="code">oWindows</span> object where it can be accessed through the task variable <span class="code">wn</span>. To look at the WinInstID information you would issue:</p> <p><span class="code">Do wn.$retWinListRow('CountryList') Return Row</span></p> <p>Virtually every WinInstID is instantiated as a subwindow inside of a parent shell window.</p> <p>The <span class="nav">Main Window</span> is a subclass of <span class="code">wShell</span> with a navigation treelist subwindow added to it.</p> <p>The <span class="code">wShell</span> window class is designed so that it can instantiate an infinite number of subwindows. The <span class="code">wShell</span> window class keeps track of the subwindows it instantiates in its own subwindows list and subwindows stack.</p> <p>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 <span class="code">WinInstID</span>. 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 <span class="code">$classname</span> property of the new subwindow field. This immediately instantiates the window class inside the subwindow field. The subwindow is <em>brought to the front</em> by hiding the previous subwindow and making the new subwindow visible.</p> <p>Each subwindow in StudioWorks is designed to be <em>self-contained</em>. 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 <span class="code">CountryList</span> subwindow constructs its own headed list, toolbar, and search bar. When a user presses <span class="nav">New</span> on the toolbar a <span class="code">$new</span> message is sent to the subwindow. The subwindow finds out from the meta-data that the WinInstID, <span class="code">CountryEdit</span>, is used for creating new records and it sends a message to its shell asking the shell to prepare a <span class="code">CountryEdit</span> WinInstID. The shell prepares the WinInstID as a subwindow and returns a reference to the $new method which then sends a <span class="code">$newRecord</span> message to <span class="code">CountryEdit</span>. <span class="code">CountryList</span> also attaches itself as an observer to <span class="code">CountryEdit</span> asking to be notified if the database is changed. <span class="code">CountryEdit</span> is self-contained, so it knows what to do.</p> 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 <span class="nav">New</span> button and see the very same behavior. <a name="containerwindows" /> <h3>Container Windows</h3> <p>The next level in using subwindows is to combine several subwindows inside a container window. Instead of instantiating just a <span class="code">ContactsList</span> window, you might want to have the <span class="code">ContactList</span> subwindow in the top half of the window and the <span class="code">ContactEdit</span> 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 <em>container</em> window.</p> <p>The <span class="nav">Town/City Lookups</span> window is a container window. It has a tab pane interface with each tab being a separate WinInstID.</p> <p>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.</p> <p>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 <span class="code">$:InitialWinInstID</span> property method. Enter the WinInstID you want that subwindow to intially be instantiated as.</p> <p>Good luck. Feel free to create you own custom container windows. The container windows gets into the realm of <em>your domain</em> because this is where things get very application and developer specific.</p> <p>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.</p> 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 <span class="code">$editRecord</span> 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. <a name="writingreports" /> <h3>Writing Reports</h3> See StudioWorks > Reports documentation. Let Doug know if you need more documentation or examples. <p class="footer">StudioWorks Documentation - Copyright 2005 Vencor Software </p></div> </body> </html>