User Tools

Site Tools


lc_clarion_language_tutorial.htm
Navigation:  User's Guide and Tutorials > Guided Tour of Working in Clarion >====== 13 - Clarion Language Lesson ====== Previous pageReturn to chapter overviewNext page

Clarion'the Programming Language

The foundation of the Clarion application development environment is the Clarion programming language. Clarion is a 4th Generation Language (4GL) that is both business-oriented and general-purpose. It is business-oriented in that it contains data structures and commands that are highly optimized for data file maintenance and business programming needs. It is also general-purpose because it is fully compiled (not interpreted) and has a command set that is functionally comparable to other 3GL langauges (such as C/C++, Modula-2, Pascal, etc.).

By now, you should have completed all the Application Generator lessons in the preceding sections. The purpose of this language lesson is to introduce you to the fundamental aspects of the Clarion language'particularly as it relates to business programming using the Windows event-driven paradigm. Clarion language keywords are in ALL CAPS and this lesson concentrates on explaining the specific use of each keyword and its interaction with other language elements only in the specific context within which it is used. You should always refer to the Language Reference for a more complete explanation of each individual keyword and its capabilities.

When you complete this lesson, you will be familiar with:

·The basic structure of a Clarion procedural program.

·The most common event-handling code structure.

·How to compile and link hand-coded programs.

Event-driven Programming

Windows programs are event-driven. The user causes an event by CLICKing the mouse on a screen control or pressing a key. Every such user action causes Windows to send a message (an event) to the program which owns the window telling it what the user has done.

Once Windows has sent the message signaling an event to the program, the program has the opportunity to handle the event in the appropriate manner. This basically means the Windows programming paradigm is exactly opposite from the DOS programming paradigm'the operating system (Windows) tells the program what the user has done, instead of the program telling the operating system what to do.

This is the most important concept in Windows programming'that the user is in control (or should be) at all times. Therefore, Windows programs are reactive rather than proactive; they always deal with what the user has done instead of directing the user as to what to do next. The most common example of this is the data entry dialog. In most DOS programs, the user must follow one path from field to field to enter data. They must always enter data in one field before they can go on to the next (and they usually can only go on to a specific “next” entry field). This makes data validation code simple'it simply executes immediately after the user has left the field.

In Windows programs, the user may use a mouse or an accelerator key to move from control to control, at any given time, in no particular order, skipping some controls entirely. Therefore, data validation code should be called twice to ensure that it executes at least once: once when the user leaves the entry control after entering data, and again when the user presses OK to leave the dialog. If it isn't executed on the OK button, required data could be omitted. This makes Windows programs reactive rather than proactive.

Hello Windows

Traditionally, all programming language lessons begin by creating a “Hello World” type of program'and so does this one.

Starting Point:

The Clarion environment should be open. The LCLESSON.APP should now be closed.

TipBox.jpg

You should not need to type any code in this lesson. Simply COPY what you need directly from this help file into the appropriate areas.

Create the Source file

1.Using the appropriate tool in your operating system (File Manager, Explorer, etc.), create a new folder called Language under the CLARION7 folder.

2.Return to the Clarion IDE.

3.Choose File BLTTRIA.jpg New BLTTRIA.jpg Solution, Project or Application.

The New Project dialog appears. It's a special dialog, allowing you to change the directory and type in the filename.

4.Type Hello in the Name entry.

5.In the Location field, select the \CLARION7\LANGUAGE folder.

6.Uncheck the Auto create project subdir check box.

7.Press the Create button.

This creates an empty HELLO.CLW (.CLW is the standard extension for Clarion source code files) and places you in the Text Editor.

Examine the Project System

The Clarion Win32/Clarion# project system has been updated and upgraded to use the MSBuild project engine. MSBuild is the new extensible, XML-based build engine that ships with the .NET Framework 2.0 and higher. MSBuild simplifies the process of creating and maintaining build scripts, and formalizes the steps required to institute a formal build process.

All Clarion Win32 projects use a default extension of *.CWPROJ.

All projects compiled in Clarion Win32 are stored in solution files (*.SLN). These files can store multiple projects, and allow multiple resources to be referenced and included.

These files are identified in the Solution Explorer, shown here:

L13NewProject.jpg

1.RIGHT-CLICK on the Hello project, and select the Properties item in the popup menu.

The Project Properties dialog appears. Every Clarion Win32 project is divided into 4 project areas: Application, Compiling, Debug, and Build Events.

2.Type “.\” (without the quotes) in the Output Path box (or use the ellipsis button to select the directory from the Browse for Folder dialog).

3.Verify that the default Output type is set to Exe. This controls the type of file that the Build action will create.

4.Verify that the default Link Mode is set to Dll. This controls the files needed with the Output Type, and the default uses the necessary runtime libraries.

5.Save your project changes by pressing the Save button on the IDE toolbar, and close the Project Properties by pressing the Close ( X ) button.

The HELLO.CLW file in the Text Editor now has focus.

Notice that the environment's title bar text now reads: Hello [Clarion Version] (Debug) - Clarion.

Write the Program

1.Replace the default code in Hello.clw with the following:

PROGRAM

MAP

END

MyWin  WINDOW('Hello Windows'),SYSTEM

      END

CODE

OPEN(MyWin)

ACCEPT

END

This code begins with the PROGRAM statement. This must be the first non-comment statement in every Clarion program. Notice that the keyword PROGRAM is indented in relation to the word MyWin. In Clarion, the only statements that begin in column one (1) of the source code file are those with a statement label. A label must begin in column one (1), by definition. The PROGRAM statement begins the Global data declaration section.

Next there is an empty MAP structure. The END statement is a required terminator of the MAP data structure. A MAP structure contains prototypes which define parameter data types, return data types, and various other options that tell the compiler how to deal with your procedure calls (this is all covered later in this lesson). A MAP structure is required when you break up your program's code into PROCEDUREs. We haven't done that yet, but we still need it because there is an OPEN statement in the executable code.

When the compiler processes a MAP structure, it automatically includes the prototypes in the \CLARION7\LIBSRC\BUILTINS.CLW file. This file contains prototypes for almost all of the Clarion language built-in procedures (including the OPEN statement). If the empty MAP structure were not in this code, the compiler would generate an error on the OPEN statement.

MyWin is the label of the WINDOW data structure (the “M” must be in column one). In Clarion, windows are declared as data structures, and not dynamically built by executable code statements as in some other languages. This is one of the aspects of Clarion that makes it a 4GL. Although Clarion can dynamically build dialogs at runtime, it is unnecessary to do so. By using a data structure, the compiler creates the Windows resource for each dialog, enabling better performance at runtime.

The ('Hello Windows') parameter on the WINDOW statement defines the title bar text for the window. The SYSTEM attribute adds a standard Windows system menu to the window. The END statement is a required terminator of the WINDOW data structure. In Clarion, all complex structures (both data and executable code) must terminate with an END or a period (.). This means the following code is functionally equivalent to the previous code:

PROGRAM

MAP.

MyWin  WINDOW('Hello Windows'),SYSTEM.

CODE

OPEN(MyWin)

ACCEPT.

Although functionally equivalent, this code would become much harder to read as soon as anything is added into the MAP, WINDOW, or ACCEPT structures. By convention, we use the END statement to terminate multi-line complex statements, placing the END in the same column as the keyword it is terminating while indenting everything within the structure. We only use the period to terminate single-line structures, such as IF statements with single THEN clauses. This convention makes the code easier to read, and any missing structure terminators much easier to find.

The CODE statement is required to identify the beginning of the executable code section. Data (memory variables, data files, window structures, report structures, etc.) are declared in a data section (preceding the CODE statement), and executable statements may only follow a CODE statement.

