<?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 - About (All Contents)</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 About (All Contents)</p> <a name="studioworks" /> <h2>StudioWorks</h2> <p>StudioWorks version 4 is an exciting new release of StudioWorks.</p> <p>Writing the StudioWorks version 4 release took close to two man years. (In addition to several man years invested in the earlier versions of StudioWorks.)</p> <p>Omnis Studio version 4 provided some great new features which could be utilized in StudioWorks version 4.</p> <p>One example is the addition of a <span class="code">$userinfo</span> property which was added to every class and object in Omnis Studio. StudioWorks version 4 stores meta-data for each schema class column in a row variable which is then stored in the <span class="code">$userinfo</span> property of the schema class column.</p> Many new time saving developer tools have been added to make StudioWorks version 4 a truly rapid application development framework. <a name="features" /> <h2>Features</h2> The following is an overview of the features in StudioWorks. This is not an all inclusive list of the features in StudioWorks. <a name="sqlmetadata" /> <h3>SQL Meta-Data</h3> <p>The schema classes in Omnis are used to map list and row variables in your application to the SQL database. Each schema class is mapped to a database table and the schema class columns are mapped to database table's columns.</p> <p>The Omnis schema classes keeps track of the column name, datatype, whether on not the column is a primary key column, and whether or not null values are allowed. However, the Omnis schema classes are <strong>not</strong> able to keep track of whether or not the column is indexed, if it is a foriegn key and if so what table and column the foreign key references, and other column constraints.</p> <p>The <span class="nav">StudioWorks SQL Meta-Data Editor</span> allows you to store addition information (meta-data) about each schema class column. The meta-data is kept in a row variable which is stored in the <span class="code">$userinfo</span> of each schema class column.</p> <p class="image"><img src="./images/sqlmetadataeditor.gif" alt="sqlmetadataeditor.gif" /></p> The <span class="nav">StudioWorks SQL Meta-Data Editor</span> not only stores database meta-data, it also stores GUI meta-data. The GUI meta-data allows you to preset if and how the column will be displayed in lists and entry fields, when and how users can edit the field, whether to include the field in prompts, default sorting, etc. Even the label, tooltip, and abbreviation (for list and report column headings), are stored with the GUI meta-data. Storing the label and tooltip text in the meta-data ensures that where ever the column is displayed in a window or on a report the label will be consistent and if it is used in several windows you only need to change it in a single location. <a name="databaseadministrator" /> <h3>Database Administrator</h3> <p>Setting up and keeping your database in sync with your Studio application can be a lot work. The Database Administrator tool makes it easy. Using the SQL meta-data the Database Administrator can intelligently add tables, add columns, alter column datatypes, add indexes, add primary and foreign keys, set constraints, and collations.</p> <p class="image"><img src="./images/databaseadministrator.gif" alt="databaseadministrator.gif" /></p> In an ideal world the SQL92 standard would be the same for every DBMS, but unfortunately we aren't living in an ideal world. The SQL used to rename a column for one DBMS is not necessarily the same as the next. The database administrator is structured so that custom SQL can easily be added for each DBMS. <a name="signinwindow" /> <h3>Sign In Window</h3> <p>StudioWorks provides you with a prebuilt Sign-In (Logon) window.</p> <p class="image"><img src="./images/signinwindow.gif" alt="signinwindow.gif" /></p> <p>The code that is included with the Sign-In window supports using the DBMS users security as well as the list of application users which you store a table in the database. The application users table is where you would store the user's formal name, group memberships, user security settings, email address, etc.</p> The Sign-In window defaults to the last session. Users can change session settings. <a name="sessionsmanager" /> <h3>Sessions Manager</h3> <p>Getting the session settings correct for different DBMS back ends can be difficult for developers, let alone end users. The StudioWorks Session Manager assists both developers and end users.</p> <p class="image"><img src="./images/sessionsmgrfrontbase.gif" alt="sessionsmgrfrontbase.gif" /></p> <p>Templates are provided for each DAM with text explaining what information to fill in which field. If the Omnis DAM is select the fields which don't apply are hidden and the host name field is resized accordingly.</p> <p class="image"><img src="./images/sessionsmgromnis.gif" alt="sessionsmgrfrontbase.gif" /></p> Session settings are automatically saved to the local preference data file. <a name="concretizer" /> <h3>Concretizer</h3> <p>StudioWorks reduces development and maintenance time by generating window instances on the fly based on the SQL meta-data. Lists, entry files, labels, and tooltips are added to windows the instance they are opened.</p> <p>The disadvantage of generating window instances on the fly is that window instantiation time suffers. There is a performance hit which the user experiences. To overcome this the <span class="nav">Concretizer</span> creates 'concrete window classes' for each of the window instances. Prior to releasing a runtime version you 'runtimize' the window instances. The runtimized window classes are put in a separate library which is then released with the development libraries. If a runtimized class is available it will be used.</p> <p class="image"><img src="./images/concretizer.gif" alt="concretizer.gif" /></p> The <span class="nav">Concretizer</span> can also be used to create a developer window class that is mapped to a schema class or a query class. The developer simply selects the window instance in the Concretizer and clicks the Developerize button. A window class with all of the fields and labels is created in the same library as the schema or query class. The window class can then be modified by the developer. <a name="errorhandler" /> <h3>Error Handler</h3> <p>Object-oriented programming places code in visual and non-visual classes. Visual classes are windows, menus, toolbars, reports, and remote forms. Non-visual classes are table classe, object classes, and task classes. By making a clear separation between visual and non-visual classes your classes can be much more reuseable. Non-visual classes used by a window class can also be used by a remote form, or even a web page that communicaes with a remote task. Non-visual classes can not have prompts, okay messages, working messages, or error messages. The dilema we run into is what to do inside a non-visual class method when it hits an error. How can it easily communicate the error back to the visual class that called the method in the non-visual class. The call quite often will have been passed through several different methods.</p> <p>One solution is to develop a large series of error codes and return the appropriate error code back to sender, however developing and remembering a table of error codes is a lot of work to create and maintain. And how do pass an error code back to a method which is expecting a list or a result value?</p> <p>The solution to all of this in StudioWorks is the error handler object. The error handler sits on the sidelines waiting for any method to log an error. The first parameter sent the log message is a reference to the method where the error occurred. Additionally the error message text, optional details, and an optional error code are sent by the method where the error occurred. The method where the error occured return false or null as may be appropriate to its sender. The error handler immediately writes the error to a log file and stores the error in a list. When the visual object where the request originated receives a returned false or null value it sends a message to the error handler asking it for the last error which was logged. The visual object then prompts the user with the error or makes an appropriate decision based on the error code.</p> By making a clear distinction between visual and non-visual classes and using the StudioWorks error handler you are able to easily develop non-visual classes which can be used by different visual classes. <a name="iconsbrowser" /> <h3>Icons Browser</h3> <p>Finding the icon you need in the Omnis icons can be tedious and time consuming. Keeping track of icons you create or trying to share icons among different libraries is a hassle. Remembering the icon by its number isn't much fun.</p> <p>The <span class="nav">StudioWorks Icons Browers</span> solves all of the above.</p> <p class="image"><img src="./images/iconsbrowser.gif" alt="iconsbrowser.gif" /></p> <p>The icons are stored in a single library. You assign a name and number to each icon. You can also assign search words and synonyms in a search list. When you need to find an icon, you open the Icons Browser, enter a search word, and hit return. The Icons Browser displays a list of icons which march or are related to your search word.</p> <p>An icons object in the StudioWorks icons library can be instantiated by any of your application's libraries. You can ask the icons object to return to you the <span class="code">iconid</span> of any named icon.</p> <p class="code">Do oIcon.$retIconID('BookPencil') Returns IconID <span class="omcomment">;; The iconid 2143 will be returned.</span></p> The StudioWorks icons library makes it easy for StudioWorks members to add icons and share icons. <a name="preferences" /> <h3>Preferences</h3> <p>Most applications need to store various user preferences on the local client computer. It make for a nicer user experience if your application remembers the last user ID, the last session settings, the last language, etc. when the user reopens your application.</p> <p>The StudioWorks preferences object provides you with this functionality. You simply add the preferences you wish to store on the local client computer to the preferences schema list definition. StudioWorks adds the appropriate getter and setter methods to the preferences object. When you close your application StudioWorks looks for a local preferences Omnis data file, and if necessary creates it, and then saves the preference values to the file. When you reopen your application StudioWorks loads the preferences from the local Omnis data file into the preferences object where they can easily be access by the your application code.</p> The local preferences data file is stored away from your application to that reinstalling the application or updating it won't wipe out the user's preferences. <a name="prompts" /> <h3>Prompts</h3> <p>The built in OK, Yes/No, No/Yes, Prompt for input prompts which come with Omnis Studio are a bit behind the times. With a 255 character limit your messages are often truncated. If you want to prompt the user with list, radio buttons, check boxes, mutliple inputs... you have to create your own prompt windows.</p> <p>StudioWorks version 4 has flexible prompt window which supports unlimited length text message, an option for including additonal message details in small text, list prompts, any mix of radio buttons, check boxes, and multiple inputs in a single prompt. Often used prompts such as Delete Record? or Save Changes? are already built for you and designed to look good on multiple platforms.</p> <p class="image"><img src="./images/promptsave.gif" alt="promptsave.gif" /></p> <p>The above prompt was opened with the following line of code:</p> Do prmpt.$promptSave('Save Changes?) Returns ButtonPressed <a name="referencesmodule" /> <h3>References Module</h3> <p>Often times in application development you need a simple lookup with optional or mandatory values which the user can enter in a field. (e.g. Mr, Ms, Mrs, Dr, Lord, Duke,...) Hard coding these into your application is not a a good idea. Creating a table in your database for each of these is overkill.</p> <p>Another scenario is that you might need a counter to keep track of the last purchase order, last invoice number, last customer ID, or last primary key assigned in each table.</p> <p>The References module is the solution for these and many more situations.</p> <p>You simply declare a unique group/subgroup combination of values (e.g. PO/PONum), a datatype (integer), and a reference type (counter). When you need the next PO number you simply an appropriate message to the references object:</p> <p class="code">Do refs.$retNextNum('PO','PONum') Returns NextPO</p> <p>or for a lookup list of name titles</p> <p class="code">Do refs.$retLookupList('NameTitles','UK') Returns UKNameTitlesList</p> There's plenty more than you can do with the references library. Store user preferences for reports, user defined businesss rules, soft coded SQL query text for reports,... the list goes on and on. <a name="reportbuilder" /> <h3>Report Builder</h3> <p>Making end user reports can be a time consuming job. Adding fields to a report class, positioning, sizing, and setting the field properties, adding report titles and column headings... use up your valuable time doing rather low level work. Time better spent elsewhere.</p> <p>The report builder object is able to generate reports on the fly for you. The StudioWorks Report Builder creates a report class and based the SQL meta-data of the schema or query class used to fetch the report records it adds the fields, labels, and titles to the report class and then prints the report. The developer can further customize the report class after it has been generated by the report builder.</p> The column heading labels use the same text as the headed list heading which is all sources from the meta-data. <a name="security" /> <h3>Security</h3> <p>Most applications require some form of security. Control over which users can access which windows and what reports. Which users can view, edit, insert, or delete records in which tables. If the DBMS supports grant and revoke privileges keeping your application's users in sync with the database privileges can be a lot work.</p> <p>Administering security on a user by user basis can be time consuming and require constant updating. Being able to put users into groups and then administernig security on a group by group basis is simpler and more practical.</p> <p>The StudioWorks Security Manager helps out with all of the above. Users can be added to groups. Both users and groups can be assigned schema class, window instance, and report instance privileges. The schema class privileges can be synchronized with the database. Users who are members of multiple groups are given the highest level of authority for each schema, window, and report for the summary of all the groups they are in.</p> <p class="image"><img src="./images/securitymanager.gif" alt="securitymanager.gif" /></p> <p>The Security Manager window provides you with simple user interface and can be instantiated as a subwindow in one of your own window classes.</p> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>Report instance security and DBMS synchronization is not yet implemented as of 2005-04-20.</p> </div> <a name="stringtables" /> <h3>String Tables</h3> <p>If now or at some point in time you wish to sell your application in another county, or even different region of one country, you'll need to be translate the labels, tooltips, column headings, menus, and window titles throughout your application. This can be a huge job.</p> <p>StudioWorks utilizes OMST's string tables for the labels, tooltips, column headings, menus, and wnidow titles. For the first language you develop the text is stored in the SQL meta-data.</p> <p class="image"><img src="./images/stringtablesviewer.gif" alt="stringtablesviewer.gif" /></p> <p>When it comes time to translate your application to another language you simply export all of the string table IDs and the current language text to a tab delimited file. The translator simply needs to add a column for each additional language and enter the translated text in that column. The file with the additional columns of translated text is added to the <span class="file">startupsettings</span> folder and loaded by StudioWorks during startup.</p> End users can localize your StudioWorks application by changing labels and tooltips to suit their business. One business refers to 'clients', another calls them 'customers'. Localized labels and tooltips are stored directly in the customer's database and loaded during startup. <a name="quickstarttutorial" /> <h2>Quick Start Tutorial</h2> <p>This section covers the steps to follow for getting started with writing a new application using StudioWorks. This quick start tutorial assumes that you have already installed Omnis Studio v4.x and are familiar with creating libraries, classes, and writing notation in Omnis Studio.</p> <p>If you are not familiar with Omnis Studio read the PDFs that come with Omnis Studio and go through the <em>StudioTips Basics Tutorial</em>.</p> <p>If are not familiar with SQL (Sequential Query Language) read some books on SQL to learn the basics of SQL.</p> <p>The StudioWorks framework will jump start writing new applications in Omnis Studio, but you need to know the basics. (Omnis Studio, writing notation, SQL)</p> <p>Print the StudioTips and StudioWorks <span class="nav">Naming Conventions</span>. Read them. Keep them handy near your desk. Learning, understanding, and using the StudioWorks naming and coding conventions will make it much easier to follow the code in StudioWorks.</p> It is highly recommended that you print this tutorial from the on-line version before attemping to do it. You can check off the steps and add your own notes to your printed copy as go through the tutorial. <a name="settingfileandlibrarynames" /> <h3>Setting File And Library Names</h3> <p>The first step is to set up the files and libraries for your new application.</p> <ol> <li>Download and unzip the lastest <span class="file">StartNewApp</span> zip file from the StudioWorks FTP server.</li> <li>Decide on a 2 or 3 letter lower case acronym for your company name which you will use as a prefix for your library names. e.g. <em>Vencor Software</em> uses the 2 letter acronym <span class="code">vs</span> for prefixing library names. <br /> <br /> The company prefix <span class="code">az</span> will be used in this quick start guide. You can substitute your own company 2 or 3 letter acronym whenever you see <span class="code">az</span> in this quick start tutorial or use <span class="code">az</span> for the tutorial. </li> <li>Decide on a short name for your application.<br /> <br /> The short app name <span class="code">Drivers</span> will be used in this quick start guide. You can substitute your own short app name whenever you see <span class="code">Drivers</span> in this quick start tutorial or use the app name <span class="code">Drivers</span>.</li> <li>Rename the unzipped <span class="file">StartNewApp</span> folder to <span class="file">Drivers</span>. The <span class="file">Drivers</span> folder is referred to as the <span class="file">APP</span> folder. The entire application is enclosed inside the <span class="file">APP</span> folder.</li> <li>Inside the <span class="file">Drivers</span> folder, rename <span class="file">open_myApp</span> to <span class="file">open_Drivers</span></li> <li>Inside the <span class="file">APP/libraries</span> folder, rename <span class="file">myAppMain</span> to <span class="file">azDriversMain</span></li> <li>Inside the <span class="file">APP/libraries/modules</span> folder, rename <span class="file">myAppModule</span> to <span class="file">azDrivers</span></li> <li>Inside the <span class="file">APP/libraries/modules</span> folder, rename <span class="file">mySysAdmin</span> to <span class="file">azSysAdmin</span></li> <li>Inside the <span class="file">APP/libraries/modules</span> folder, <strong>delete</strong> all of the other libraries. They are just demo modules.</li> <li>Inside the <span class="file">APP/data</span> folder, rename <span class="file">myAppData</span> to <span class="file">DriversData</span></li> <li>We have renamed the files, but we also need to change the <span class="code">$defaultname</span> property of each library before we <em>open</em> the Drivers application.</li> <li>Open your developer version of Omnis Studio.</li> <li>Under the <span class="nav">File</span> menu select <span class="nav">Open Libraries...</span> and navigate to the <span class="file">azDriversMain</span> library but do <strong>not</strong> click the <span class="nav">Open</span> button. Select the file but don't open it up yet.</li> <li>Hold down the <kbd>Ctrl/Cmnd</kbd> key and <em>then</em> click the <span class="nav">Open</span> button. This opens the library without running the <span class="code">Startup_Task</span></li> <li><span class="nav">F2 Browser</span> > select the <span class="code">myAppMain</span> library > <span class="nav">F6 Properties</span> > <span class="nav">Default</span> tab. Set the <span class="code">$defaultname</span> property to <kbd>azDriversMain</kbd><br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>With multiple library applications it is important that the library name remain consistent. If you don't set the <span class="code">$defaultname</span> property, the library name will change if the user changes the file name. Not a good thing!</p> </div></li> <li><span class="nav">F2 Browser</span> > select the <span class="code">myAppMain</span> library > click <span class="nav">Close Library</span></li> <li>The <span class="code">Startup_Task</span> in each module doesn't do anything so we can open those modules normally.</li> <li>Under the <span class="nav">File</span> menu select <span class="nav">Open Libraries...</span> and navigate to the <span class="file">azDrivers</span> module and click the <span class="nav">Open</span> button.</li> <li><span class="nav">F2 Browser</span> > select the <span class="code">myAppModule</span> library > <span class="nav">F6 Properties</span> > <span class="nav">Prefs</span> tab. Set the <span class="code">$defaultname</span> property to <kbd>azDrivers</kbd></li> <li><span class="nav">F2 Browser</span> > select the <span class="code">myAppModule</span> library > click <span class="nav">Close Library</span></li> <li>Under the <span class="nav">File</span> menu select <span class="nav">Open Libraries...</span> and navigate to the <span class="file">azSysAdmin</span> module and click the <span class="nav">Open</span> button.</li> <li><span class="nav">F2 Browser</span> > select the <span class="code">mySysAdmin</span> library > <span class="nav">F6 Properties</span> > <span class="nav">Prefs</span> tab. Set the <span class="code">$defaultname</span> property to <kbd>azSysAdmin</kbd></li> <li><span class="nav">F2 Browser</span> > select the <span class="code">mySysAdmin</span> library > click <span class="nav">Close Library</span></li> </ol>You are now ready to officially open the <span class="nav">Drivers</span> application. <a name="openingthedriversapp" /> <h3>Opening the Drivers App</h3> <p>To help you get started with StudioWorks the starter app comes with a prepared Omnis data file which has some tables already created and records inserted.</p> <ol> <li>Open the <span class="file">open_Driver</span> library. This opens the <span class="file">azDriversMain</span> library, which in turn opens all the libraries located in the <span class="file">APP/libraries/modules</span> folder and the libraries at the first level in the <span class="file">APP/studioworks</span> folder. The StudioWorks framework libraries are located inside the <span class="file">studioworks</span> folder.</li> <li>All going well you will get to the <span class="nav">Sign-In</span> window. </li> <li>Enter user name: <kbd>SYS</kbd> and password: <kbd>pass</kbd>. Click the <span class="nav">Sign-In</span> button. </li> <li>All going well the <span class="nav">My App</span> main menu will be installed and the main window will be opened.<br /> <br /> Feel free to click on the various nodes in the main window treelist to view lists, edit records, insert new records, etc. </li> <li><span class="nav">My App</span> menu > <span class="nav">Programmer Menu</span> > <span class="nav">Programmer Workbench</span>. This opens the StudioWorks <span class="nav">Programmer Workbench</span> window. <br /> <br /> The <span class="nav">Programmer Workbench</span> is <em>command central</em> for StudioWorks developers.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/tip.gif" alt="Tip" /><p>Use the shortcut key combination <kbd>Shift+Ctrl/Cmnd+Z</kbd> to open the <span class="nav">Programmer Workbench</span>.</p> </div></li> <li>Select the <span class="nav">String Tables</span> tab > <span class="nav">String Tables _stb Schemas Editor</span> tab</li> <li>Expand <span class="code">azDriversMain</span> node if it isn't already expanded. </li> <li>Select the <span class="code">sMn_stb</span> child node. (Click it twice of the list doesn't refresh)</li> <li>Change the description for the <span class="code">AppName</span> column from <span class="code">My App</span> to <span class="code">Drivers</span>. StudioWorks will now translate <span class="code">AppName</span> to <em>Drivers</em> where ever <span class="code">AppName</span> is used as a string table ID.</li> <li>Tab out of the label field. The main menu title should change from <span class="nav">My App</span> to <span class="nav">Drivers</span>.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/tip.gif" alt="Tip" /><p>If you wish to change an ID, click the yellow edit pencil in the ID column heading. This enables the ID fields in the grid. Click the pencil again to disable the ID column.</p> </div></li> </ol> <a name="addingatable" /> <h3>Adding a Table</h3> <p>The StudioWorks starter app comes with a set of tables and window classes which links contacts to towns/cities, and town/cities to states/provinces and countries.</p> <p>For our <span class="nav">Drivers</span> app we will add a table to store <em>autos</em> which are linked to <em>auto types</em>.</p> <p>We'll start with the parent table, <span class="code">Autotype</span>.</p> <p><strong>Create the schema class</strong></p> <ol> <li><span class="nav"><nav>Programmer Workbench</span></nav> > <span class="nav">SQL Meta-Data</span> tab > right-click on the <span class="code">azDrivers</span> node of the treelist > select <span class="nav">New Schema...</span></li> <li>Enter the name of the new schema, <kbd>sAutotype</kbd>. StudioWorks creates the <span class="code">sAutotype</span> schema for you.</li> <li>Select the <span class="code">sAutotype</span> node in the treelist, and click the <span class="nav">SQL Class Editor</span> tab. <br /> <br /> StudioWorks created the new schema for you with various default values and colulmns which you can now modify. The <span class="nav">Class Name</span>, <span class="nav">Description</span>, and <span class="nav">Server Table Name </span>are properties of the <span class="code">sAutotype</span> schema <em>class</em>. The rest of the fields in the window are <em>meta-data</em>. StudioWorks store the meta-data in the <span class="code">oModuleData</span> object class of each library.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>The power of StudioWorks lies in the meta-data. The fields in the meta-data editor have tooltips which give more information about the field. If you are unsure about a certain meta-data field hover your mouse over the field and read the tooltip.</p> </div></li> <li>Change the <span class="nav">Label (Singular)</span> from <span class="code">Autotype</span> to <kbd>Auto Type</kbd>. Tab to the next field.<br /> <br /> Notice that they <span class="nav">Label (Plural)</span> automatically changed from <span class="code">Autotypes</span> to <span class="code">Auto Types</span>. StudioWorks tries to set the plural value for you automatically.<br /> <br /> The label singular is used for the window title when you are editing a record of that table. The label plural is used for the window title when you are looking at a list of records of that table.</li> <li>Click the <span class="nav">Fetch All</span> checkbox so that it is checked. This tells StudioWorks to fetch all of the active <span class="code">autotype</span> records whenever you view them in a list window.</li> <li>Click the <span class="nav">SQL Columns Editor</span> tab.</li> <li>Right-click on the column names list and select <span class="nav">New Column...</span></li> <li>Enter the column name <kbd>AutoTypeName</kbd>.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/warning.gif" alt="Warning" /><p>You must be very careful to use the correct <em>case</em> for schema class and column names. Omnis Studio is case-sensitive with class names and column names. Naming the schema class <span class="code">sAutotype</span> and then referring to it as <span class="code">sAutoType</span> in another place will not work. Your code will fail if you name a schema column <span class="code">AutoTypeName</span>, but then refer to it as <span class="code">AuotypeName</span> in a calculation.</p> </div></li> <li>Drag the <span class="nav">AutoTypeName</span> column to the 2nd row of the columns list if it isn't already the 2nd row.</li> <li>Set the character length field to 20 characters.</li> <li>Set the <span class="nav">Label</span> to <kbd>Auto Type Name</kbd>. Set the <span class="nav">Abbreviation</span> to <kbd>Auto Type</kbd>.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>StudioWorks will default to use the label value for the abbreviation. Abbreviations are used for headed list column headings, report column headings, and pushbuttons.</p> </div></li> <li>Set the <span class="nav">Unique Index</span> checkbox to checked.</li> <li>Uncheck <span class="nav">Null</span> and <span class="nav">Blank</span> in the <span class="nav">Values Allowed</span> group box.</li> <li>Check <span class="nav">Include in Lists</span>, <span class="nav">Prompts/Lookups</span>, and <span class="nav">Include in Search Columns</span> in the <span class="nav">Lists</span> group box.</li> <li>Set the <span class="nav">Sort Column</span> checkbox to checked.</li> </ol><div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>Entry fields in the meta-data editor with a blue background are default values. If you edit a default value the background will change to white, indicating a non-default value. To revert to a default value, clear the entry field and tab out of the field.</p> </div><p><strong>Create the table in the database</strong></p> <ol> <li>Click the <span class="nav">Database Admin</span> tab in the <span class="nav">Programmer Workbench</span>. The <span class="nav">Database Admin</span> window is where you can sync your database tables to match your application's schemas. It also lets you view data, update primary keys, and a number of other database related functions.</li> <li>Click the <span class="nav">Schemas and Tables</span> tab.</li> <li>Select <span class="code">autotype</span> in the <span class="nav">Schema Table</span> list. </li> <li>Click the <span class="nav">Sync Database to Match Selected Schema</span> button. This creates the new table in the database with the appropriate indexes. An information window will be opened that tells you the SQL script that was executed, actions taking, and if applicable and warnings or errors that were reported during the sync operation. Review the information and close the window.</li> </ol>You now have a table in the database ready to store records. <a name="addingawindow" /> <h3>Adding a Window</h3> <p>Every window instance which you open in your StudioWorks application must first be <em>declared</em>. Each module declares its own window instances.</p> <p>Each window instance must have a unique <span class="code">wininstid</span>. StudioWorks lets you set properties for each window instance.</p> <p>The <span class="nav">Window Instance</span> tab of the <span class="nav">Programmer Workbench</span> is where we can view and declare window instances.</p> <ol> <li>Click the <span class="nav">Window Instances</span> tab in the <span class="nav">Programmer Workbench</span>.</li> <li>Expand the <span class="code">azDrivers</span> node in the treelist.</li> <li>Note the <span class="code">AutotypeEdit</span> and <span class="code">AutotypeList</span> nodes in the treelist. StudioWorks automatically declares an <em>edit</em> and a <em>list</em> window instance for each schema class that you create.</li> <li>Click the <span class="code">AutotypeList</span> node and look at the window instance properties. The fields in the upper portion of the window are display-only because this is a <em>default</em> window instance.</li> <li>Make sure the <span class="code">toolbarvisible</span> and <span class="code">searchbarvisible</span> checkboxes are checked for the <span class="code">AutotypeList</span> window instance.</li> </ol> <a name="testingthewindow" /> <h3>Testing the Window</h3> <p>We are now ready to test opening the <span class="code">AutotypesList</span> window instance.</p> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/tip.gif" alt="Tip" /><p>Omnis Studio seems to cache the list of database tables when working with the Omnis data file. After you add some new tables you might need to <span class="nav">Quit Omnis</span> and reopen the application to force Omnis Studio to rebuild the database tables list. This only applies to the Omnis data file. If in the next few steps or later in the tutorial that you get some SQL errors after you've added a new table to the Omnis data file, try quitting Omnis and reopening the app.</p> </div><ol> <li>With the <span class="code">AutotypeList</span> node selected in the treelist, click the <span class="nav">Open WinInst</span> button at the bottom of the <span class="nav">Programmer Workbench</span> window.</li> <li>Click the <span class="nav">New</span> button in the toolbar. The <span class="code">AutotypeEdit</span> autoconfig window instance should appear.</li> <li>Enter the <span class="nav">Auto Type Name</span> <kbd>Truck</kbd>. Click the <span class="nav">Save and Close</span> button or press the <span class="nav">Return</span> key.</li> <li>Click the <span class="nav">New</span> button in the toolbar.</li> <li>Enter the <span class="nav">Auto Type Name</span> <kbd>Car</kbd>. Click the <span class="nav">Save and Close</span> button.</li> <li>Click the <span class="nav">New</span> button in the toolbar.</li> <li>Enter the <span class="nav">Auto Type Name</span> <kbd>Bus</kbd>. Click the <span class="nav">Save and Close</span> button.</li> </ol>Congratulations you have created a table and inserted some records into the table! Easy, eh?! <a name="mappingicons" /> <h3>Mapping Icons</h3> <p>In StudioWorks you can map an <span class="code">iconid</span> to a <span class="code">wininstid</span> or a <span class="code">basetable</span>. Mapping an <span class="code">iconid</span> to a <span class="code">basetable</span> is more efficient because multiple window instances that use to the same <span class="code">basetable</span> will immediately be mapped to the same <span class="code">iconid</span>.</p> <p>For window instances that do not have a <span class="code">basetable</span> (e.g. container windows), or window instances that you want to map to a different <span class="code">iconid</span>, you can map an <span class="code">iconid</span> to the <span class="code">wininstid</span>.</p> <ol> <li>Click the <span class="nav">Icons List Editor</span> tab in the <span class="nav">Programmer Workbench</span>.</li> <li>Click the <span class="nav">Open Icons Browser</span> button and take a look at the catalogue of StudioWorks icons available to you. The StudioWorks <span class="nav">Icons Browser</span> is a collection of icons in the <span class="code">swIcons4</span> library. Each <span class="code">iconid</span> is mapped to an <em>icon group</em> and an <em>icon name</em>.</li> <li>Enter <kbd>car</kbd> in the search field and click the search button.</li> <li>Right-click on the <span class="code">PaperCar</span> icon and select <span class="nav">Copy Icon Full Name to Clipboard</span>.</li> <li>Close the StudioWorks <span class="nav">Icons Browser</span> window.</li> <li>Select the <span class="code">azDrivers</span> library in the <span class="nav">Icons List Editor</span> droplist.</li> <li>Right-click on the icons list and select <span class="nav">New Icon...</span> </li> <li>Enter <kbd>Autotype</kbd> in the following prompt for input window. Autotype will be added to the icons list.</li> <li>Set the <span class="nav">schemacoldesc</span> field to <kbd>vs.PaperCar</kbd> (You can paste this in from the clipboard). When you tab out of the field, the <span class="code">Autotype</span> icon in the icons list should change to the <em>PaperCar</em> icon.</li> <li>Click the <span class="nav">Window Instances</span> tab in the <span class="nav">Programmer Workbench</span>. The <span class="code">AutotypeEdit</span> and <span class="code">AutotypeList</span> window instances should now be mapped to the <span class="code">$iconid</span> of the <span class="code">vs.PaperCar</span> icon name.</li> <li>Go back to the <span class="nav">Icons List Editor</span> and select the <span class="code">azDriversMain</span> library in the droplist.</li> <li>Change the <span class="nav">schemacoldesc</span> to <kbd>vs.Truck</kbd> and tab out of the field.</li> <li>Close and reopen the <span class="nav">Main Window</span>. The root node of the navigation treelist should have changed from a <em>cube</em> icon to a <em>truck</em> icon.</li> </ol><div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>When you add an icon node to the <span class="nav">Icons List Editor</span> treelist, you are really adding a new row to the <span class="code">sIcons</span> schema class of the parent node library. The <span class="nav">Icons List Editor</span> is just an interface for editing the <span class="code">sIcons</span> schema classes. The advantage of using the <span class="nav">Icons List Editor</span> is that you immediately see a represention of the icon which you assigned, and the cached lists affected by your changes are immediately updated. (icons master list, windows master list, navigation menus list)</p> </div> <a name="addingawindowtothenavigationlist" /> <h3>Adding a Window to the Navigation List</h3> <p>The <span class="nav">Navigation List Editor</span> is used to add/remove window instances to/from the main window's navigation treelist.</p> <p>Let's add the <span class="nav">AutotypesList</span> window instance to the navigation treelist.</p> <ol> <li>Click the <span class="nav">Window Instances</span> tab in the <span class="nav">Programmer Workbench</span>.</li> <li>Click the <span class="nav">Navigation Menus List Editor</span> tab.</li> <li>Expand the <span class="code">Drivers</span> node in the treelist.</li> <li>Right-click the <span class="code">Drivers</span> node and select <span class="code">Add WinInstID...</span></li> <li>Select the <span class="code">azDrivers</span> > <span class="code">AutotypeList</span> node in the prompt window and click the <span class="nav">Continue</span> button.</li> <li>All going well the <span class="code">AutotypeList</span> window instance will be added as a child node to the <span class="code">Drivers</span> root node.</li> <li>You can reorder the navigation list by dragging and dropping node(s) in the treelist.</li> <li>Open the main window (Ctrl/Cmnd+M). Expand the <span class="nav">Drivers</span> root node. All going well the <span class="nav">Auto Types</span> window instance will be appear as a child node.</li> <li>Click on the <span class="nav">Auto Types</span> node. The <span class="code">AutotypeList</span> window instance will appear in a subwindow to the right of the navigation treelist.</li> </ol> <a name="addingachildtable" /> <h3>Adding a Child Table</h3> <p>The steps for creating the <span class="code">Auto</span> table are pretty much the same as the steps for creating the <span class="code">Autotype</span> table.</p> <p><strong>Create the schema class</strong></p> <ol> <li><span class="nav"><nav>Programmer Workbench</span></nav> > <span class="nav">SQL Meta-Data</span> tab > <span class="code">azDrivers</span> > right-click <span class="nav">New Schema...</span>. </li> <li>Name the new schema, <kbd>sAuto</kbd>.</li> <li>Select the <span class="nav">SQL Class Editor</span> tab.</li> <li>The <span class="nav">Label (Singular)</span> and the <span class="nav">Label (Plural)</span> values should already be set for you. If not, set them to <kbd>Auto</kbd> and <kbd>Autos</kbd>.</li> <li>Set the <span class="nav">Fetchall</span> checkbox to checked.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>If the <span class="code">Auto</span> table was going to contain hundreds of records you would <strong>not</strong> check fetchall.</p> </div></li> <li><span class="nav">SQL Columns Editor</span> tab > right-click on the columns list > select <span class="nav">New Column...</span></li> <li>Enter the name: <kbd>AutoName</kbd></li> <li>Set the character length to <kbd>50</kbd>.</li> <li>Drag the <span class="code">AutoName</span> column to the 2nd row in the second row in the columns list.</li> <li>Set the <span class="nav">Label</span> to <kbd>Auto Name</kbd> if it isn't already set for you. Leave the abbreviation as is.</li> <li>Set the <span class="nav">Unique Index</span> checkbox to checked.</li> <li>Uncheck <span class="nav">Null</span> and <span class="nav">Blank</span>.</li> <li>Check <span class="nav">Include in Lists</span>, <span class="nav">Prompts/Lookups</span>, <span class="nav">Include in Search Columns</span>, and <span class="nav">Sort Column</span>.</li> <li>Add another column <span class="code">Autotype_fkey</span>.</li> <li>Drag the <span class="code">Autotype_fkey</span> column to the 3rd row in the columns list.</li> <li>Set the data type to <kbd>Integer</kbd>, and the subtype to <kbd>Long Int</kbd><br /> <br /> This is the <em>foreign key</em> column which is used to link a <em>child</em> <span class="code">Auto</span> record to its <em>parent</em> <span class="code">Autotype</span> record.</li> <li>Set the <span class="nav">Foreign Key</span> checkbox to checked. Based on the column name StudioWorks will find the autotype table and set it as the referenced table, and then find the primary key column in the autotype table and set it to the referenced column name.</li> <li>Set the <span class="nav">Cascade Delete</span> checkbox to <strong>unchecked</strong> if it checked.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/warning.gif" alt="Warning" /><p>If cascade delete is checked, child records are automatically deleted when the parent record is deleted. You want to be very careful with cascade deletes!</p> </div></li> <li>Set the <span class="nav">Null</span>, <span class="nav">Zero</span> and <span class="nav">Negative</span> checkboxes to unchecked in the values allowed group. This enforces every child record to be linked to a parent record. Allowing null or zero values in the foreign key makes the child/parent link optional. </li> <li>Set the <span class="nav">Hidden</span> checkbox to checked if it is unchecked.</li> </ol> <p><strong>Create the table in the database</strong></p> <ol> <li><span class="nav">Programmer Workbench</span> > <span class="nav">Database Admin</span> tab > <span class="nav">Schemas and Tables</span> tab.</li> <li>Select <span class="code">Auto</span> in the <span class="nav">Schema Table</span> list. </li> <li>Click the <span class="nav">Sync Database to Match Selected Schema</span> button. This creates the new table in the database with the appropriate indexes.</li> </ol> <a name="creatingqueryclasses" /> <h3>Creating Query Classes</h3> <p>We need to create query classes which joins the child <span class="code">Auto</span> records to their parent <span class="code">Autotype</span> records.</p> <p><strong>Create the list view query class</strong></p> <ol> <li><span class="nav">Programmer Workbench</span> > <span class="nav">Query Builder</span> tab.</li> <li>Expand the <span class="code">azDrivers</span> node in the <span class="nav">Queries</span> treelist on the <strong>left</strong> side of the <span class="nav">Query Builder</span> window.</li> <li>Right-click on the <span class="code">azDrivers</span> node and select <span class="nav">New Query...</span>.</li> <li>Name the new query, <kbd>qAutoList</kbd></li> <li>Expand the <span class="code">azDriver</span> node in the <span class="nav">Schemas</span> treelist on the <strong>right</strong> side of the <span class="nav">Query Builder</span> window.</li> <li>Select the <span class="code">sAuto</span> schema class node.</li> <li>Drag the columns: <span class="code">auto_pkey</span> and <span class="code">AutoName</span> onto the <span class="code">qAutoList</span> query class.</li> <li>Select the <span class="code">sAutotype</span> schema class node.</li> <li>Drag the column: <span class="code">AutoTypeName</span> onto the <span class="code">qAutoList</span> query class.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>Based on the foreign keys meta-data the StudioWorks <span class="nav">Query Builder</span> will make its best guess at calculating the <span class="code">$extraquerytext</span> to join the query class tables as you add or remove query class columns. If the <span class="code">$extraquerytext</span> is not being calculated click the <span class="nav">Rebuild Meta-Data</span> button below the schemas treelist and then click the <span class="nav">Extra Query Text</span> button to recalculate the <span class="code">$extraquerytext</span>.<br /> <br /> Always check, and if needed, correct the <span class="code">$extraquerytext</span> when you have finished changing the columns. You are ultimately responsible for the final <span class="code">$extraquerytext</span>.</p> </div></li> <li>The <span class="nav">Extra Query Text</span> in <span class="nav">Query Builder</span> should read as follows:<br /> <br /> <p class="code">WHERE auto.autotype_fkey = autotype.autotype_pkey</p> <br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/tip.gif" alt="Tip" /><p>The Omnis data file is picky about the order of the foreign key and primary key columns. To help you remember the order think about alphabetic order... <em>fkey</em> comes before <em>pkey</em>.</p> </div></li> </ol> <p><strong>Create the edit view query class</strong></p> <ol> <li>Right-click on the <span class="code">azDrivers</span> node in the <span class="nav">Queries</span> treelist on the <strong>left</strong> side and select <span class="nav">New Query...</span>.</li> <li>Name the new query, <kbd>qAutoEdit</kbd></li> <li>Expand the <span class="code">azDriver</span> node in the <span class="nav">Schemas</span> treelist on the <strong>right</strong> side of the <span class="nav">Query Builder</span> window.</li> <li>Drag and drop the <span class="code">sAuto</span> schema class node on the <span class="nav">Query Columns</span> list. The column name value in the query columns list will be empty. By leaving the column name empty Omnis Studio will automatically include <strong>all</strong> of the columns from <span class="code">sAuto</span> schema class in the query class.</li> <li>Select the <span class="code">sAutotype</span> schema class node.</li> <li>Drag the column: <span class="code">AutoTypeName</span> onto the <span class="code">qAutoList</span> query class.</li> <li>Make sure the <span class="code">$extraquerytext</span> reads as follows: <br /> <br /> <p class="code">WHERE auto.autotype_fkey = autotype.autotype_pkey</p> </li> </ol> <a name="addingthechildwindow" /> <h3>Adding the Child Window</h3> <p>Map an icon to the <span class="code">Auto</span> table adding it to the icons list.</p> <ol> <li>Click the <span class="nav">Icons List Editor</span> tab in the <span class="nav">Programmer Workbench</span>.</li> <li>Select the <span class="code">azDrivers</span> library in the droplist. </li> <li>Right-click on the icons list and select <span class="nav">New Icon...</span> </li> <li>Enter <kbd>Auto</kbd> in the prompt window.</li> <li>Enter <kbd>vs.Car</kbd> in the <span class="nav">schemacoldesc</span> field. When you tab out of the field, the Auto icon should display a car.</li> </ol> <p>Add the <span class="nav">Autos</span> window to the navigation list.</p> <ol> <li><span class="nav">Programmer Workbench</span> > <span class="nav">Window Instances</span> tab > <span class="nav">Navigation Menus List Editor</span> tab.</li> <li>Expand the <span class="code">Drivers</span> node in the treelist.</li> <li>Right-click the <span class="code">Drivers</span> node and select <span class="code">Add WinInstID...</span></li> <li>Select the <span class="code">azDrivers</span> > <span class="code">AutoList</span> node in the prompt window and click the <span class="nav">Continue</span> button.</li> <li>All going well the <span class="code">AutoList</span> window instance will be added as a child node to the <span class="code">Drivers</span> root node.</li> <li>You can drag the <span class="code">AutoList</span> node to the position you want it to appear in the navigation list.</li> </ol> <p>We are now ready to test opening the <span class="code">AutoList</span> window instance.</p> <ol> <li><span class="nav">Drivers</span> > menu > select <span class="nav">Main Window</span>. <span class="nav">Autos</span> should now appear in the main window navigation treelist as a child of the <span class="nav">Drivers</span> node. </li> <li>Click the <span class="nav">Autos</span> node. The <span class="code">AutosList</span> autoconfig window instance should appear in the main window navigation treelist. If the main window was open you will need to close and reopen it.</li> <li>Click the <span class="nav">New</span> button in the toolbar. The <span class="code">AutoEdit</span> autoconfig window instance should appear.</li> <li>Enter the <span class="nav">Auto Name</span> <kbd>Ford F150</kbd>.</li> <li>Tab to the <span class="nav">Auto Type Name</span> field. The red border on the field indicates that it is a <em>lookup type-ahead</em> entry field.</li> <li>Type a <kbd>?</kbd> character in the field. StudioWorks displays a list of the possible auto types to select from.</li> <li>Hit the <kbd>down</kbd> and <kbd>up</kbd> arrow keys. StudioWorks scrolls the selected line in the droplist.</li> <li>Hit the <kbd>backspace</kbd> key and type the character <kbd>T.</kbd> StudioWorks displays a list of the auto types which begin with the letter T.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>The Omnis data file is case-sensitive, so you must type upper case T for this example. For other databases the lookups are case-insensitive.</p> </div></li> <li>Click the droplist button to the right of the <span class="nav">Auto Type Name</span> field. StudioWorks displays a list of the possible auto types to select from.</li> <li>Click the <kbd>Truck</kbd> auto type in the droplist. StudioWorks closes the droplist and enters <kbd>Truck</kbd> in the entry field.</li> <li>Tab out of the field.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>On <span class="code">evAfter</span> StudioWorks sets the <span class="code">Autotype_fkey</span> column in the <span class="code">qAutoEdit</span> list to the <span class="code">Autotype_pkey</span> of the selected auto type record.</p> </div></li> <li>Click the <span class="nav">Save and Close</span> button.</li> <li>Click the <span class="nav">New</span> button in the toolbar.</li> <li>Enter the <span class="nav">Auto Name</span> <kbd>Jaguar</kbd> and the <span class="nav">Auto Type Name</span> <kbd>Car</kbd>. </li> <li>Click the <span class="nav">Save and Close</span> button.</li> </ol>Congratulations you have created a child table and inserted some child records linked to parent records! <a name="linkingtoanotherparenttable" /> <h3>Linking to Another Parent Table</h3> <p>Try the following assignment on your own.</p> <ol> <li>Add a foreign key column to the <span class="code">sAuto</span> schema to link it to the <span class="code">sContact</span> schema. </li> <li>Set the meta-data. <br /> <br /> You will need to set the <span class="nav">Null</span> allowed and <span class="nav">Zero</span> allowed values to checked so that linking auto records to contacts records will be optional.</li> <li>Sync the <span class="code">sAuto</span> table to the database.<br /> <br /> The <span class="code">contact_fkey</span> column in the <span class="code">Auto</span> table will be empty for the existing records in the <span class="code">Auto</span> table. This will break the joins for those records. To fix this we will need to add a zero primary key record to the <span class="code">contact</span> table and set the <span class="code">contact_fkey</span> to zero for all the columns in the <span class="code">auto</span> table as follows:</li><ol> <li><span class="nav">Programmer Workbench</span> > <span class="nav">Database Admin</span> tab > <span class="nav">Schemas and Tables</span> tab > select <span class="code">contact</span></li> <li>Click the <span class="nav">Insert Empty Records</span> button. This checks for an empty record and if not found inserts an empty record with the primary key set to zero.</li> <li>Select the <span class="nav">Interactive SQL</span> tab and enter the following SQL statement:<br /> <br /> <span class="code">UPDATE auto SET contact_fkey = 0 WHERE contact_fkey IS NULL</span></li> <li>Click the <span class="nav">Execute SQL</span> button.</li> <li>Click the <span class="nav">Data Viewer</span> tab and select the <span class="code">auto</span> table. Scroll across to the far right column to check the values in the <span class="code">contact_fkey</span> column.</li> </ol> <p> <li> Using <span class="nav">Query Builder</span> modify the <span class="code">qAutoList</span> and <span class="code">qAutoEdit</span> query classes to include the <span class="code">ContactSortName</span> column from <span class="code">sContact</span>.</p> <p>Be sure the <span class="code">$extraquerytext</span> text joins the <span class="code">Auto</span> table to the <span class="code">Contact</span> table.</p> <p> <li> If you are using the Omnis data file you may need to quit Omnis and reopen your application before proceeding.</p> <p> <li> Select the <span class="nav">Autos</span> node in the main window navigation treelist.</p> <p> <li> Edit an existing <span class="code">auto</span> record and link it to a contact.</p> <p> <li> Insert a new <span class="code">auto</span> record linked to an <span class="code">autotype</span> and a <span class="code">contact</span>.<br /> </ol></p> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>The <span class="nav">Contact Sort Name</span> labels can be changed to <span class="nav">Driver Sort Name</span> in the <span class="code">AutoList</span> and <span class="code">AutoEdit</span> window instances by changing the <span class="nav">String Table</span> name in the class meta-data of the <span class="code">qAutoList</span> and <span class="code">qAutoEdit</span> query classes. The quick start tutorial doesn't cover how to do this, but it is good know that you can create different labels for different query classes with the meta-data.</p> </div> <a name="defaultreports" /> <h3>Default Reports</h3> <p>The StudioWorks framework auto-creates a report when you click the <span class="nav">Print</span> button in the list view toolbar. The report includes all of the columns and records data displayed in the list.</p> <p>After you print a report you will find an <span class="code">X_</span> prefixed report class in the same library as the list query class. The report class name is based on the <span class="code">WinInstID</span> of the list view window instance. If you remove the <span class="code">X_</span> prefix from the report class, the StudioWorks framework will continue to use the report class as the default report for that <span class="code">WinInstID</span>. You can then open the default report class, reposition fields, and make as many modifications as you like. Your changes will be evident the next time you click the <span class="nav">Print</span> button in the list view toolbar.</p> See the StudioTips > StudioWorks group > <span class="nav">Reports</span> tab for information on creating custom reports. <a name="exportingdata" /> <h3>Exporting Data</h3> If you click the <span class="nav">Export</span> button in a list view window the StudioWorks framework exports the columns and records data displayed in the list. The records are exported to a tab delimited text file. The column headings are included at the top of the file. <a name="addingmenus" /> <h3>Adding Menus</h3> <p>You can add menus to any window instance. The menus appear in the toolbar.</p> <p>To add a <span class="nav">Special</span> menu to the <span class="code">AutoList</span> window instance:</p> <ol> <li><span class="nav">Programmer Workbench</span> > <span class="nav">Window Instances</span> tab > <span class="nav">Window Menus List Editor</span> tab.</li> <li>Expand the <span class="code">azDrivers</span> library node.</li> <li>Right-click on the <span class="code">AutoList</span> node and select <span class="nav">Add Special Menu...</span></li> <li>Enter <kbd>ExportAutoTables</kbd> as the <span class="code">menulineid</span> in the prompt.<br /> <br /> All going well a <span class="code">Special</span> menu child node will be added to the <span class="code">Autos</span> node with the menu line, <span class="code">Export Auto Tables</span>, added as a child node of the <span class="code">Special</span> menu.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>When you add a menu line or change the <span class="nav">menulineid</span>, StudioWorks automatically adds the <span class="code">menulineid</span> the <span class="code">sMn_stb</span> schema class in that library and updates the string tables. You can further edit the string table text in the <span class="nav">String Tables _stb Schemas Editor</span>.<br /> <br /> StudioWorks adds a <em>recipient</em> method with the same name as the <span class="code">menulineid</span> to the observer object class specified for the menu line. If the <span class="code">menuid</span> is <span class="code">Reports</span>, then a recipient method is also added to the <span class="code">oPrintReport</span> object in the same library.<br /> <br /> You can click the edit pencil next to the menuclassname to go to the specified menu class. You can click the edit pencil next to the observer objectclassname to go to th observer object method that is called by this menu item.</p> </div></li> <li>Click the <span class="code">Export Auto Tables</span> node if it isn't already selected. The default values for the menu line are displayed in the fields to the right of the treelist. You can change the menu line's properties by editing these fields.</li> <li>Close and reopen the <span class="nav">Main Window</span> and select the <span class="nav">Autos</span> node.</li> <li>Click the <span class="nav">Special</span> menu in the far right side of the toolbar and select <span class="nav">Export Autos Table</span>. <br /> <br /> All going well you will end up at the breakpoint of the <span class="code">$ExportAutoTable</span> method of the <span class="code">oSpecialMenuObserver</span> object. You would write your code in this method to fetch all the records from the auto table and export them to a file.</li> </ol> <p>To add a <span class="nav">Reports</span> menu to the <span class="code">AutoList</span> window instance:</p> <ol> <li><span class="nav">Programmer Workbench</span> > <span class="nav">Window Instances</span> tab > <span class="nav">Window Menus List Editor</span> tab.</li> <li>Expand the <span class="code">azDrivers</span> library node.</li> <li>Right-click on the <span class="code">AutoList</span> node and select <span class="nav">Add Reports Menu...</span></li> <li>Enter <kbd>AutosReportSortByType</kbd> as the <span class="code">menulineid</span> in the prompt.<br /> <br /> All going well a <span class="code">Reports</span> menu child node will be added to the <span class="code">Autos</span> node, with the <span class="nav">Autos Report Sort By Type</span> menu line as a child node of the <span class="code">Reports</span> menu.<br /> <br /> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>The StudioWorks framework will add the object classes <span class="code">oReportsMenuObserver</span> and <span class="code">oPrintReport</span> to a module if they don't exist when the first report menu item has been declared. <br /> <br /> The <span class="code">oReportsMenuObserver</span> is classified as a <em>visual</em> object class. If you need to prompt the user to enter/select criteria before printing the report, do the prompts from this <em>visual</em> menu observer object. <br /> <br /> The <span class="code">oPrintReport</span> object is classified as a <em>non-visual</em> object which means you should never prompt the user or open any error prompt windows in the <span class="code">oPrintReport</span> object class methods. <br /> <br /> By splitting apart the visual code which prompts the user, from the non-visual print report code, you will be able to reuse the non-visual <span class="code">oPrintReport</span> code when you add a web interface to your StudioWorks app. Trust me, it is worth doing. Clearly separating visual class code from non-visual make for cleaner reuseable code.</p> </div></li> <li>Close and reopen the <span class="nav">Main Window</span> and select the <span class="nav">Autos</span> node.</li> <li>Click the <span class="nav">Reports</span> menu in the right side of the toolbar and select <span class="nav">Autos Report Sort By Type</span>. <br /> <br /> All going well you will end up at the breakpoint of the <span class="code">$AutosReportSortByType</span> method of <span class="code">oReportsMenuObserver</span>. You would write your code to prompt the user for the auto types they wish to include in the report and then pass this via parameters to the non-visual <span class="code">$AutosReportSortByType</span> method of <span class="code">oPrintReport</span> object.<br /> <br /> For prompting the user, use the <span class="code">oPromptModeless</span> object which is instantiated by the task variable <span class="code">modelessprmpt</span>. For more information see StudioTips > StudioWorks group > <span class="nav">Prompts</span> tab.<br /> <br /> For reports that do not require any user prompts you can remove the breakpoint and simply forward the message from the <span class="code">oReportsMenuObserver</span> object to the <span class="code">oPrintReport</span> object.</li> </ol><div class="image_sideline"><img src="http://www.studiotips.net/css/images/note.gif" alt="Note" /><p>You can associate an icon with any menu line by mapping an icon to the <span class="code">menulineid</span> in the <span class="nav">Icons List Editor</span>.</p> </div> <a name="customizingawindow" /> <h3>Customizing a Window</h3> <p>The objective of the StudioWorks framework is to allow quickly move from database design to a basic application that allows you (and your client) to insert and link records, view lists and print simple reports.</p> <p>If you are converting an existing application you can import data from the old application and then play around with viewing and massaging the imported data in the new application.</p> <p>Once you get through the database design and generating the basic application you will be into the phase of creating custom windows with added bells and whistles to make data entry and modification easier for the users.</p> <p>To create a concrete window class from an autoconfig window class:</p> <ol> <li><span class="nav">Programmer Workbench</span> > <span class="nav">Window Instances</span> tab > <span class="nav">Window Instances Editor</span> tab</li> <li>Select the <span class="code">AutoEdit</span> node.</li> <li>Click the <span class="nav">Developerize</span> button.<br /> <br /> The StudioWorks framework creates a <span class="code">wAutoEdit</span> window class in the <span class="code">azDrivers</span> module. In StudioWorks we call this a <em>developerized</em> window class. In object-oriented programming terms it is a <em>concrete</em> window class.</li> <li>Click the <span class="nav">Modify</span> button. This opens the <span class="code">wAutoEdit</span> window class in the IDE.</li> <li>Rearrange the fields on the window and add a pushbutton object from the <span class="nav">F3 Component Store</span>.</li> <li>Close the <span class="code">wAutoEdit</span> window class.</li> <li>Click the <span class="nav">Open WinInstID</span> button to open an instance of the <span class="code">wAutoEdit</span> window class.<br /> <br /> Your changes to the <span class="code">wAutoEdit</span> window class should be evident in the edit window instance.</li> </ol> <p>Using normal Omnis Studio code you can customize your window class by adding field event methods, window objects, etc. to your concrete window class.</p> <p>The following sample code is from the <span class="code">$event</span> method of the <span class="code">ContactFormalName</span> entry field of the <span class="code">wContactEdit</span> window class.</p> <p class="code">On evBefore<br /> If len($cobj.$contents)=0<br /> &nbsp;&nbsp;&nbsp;If iList.isHousehold<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate SortName as iList.ContactSortName<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;If pos(',',SortName)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate FirstName as trim(mid(SortName,pos(',',SortName)+1))<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate LastName as trim(mid(SortName,1,pos(',',SortName)-1))<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate [$cobj.$dataname] as con(FirstName,' ',LastName)<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Else<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate [$cobj.$dataname] as iList.ContactSortName<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;End If<br /> &nbsp;&nbsp;&nbsp;Else<br /> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Calculate [$cobj.$dataname] as iList.ContactSortName<br /> &nbsp;&nbsp;&nbsp;End If<br /> &nbsp;&nbsp;&nbsp;Do $cobj.$redraw()<br /> End If<br /> Quit event handler (Pass to next handler)</p> <p>The code copies the <span class="code">ContactSortName</span> to the <span class="code">ContactFormalName</span> if the <span class="code">ContactFormalName</span> is empty. For households the last name and first name are flipped from the <span class="code">ContactSortName</span> to the <span class="code">ContactFormalName</span>.</p> <div class="image_sideline"><img src="http://www.studiotips.net/css/images/tip.gif" alt="Tip" /><p>It is important to end your <span class="code">$event</span> method code with <span class="code">Quit event handler (Pass to next handler)</span> so that the StudioWorks framework's field handler can properly decorate the field on <span class="code">evBefore</span> and <span class="code">evAfter</span>.</p> </div> <a name="summary" /> <h3>Summary</h3> <p>Hopefully this quick start tutorial has given you a sense of how the StudioWorks framework can assist you with rapid application development.</p> <p>We have really only scratched the surface in this tutorial. It is extremely difficult to write a tutorial that satisfies the wants and needs of all developers. Some developers like to have incredible detail, others get bored with too much detail.</p> <p>The real learning happens when you write your own application and have to deliver it to a client.</p> <p>Don't try to get too fancy too soon. Follow the pattern of the quick start tutorial by focusing on the database design, meta-data, and auto-generated window instances. Don't get side tracked into building custom window classes too early on. Get some real data into the database and generate some reports before tackling custom window classes.</p> <p>The farther you can get with using meta-data and auto-generated windows the better off you are.</p> <p>As you start adding custom features and enhancement to your StudioWorks app you will need to search the StudioWorks documentation, look at the methods and code inside the classes, and ask questions on the StudioWorks members list.</p> <p>The real learning is about to begin. Omnis Studio, StudioWorks, and SQL are a powerful combination - but you'll need to roll up your sleeves and be very persistent in order to learn how to harness the power.</p> <p>The StudioWorks members are a great community of developers, but they can't write your application for you (at least not for free). You can ask the questions, but you have to be persistent with the learning.</p> <p>There may be days you question your decision to invest in StudioWorks, but trust me, there are several man years of code included in the framework and many more years of experience that have been invested into learning Omnis Studio and object-oriented programming techniques. There are tools and features included in the StudioWorks framework which are the combined efforts of several StudioWorks devlopers. You would spend years writing (and debugging) these tools and features on your own.</p> <p>Working together we all win!</p> <p>Happy coding,</p> Doug Kuyvenhoven<br /> <a href="http://www.vencor.ca">Vencor Software</a> <a name="whatnext" /> <h3>What Next?</h3> <p>The following is a recommended list of things to do as you move forward with developing your new StudioWorks application.</p> <ol> <li>If you haven't already done so, print the StudioTips and StudioWorks <span class="nav">Naming Conventions</span>. Read them. Keep them handy near your desk. Learning, understanding, and using the StudioWorks naming and coding conventions will make it much easier to follow the code in StudioWorks.</li> <li>If you are new to SQL or don't have formal database design training order a copy of <span class="nav">Database Design for Mere Mortals</span> by Micheal Hernandez from amazon.com. Read the book. I wish I read this book before writing my first app. Much of the thinking behind the <span class="nav">SQL Meta-Data Editor</span> came from reading this book.</li> <li>If you are new to SQL order a copy of <span class="nav">Mastering SQL</span> by Martin Gruber from amazon.com. I recommend this as a reference book for figuring out SQL scripts. You can do amazing things with SQL. With a little bit of SQL knowledge you can eliminate tons of list processing code in your application, and improve performance for compiling reports, etc. Spend time investing in learning advanced SQL, it is worth it.</li> <li>Learn to use Omnis Studio. To use StudioWorks you need to be familiar with Omnis Studio. If you aren't familiar with using Omnis Studio, you will be lost if when you try to write code in StudioWorks. Take the time to read the Omnis Studio PDFs and go through the tutorials and exercises they provide you with.</li> <li>Read through StudioTips to increase your knowledge of Omnis Studio.</li> <li>Do all of the StudioTips Tutorials. The tutorials help you understand StudioWorks.</li> <li>Learn Omnis Studio notation. Omnis Studio notation is powerful and StudioWorks uses Omnis Studio notation extensively. You can accomplish almost anything in Omnis Studio using notation. The notation becomes very logical once you begin to understand the syntax. There is no quick start tutorial to learning Omnis Studio notation. You can learn a lot by taking the time to study the comments and notation in the StudioWorks code.</li> <li>Learn to use the <span class="nav">F4 Notation Inspector</span> along with the <span class="nav">F6 Property Manager</span> to see the methods and properties. These two tools are the heart of where I figure out how to write Omnis Studio notation.</li> <li>Learn to use the <span class="nav">Interface Manager</span>. The <span class="nav">Interface Manager</span> shows the class methods you can call in an instance of class. <em>Sending messages</em> between instances of classes is a big part of Object-Oriented Programming. The <span class="nav">Interface Manager</span> reveals to you the messages you can send.</li> <li>Learn Object-Oriented Programming. Order a copy of <span class="nav">Building Object Applications That Work</span> by Scott Ambler from amazon.com, and read the book. Learning Object-Oriented Programming is a process that takes years. I'm still learning. The StudioWorks framework comes with loads of prewritten, well commented, reuseable, Object-Oriented Programming code. This gives you the advantage of live examples which you can learn from.</li> <li>Order a copy of <span class="nav">Code Complete</span> by Steve McConnell from amazon.com and read the book. This is one of the best books on writing good code. Loads of great advice. Much of the StudioWorks naming conventions are based on the recommendations in this book.</li> <li>Attend the StudioWorks <em>training camps</em> when they are held. There is nothing like in-class training. In a few days you will advance months in your application development.</li> <li>Attend Omnis conferences when they are held.</li> </ol> <p>The above list might seem daunting. If it was easy, anybody could write custom software solutions. Once you gain the above knowledge and experience you can use Omnis Studio and StudioWorks to create powerful, flexible, and maintainable custom applications in an incredibly short period of time.</p> The next tutorial, <span class="nav">Customizing Your App</span>, 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>. <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 cont