Chapter 6: Acting on User Responses

Introducing User Defined Functions (UDFs)

In chapter 5, we created a button control which displayed a standard pop-up message when the user clicked on it. In this chapter, we'll explore ways to instruct your program to perform a "custom" action if the user clicks on the control.

To perform a "custom" action, you need to create a user defined function (UDF) and associate it as an ACTION with the button. So far, all of the programs you have written up to this point are actually UDFs and, whilst it is entirely possible to create a program which only uses one UDF, this becomes more difficult to do the bigger and more complex the program becomes! Using UDFs makes the task of writing big programs simpler to do by enabling modular programming techniques where you create specific individual modules to  perform specific tasks within your program. This is especially useful where different parts of the program need to perform  broadly similar tasks such as opening a database file.

Firstly, we'll create our own user defined function (UDF) to  assign as an ACTION when the user clicks on the button.

Secondly, we will develop a little more understanding of LOCAL variables by exploring how to capture and use the value returned by a standard function when it completes execution through the use of logic in your programs.

Using your chosen editor, enter the code below and save  the file to the MySourceCode folder naming it HMGGuide-Ch6.prg then compile the program using the " ..\build HMGGuide-ch6" command.

  #include "hmg.ch"

  FUNCTION Main()

  LOCAL cMainWinTitle := "ePortfolio"
  LOCAL cBtn1Cap := "Button 1"
  LOCAL nBtn1Row := 32
  LOCAL nBtn1Col := 64
  LOCAL nBtn1Wide := 60
  LOCAL nBtn1High := 100

  DEFINE WINDOW Win_1 ;
    AT 0,0 ;
    WIDTH 760 ;
    HEIGHT 640 ;
    TITLE cMainWinTitle ;
    WINDOWTYPE MAIN

    DEFINE BUTTON btn1
      ROW nBtn1Row
      COL nBtn1Col
      WIDTH nBtn1Wide
      HEIGHT nBtn1High
      CAPTION cBtn1Cap
      PICTURE "c:\hmg.3.4.0\MySourceCode\Icons\IconCh4.bmp"
      ACTION UDFbnt1Clicked()
      END BUTTON

  END WINDOW

  CENTER WINDOW Win_1
  ACTIVATE WINDOW Win_1

  Return Nil

  /* -- This is a separator line -- */

  FUNCTION UDFbnt1Clicked()

  LOCAL lResponse

  lResponse := MsgYesNo("Do you want to proceed?", "Yes or No Box")

  IF lResponse
      MsgInfo("The user selected 'Yes'", "Info Box")
    ELSE
      MsgStop("The user selected 'No'", "Stop Box")
  ENDIF

  RETURN Nil

You can download a copy of the source code file from here - HMGGuide-Ch6.prg

Changes to FUNCTION Main()

As before, let's go through each change we have made to the program in this chapter.

ACTION UDFbnt1Clicked()

The function UDFbnt1Clicked() will now be run (or called as it is referred to in programming) when the user clicks on the button defined in the main window of the program. In programming terms, the DEFINE BUTTON btn1 definition has a new ACTION property with an attribute of UDFbnt1Clicked(), rather than MsgBox()which we had used at the beginning of chapter 5.

All UDFs must have a unique name no matter where or how they appear within your code and the name must not clash with the name of any of the standard HMG functions. It is possible to have two completely different UDFs which happen to have the same name, but this requires special handling when writing your code (they need to be declared as a LOCAL FUNCTION). Even so, I still recommend you use a unique name as far as possible as this will make updating or debugging code a little easier than if you happen to have UDFs with the same name!

/* -- This is a separator line-- */

Welcome the first comment in your source code! The HMG compiler will completely ignore anything between /* and */, as well as the actual symbols themselves, meaning you can say pretty much anything you like between these symbols. This is useful for organising code files or for including comments, known as internal documentation, which may be helpful in future when reviewing the code, especially when that code is complex. In this example, I have used the comment simply to separate FUNCTION Main() from FUNCTION UDFbnt1Clicked() which I find helpful in my source code files.

The concept of internal documentation is another example of 'good practice' and we will go into a little more detail on this in the next chapter.