Since this program does not contain any PROCEDUREs (we'll get to them in the next chapter), it only has a Global Data section followed by three lines of executable code. Variables declared in the Global Data section are visible and available for use anywhere in a program.

The OPEN(MyWin) statement opens the window, but does not display it. The window will only appear on screen when a DISPLAY or ACCEPT statement executes. This feature allows you to dynamically change the properties of the window, or any control on the window, before it appears on screen.

ACCEPT is the event processor. Most of the messages (events) from Windows are automatically handled internally for you by ACCEPT. These are the common events handled by the runtime library (screen re-draws, etc.). Only those events that actually may require program action are passed on by ACCEPT to your Clarion code. This makes your programming job easier by allowing you to concentrate on the high-level aspects of your program.

The ACCEPT statement has a terminating END statement, which means it is a complex code structure. ACCEPT is a looping structure, “passing through” all the events that the Clarion programmer might want to handle (none, in this program'we'll get back to this shortly), then looping back to handle the next event.

An ACCEPT loop is required for each window opened in a Clarion program. An open window “attaches” itself to the next ACCEPT loop it encounters in the code to be its event processor.

For this program, ACCEPT internally handles everything the system menu (placed on the window by the SYSTEM attribute) does. Therefore, when the user uses the system menu to close the window, ACCEPT automatically passes control to any statement immediately following its terminating END statement. Since there is no other explicit Clarion language statement to execute, the program ends. When any Clarion program reaches the end of the executable code, an implicit RETURN executes, which, in this case, returns the user to the operating system.

2.CLICK on the Build and Run button BuildRun.jpg.

The program compiles and links, then executes. The window's title bar displays the “Hello Windows” message, and you must close the window with the system menu.

L13Hello1.jpg

blk2blue.jpg

Hello Windows with Controls

The program you just created is close to the smallest programs possible to create in Clarion. Now we'll expand on it a bit to demonstrate adding some controls to the window and handling the events generated by those controls.

Change the Source code

1.Edit the code to read:

PROGRAM

MAP

END

MyWin  WINDOW('Hello Windows'),AT(,,100,100),SYSTEM      !Changed

       STRING('Hello Windows'),AT(26,23),USE(?String1)  !Added

       BUTTON('OK'),AT(34,60),USE(?Ok),DEFAULT          !Added

      END

CODE

OPEN(MyWin)

ACCEPT

 IF ACCEPTED() = ?Ok THEN BREAK.                        !Added

END

NoteBox.jpg

The Window Designer is available to you in the Text Editor, just as it is in the Application Generator. To call the Window Designer, place the insertion point anywhere within the WINDOW structure then press CTRL+F. The only restrictions are that the Control Template and Dictionary Field tools are unavailable (they are specific to the Application Generator).

The change is the addition of the STRING and BUTTON controls to the WINDOW structure. The STRING places constant text in the window, and the BUTTON adds a command buttton.

The only other addition is the IF ACCEPTED() = ?Ok THEN BREAK. statement. This statement detects when the user has pressed the OK button and BREAKs out of the ACCEPT loop, ending the program. The ACCEPTED statement returns the field number of the control for which EVENT:Accepted was just generated (EVENT:Accepted is an EQUATE contained in the \CLARION7\LIBSRC\EQUATES.CLW file, which the compiler automatically includes in every program).

?Ok is the Field Equate Label of the BUTTON control, defined by the control's USE attribute (see Field Equate Labels in the Language Reference). The compiler automatically equates ?Ok to the field number it assigns the control (using Field Equate Labels helps make the code more readable).

When the ACCEPTED procedure returns a value equal to the compiler-assigned field number for the OK button, the BREAK statement executes and terminates the ACCEPT loop.

2.CLICK on the Build and Run button BuildRun.jpg.

The program compiles and links, then executes. The window's title bar still displays the “Hello Windows” message, and now, so does the constant text in the middle of the window. You can close the window either with the system menu, or the OK button.

L13Hello2.jpg

Common form Source code

There are other ways to write the code in the ACCEPT loop to accomplish the same thing. We'll go straight to the most common way, because this is more similar to the style of code that the Application Generator generates for you from the Clarion ABC Templates.

1.Edit the code to read:

PROGRAM

MAP

END

MyWin  WINDOW('Hello Windows'),AT(,,100,100),SYSTEM

       STRING('Hello Windows'),AT(26,23),USE(?String1)

       BUTTON('OK'),AT(34,60),USE(?Ok),DEFAULT

      END

CODE

OPEN(MyWin)

ACCEPT

 CASE FIELD()         !Added

 OF ?Ok               !Added

  CASE EVENT()         !Added

  OF EVENT:Accepted   !Added

   BREAK               !Added

  END                 !Added

 END                   !Added

END

In this code you have one CASE structure nested within another. A CASE structure looks for an exact match between the expression immediately following the keyword CASE and another expression immediately following an OF clause (although these only show one OF clause each, a CASE structure may have as many as necessary).

The CASE FIELD() structure determines to which control the current event applies. When the FIELD procedure returns a value equal to the field number of the OK button (the ?Ok Field Equate Label) it then executes the CASE EVENT() structure.

The CASE EVENT() structure determines which event was generated. When the EVENT procedure returns a value equal to EVENT:Accepted (an EQUATE contained in the \CLARION7\LIBSRC\EQUATES.CLW file) it then executes the BREAK statement.

Nesting CASE EVENT() within CASE FIELD() allows you to put all the code associated with a single control in one place. You could just as easily nest a CASE FIELD() structure within a CASE EVENT() structure, reversing the code, but this would scatter the code for a single control to multiple places.

2.CLICK on the Build and Run button BuildRun.jpg.

Again, you can close the window either with the system menu, or the OK buttton, just as with the previous code, but now the code is structured in a common style.

Hello Windows with Event Handling

There are two types of events passed on to the program by ACCEPT: Field-specific and Field-independent events.

A Field-specific event occurs when the user does anything that may require the program to perform a specific action related to a single control. For example, when the user presses tab after entering data in a control, the field-specific EVENT:Accepted generates for that control.

A Field-independent event does not relate to any one control but may require some program action (for example, to close a window, quit the program, or change execution threads).

Nesting two CASE structures as we just discussed is the most common method of handling field-specific events. The most common method of handling field-independent events is a non-nested CASE EVENT() structure, usually placed immediately before the CASE FIELD() structure.

Change the Source code

1.Edit the code to read:

PROGRAM

MAP

END

MyWin  WINDOW('Hello Windows'),AT(,,100,100),SYSTEM

       STRING('Hello Windows'),AT(26,23),USE(?String1)

       BUTTON('OK'),AT(34,60),USE(?Ok),DEFAULT

      END

CODE

OPEN(MyWin)

ACCEPT

 CASE EVENT()               !Added

 OF EVENT:OpenWindow       !Added

  MESSAGE('Opened Window') !Added

 OF EVENT:GainFocus         !Added

  MESSAGE('Gained Focus')   !Added

 END                       !Added

 CASE FIELD()

 OF ?Ok

  CASE EVENT()

  OF EVENT:Accepted

   BREAK

  END

 END

END

The new CASE EVENT( ) structure handles two field-independent events: EVENT:OpenWindow and EVENT:GainFocus. The MESSAGE procedure used in this code is just to visually display to you at runtime that the event was triggered. Instead of the MESSAGE procedure, you would add here any code that your program needs to execute when the user triggers these events.

This demonstrates the basic logic flow and code structure for procedural window procedures'an ACCEPT loop containing a CASE EVENT() structure to handle all the field-independent events, followed by a CASE FIELD() structure with nested CASE EVENT() structures to handle all field-specific events.

2.CLICK on the Build and Run button BuildRun.jpg.

Notice that EVENT:OpenWindow generates when the window first displays (in that order). EVENT:GainFocus will generate when you ALT+TAB to another application then ALT+TAB back to Hello Windows.

L13Hello3.jpg

Adding a PROCEDURE

In Hello Windows we have an example of a very simple program. Most modern business programs are not that simple'they require the use of Structured Programming techniques. This means you break up your program into functional sections, where each performs a single logical task. In the Clarion language these functional sections are called PROCEDUREs.

First, we'll add a PROCEDURE to the Hello Windows program.

Change the Source code

1.Edit the code to read:

PROGRAM

MAP

Hello PROCEDURE                             !Added

END

CODE                                       !Added

Hello                                      !Added

Hello   PROCEDURE                           !Added

MyWin  WINDOW('Hello Windows'),AT(,,100,100),SYSTEM

       STRING('Hello Windows'),AT(26,23),USE(?String1)

       BUTTON('OK'),AT(34,60),USE(?Ok),DEFAULT

      END

CODE

OPEN(MyWin)

ACCEPT

 CASE EVENT()

 OF EVENT:OpenWindow

  MESSAGE('Opened Window')

 OF EVENT:GainFocus

  MESSAGE('Gained Focus')

 END

 CASE FIELD()

 OF ?Ok

  CASE EVENT()

  OF EVENT:Accepted

   BREAK

  END

 END

END

The only changes are at the beginning of the program. Inside the MAP structure we now see the Hello PROCEDURE statement which prototypes the Hello procedure. A prototype is the declaration of the procedure for the compiler, telling the compiler what to expect when your code calls the procedure. This prototype indicates that the procedure takes no parameters and does not return a value. All PROCEDUREs in your program must be prototyped in a MAP structure. See PROCEDURE prototypes in the Language Reference for more on prototypes.

The keyword CODE immediately following the MAP structure terminates the Global data section and marks the beginning of the Global executable code section, which only contains the Hello statement'a call to execute the Hello procedure. A PROCEDURE which does not return a value is always called as a single executable statement in executable code.

The second Hello PROCEDURE statement terminates the Global executable code section and marks the beginning of the code definition of the Hello procedure.

A PROCEDURE contains a data declaration section just as a PROGRAM does, and so, also requires the keyword CODE to define the boundary between data declarations and executable code. This is why the rest of the code did not change from the previous example. This is the Local data declaration section for the PROCEDURE.

The biggest difference between Global and Local data declarations is the scope of the declared data. Any data item declared in a Local data section is visible only within the PROCEDURE which declares it, while any data item declared in the Global data section is visible everywhere in the program. See Data Declarations and Memory Allocation in the Language Reference for a full discussion of all the differences.

2.CLICK on the Build and Run button BuildRun.jpg.

The program executes exactly as it did before. The only difference is that the Hello PROCEDURE can now be called from anywhere within the program'even from within another PROCEDURE. This means that, even though the program may execute the procedure many times, the code for it exists just once.

Adding Another PROCEDURE

A Clarion PROCEDURE which does not directly RETURN a value can only be called as a separate executable statement'it cannot be used in an expression, parameter list, or an assignment statement. A PROCEDURE which does directly RETURN a value must always contain a RETURN statement and may be used in expressions, parameter lists, and assignment statements. You can call a PROCEDURE which does directly RETURN a value as a separate statement if you do not want the value the PROCEDURE returns, but doing this generates compiler warnings (unless the PROCEDURE's prototype has the PROC attribute).

Structurally, both types of PROCEDURE are equivalent'they both have Local data sections, followed by the executable code section that begins with the keyword CODE.

Change the Source code

1.Edit the MAP structure to read:

MAP

Hello    PROCEDURE

EventString PROCEDURE(LONG PassedEvent),STRING        !Added

