Difference between revisions of "Fundamental Visual Prolog - GUI"
m (→Creating a Modal Dialog)
m (→Modifying the Menu)
|Line 131:||Line 131:|
==Modifying the Menu==
==Modifying the Menu==
Now we shall modify the main menu of the program. As noted before, the IDE would have provided a default menu consisting of some standard menu items which are found in many programs. We need to edit that menu to suit our program's functionality. Using the project tree,
Now we shall modify the main menu of the program. As noted before, the IDE would have provided a default menu consisting of some standard menu items which are found in many programs. We need to edit that menu to suit our program's functionality. Using the project tree, click on the TaskMenu.mnu item as seen below:
Revision as of 09:23, 16 September 2013
|Fundamental Visual Prolog|
In this tutorial, we shall add a simple Graphical User Interface (GUI) front end to the family project, which was developed in an earlier tutorial. We shall learn to do create and edit most of the GUI components of a simple Windows program directly within the Visual Prolog IDE (Integrated Development Environment). We shall learn a bit about handling of GUI events (like clicking a button on a specific part of the GUI, etc.), and how they work within a Visual Prolog program. We shall also learn about modal dialogs with two examples: one, which is inbuilt by the Windows operating system, and one, which we shall build for ourselves for our program.
Download source files of the example project used in this tutorial:
- Visual Prolog 7.4 and 7.3 install examples in the IDE:
- Help -> Install Examples...
- Visual Prolog 7.2 version.
- Visual Prolog 7.1 version.
- 1 Introduction to a GUI
- 2 GUI: Responding to Events
- 3 Starting a GUI Project
- 4 Creating a Modal Dialog
- 5 Modifying the Menu
- 6 Modifying the Toolbar
- 7 Getting the Main Code into the Program
- 8 Encapsulating the Interactive Code
- 9 Understanding What we have Done So Far
- 10 Running the Program
- 11 Conclusion
- 12 References
Introduction to a GUI
A GUI is simply an acronym for Graphical User Interface. In the Windows operating system environment the term represents the familiar windows with the menu bar, toolbar, the little buttons on the top right hand corner of a window, etc. Each of those elements is known as a GUI component, and in well designed programs, these components work as per conventionally accepted norms.
In programming terms, a GUI does two things. It uses complex graphical routines to put (and restore) graphical images on the relevant parts of the computer monitor. (E.g. the little square with an X on it on the top right corner of a window). It also controls the behaviour of the mouse and other input devices over these graphical areas. Mercifully, both these detailed programming are done by the operating system. As a programmer one need not get down on our knees to program such graphic, mouse and keyboard functions. Windows does it for us. It has given an API (Application Programming Interface) which can be used to setup the GUI required for any program.
Furthermore, Visual Prolog has added one more layer - the PFC (Prolog Foundation Classes) to help out not only with the GUI but with other areas of programming too.
And even more; the Visual Prolog IDE can be used to visually create the mock-ups of the final GUI that would be used by the program that you would be developing. In this tutorial we shall set out to do just that. We will be using the logic of the family example that was tackled in a previous tutorial.
There is a lot of difference between a GUI and console programs. Though both console programs and GUI programs always starts at a fixed entry point (from the Goal procedure), that is where the similarity ends. Console programs do not show graphical elements, so they have the user input either from start-up parameters or from direct questions setup by the programmer in advance. The sequence and direction of activities in the program would have to be determined by the programmer. The user cannot modify that sequence. In a console program, the program is started from the Goal procedure and it is then worked logically from that point onwards, straitjacketing the user through a series of logical steps set out by the programmer.
With GUI programs, the programmer can choose to be flexible. The user can be given options what should be done on which part of the program. For example; if you start any conventional Windows program, it normally will not press you to do this or do that. You can laze around casually flipping through the menus without really clicking on any of them. Consider this scenario: The File menu will have several menu items. You can take the mouse cursor through each one of them without really being compelled to click on any of the menu item you may find there. In fact, one of the common methods by which people get to grips with a piece of software is by such casual browsing over the GUI of a program.
Anyway, to cut a long story short; the programmer has to use a different strategy when programming GUI programs. On one hand, the programming is easier because there is no rigidity or regimentation in the activities of the program that will be carried out.
But on the other hand, the programmer needs to know how each of the GUI components works, what are conventionally accepted practices and how to adhere to those practices so that the user is not given unnecessary surprises when using the program. Also, sometimes it would be difficult to predict the consequences that may follow due to some GUI event being triggered. Hence the programmer has to carefully isolate the logic so that it remains valid irrespective of how the GUI was used, and carefully accommodate those situations, where the program's logic may be unable to accommodate (using polite, easy to understand warning dialogs, etc.).
GUI: Responding to Events
In a GUI program, all the GUI components wait for inputs from the keyboard and/or mouse (and/or other pointing devices such as digitizer puck, etc.).
The information from such input devices (click of a mouse and other such GUI activity) is known as an event. There are other events to which a GUI responds - some of them are internally generated by the operating system such as ticking of the internal clock, interaction with other programs running concurrently, ActiveX events, etc. Depending on what the program is expected to do, the programmer earmarks appropriate portions of code to respond to relevant events. PFC GUI package provides the set of listeners and responders to handle GUI events.
When the program responds to an event, the program can either listen the event or handle the event. If an event is listened, then it means the program can just do something. For example the show event comes, when a window is shown, hence program can make some initialization here. If an event is responded, then it means the program can change the behaviour of the system. For example, on the close event the program can respond either to close window or to keep it open.
Please notice also that the GUI program actually does nothing but waits till a GUI event is invoked.
One important feature of event handling must be understood here:
Because the code of a listener and a responder wait till an event has happened; it may seem as if there is no obvious logic to it. Other modules need not even know what is happening privately within the class which was actually handling a GUI event. In our earlier tutorial, we noticed that all the logic was well contained and logically connected to one another. But in case of GUI programs, parts of the code (the one that handles GUI events) would be fragmented and placed under different listeners and responders.
In this tutorial, we shall go about taking the same logic of the family example which was dealt in an earlier tutorial, and then use that logic via a GUI interface.
Starting a GUI Project
Let us start at the very beginning (a very good place to start!). When we create the project in the Visual Prolog IDE, make sure that the Project Kind is set to GUI application.
The IDE then creates the initial set of modules required to handle the GUI along with the GUI component resources: the main menu, one top toolbar, one bottom status bar, the About dialog and the main Task Window of the program.
Just after you create a project in this fashion, you can immediately compile it to get an empty GUI program. At this stage, the program actually would not do anything. However, it will have all the basic functionality expected in a GUI program. Visual Prolog has simply constructed the essential skeleton of a working GUI program and has provided some features commonly required.
For example; within the main window (known here as the Task Window) of the application Visual Prolog gives another window titled Messages. This window is used internally to act as the console. When the programmer uses the stdio::write(...) predicate in the program, the output would get directed to this Messages window. If Visual Prolog has not redirected the output of PFC class stdio to the Messages window, those strings would not be seen, as a GUI environment does not have a console area by default.
In a console application, the console is always available as a blackboard onto which the programmer can deposit output. That is why those applications are known as console applications. We saw that in an earlier tutorial that we could simply write to the console in such an application using stdio::write(...) predicate.
When you run our compiled GUI program at this stage, you would notice that you can flip through the menus, re-size the outer main window (or Task Window) of the program, double click on the Messages window to zoom it full extents within the Task Window, etc. Visual Prolog even gives a small pop-up menu for the Messages window which is often convenient. Right click anywhere inside the Messages window, and a small menu would open up with which you can clear the contents of the Messages window and do other activities.
But at this point this simple GUI program does not have any logical functionality that we desire. We need to do some further work to achieve this functionality.
Before we embark on populating the project with the actual working logic of the program, we shall create and/or modify some GUI components that we need. Under normal circumstances, it is here where the programmer would have to spend some time thinking out a strategy for the GUI of the program. A clear list of GUI components would have to be made, and only then one should embark on the creation and/or modification of the components. This activity should not be carried out in an unplanned manner. But for the sake of simplicity, we shall proceed in this tutorial with the assumption that this planning process is over.
All GUI components are stored as separate resource files during the coding phase. In most other programming languages; these resource files are separately compiled and then included into the main code during the linking process. Visual Prolog deals with all this resource compilation and linkage issues automatically without unnecessarily bothering the user.
Creating a Modal Dialog
Now we shall add another GUI component that we would need later on in our program. It is a dialog box which will be used to feed the name of a person to the program for the program to process. In the Project tree (where all the modules and resources are listed in a clearly laid out tree menu), right click on the Task Window and from the context menu, select New in Existing Package...
The Create Project Item dialog would be presented. Ensure that Dialog is selected from the left hand side and on the right hand side enter the AncestorDialog.
A default dialog is created for you to edit. As we will not be implementing a Help function for this dialog, we will click on that particular GUI component (i.e. the Help button) and we shall delete that by pressing the DEL key.
Then the other two buttons should be selected and should be shifted about on the dialog box in order to make the dialog look presentable. The final result would look something as shown below:
Using the dialog editing controls, we shall now insert a static text. (The word static implies a non-clickable GUI element). Refer the previous image. On clicking the static text creation button, we can mark out the rectangular area, where our text would appear as seen below:
On releasing the mouse you will see the control on the dialog. Left click on the just created control and the properties panel would show the properties of the created control. Under the Text field, write "Person": as seen below. That would be the text which would appear inside the dialog.
In a similar fashion, we shall use the dialog editing controls to insert an editable text field or an edit control, as seen below:
This time, we shall leave the text field empty and instead change the name to something meaningful (instead of the default name which is given by the IDE) as seen below. This name is used within the program code to refer to this edit field in the program's code as fact variable.
This is how finally our dialog would look like:
We have one last step which is needed to be performed. Each dialog has a property known as a Visit Order. That is the order by which the controls (i.e. GUI components that interact with the user) on that dialog are visited, when the user clicks on the TAB key. The phrase "visit a control" means that the said control receives the input focus. (But then that is more jargon!) The phrase the control receiving the input focus, means the said control which is immediately receptive to inputs from the keyboard. For example; if the edit field had the input focus one would see the blinking caret within the field ready to accept characters entered through the keyboard.
In order to change the visit order, right click on the dialog, and click on Visit Order in the context menu that opens up.
Small buttons will appear on some of the GUI components of the dialog box as shown in the figure below. As seen here, the text edit control (idc_ancestordialog_personname) has a visit order of 3 whereas the visit order number for the OK button is 1.
This means that when the dialog is presented to the user, the text edit will not be the one which will receive keyboard characters immediately. When the user presses the TAB button, then the focus will shift to the Cancel button, and only on pressing the TAB button one more time would the focus will go to the edit control. Only then the text edit control would receive keyboard input.
In short, the edit field can be said to have the last visit order number, with respect to all the controls in the dialog.
We need to change this. Click on the small button labeled 3. This will open up the Visit Order dialog box as shown above. You can change the order using the + and - buttons. In our case, we will use the change the visit order so that idc_ancestordialog_personname has a visit order of 1. Now, when the dialog gets used in the program; the input focus will first be on the edit control.
Please note that in dialogs, there is another property called the default push button which can be set separately from the visit order. An event for the button that has been set to be the default push button will be triggered on pressing the ENTER key irrespective of which control had the input focus. Usually, the default push button is set to be the OK button.
When you right-click on the dialog, you can set the overall dialog attributes using the following dialog. If you notice, the Type of the dialog is set to Modal. The term modal reflects the fact that whenever the dialog is presented to the user, the GUI would stop responding to other parts of the program, when the dialog is visible. Only when the dialog is disposed off by the user (by pressing the OK or Cancel button) the GUI will become responsive once again to all GUI activity.
When you click on the dialog, you can set the overall dialog properties on the properties panel. If you notice, the Type of the dialog is set to Modal. The term modal reflects the fact that whenever the dialog is presented to the user, the GUI would stop responding to other parts of the program, when the dialog is visible. Only when the dialog is disposed off by the user (by pressing the OK or Cancel button) the GUI will become responsive once again to all GUI activity.
A modeless dialog on the other hand does not make this restriction. The entire GUI interface is responsive even if the dialog is active. Programming a modeless dialog requires a little bit more thought and it is not touched upon in this tutorial.
Using the same Dialog Attributes (see figure above), you should also change the title to the "Ancestor of ...".
Modifying the Menu
Now we shall modify the main menu of the program. As noted before, the IDE would have provided a default menu consisting of some standard menu items which are found in many programs. We need to edit that menu to suit our program's functionality. Using the project tree, right click on the TaskMenu.mnu item as seen below:
This will start the Menu Editor. The main dialog of the Editor is seen below. Click on the Edit button after ensuring that the TaskMenu.mnu is selected in the project window.
This will open up the following Menu Item Attributes dialog, as shown above. We will change the name from &Edit to &Query. The ampersand sign (&) before Q indicates the letter which would get underlined in the menu. Users can use that alphabet to quickly access the menu.
You can test this feature by clicking on the Test button in the above dialog. The top menu of the IDE will be temporarily replaced by the menu you are designing. You can then browse the menu just to get a feel of it. You can open it submenus, etc. To exit the test mode, you can click somewhere in the IDE window out of the tested menu.
We shall now return back to the main TaskMenu dialog. Double click on the &Query entry, and you would notice that it still contains the menu items of the old Edit menu (Undo, Redo, Cut, Copy and Paste). Delete all the menu items you see within for that main menu entry (&Query). Also change the Constant Prefix setting to id_query. The constant prefix is used internally by the IDE to construct the constants that would represent the various menu items. Those constants would be used internally in the code to refer to the menu items.
Now we shall add some menu items under the main Query menu item. To do that, click on the New SubItem item and enter the information you see in the following figure:
You would note that the Constant is automatically created for you depending on the text you enter for the menu item. In the above example it would be id_query_father as seen in the previous figure. In a similar fashion, create menu entries for &Grandfather and &Ancestor of ...
Note that by convention an ellipsis (three dots) is used at the end of those menu items which will yield another dialog before any work is carried out. In the above example, &Father and &Grandfather menu items will not have the ellipsis. But the &Ancestor of ... entry will have the ellipsis, because on invoking that menu item, a dialog would open up before any activity gets done.
One last step is remaining in the editing of the TaskWindow menu. By default, when the IDE creates the menu, the File|Open menu is disabled. You would have to locate that menu item, and enable it by removing the checkmark from that menu item's Menu Item Attributes dialog, as seen below:
By the way, in the previous figure you would note that you can set the accelerator (i.e. the hot-key) which can be invoked by the user to quickly invoke the same function as the menu item. In the above example, it would be the F8 function key. It should also be noted that the menu item for opening a file is indicated here as &Open (without an ellipsis). You should correct that so that it reads &Open ... to suit the ellipsis convention.
When you close the TaskMenu creation dialog, the IDE would confirm whether you want to save the menu. Click on Save.
Modifying the Toolbar
The toolbar of the application is another useful GUI component. Usually, it would contain buttons representing some of the functions of various menu items. In short, those buttons act as short cuts to the menu. We shall now go about editing the program's toolbar. A default toolbar is created for the program by the Visual Prolog IDE when you first create the project. From the Project Tree, double-click on the ProjectToolbar.tb.
This will invoke the toolbar editor. Note that the top portion represents the toolbar that you are editing and the bottom portion indicates the various controls that are available for you to edit those toolbar components.
In the toolbar, you would notice a set of buttons with predefined iconic images (as commonly accepted in GUI programs) If so desired, you can change those iconic images too but in this tutorial we shall not get into that fine detail. It should be indicated here that the Visual Prolog IDE does contain a nifty little icon editing program right inside it. For larger images, the IDE opens MS Paint for editing.
The buttons have been mapped out to a set of menu-item functions. But as we have now edited the menu items, we shall also edit the toolbar buttons and map it to the correct locations.
Firstly, we need to remove the buttons representing cut, copy and paste as our program does not have those capabilities. Hence select those buttons and delete them from the toolbar. After deletion the toolbar should look as follows:
We shall now map the Undo, Redo and the Help buttons to represent the following menu items: Query|Father..., Query|Grandfather... and Query|Ancestor of ...
(As noted before, we would not be changing the images of those buttons in this tutorial.)
Double click on the Undo toolbar button and in the Button Attributes dialog that is presented, change the Constant that internally represents the toolbar button from id_edit_undo to id_query_father.
In the same dialog box, you should change the Status Text from:
Query fathers;List of all fathers listed in the database
The semicolon in the above string breaks up the string into two parts. The first part is displayed as a tool-tip on the button itself and the second one will appear in the status-line of the main window.
In a similar fashion; change the Redo button's constant so that it is now having the value of id_query_grandfather. And the Help button's constant should get the value of id_query_ancestor_of. The status-line of these two buttons also should be suitably modified.
Getting the Main Code into the Program
We have finished all the work for all the GUI functionality that we need from the program. Now we shall start inserting the logic of the code. Before we do that, let us understand some important differences between the way programming used to be done and how it would now be done for a GUI program.
When a user uses a GUI program, it is like the user is using a room. The user comes into the room through the main door all right but once inside the room; the user is free to decide which part of the room would be put to use. Undoubtedly, the programmer has the last say on how each of those parts would actually work. But by and large the user is free to decide which part of the program to activate. The Goal procedure is still present in a GUI program but here all it does is to just set up the stage for the user to work on. It is like the main door to the room in our analogy.
What does this imply for the programmer? The logic of the code earlier was contained inside one module. Now it will be spread over several modules, depending on the logic which is controlled by corresponding GUI components.
Let us add some basic logical code that is needed by the program. Open TaskWindow.pro and locate the editing caret just after the line:
...once the editing caret is there, then insert the following code into that location:
domains gender = female(); male(). class facts - familyDB person : (string Name, gender Gender). parent : (string Person, string Parent). class predicates father : (string Person, string Father) nondeterm anyflow. clauses father(Person, Father) :- parent(Person, Father), person(Father, male()). class predicates grandFather : (string Person, string Grandfather) nondeterm anyflow. clauses grandfather(Person, Grandfather) :- parent(Person, Parent), father(Parent, Grandfather). class predicates ancestor : (string Person, string Ancestor) nondeterm anyflow. clauses ancestor(Person, Ancestor) :- parent(Person, Ancestor). ancestor(Person, Ancestor) :- parent(Person, P1), ancestor(P1, Ancestor). class predicates reconsult : (string FileName). clauses reconsult(Filename) :- retractFactDB(familyDB), file::consult(Filename, familyDB).
The above code is the logical core of the program. In this tutorial, we would directly be inserting it into the module TaskWindow.pro because we want you to see how the GUI components reach into the logical core to perform various actions. In more complex examples; the logical core would be residing separately, sometimes spread over several modules. The GUI modules (such as TaskWindow, etc.) would then be referring it to those modules separately by increasing the scope of the module (discussed in an earlier tutorial).
Actually, it would be good practice to keep the logical core of the program separate from all GUI related modules as far as possible, but in this tutorial we shall knowingly ignore this sound advice.
Encapsulating the Interactive Code
Now that the very core logic has been taken care of, we need to insert the interactive bits. In the Project Tree right click on TaskWindow.win entry. This entry represents the Windows resource for that main TaskWindow. All clicks, menu events, etc. happening within that TaskWindow will have to be handled using event handlers written for that window. On right clicking at that selection, a small context menu would open up as shown below. Select Code Expert from that menu.
The Dialog and Window Expert (shown below) helps you set default listeners and responders for controls (interactive GUI components) of a particular window or a dialog. The blue filled circle indicates that there is no listener or responder for the indicated control. The green tick-mark indicates that a listener or responder is present.
Using the above dialog, ensure that an event handler is set for the menu item represented by the constant id_file_open.
Now, in the Project tree click on the TaskWindow.pro module so that it becomes selected, and using the Build menu just compile that module. If the TaskWindow.pro module is not selected then the Compile menu item in the Build menu would be disabled, so beware!
When Visual Prolog compiles a module, it reorganizes the project tree so that all the pertinent predicates/domains, etc. get laid out cleanly for us to access. Hence, we would be using this feature to ensure that the IDE lists out all the predicates that are declared in the TaskWindow.pro module. We can then navigate to the predicate we want.
Note that the IDE intelligently recognizes that TaskWindow.pro may need some additional modules, and it compiles the module in two passes.
After the compilation is over; when we refer to the TaskWindow predicates, in the project tree you would notice that all the predicates are seen:
You will find that the Predicates portion of the taskwindow.pro module would reveal the onFileOpen predicate -- which was absent earlier.
When you double-click on that predicate, it will open the editor directly at the location of the clause of the onFileOpen predicate. In case there are multiple clauses, then it would take the cursor to the first clause body.
The onFileOpen predicate is called a listener. As a programmer you need not call this predicate on your own. Windows will automatically call it when the appropriate GUI component is activated (in this case the menu item is clicked)
By default the following code would have been inserted for this event handler:
Replace that code with the following:
clauses onFileOpen(_Source, _MenuTag) :- Filename = vpiCommonDialogs::getFileName( "*.txt", ["Family data files (*.txt)","*.txt","All files", "*.*"], "Load family database", , ".", _), ! , reconsult(Filename), stdIO::writef("Database % loaded\n", Filename). onFileOpen(_Source, _MenuTag).
If you examine the above code, you would notice that we have simply added a clause body above the one given by the IDE. The first clause body will open up the standard Windows dialog box from where the database can be loaded. The second clause body acts as a fail-safe mechanism in case the person cancels that dialog box.
If we now build the program at this stage and run it, you would be able to load a family database into the program using the File|Open menu item. To test this, you can use the same fa.txt database that was developed for the console version of this program. A message appearing in the Messages window will inform you in case the program was able to load a database successfully.
Note that though the dialog would be asking for ".txt" files, they should not be confused with common text files used in computers. The files that are to be used are to be formatted exactly as per our programs requirements. Any other file would result in an error.
If the file gets loaded correctly, then the stdIO::writef(...) predicate is called to indicate that result. A GUI program does not have a regular console but Visual Prolog GUI programs automatically provide a Messages which acts as a stdout console. Hence the stdIO::writef(...) predicate results gets presented in the Messages. (If you had closed the window, then you would not be able to se the result). You can even re-size or zoom the Messages window, to suit your taste.
Now you should go back to the Dialog and Window Expert, and ensure that the listeners for the Query|Father, Query|Grandfather and Query|Ancestor of ... menu items are also set as shown below:
Now for the Query|Father menu item, add the following clause body before the default clause body inserted automatically by Visual Prolog:
predicates onQueryFather : window::menuItemListener. clauses onQueryFather(_Source, _MenuTag):- stdIO::write("\nfather test\n"), father(X, Y), stdIO::writef("% is the father of %\n", Y, X), fail. onQueryFather(_Source, _MenuTag).
For the Query|Grandfather... menu item, add the following clause body before the default clause body inserted automatically by Visual Prolog:
predicates onQueryGrandfather : window::menuItemListener. clauses onQueryGrandFather(_Source, _MenuTag) :- stdIO::write("\ngrandFather test\n"), grandfather(X, Y), stdIO::writef("% is the grandfather of %\n", Y, X), fail. onQueryGrandFather(_Source, _MenuTag).
Understanding What we have Done So Far
Let us now pause our tutorial and take a breather (phew!). "Where is the encapsulation?", - you should ask. Well, we have broken up the code into two parts. There is the non-interactive logical core which we took care of earlier. But those parts which requires inputs from the user, has been squirrelled away in separate portions into different event handlers. As far as the rest of the program is concerned, it need not be even aware where those event handlers are actually written in the program. Now, let us insert some more interactive code into yet another event handler.
For the Query|Ancestor of ... menu item, add the following clause body before the default clause body inserted automatically by Visual Prolog:
predicates onQueryAncestorOf : window::menuItemListener. clauses onQueryAncestorOf(_Source, _MenuTag) :- X = ancestorDialog::tryGetName(This), stdIO::writef("\nancestor of % test\n", X), ancestor(X, Y), stdIO::writef("% is the ancestor of %\n", Y, X), fail. onQueryAncestorOf(_Source, _MenuTag).
The strategy for the above code is to acquire a string from a modal dialog box presented by the ancestorDialog which we had created earlier. The above predicate assumes that there is a globally accessible predicate called tryGetName available in the ancestorDialog module which will return the name of the person whose ancestors we are seeking.
As was seen in the onFileOpen Event Handler, we had sought a string (the filename) returned from a modal dialog. The modal dialog itself was invoked by the predicate vpiCommonDialogs::getFileName(...) It is the same strategy that we are adopting for obtaining a string from the ancestorDialog. The only difference is that vpiCommonDialogs::getFileName(...) gave a built-in standard Windows modal file dialog. But for our home grown ancestorDialog, we would have to do some more coding as we shall shortly see.
On compiling the program, there will be one error:
error c229: Undeclared identifier 'ancestorDialog::tryGetName/1->'
The reason for this error is because the onQueryAncestorOf predicate is expecting this globally accessible predicate called tryGetName from the module ancestorDialog.pro. But we haven't written that yet! Let us now get down to rectifying that error.
This predicate is defined within one module, but called from another module. Hence we need to ensure that the declaration is kept not in the .pro l parts of the program. The class declaration file of a module (extension .cl) is one such place. Therefore, let us now open ancestorDialog.cl and insert the following piece of code. (It is a declaration statin called tryGetName is implemented within the module, and that predicate can be called from other modules too.)
predicates trygetName : (window Parent) -> string Name determ.
In ancestorDialog.pro, let us now insert the core logic relevant to that module. Just the way we did for Taskwindow.pro, it is inserted just after the following line:
Here is the code to be inserted:
domains optionalString = none(); one(string Value). class facts name : optionalString := none(). clauses tryGetName(Parent) = Name :- name := none(), _ = ancestorDialog::display(Parent), one(Name) = name.
Now there is one last issue which is remaining. We need to change the event handler for the OK button. This is required, so that the dialog would assert the value entered by the user into the name class facts. If that is not done, then the above predicate will find an empty string.
The default code given by Visual Prolog is shown below:
predicates onOkClick : button::clickResponder. clauses onOkClick(_Source) = button::defaultAction.
The above code now will have to be changed into the following:
predicates onOkClick : button::clickResponder. clauses onOkClick(_Source) = button::defaultAction :- Name = idc_ancestordialog_personname:getText(), name := one(Name).
Now we have finally finished our program. If you now compile and run the program, you should not get any errors.
Running the Program
Once you start the program, you would notice that no activity gets performed immediately, the way it was done in the console program which we had written earlier. As explained somewhere before, starting up a GUI program is like entering a room where the user is free to do things in whichever order he/she chooses. The room simply waits for inputs from the user in whichever order the user decides to give.
Analogously, in our little program, we can invoke the Query|Query Father menu without loading any data. It would not yield any results or any error either because our main logic also takes care of the situation where the data is absent. We can invoke the File|Open... menu item at our choice, and load the family database (the same one which was used in the earlier tutorial).
After that, we can test the Query|Query Father, Query|Query Ancestor and Query|Ancestor of... to see the same results that were obtained in the console program. The results would be displayed in the Messages. (Be careful not to close the Messages window, else the results would not get displayed). The advantage of a GUI program would be recognized when we find that we can run our queries any number of times. Also, we are free to load different data at any point in time.
In this tutorial, we have gone through the basics of GUI and how a GUI program can easily be developed in Visual Prolog. We found that the IDE (Integrated Development Environment) given by Visual Prolog allows us complete control over all the GUI components we may wish to use. We were able to edit menus, dialogs and toolbars to suit the functionality that we desire.
Then we were able to modularize the Prolog code and insert it into different parts of the program, so that they are safely encapsulated into different listeners and responders. The non-interactive logical core was kept separately.
A GUI program thus developed can then be used in a very flexible manner, with no compulsion to the user regarding the sequence of activities to be performed with the program.