Understanding the New UDF

Before we go into the details of the new code that appears beneath the separator line, I'd like to stress that this is actually the second UDF you have written! You have developed FUNCTION Main() to display a window of a certain size and in a certain position and got it to display a picture icon that can be clicked on. That, therefore, is your first UDF and we have now created our second UDF which we will now explore in a more detail.

FUNCTION UDFbnt1Clicked()

As with FUNCTION Main(), this UDF must be declared as a FUNCTION and be given a unique name with brackets. Notice also that the function is completed with the statement RETURN Nil after the actual code for the function? This demarcates the end of the function meaning that any code in between is specific to that function. You will understand the importance of return values, and how to make use of them, very shortly.

LOCAL lResponse

I hope by now that you recognise that we are declaring a LOCAL memory variable that will only be visible in the UDFbnt1Clicked() function and have guessed it will hold a logical value because of the letter 'l' at the beginning of the variable name. What is perhaps less clear is why I have not assigned any value to it as I did with every previous declaration of a memory variable. The reason for this is that I have been deliberately lazy in order to explain to that it is completely possible to declare a variable without giving it a value when you declare it. A default value (i.e. one that is used in the absence of any explicit value) of Nil will be assumed instead.

lResponse := MsgYesNo("Do you want to proceed?",    "Yes or No Box")

This is the first line of code that the program will actually execute when UDFbnt1Clicked() is called. This will only happen if the user clicks on the picture button, so, technically, it may never actually be run while the program is running.

When this line of code does run, the first thing that happens is that the program is told to capture a value returned by the standard control MsgYesNo() once that completes running. This is expressed by telling the program to change the value of lResponse from Nil to a logical value of either true or false (.T. or .F.) by the use of an assignment operator (:=) in exactly the same way as assign a value when declaring a variable. We know that MsgYesNo() will return a logical value because if we review the HMG help for this function, the syntax tells you to expect a logical value. The HMG help might not explicitly state which value is returned by each button but it's fairly easy to guess (true if yes, false if no) and we can test it to make sure!

Now that the program is able to capture a logical value based on whether the user clicks on 'yes' or 'no', how is the program instructed to perform an action depending on that choice? That's when logic is used in computer programs which we will explore next.

Introducing Logic

This command (notice that there isn't any opening and closing brackets after IF) instructs the program to look at the value of the variable lResponse. In chapter 3, I said you can picture a variable as being a small 'box' in the computer's memory that contains a piece of information that may be used by the program to perform certain actions. The IF command used here will 'look inside the box' for lResponse to see which value was stored in there by MsgYesNo().

IF lResponse
   MsgInfo("The user selected 'Yes'", "Info Box")

In the event that the value of lResponse is true (.T.), the IF command will execute the instruction that appears immediately next in the sequence. In this case, that would be to display an information message by calling the standard message control MsgInfo(). However, what happens if lResponse is false (.F.)?

ELSE
   MsgStop("The user selected 'No'", "Stop Box")

When the value stored by lResponse is false (.F.) the IF command will look for an instruction that tells it what ELSE to do and then execute the instruction that appears immediately after that in the sequence. In this case that will be to display a stop message by calling the standard control MsgStop(). Where no ELSE is stated, the program simply continues the next line that appears after the ENDIF. In other words, it does not do anything else if lResponse is false.

ENDIF

As is the case with many of the functions, commands and other capabilities available in HMG, you need to close or complete the IF command by using an ENDIF statement.

The most important thing to remember about the IF command is that it requires a logical condition – one that is either true or false – for it to work. We will explore logical conditions, and how to create them by comparing other types of variable to each other, in greater detail later in this guide.

RETURN Nil

All UDFs including the function UDFbnt1Clicked() also need to be "completed". This is done with the RETURN statement and we have decided to return a <span class="code">Nil</span> value to any function that calls UDFbnt1Clicked().

I have decided not to include any suggestions for you to try for yourself in this chapter as we will be expanding on how to use UDFs in the next chapter.

Chapter 5: Using a Button <<     >> Chapter 7: User Defined Functions