END

This adds the prototype for the EventString PROCEDURE. EventString receives a LONG parameter called PassedEvent that may not be omitted, and returns a STRING. The data types of all parameters passed to a PROCEDURE are specified inside the parentheses following PROCEDURE, each separated by a comma (if there are multiple parameters being passed). The data type of the return value of a PROCEDURE is specified following the closing parenthesis of the parameter list. Both types of PROCEDUREs may receive parameters, see Prototype Parameter Lists in the Language Reference for a more complete discussion of parameter passing.

2.Add the EventString PROCEDURE definition to the end of the file:

EventString   PROCEDURE(LONG PassedEvent)

ReturnString  STRING(255)

CODE

CASE PassedEvent

OF EVENT:OpenWindow

 ReturnString = 'Opened Window'

OF EVENT:GainFocus

 ReturnString = 'Gained Focus'

ELSE

 ReturnString = 'Unknown Event: ' & PassedEvent

END

RETURN(ReturnString)

The EventString label (remember, it must be in column one) on the PROCEDURE statement names this procedure, while the parameter list attached to the PROCEDURE keyword names the LONG parameter PassedEvent. There must always be an equal number of parameter names listed on the PROCEDURE statement as there are parameter data types listed in the prototype for that PROCEDURE.

ReturnString is a local variable declared as a 255 character STRING field, on the stack. The CODE statement terminates the procedure's Local data section. The CASE PassedEvent structure should look familiar, because it is the same as a CASE EVENT() structure, but its CASE condition is the PassedEvent instead of the EVENT() procedure. This CASE structure simply assigns the appropriate value to the ReturnString variable for each event that is passed to the procedure.

The interesting code here is the RETURN(ReturnString) statement. A PROCEDURE without a return value does not require an explicit RETURN, since it always executes an implicit RETURN when there is no more code to execute. However, a PROCEDURE prototyped to return a value always contains an explicit RETURN statement which specifies the value to return. In this case, the RETURN statement returns whichever value was assigned to the ReturnString in the CASE structure.

3.Edit the Hello procedure's CASE EVENT() structure to read:

  CASE EVENT()

  OF EVENT:OpenWindow

   MESSAGE(EventString(EVENT:OpenWindow))      !Changed

  OF EVENT:GainFocus

   MESSAGE(EventString(EVENT:GainFocus))       !Changed

  END

This changes the MESSAGE procedures to display the returned value from the EventString procedure. The event number is passed to EventString as the event EQUATE to make the code more readable.

4.CLICK on the Build and Run button BuildRun.jpg.

The program still executes and looks exactly as it did before.

blk2blue.jpg

Moving Into the Real World'Adding a Menu

Hello Windows is a nice little demonstration program, but it really doesn't show you much to do with real-world business programming. Therefore, we're now going to expand it to include some real-world functionality, starting with a menu.

Change the Source code

1.Edit the beginning of the file to read:

PROGRAM

MAP

Main        PROCEDURE                !Added

Hello       PROCEDURE

EventString PROCEDURE(LONG PassedEvent),STRING

END

CODE

Main                                !Changed

This adds the Main PROCEDURE prototype to the MAP structure and replaces the call to Hello with a call to the Main procedure.

2.Add the Main PROCEDURE definition to the end of the file:

Main   PROCEDURE

AppFrame APPLICATION('Hello Windows'),AT(,,280,200),SYSTEM,RESIZE,MAX

     MENUBAR

      MENU('&File'),USE(?File)

       ITEM('&Browse Phones'),USE(?FileBrowsePhones)

       ITEM,SEPARATOR

       ITEM('E&xit'),USE(?FileExit),STD(STD:Close)

      END

      ITEM('&About!'),USE(?About)

     END

    END

CODE

OPEN(AppFrame)

ACCEPT

 CASE ACCEPTED()

 OF ?About

  Hello

 END

END

The Main PROCEDURE accepts no parameters. It contains the AppFrame APPLICATION structure. An APPLICATION structure is the key to creating Windows Multiple Document Interface (MDI) programs. An MDI application can contain multiple execution threads This is the MDI parent application frame that is required to create an MDI application.

The MENUBAR structure defines the menu items available to the user. The MENU('&File') structure creates the standard File menu that you see in most Windows programs. The ampersand (&) preceding the “F” specifies that the “F” is the menu's accelerator key, and will be underlined at runtime by the operating system.

The ITEM('&Browse Phones') creates a menu item the we will use to call a procedure (we'll get to that shortly). The ITEM,SEPARATOR statement creates a dividing line in the menu following the Browse Phones selection.

The ITEM('E&xit') creates a menu item to exit the procedure (and the program, since this procedure is the only procedure called from the Global executable code). The STD(STD:Close) attribute specifies the standard window close action to break out of the ACCEPT loop. This is why you don't see any executable code associated with this menu item in the ACCEPT loop'the Clarion runtime library takes care of it for you automatically.

The ITEM('&About!') statement creates a menu item on the action bar, right next to the File menu. The trailing exclamation point (!) is a programming convention to give end-users a visual clue that this item executes an action and does not drop down a menu, despite the fact that it is on the action bar.

The ACCEPT loop contains only a CASE ACCEPTED() structure. The ACCEPTED procedure returns the field number of the control with focus when EVENT:Accepted is generated. We can use the ACCEPTED procedure here instead of the FIELD and EVENT procedures because menu items only generate EVENT:Accepted. The OF ?About clause simply calls the Hello procedure.

3.CLICK on the Build and Run button BuildRun.jpg.

The program executes and only the About! and File BLTTRIA.jpg Exit items actually do anything. Notice, though, that File BLTTRIA.jpg Exit does terminate the program, despite the fact that we wrote no code to perform that action.

blk2blue.jpg

Really Moving Into the Real World'Adding a Browse and Form

Having a menu is nice, but now it's time to do some real-world business programming. Now we're going to add a data file and the procedures to maintain it.

Change the Global code

1.Edit the beginning of the file to read:

PROGRAM

INCLUDE('Keycodes.CLW')                      !Added

INCLUDE('Errors.CLW')                        !Added

MAP

Main     PROCEDURE

BrowsePhones PROCEDURE                        !Added

UpdatePhones PROCEDURE(LONG Action),LONG      !Added

Hello        PROCEDURE

EventString  PROCEDURE(LONG PassedEvent),STRING

END

Phones    FILE,DRIVER('TopSpeed'),CREATE      !Added

NameKey    KEY(Name),DUP,NOCASE               !Added

Rec        RECORD                             !Added

Name        STRING(20)                        !Added

Number      STRING(20)                        !Added

          END                                !Added

         END                                 !Added

InsertRecord   EQUATE(1)                      !Added

ChangeRecord   EQUATE(2)                      !Added

DeleteRecord   EQUATE(3)                      !Added

ActionComplete EQUATE(1)                      !Added

ActionAborted  EQUATE(2)                      !Added

   CODE

   Main

The two new INCLUDE statements add the standard EQUATEs for keycodes and error numbers. Using the EQUATEs instead of the numbers makes your code more readable, and therefore, more maintainable.

The MAP structure has acquired two more procedure prototypes: the BrowsePhones PROCEDURE and UpdatePhones PROCEDURE(LONG Action),LONG. The BrowsePhones procedure will display a list of the records in the file and UpdatePhones will update individual records.

In the interest of coding simplicity (you'll see why shortly), the BrowsePhones procedure will simply display all records in the file in a LIST control. This is not exactly the same as a Browse procedure in template generated code (which is page-loaded in order to handle very large files), but will serve a similar purpose in this program.

Also in the interest of coding simplicity, UpdatePhones is a PROCEDURE that takes a parameter. The LONG parameter indicates what file action to take: ADD, PUT, or DELETE. The LONG return value indicates to the calling procedure whether the user completed or aborted the action. Again, this is not the same as a Form procedure in template generated code (which is a PROCEDURE using Global variables to signal file action and completion status), but will serve the same purpose in this simple program.

The Phones FILE declaration creates a simple data file using the TopSpeed file driver. There are two data fields: Name and Number which are both declared as STRING(20). Declaring the Number field as a STRING(20) allows it to contain phone numbers from any country in the world (more about that to come).

The five EQUATE statements define constant values that make the code more readable. InsertRecord, ChangeRecord, and DeleteRecord all define file actions to pass as the parameter to the UpdatePhones procedure. The ActionComplete and ActionAborted EQUATEs define the two possible return values from the UpdatePhones procedure.

2.Edit the Main procedure's CASE ACCEPTED() code to read:

 CASE ACCEPTED()

 OF ?FileBrowsePhones                !Added

  START(BrowsePhones,25000)          !Added

 OF ?About

  Hello

 END

The START(BrowsePhones,25000) statement executes when the user chooses the Browse Phones menu selection. The START procedure creates a new execution thread for the BrowsePhones procedure and the second parameter (25000) specifies the size (in bytes) of the new execution thread's stack. You must use the START procedure when you are calling a procedure containing an MDI child window from the APPLICATION frame, because each MDI child window must be on a separate execution thread from the APPLICATION frame. If you do not START the MDI child, you get the “Unable to open MDI window on APPLICATION's thread” runtime error message when you try to call the BrowsePhones procedure and the program immediately terminates.

Once you have started a new thread, an MDI child may simply call another MDI child on its same thread without starting a new thread. This means that, although BrowsePhones and UpdatePhones both contain MDI windows, only BrowsePhones must START a new thread, because it is called from the application frame. BrowsePhones will simply call UpdatePhones without starting a new thread.

Add the BrowsePhones PROCEDURE

3.Add the data section of the BrowsePhones PROCEDURE definition to the end of the file:

BrowsePhones PROCEDURE

PhonesQue   QUEUE

Name         STRING(20)

Number       STRING(20)

Position     STRING(512)

           END

window WINDOW('Browse Phones'),AT(,,185,156),SYSTEM,GRAY,RESIZE,MDI

        LIST,AT(6,8,173,100),ALRT(MouseLeft2),USE(?List) |

        ,FORMAT('84L|M~Name~80L~Number~'),FROM(PhonesQue),VSCROLL

        BUTTON('&Insert'),AT(20,117),KEY(InsertKey),USE(?Insert)

        BUTTON('&Change'),AT(76,117,35,14),KEY(EnterKey),USE(?Change)

        BUTTON('&Delete'),AT(131,117,35,14),KEY(DeleteKey),USE(?Delete)

        BUTTON('Close'),AT(76,137,35,14),KEY(EscKey),USE(?Close)

      END

The PhonesQue QUEUE structure defines the data structure that will contain all the records from the Phones FILE to display in the LIST control. A Clarion QUEUE is similar to a data file because it has a data buffer and allows an indeterminate number of entries, but it only exists in memory at runtime. A QUEUE could also be likened to a dynamically allocated array in other programming languages.

The PhonesQue QUEUE contains three fields. The Name and Number fields duplicate the fields in the Phones FILE structure and will display in the two columns defined in the LIST control's FORMAT attribute. The Position field will contain the return value from the POSITION procedure for each record in the Phones FILE. Saving each record's Position will allow us to immediately re-get the record from the data file before calling UpdatePhones to change or delete a record.

The window WINDOW structure contains one LIST control and four BUTTON controls. The LIST control is the key to this procedure. The ALRT(MouseLeft2) on the LIST alerts DOUBLE-CLICK so the ACCEPT statement will pass an EVENT:AlertKey to our Clarion code. This will let us write code to bring up the UpdatePhones procedure to change the record the user DOUBLE-CLICKs on.

The vertical bar (|) at the end of the LIST statement is the Clarion line continuation character, meaning that the LIST control continues on the next line with the FORMAT('84L|M~Name~80L~Number~') attribute (you can put it all on one line if you want). The parameter to the FORMAT attribute defines the appearance of the LIST control at runtime.

TipBox.jpg

It is best to let the List Box Formatter tool in the Window Designer write format strings for you, since they can become very complex very quickly. This format string defines two columns. The first column is 84 dialog units wide (84), left justified(L), has a right border (|) that is resizable (M) and “Name” is its heading (~Name~). The second column is 80 dialog units wide (80), left justified(L), and “Number” is its heading (~Number~).

FROM(PhonesQue) on the LIST specifies the source QUEUE the LIST control will display, and VSCROLL adds the vertical scroll bar. The LIST will display the Name and Number fields of all entries in the PhonesQue while ignoring the Position field because the FORMAT attribute only specified two columns.

The LIST automatically handles users scrolling through the list without any coding on our part. The LIST does this because there is no IMM (immediate) attribute present. If there were an IMM attribute, we would have to write code to handle scrolling records (as the page-loaded Browse procedure template does).

The BUTTON('&Insert') statement defines a command button the user will press to add a new record. The KEY(InsertKey) attribute specifies that the button is automatically pressed for the user when they press insert on the keyboard. Notice that the other three BUTTON controls all have similar KEY atributes. This means you don't have to write any special code to handle keyboard access to the program versus mouse access.

4.Add the beginning of the executable code section of the BrowsePhones PROCEDURE definition to the end of the file:

CODE

DO OpenFile

DO FillQue

OPEN(window)

ACCEPT

 CASE EVENT()

 OF EVENT:OpenWindow

  DO QueRecordsCheck

 OF EVENT:AlertKey

  IF KEYCODE() = MouseLeft2

   POST(EVENT:Accepted,?Change)

  END

 END

END

The beginning of the CODE section starts with the DO OpenFile statement. DO executes a ROUTINE, in this case, the OpenFile ROUTINE, and when the code in the ROUTINE is done executing, control returns to the next line of code following the DO statement that called the ROUTINE.

The code defining a ROUTINE must be at the bottom of the procedure, following all the main logic, because the ROUTINE statement itself terminates the executable code section of a PROCEDURE.

There are two reasons to use a ROUTINE within a PROCEDURE: to write one set of code statements that need to execute at multiple logical points within the procedure, or to make the logic more readable by substituting a single DO statement for a set of code statements that perform a single logical task.

In this case, the DO OpenFile statement serves the second purpose by moving the code that opens or creates the data file out of the main procedure logic. Next, the DO FillQue statement executes the code that fills the PhonesQue QUEUE structure with all the records from the data file. These two DO statements make it very easy to follow the logic flow ofthe procedure.

The CASE EVENT() structure's OF EVENT:OpenWindow clause executes DO QueRecordsCheck to call a ROUTINE that checks to see if there are any records in PhonesQue.

Next, the OF EVENT:AlertKey clause contains the IF KEYCODE() = MouseLeft2 structure to check for DOUBLE-CLICK on the LIST. Since the ALRT(MouseLeft2) attribute only appears on the LIST control, we know that the POST(EVENT:Accepted,?Change) statement will only execute when the user DOUBLE-CLICKs on the LIST.

The POST statement tells ACCEPT to post the event in its first parameter (EVENT:Accepted) to the control in its second parameter (?Change). The effect of the POST(EVENT:Accepted,?Change) statement is to cause the EVENT:Accepted code for the Change button to execute, just as if the user had pressed the button with the mouse or keyboard.

This illustrates a very good coding practice: write specific code once and call it from many places. This is the structured programming concept that gave us PROCEDUREs and ROUTINEs. Even though the EVENT:Accepted code for the change button is not sectioned off separately in a PROCEDURE or ROUTINE, using the POST statement this way means that the one section of code is all you need to maintain'if the desired logic changes, you'll only have to change it in one place.

5.Add the rest of the executable code section of the BrowsePhones PROCEDURE definition to the end of the file:

 CASE FIELD()

 OF ?Close

  CASE EVENT()

  OF EVENT:Accepted

   POST(EVENT:CloseWindow)

  END

 OF ?Insert

  CASE EVENT()

  OF EVENT:Accepted

   IF UpdatePhones(InsertRecord) = ActionComplete

    DO AssignToQue

    ADD(PhonesQue)

    IF ERRORCODE() THEN STOP(ERROR()).

    SORT(PhonesQue,PhonesQue.Name)

    ENABLE(?Change,?Delete)

   END

   GET(PhonesQue,PhonesQue.Name)

   SELECT(?List,POINTER(PhonesQue))

  END

 OF ?Change

  CASE EVENT()

  OF EVENT:Accepted

   DO GetRecord

   IF UpdatePhones(ChangeRecord) = ActionComplete

    DO AssignToQue

    PUT(PhonesQue)

    IF ERRORCODE() THEN STOP(ERROR()).

    SORT(PhonesQue,PhonesQue.Name)

   END

   GET(PhonesQue,PhonesQue.Name)

   SELECT(?List,POINTER(PhonesQue))

  END

 OF ?Delete

  CASE EVENT()

  OF EVENT:Accepted

   DO GetRecord

   IF UpdatePhones(DeleteRecord) = ActionComplete

    DELETE(PhonesQue)

    IF ERRORCODE() THEN STOP(ERROR()).

    DO QueRecordsCheck

    SORT(PhonesQue,PhonesQue.Name)

   END

   SELECT(?List)

  END

 END

END

FREE(PhonesQue)

CLOSE(Phones)

Following the CASE EVENT() structure is the CASE FIELD() structure. Notice that each OF clause contains its own CASE EVENT() structure, and each of these only contains an OF EVENT:Accepted clause. Because of this, we could have replaced the CASE FIELD() structure with a CASE ACCEPTED() structure and eliminated the nested CASEs. This would actually have given us slightly better performance'too slight to actually notice on-screen, though. The reason we didn't is consistency; you will more often have occasion to trap more field-specific events than just EVENT:Accepted, and when you do, this nested CASE structure code is the logic to use, so it's a good habit to make now. It also demonstrates the kind of code structure that is generated for you by Clarion's templates in the Application Generator.

The OF ?Close clause executes the POST(EVENT:CloseWindow) when the user presses the Close button. Since there's no second parameter to the POST statement naming a control, the event posts to the WINDOW (and should be a field-independent event). EVENT:CloseWindow causes the ACCEPT loop to terminate and execution control drops to the first statement following the ACCEPT's terminating END statement. In this case, control drops to the FREE(PhonesQue) statement, which frees all the memory used by the QUEUE entries (effectively closing the QUEUE). The CLOSE(Phones) statement then closes the data file. Since there are no other statements to execute following CLOSE(Phones) the procedure executes an implicit RETURN and goes back to the Main procedure (where it was called from).

The OF ?Insert clause contains the IF UpdatePhones(InsertRecord) = ActionComplete structure. This calls the UpdatePhones PROCEDURE, passing it the InsertRecord constant value that we defined in the Global data section, and then checks for the ActionComplete return value.

The DO AssignToQue statement executes only when the user actually adds a record. AssignToQue is a ROUTINE that assigns data from the Phones FILE's record buffer to the PhonesQue QUEUE's data buffer. Then the ADD(PhonesQue) statement adds a new entry to PhonesQue. The IF ERRORCODE() THEN STOP(ERROR()). statement is a standard error check that you should execute after any FILE or QUEUE action that could possibly return an error (another good habit to form).

The SORT(PhonesQue,PhonesQue.Name) statement sorts the PhonesQue QUEUE entries alphabetically by the Name field. Since there is no PRE attribute on the PhonesQue QUEUE structure, you must reference its fields using Clarion's Field Qualification syntax by prepending the name of the structure containing the field (PhonesQue) to the name of the field (Name), connecting them with a period (PhonesQue.Name). See Field Qualification in the Language Reference for more information.

The ENABLE(?Change,?Delete) statement makes sure the Change and delete buttons are active (if this was the first entry in the QUEUE, these buttons were dimmed out by the QueRecordsCheck ROUTINE). The GET(PhonesQue,PhonesQue.Name) statement re-gets the new record from the sorted QUEUE, then SELECT(?List,POINTER(PhonesQue)) puts the user back on the LIST control with the new record highlighted.

The code in the OF ?Change clause is almost identical to the code in the OF ?Insert clause. There is an added DO GetRecord statement that calls a ROUTINE to put the highlighted PhonesQue entry's related file record into the Phones file record buffer. The only other difference is the PUT(PhonesQue) statement that puts the user's changes back in the PhonesQue.

The code in the OF ?Delete clause is almost identical to the code in the OF ?Change clause. The difference is the DELETE(PhonesQue) statement that removes the entry from the PhonesQue and the call to DO QueRecordsCheck to see if the user just deleted the last record.

6.Add the ROUTINEs called in the BrowsePhones PROCEDURE definition to the end of the file:

AssignToQue ROUTINE

PhonesQue.Name = Phones.Rec.Name

PhonesQue.Number = Phones.Rec.Number

PhonesQue.Position = POSITION(Phones)

QueRecordsCheck ROUTINE

IF NOT RECORDS(PhonesQue)

 DISABLE(?Change,?Delete)

 SELECT(?Insert)

ELSE

 SELECT(?List,1)

END

GetRecord ROUTINE

GET(PhonesQue,CHOICE(?List))

IF ERRORCODE() THEN STOP(ERROR()).

REGET(Phones,PhonesQue.Position)

IF ERRORCODE() THEN STOP(ERROR()).

OpenFile ROUTINE

OPEN(Phones,42h)

CASE ERRORCODE()

OF NoError

OROF IsOpenErr

 EXIT

OF NoFileErr

 CREATE(Phones)

 IF ERRORCODE() THEN STOP(ERROR()).

 OPEN(Phones,42h)

 IF ERRORCODE() THEN STOP(ERROR()).

ELSE

 STOP(ERROR())

 RETURN

END

FillQue ROUTINE

SET(Phones.NameKey)

LOOP

 NEXT(Phones)

 IF ERRORCODE() THEN BREAK.

 DO AssignToQue

 ADD(PhonesQue)

 IF ERRORCODE() THEN STOP(ERROR()).

END

As you can see, there are five ROUTINEs for this procedure. Notice that, although these ROUTINEs look similar to a PROCEDURE, they do not contain CODE statements. This is because a ROUTINE shares the procedure's Local data and does not usually have a data declaration section of its own (a ROUTINE can have its own data section'see ROUTINE in the Language Reference for a full discussion of this issue).

The AssignToQue ROUTINE performs three assignment statements. The PhonesQue.Name = Phones.Rec.Name statement copies the data in the Name field of the Phones FILE record buffer and places it in the Name field of the PhonesQue QUEUE data buffer. Since there is no PRE attribute on the Phones FILE structure, you must also reference its fields using Clarion's Field Qualification Syntax by stringing together the FILE name (Phones), the RECORD name (Rec), and the name of the field (Name), connecting them all with a period (Phones.Rec.Name).

The PhonesQue.Number = Phones.Rec.Number statement assigns the data in the Phones file's Number field to the PhonesQue's Number field. The PhonesQue.Position = POSITION(Phones) statement assigns the return value of the POSITION procedure to the PhonesQue.Position field. This value lets us retrieve from disk the one specific record that is currently in the Phones file's record buffer. The POSITION procedure does this for every Clarion file driver, and is therefore the recommended method of specific record retrieval across multiple file systems.

The QueRecordsCheck ROUTINE checks to see if there are any records in the PhonesQue. The IF NOT RECORDS(PhonesQue) structure uses the logical NOT operator against the return value from the RECORDS procedure. If RECORDS(PhonesQue) returns zero, then the NOT makes the condition true and the code following the IF executes (zero is always false and the NOT makes it true). If RECORDS(PhonesQue) returns anything other than zero, the code following the ELSE will execute (any non-zero number is always true and the NOT makes it false). Therefore, if there are no records in the PhonesQue, DISABLE(?Change,?Delete) executes to dim out the Change and Delete buttons, then the SELECT(?Insert) statement places the user on the Insert button (the next logical action). If there are records in the PhonesQue, then the SELECT(?List) statement places the user on the LIST.

The GetRecord ROUTINE synchronizes the Phones file's record buffer and PhonesQue's data buffer with the currently highlighted entry in the LIST. The GET(PhonesQue,CHOICE(?List)) statement uses the CHOICE procedure to “point to” the currently highlighted entry in the LIST and GET the related QUEUE entry into the PhonesQue's data buffer (of course, always checking for unexpected errors). Then the REGET(Phones,PhonesQue.Position) statement uses the saved record position information to retrieve the Phones FILE record into the record buffer.

The OpenFile ROUTINE either opens or creates the Phones FILE. The OPEN(Phones,42h) statement attempts to open the Phones file for shared access. The second parameter (42h) is a hexadecimal number (signaled by the trailing “h”). Clarion supports the Decimal, Hexadecimal, Binary, and Octal number systems. This number represents Read/Write, Deny None access (fully shared) to the file (see the OPEN statement in the Language Reference for more on file access modes). We're requesting shared access because this is an MDI program and the user could call multiple copies of this procedure in the same program. However, this program does not do all the concurrency checking required for a real multi-user application. See Multi-User Considerations in the Programmer's Guide for more on the concurrency checking issue.

The CASE ERRORCODE() structure checks for any error on the OPEN. The OF NoError OROF IsOpenErr clause (now you can see why we included ERRORS.CLW file) executes the EXIT statement to immediately return from the ROUTINE. It is very important not to confuse EXIT with RETURN, since RETURN immediately terminates the PROCEDURE, while EXIT only terminates the ROUTINE. RETURN is valid to use within a ROUTINE, just be sure you want to terminate the PROCEDURE and not simply terminate the ROUTINE.

The OF NoFileErr clause detects that there is no data file to open. The CREATE(Phones) statement will then create an new empty data file for us. You must be sure that, if you intend to use the CREATE statement that the FILE declaration also contains the CREATE attribute, otherwise the CREATE statement will not be able to create the file for you. The CREATE statement does not open the file for processing, so that explains the second OPEN(Phones,42h) statement. The code in the ELSE clause executes if any error other than these occur. The STOP(ERROR()) statement displays the ERROR procedure's message to the user in a system modal window allowing the user the opportunity to abort the program (returning to Windows) or ignore the error. The RETURN statement then terminates the procedure if the user chooses to ignore the error.

The FillQue ROUTINE fills the PhonesQue QUEUE with all the records in the Phones file. The SET(Phones.NameKey) statement sets up the processing order and starting point for the Phones file. The Phone.NameKey parameter makes the processing order alphabetic based on the Name field. The absence of a second parameter to the SET statement makes the starting point the beginning (or end) of the file. The LOOP structure has no condition, which means you must place a BREAK statement somewhere in the LOOP or else get an infinite loop. The NEXT(Phones) statement retrieves the next record from the Phones data file, then the IF ERRORCODE() THEN BREAK. statement ensures that we will BREAK out of the LOOP when all the records have been read. The DO AssignToQue statement calls the AssignToQue ROUTINE that we've already discussed, and ADD(PhonesQue) adds the new record to the QUEUE.

Add the UpdatePhones PROCEDURE

7.Add the data section of the UpdatePhones PROCEDURE definition to the end of the file:

UpdatePhones PROCEDURE(LONG Action)

ReturnValue  LONG,AUTO

window WINDOW('Update Phone'),AT(,,185,92),SYSTEM,GRAY,RESIZE,MDI

       PROMPT('N&ame:'),AT(14,14),USE(?Prompt1)

       ENTRY(@s20),AT(68,13),USE(Phones.Rec.Name),REQ

       PROMPT('N&umber:'),AT(14,43),USE(?Prompt2)

       ENTRY(@s20),AT(68,42),USE(Phones.Rec.Number)

       BUTTON('OK'),AT(45,74),USE(?Ok),REQ,DEFAULT

       BUTTON('Cancel'),AT(109,74,32,14),USE(?Cancel)

      END

The UpdatePhones PROCEDURE(LONG Action) statement defines a PROCEDURE that receives a single LONG data typed parameter that will be called “Action” within the PROCEDURE (no matter what variable or constant is passed in).

ReturnValue LONG, AUTO declares a LONG variable that remains uninitialized by the compiler (due to the AUTO attribute). By default, memory variables in Clarion are initialized to all blanks or zero (depending on their data type). Specifying AUTO saves a bit of memory, but the caveat is that you must be sure you are going to assign a value to the uninitialized variable before you ever check its contents, otherwise you could create an intermittently occurring bug that would be really difficult to track down.

The WINDOW structure has the MASK attribute, which means that the user's data entry patterns are checked as the data is input, instead of the default Standard Windows Behavior (SWB) of “free-form” data entry.

The two PROMPT and ENTRY controls combine to provide the user's data entry controls. The two BUTTON controls will allow the user to complete or abort the current file action.

The PROMPT controls define the screen prompt text and the accelerator key to navigate to the ENTRY control following the PROMPT, The accelerator keys are formed using alt plus the letter following the ampersand. For example, ('N&ame:') indicates alt+a will give input focus to the ENTRY(@s20),AT(68,13),USE(Phones.Rec.Name) control.

The USE attributes of the ENTRY controls name the data fields that automatically receive the user's input at runtime. The runtime library ensures that the current value in the variable named in the USE attribute displays when the control gains input focus. When the user enters data in the ENTRY control then moves on to another control, the runtime library ensures that the variable named in the USE attribute receives the current value displayed in the control.

The REQ attribute on the first ENTRY control means that the user cannot leave it blank, while the REQ attribute on the OK button checks to make sure that the user entered data into all the ENTRY controls with the REQ attribute. This required fields check is only done when the button with the REQ attribute is pressed, because the user may not have even gone to the the ENTRY with the REQ attribute.

8.Add the main logic of the UpdatePhones PROCEDURE definition to the end of the file:

CODE

OPEN(window)

DO SetupScreen

ACCEPT

 CASE FIELD()

 OF ?Phones:Rec:Number

  CASE EVENT()

  OF EVENT:Selected

   DO SetupInsert

  END

 OF ?Ok

  CASE EVENT()

  OF EVENT:Accepted

   EXECUTE Action

    ADD(Phones)

    PUT(Phones)

    DELETE(Phones)

   END

   IF ERRORCODE() THEN STOP(ERROR()).

   ReturnValue = ActionComplete

   POST(EVENT:CloseWindow)

  END

 OF ?Cancel

  CASE EVENT()

  OF EVENT:Accepted

   ReturnValue = ActionAborted

   POST(EVENT:CloseWindow)

  END

 END

END

RETURN(ReturnValue)

The DO SetupScreen statement calls the SetupScreen ROUTINE to perform some window initialization code. Notice that it follows the OPEN(Window) statement. When you are going to dynamically alter the window in a procedure, it must be opened first.

The OF ?Phones:Rec:Number clause in the CASE FIELD() structure demonstrates two important points. The first is the Field Equate Label, itself. The USE(Phones.Rec.Number) attribute contains periods in the field name and periods are not valid in Clarion labels. Therefore, to construct a Field Equate Label for Phones.Rec.Number, the compiler substitutes colons for the periods (because colons are valid in a Clarion label).

The second important point is the OF EVENT:Selected clause in the CASE EVENT() structure. EVENT:Selected generates when the control gains input focus but before the user gets to input data. The DO SetupInsert statement executes to offer the user an option then setup the display and data entry format of the ENTRY control.

The OF EVENT:Accepted code in OF ?Ok is the code that actually writes the record to disk. The EXECUTE Action structure executes exactly one of the ADD(Phones), PUT(Phones), or DELETE(Phones) statements.

An EXECUTE structure is similar to the IF and CASE structures in that it conditionally executes code based on the evaluation of a condition. The EXECUTE condition must evaluate to an integer in the range of 1 to n (where n is the number of code statements within the EXECUTE structure), then it executes the single ordinal line of code within the structure that corresponds to the value of the condition.

In this code, EXECUTE looks at the Action parameter then executes ADD(Phones) if the value of Action is one (1), PUT(Phones) if Action is two (2), or DELETE(Phones) if Action is three (3).

Generally, when you evaluate which Clarion code structure to use for an instance of conditional code execution (IF/ELSIF, CASE, or EXECUTE) the IF/ELSIF structure is the most flexible and least efficient, CASE is less flexible but much more efficient than IF/ELSIF, and EXECUTE is not flexible but highly efficient. Therefore, when the condition to evaluate can resolve to an integer in the range of 1 to n, use EXECUTE, otherwise try to use CASE. If CASE it is not flexible enough, then resort to IF/ELSIF.

The IF ERRORCODE() THEN STOP(ERROR()). statement will check for an error, no matter which statement EXECUTE performed. The ReturnValue = ActionComplete statement sets up the return to the calling procedure, signalling that the user completed the file action, then the POST(EVENT:CloseWindow) terminates the ACCEPT loop, dropping control to the RETURN(ReturnValue) statement.

The OF ?Cancel code does almost the same thing, without writing anything to disk. The ReturnValue = ActionAborted assignment statement sets up the return to the calling procedure, signaling that the user aborted the file action, then the POST(EVENT:CloseWindow) terminates the ACCEPT loop, dropping control to the RETURN(ReturnValue) statement.

9.Add the ROUTINEs called in the UpdatePhones PROCEDURE definition to the end of the file:

SetupScreen  ROUTINE

CASE Action

OF InsertRecord

 CLEAR(Phones.Rec)

 TARGET{PROP:Text} = 'Adding New Number'

OF ChangeRecord

 TARGET{PROP:Text} = 'Updating ' & CLIP(Phones.Rec.Name) |

            &amp; 's Phone Number'  IF Phones:Rec:Number[1] <;> '+'    ?Phones:Rec:Number{PROP:Text} = '@P###-###-####P'  END OF DeleteRecord  TARGET{PROP:Text} = 'Deleting ' &amp; CLIP(Phones.Rec.Name) |            &amp; 's Phone Number'

 DISABLE(FIRSTFIELD(),LASTFIELD())

 ENABLE(?Ok,?Cancel)

END

SetupInsert  ROUTINE

IF Action = InsertRecord

 CASE MESSAGE('International?','Format',ICON:Question, |

               BUTTON:Yes+BUTTON:No,BUTTON:No,1)

 OF BUTTON:Yes

  TARGET{PROP:Text} = 'Adding New International Number'

  ?Phones:Rec:Number{PROP:Text} = '@S20'

  Phones:Rec:Number[1] = '+'

  DISPLAY

  SELECT(?,2)

 OF BUTTON:No

  TARGET{PROP:Text} = 'Adding New Domestic Number'

  ?Phones:Rec:Number{PROP:Text} = '@P###-###-####P'

 END

END

The SetupScreen ROUTINE starts by evaluating CASE Action. When the user is adding a record (OF InsertRecord) the CLEAR(Phones.Rec) statement clears out the record buffer by setting all the fields to blank or zero. The TARGET{PROP:Text} = 'Adding New Number' statement uses Clarion's runtime property syntax to dynamically change the title bar text for the window to “Adding New Number.” Clarion's property syntax allows you to dynamically change any property (attribute) of an APPLICATION, WINDOW, or REPORT structure in executable code. See Appendix C - Property Assignments in the Language Reference for more on properties.

TARGET is a built-in variable that always “points to” the currently open WINDOW structure. The curly braces ({}) delimit the property itself, and PROP:Text is an EQUATE (contained in PROPERTY.CLW, automatically included by the compiler like EQUATES.CLW) that identifies the parameter to the data element (in this case, the WINDOW statement).

The OF ChangeRecord code TARGET{PROP:Text} = 'Updating ' &amp; CLIP(Phones.Rec.Name) &amp; '''s Phone Number' does the same thing, but changes the title bar text to read “Updating Someone's Phone Number.” The ampersand (&amp;) is the Clarion string concatenation operator and the CLIP(Phones.Rec.Name) procedure removes trailing spaces from the name. The IF Phones:Rec:Number[1] <;> '+' structure checks for a plus sign in the first character of the Number string field. The plus sign is used here as a signal that the number is an international number.

Notice that Phones:Rec:Number[1] is addressing the first byte of the field as if it were an array. But you'll recall that there was no DIM attribute on the declaration (the DIM attribute declares an array). All STRING, CSTRING, and PSTRING data types in Clarion are also implicitly an array of STRING(1),DIM(SIZE(StringField)). This means you can directly refer to any single character in any string, whether it was declared as an array or not.

If the number is not international, the ?Phones:Rec:Number{PROP:Text} = '@P###-###-####P' statement uses the same type of property syntax to change the control's entry picture token. Notice that PROP:Text is used to do this, just as it was used previously to change the window's title bar text. The reason is that PROP:Text refers to whatever is the parameter of the control. Therefore, on a WINDOW('title text') it refers to the title text, and on an ENTRY(@S20) it refers to the picture token (@S20).

The OF DeleteRecord code is similar to the ChangeRecord code. The DISABLE(FIRSTFIELD(),LASTFIELD()) statement uses the FIRSTFIELD() and LASTFIELD() procedures to dim out all the controls on the window, then ENABLE(?Ok,?Cancel) un-dims just the OK and Cancel buttons.

The SetupInsert ROUTINE executes just before the user gets to the Number ENTRY control. The IF Action = InsertRecord checks Action and only executes the CASE MESSAGE structure when the user is adding a new record. The MESSAGE procedure can be used to create simple Yes/No choices for users. In this case, the user is asked whether the new number is International.

The OF BUTTON:Yes code executes when the user has pressed the Yes button on the MESSAGE dialog. The TARGET{PROP:Text} = 'Adding New International Number' statement changes the window's title bar text, then ?Phones:Rec:Number{PROP:Text} = '@S20' changes the ENTRY control's picture token. The Phones:Rec:Number[1] = '+' statement places a plus sign in the first character position, then DISPLAY displays it and SELECT(?,2) places the user's insertion point at the second position in the current control.

The OF BUTTON:No code is similar, changing the window's title bar text and the control's entry picture token.

Update the Project file

Since we added a FILE structure to the program, we have to add its file driver to the Project so it can be linked into the program. If you don't, you'll get an error message something like “link error: TOPSPEED is unresolved in file hello.obj.”

L13AddFileDriver.jpg

10.The Solution Explorer should still be opened. If not, open it again by pressing CTRL + ALT + L.

11.RIGHT-CLICK on the File Drivers project node, and select Add File Driver from the popup menu.

12.Highlight TOPSPEED(TPS) in the Select File Drivers window, then press the Select button.

13.CLICK on the Build and Run button BuildRun.jpg.

After exiting the program, close the Text Editor, and return to the Clarion IDE.

blk2blue.jpg

ABC Template Generated OOP Code

When you examine the source code generated for you by the Application Generator, you'll see some fundamental differences from the code we just wrote. The reason for that is the Application Builder Class (ABC) templates generate code that extensively uses the ABC Library'a set of Object Oriented Programming (OOP) classes.

The code we just finished writing is Procedural code, not OOP code. Now we want to take a quick look at the generated OOP code to show you how what you just learned relates to the code you'll see generated for you. This will be just a quick look to highlight the major differences.

Load a simple application

In the '\Lessons\LearningClarion\Lesson13App folder, we have created a very smple ABC template based application, based on a single Phones table.

1.Load the Phones application into the Clarion IDE, and open the Application Tree.

L13PhonesApp.jpg

Look at the Program Source

Now let's examine the source code that was generated for you.

2.Switch to the Module Tree Mode.

3.RIGHT-CLICK on Phones.clw, and choose Module from the popup menu.

The PROGRAM Module

This is the PROGRAM module (the code should look similar to this, but may not be exactly the same). The basic structure of a Clarion OOP program is the same as the procedural. The first two statements are EQUATE statements which define constant values that the ABC Library requires. Following those are several INCLUDE statements. The INCLUDE statement tells the compiler to place the text in the named file into the program at the exact spot the INCLUDE statement.

 PROGRAM

INCLUDE('ABERROR.INC'),ONCE

INCLUDE('ABFILE.INC'),ONCE

INCLUDE('ABFUZZY.INC'),ONCE

INCLUDE('ABUTIL.INC'),ONCE

INCLUDE('ERRORS.CLW'),ONCE

INCLUDE('KEYCODES.CLW'),ONCE

The first four INCLUDE files (all starting with “AB” and ending with “.INC”) contain CLASS definitions for some of the ABC Library classes. The next two INCLUDE files (all ending with “.CLW”) contain a number of standard EQUATE statements used by the ABC Template generated code and ABC Library classes. The ONCE attribute on each means that if they have already been included, they won't be included again.

 MAP

  MODULE('PHONEBC.CLW')

DctInit   PROCEDURE

DctKill   PROCEDURE

  END

!— Application Global and Exported Procedure Definitions ————

  MODULE('PHONE001.CLW')

Main    PROCEDURE   !Clarion 5.6 Quick Application

  END

 END

The MAP structure contains two MODULE structures. The first declares two procedures DctInit and DctKill that are defined in the PHONEBC.CLW file. These two procedures are generated for you to properly initialize (and un-initialize) your data files for use by the ABC Library. The second MODULE structure simply names the application's first procedure to call (in this case, Main).

Phones     FILE,DRIVER('TOPSPEED'),PRE(PHO),CREATE,BINDABLE,THREAD

Name_Key     KEY(PHO:Name),DUP,NOCASE

Record      RECORD,PRE()

Name         STRING(20)

Number       STRING(20)

           END

          END

The above is your file declaration.

These next two lines of code are your first OOP statements:

Access:Phones        &amp;FileManager,THREAD

Relate:Phones        &amp;RelationManager,THREAD

The Access:Phones statement declares a reference to a FileManager object, while the Relate:Phones statement declares a reference to a RelationManager object. These two references are initialized for you by the DctInit procedure, and un-initialized for you by the DctKill procedure. Both of these procedures are embedded in the Dictionary Class, which is discussed below. These are very important statements, because they define the manner in which you will adress the data file in your OOP code. The THREAD attribute declares that the CLASS is allocated memory separately for each execution thread in the program.

The next three lines of code declare a GlobalErrorStatus object, GlobalErrors object, a search object, and an INIMgr object.

FuzzyMatcher         FuzzyClass

GlobalErrorStatus    ErrorStatusClass,THREAD

GlobalErrors         ErrorClass,THREAD

INIMgr               INIClass

These objects handle all search criteria, errors, and your program's .INI file (if any), respectively. These objects are used extensively by the other ABC Library classes, so must be present (as you will shortly see).

GlobalRequest  BYTE(0),THREAD

GlobalResponse BYTE(0),THREAD

VCRRequest     LONG(0),THREAD

Following that are three Global variable declarations that the ABC Templates use to communicate between procedures. Notice that the global variables all have the THREAD attribute. THREAD is required since the ABC Templates generate an MDI application by default, which makes it necessary to have separate copies of global variables for each active thread (which is what the THREAD attribute does).

Dictionary           CLASS,THREAD

Construct              PROCEDURE

Destruct               PROCEDURE

                    END

The Dictionary Class initializes both File and Relation Managers (contained in the DctInit generated procedure, called from Construct) whenever a new thread is started. Likewise, the Managers' kill code (contained in DctKill, called from Destruct) must be called whenever a thread is terminated. The Construct is implicitly called when the global Dictionary object is instantiated.

The global CODE section only has eight lines of code:

 CODE
 GlobalErrors.Init(GlobalErrorStatus)
 FuzzyMatcher.Init                                 ! Initilaize the browse 'fuzzy matcher'
 FuzzyMatcher.SetOption(MatchOption:NoCase, 1)     ! Configure case matching
 FuzzyMatcher.SetOption(MatchOption:WordOnly, 0)   ! Configure 'word only' matching
 INIMgr.Init('phones.INI', NVD_INI)                ! Configure INIManager to use INI file
 Main
 INIMgr.Update
 INIMgr.Kill                                       ! Destroy INI manager
 FuzzyMatcher.Kill                                 ! Destroy fuzzy matcher

The first, second, and the fourth statements call Init methods (in OOP parlance, a procedure in a class is called a “method”). These are the constructor methods for the FuzzyMatcher and INIMgr objects. You'll notice that the INIMgr.Init method takes two parameters. In the ABC Library, all object constructor methods are explicitly called and are named Init. There are several reasons for this. The Clarion language does support automatic object constructors (and destructors) and you are perfectly welcome to use them in any classes you write. However, automatic constructors cannot receive parameters, and many of the ABC Library Init methods must receive parameters. Therefore, for consistency, all ABC object constructor methods are explicitly called and named Init. This has the added benefit of enhanced code readability, since you can explicitly see that a constructor is executing, whereas with autiomatic constructors you'd have to look at the CLASS declaration to see if there is one to execute or not.

The call to the Main procedure begins execution of the rest of your program for your user. Once the user returns from the Main procedure, the INIMgr and FuzzyMatcher perform some necessary update and cleanup operations before the return to the operating system.

Dictionary.Construct PROCEDURE

 CODE

 IF THREAD()<;>1

  DctInit()

 END

Dictionary.Destruct PROCEDURE

 CODE

 DctKill()

The DctInit procedure call initializes the Access:Phones and Relate:Phones reference variables so the template generated code (and any embed code that you write) can refer to the data file methods using Access:Phones.Methodname or Relate:Phones.Methodname syntax. This gives you a consistent way to reference any file in an ABC Template generated program'each FILE will have corresponding Access: andRelate: objects.

The DctKill procedure call performs some necessary update and cleanup operations before the return to the operating system.

The UpdatePhones Module

Now let's examine the source code that was generated for you for one of your procedures. We'll look at the UpdatePhones procedure as a representative, since all ABC Template generated procedures will basically follow the same form (again, your code should look similar to this, but may not be exactly the same).

1.Choose File BLTTRIA.jpg Close BLTTRIA.jpg File, or press CTRL + F4.

2.In the Application Tree, highlight UpdatePhones, then RIGHT-CLICK and choose Module from the popup menu.

The first thing to notice is the MEMBER statement on the first line. This is a required statement telling the compiler which PROGRAM module this source file “belongs” to. It also marks the bginning of a Module Data Section'an area of source code where you can make data declarations which are visible to any procedure in the same source module, but not outside that module (see Data Declaration and Memory Allocation in the Language Reference).

 MEMBER('Phones.clw')          ! This is a MEMBER module

 INCLUDE('ABRESIZE.INC'),ONCE

 INCLUDE('ABTOOLBA.INC'),ONCE

 INCLUDE('ABWINDOW.INC'),ONCE

 MAP

  INCLUDE('PHONE004.INC'),ONCE !Local module prodecure declarations

 END

The three INCLUDE files contain CLASS definitions for some of the ABC Library classes. Notice that the list of INCLUDE files here is different than the list at the global level. You only need to INCLUDE the class definitions that the compiler needs to know about to compile this single source code module. That's why the list of INCLUDE files will likely be a bit different from module to module.

Notice the MAP structure. By default, the ABC Templates generate “local MAPs” for you containing INCLUDE statements to bring in the prototypes of the procedures defined in the module and any procedures called from the module. This allows for more efficient compilation, because you'll only get a global re-compile of your code when you actually change some global data item, and not just by adding a new procedure to your application. In this case, there are no other procedures called form this module.

The PROCEDURE statement begins the UpdatePhones procedure (which also terminates the Module Data Section).

UpdatePhones PROCEDURE     !Generated from procedure template - Window

CurrentTab           STRING(80)

ActionMessage        CSTRING(40)

History::PHO:Record  LIKE(PHO:RECORD),THREAD

Following the PROCEDURE statement are three declaration statements. The first two are common to most ABC Template generated procedures. They provide local flags used internally by the template-generated code. The ActionMessage and History::PHO:Record declarations are specific to a Form procedure. They declare a user message and a “save area” for use by the Field History Key (“ditto” key) functionality provided on the toolbar.

After the WINDOW structure comes the following object declarations:

ThisWindow   CLASS(WindowManager)

Ask           PROCEDURE(),DERIVED

Init          PROCEDURE(),BYTE,PROC,DERIVED

Kill          PROCEDURE(),BYTE,PROC,DERIVED

Run           PROCEDURE(),BYTE,PROC,DERIVED

TakeAccepted  PROCEDURE(),BYTE,PROC,DERIVED

            END

Toolbar      ToolbarClass

ToolBarForm  ToolbarUpdateClass

Resizer      CLASS(WindowResizeClass)

Init          PROCEDURE(BYTE AppStrategy=AppStrategy:Resize,|

             BYTE SetWindowMinSize=False,|

             BYTE SetWindowMaxSize=False)

            END

The two after the ThisWindow class are simple object declarations which create the local objects which will enable the user to use the toolbar. The next class enables resizing the window at runtime. The interesting code here is the ThisWindow CLASS declaration. This CLASS structure declares an object derived from the WindowManager class in which the Ask, Init, and Kill, Run, and TakeAccepted methods of the parent class (WindowManager) are overridden locally. These are all VIRTUAL methods, which means that all the methods inherited from the WindowManager class will be able to call the overridden methods. This is a very important concept in OOP.

Skipping the rest of the data declarations is all of the executable code in your procedure:

 CODE

 GlobalResponse = ThisWindow.Run()

That's right'one single, solitary statement! The call to ThisWindow.Run is the only executable code in your entire procedure! So, you ask, “Where's all the code that provides all the funtionality I can obviously see happening when I run the program?” The answer is, “In the ABC Library!” or, at least most of it is! The good news is that all the standard code to operate any procedure is built in to the ABC Library, which makes your application's “footprint” very small, since all your procedures share the same set of common code which has been extensively debugged (and so, is not likely to introduce any bugs into your programs).

All the functionality that must be explicit to this one single procedure is generated for you in the overridden methods. In this procedure's case, there are only three methods that needed to be overridden. Depending on the functionality you request in the procedure, the ABC Templates will override different methods, as needed. You also have embed points available in every method it is possible to override, so you can easily “force” the templates to override any method for which you need slightly different functionality by simply adding your own code into those embed points (using the Embeditor in the Application Generator).

OK, so let's look at the overridden methods for this procedure.

ThisWindow.Ask PROCEDURE

 CODE

 CASE SELF.Request

 OF ViewRecord

   ActionMessage = 'View Row'

 OF InsertRecord

   ActionMessage = 'Adding a phones Row'

 OF ChangeRecord

   ActionMessage = 'Changing a phones Row'

 END

 QuickWindow{Prop:Text} = ActionMessage

 PARENT.Ask

The really interesting line of code in the ThisWindow.Ask PROCEDURE is last. The last statement, PARENT.Ask, calls the parent method that this method has overridden to execute its standard functionality. The PARENT keyword is very powerful, because it allows an overridden method in a derived class to call upon the method it replaces to “do its thing” allowing the overridden method to incrementally extend the parent method's functionality.

ThisWindow.Init PROCEDURE

ReturnValue          BYTE,AUTO

 CODE

 GlobalErrors.SetProcedureName('Updatephones')

 SELF.Request = GlobalRequest

 ReturnValue = PARENT.Init()

 IF ReturnValue THEN RETURN ReturnValue.

 SELF.FirstField = ?PHO:Name:Prompt

 SELF.VCRRequest &amp;= VCRRequest

 SELF.Errors &amp;= GlobalErrors

 CLEAR(GlobalRequest)

 CLEAR(GlobalResponse)

 SELF.AddItem(Toolbar)

 SELF.HistoryKey = CtrlH

 SELF.AddHistoryFile(PHO:Record,History::PHO:Record)

 SELF.AddHistoryField(?PHO:Name,1)

 SELF.AddHistoryField(?PHO:Number,2)

 SELF.AddUpdateFile(Access:phones)

 SELF.AddItem(?Cancel,RequestCancelled)

 Relate:phones.Open

 SELF.FilesOpened = True

 SELF.Primary &amp;= Relate:phones

 IF SELF.Request = ViewRecord AND NOT SELF.BatchProcessing

   SELF.InsertAction = Insert:None

   SELF.DeleteAction = Delete:None

   SELF.ChangeAction = Change:None

   SELF.CancelAction = Cancel:Cancel

   SELF.OkControl = 0

 ELSE

    SELF.ChangeAction = Change:Caller                      ! Changes allowed

    SELF.CancelAction = Cancel:Cancel+Cancel:Query         ! Confirm cancel

   SELF.OkControl = ?OK

   IF SELF.PrimeUpdate() THEN RETURN Level:Notify.

 END

 OPEN(QuickWindow)

 SELF.Opened=True

 Do DefineListboxStyle

 IF SELF.Request = ViewRecord      ! Configure controls for View Only mode

   ?PHO:Name{PROP:ReadOnly} = True

   ?PHO:Number{PROP:ReadOnly} = True

 END

 Resizer.Init(AppStrategy:Surface,Resize:SetMinSize)

 INIMgr.Fetch('UpdatePhones',QuickWindow)   ! Restore window settings from non-volatile store

 Resizer.Resize                       !Reset required after window size altered by INI manager

 Resizer.Reset

 SELF.SetAlerts()

 RETURN ReturnValue

There are several interesting lines of code in the ThisWindow.Init PROCEDURE. This is the ThisWindow object's constructor method, so all the code in it performs the initialization tasks specifically required by the UpdatePhones procedure.

The second statement, SELF.Request = GlobalRequest, retrieves the global variable's value and places it in the SELF.Request property. SELF is another powerful Clarion OOP keyword, which always means “the current object” or “me.” SELF is the object prefix which allows class methods to be written generically to refer to whichever object instance of a class is currently executing.

The next statement calls the PARENT.Init() method (the parent method's code to perform all its standard functions) before the rest of the procedure-specific initialization code executes. Following that are a number of statements which initialize various necessary properties.

The Relate:Phones.Open statement opens the Phones data file for processing, and if there were any related child files needed for Referential Integrity processing in this procedure, iyt would also open them (there aren't, in this case).

ThisWindow.Kill PROCEDURE

ReturnValue          BYTE,AUTO

 CODE

 ReturnValue = PARENT.Kill()

 IF ReturnValue THEN RETURN ReturnValue.

 IF SELF.FilesOpened

   Relate:phones.Close

 END

 IF SELF.Opened

   INIMgr.Update('UpdatePhones',QuickWindow) ! Save window data to non-volatile store

 END

 GlobalErrors.SetProcedureName

 RETURN ReturnValue

In addition to calling the PARENT.Kill() method to perform all the standard closedown functionality (like closing the window), ThisWindow.Kill closes all the files opened in the procedure, then sets the GlobalResponse variable.

3.From the IDE Menu, select File BLTTRIA.jpg Close BLTTRIA.jpg File.

Where to Go From Here?

This lesson has been just a brief introduction to the Clarion programming language and the ABC Template generated code. There is much more to the Clarion language and the ABC Library than has been covered here, so there's a lot more to learn. So where do you go from here?

·The articles in the Programmer's Guide. These essays cover various aspects of programming in the Clarion language. Although they do not take a tutorial approach, they do provide in depth information on the specific areas they cover, and there are several articles which deal specifically with OOP in Clarion.

·The ABC Library Reference (Vol I and II) fully documents the ABC Libraries. The Template Guide documents the ABC Templates. This is your prime resource for how to get the most out of Clarion's Application Generator technology.

·The Language Reference is the “Bible” of the Clarion language. Reading the entire manual is always a good idea.

·Examine and dissect source code generated for you by the Application Generator. After doing this lesson, the basic structure of the code should look familiar, even if the specific logic does not.

·The User's Guide contains lessons on using the Debuggers. This will allow you to step through your Clarion code as it executes to see exactly what effect each statement has on the operation of your program.

·SoftVelocity offers educational seminars at various locations. Call Customer Service at (800) 354-5444 or (954) 785-4555 to enroll.

·Join (or form) a local Clarion User's Group and participate in joint study projects.

·Participate in SoftVelocity's Internet Newsgroup (comp.lang.clarion and more) to network with other Clarion programmers all around the world (Strongly recommended!).

The Getting Started and Learning Clarion lesson applications

The completed application and dictionary files are located in the

'\Lessons\GettingStarted\Solution and \(CLARION ROOT)\Lessons\LearningClarion\Solution directory. These are the files created by following the steps in the Getting Started and Learning Clarion documents.

There are two other lessons contained in this folder. The files used for the Online User's Guide's Debugger lesson can be found in the

'\Lessons\Debugger folder.

A lesson targeting the use of the Dictionary Synchronizer The files used for the Online User's Guide's Synchronizer lesson can be found in the

'\Lessons\SQL folder.

Good luck and keep going'the programming power that Clarion puts at your fingertips just keeps on growing as you learn more!

lc_clarion_language_tutorial.htm.txt · Last modified: 2021/04/15 15:57 by 127.0.0.1