![]() |
![]() |
|
![]() |
Subject: FAQ:PdoxWin:Events:2002.03.11 Version 1.0 (2001.05.26) Replacement of lost attachment 2002.03.11 edited by Paradox FAQ Team ==================== 0. Introduction ==================== This FAQ addresses Paradox Events. Thanks to Mark Pauker (for devising the Event Model) and Gwan Tan and Cary Jensen for some valuable insights into it in my earlier Paradox for Windows days. The contents of this FAQ originally appeared in a thread "Events 101", starting on Thu, 10 Feb 2000 11:17:24 EST. ------------------------------- 0.1 Legal Info and Disclaimers ------------------------------- Paradox is a trademark of Corel. Borland Database Engine (BDE) is a trademark of Inprise. The information provided in this FAQ is provided "as is" and is not warranted in any way. The information provided in this FAQ is not endorsed or authorized by Corel or Inprise in any shape, form, or manner. The editors claim NO responsibility for ANY illegal activity regarding this file, or as a result of someone reading this file. You may distribute this file, as long as the copies are complete, unaltered, and are in electronic form only. ------------- 0.2 Feedback ------------- Please send feedback in a Corel Paradox newsgroup or the news:comp.databases.Paradox newsgroup to any of the FAQ Team mentioned in the "FAQ: FAQ FAQ" document. Please preface the subject of your post with the string "PDXWIN FAQ" to alert Team members to the function of the message. Please specify the FAQ name and section number the comment applies to, if any. ------------- 0.3 Attachments ------------- There is an executable attached to this FAQ. It is a self-extracting zip, but will not run any other program. It will deliver its contents to c:\temp. It contins the following files Events101.Fsl see section 1.2 Events102.Fsl see section 2.2 SelectList.* 8 files for the table for Events102.fsl ============================== 1. Events 101 ============================== Warning: this is heavily simplified and probably looks wrong to some people. However, it works, which is what matters <g> ! ------------------- 1.1 Part 1 ------------------- Events are things that happen internally when you do something in a Paradox system. The easiest way to see what I mean by this is to open the trace window on a blank form and watch the events streaming by ! ------------------- 1.1.1 Practice ------------------- 1. Create a new form 2. Open the Form::Init method 3. Drop down the View menu and select Trace 4. Run the form. Enlarge the trace window, but don't cover the form. 5. Click on the Properties menu of the Trace window; choose Built-in Events, and select ONLY the set/remove focus and mouse enter/exit methods. We'll trace whenever they fire. 6. Make sure Properties | Show Code is checked. 7. Run the form 8. You'll immediately see two lines BuiltIn:setFocus, self = "#FormData1", target="#FormData1" BuiltIn:setFocus, self = "#Page2", target="#Page2" These show you that an event was triggered when you started the form that resulted in the form and then the page getting focus. You can look in the setFocus() methods for those two objects (via the Object Explorer) to see what the event was and get info about it. You'll see that the code for these methods starts: method setFocus(var eventInfo Event) ------------------- 1.1.2 Tip ------------------- Click on the type ("Event" and press F1 for help ! Why the practice ? Well, it shows you how to see which methods fire when something happens. Look up the Trace methods in the help and you'll see how to turn tracing on and off in your code, so you only get it at the point you're interested in. General trace is useful for finding bugs when you don't know where they're located, and also for playing with something to find out what happens. Once you know what happens, you know the places you can put code to be run when that thing occurs again in the future. ------------------- 1.2 Part 2 ------------------- Paradox' events exhibit a behaviour called "bubbling". The container of an object will receive an event if the contained object doesn't know how to deal with it. Containership is defined by the way things are drawn on the form or report. ------------------- 1.2.1 Practice ------------------- 1. Start another new form 2. Put a button in the middle of the form 3. Open the Object Explorer (OE) and set it to view "Both" and "Pin". 4. You'll see a tree-like structure in the left side of the OE. Expand this as far as it'll go. The Form contains the Page. The Page contains the button. The Button contains the Label. 5. Each item has a "noise name". The Form's is #Form1. The number is the sequence number of creation of the object, and the rest is a "#" sign followed by the type of the object. As another example, the label on the button should be #Label4. 6. Draw a Box around the Button. 7. Look in the OE and you'll see that the Box now contains the button. 8. Move the box on the form, and containership will cause it _and_ the button to move together. ------------------- 1.2.2 Note ------------------- All Paradox objects MUST have a name, so Paradox assigns them noise names. These names can - and very often do - alter at runtime, so you cannot use them in code. Instead, you rename an object, giving it a static name, and use that. ------------------- 1.3 Part 3 ------------------- In Part 2 we created a simple form with a button in a box. Now add some _very_ simple code and see what happens. ------------------- 1.3.1 Practice ------------------- 1. Click on the Form in the OE tree and, on the right-hand side of the OE, select Events. 2. Double-click on the MouseClick method. You'll get a Window with this code: if eventInfo.isPreFilter() then ;// This code executes for each object on the form else ;// This code executes only for the form endIf 3. Replace the two comments so you see this code instead: if eventInfo.isPreFilter() then msgInfo("Form", "PreFilter") else msgInfo("Form", "PostFilter") endIf 4. Run the form 5. Click on the Button. You'll see the "PreFilter" message. 6. Click anywhere else and you'll see both messages. ------------------- 1.3.2 Q & A ------------------- Two questions: A. Why two messages ? B. Why the difference between #5 and #6 ? A. Every object has some built-in default behaviour. Clicking a button makes it depress and then raise again. The default behaviours only concern themselves with events that they're interested in. If an object isn't interested in an event, its default behaviour for that event is just to pass it on to its container. A button is interested in a mouseclick, but a box isn't. B. The difference is because Paradox has an event model that is handled by the Form. EVERY event goes to the Form first, so that's why we saw "PreFilter" in both cases. The Form offers you a chance to intercept an event before it gets to its intended target. The default behaviour of the form if it's PreFiltering an Event is to pass it on to its intended target, so you can see that the code you write _always_ happens _before_ the default behaviour. Once an Event has been directed to its intended target, it's up to the target and code to deal with it. Let's see how this happens. ------------------- 1.3.3 Practice ------------------- 1. With the same form again, go to each object on the form and open its mouseClick method. 2. Add in a message like the ones in the Form::mouseClick, but stating which object the message is coming from. 3. Now run the form. Click on everything and see what messages you get! You can add other objects now and see bubbling with them, too. TableFrames can be interesting to investigate ! ------------------- 1.4 Part 4 ------------------- Lets look at four important ObjectPAL commands now: doDefault disableDefault enableDefault passEvent Briefly: doDefault is an instruction you put into your code that says to the object "do your default behaviour NOW ! Then let me resume this code" disableDefault prevents the default from happening, and enableDefault allows it again passEvent makes a copy of the event and passes it on up the containership tree. ------------------- 1.4.1 Practice ------------------- 1. In your same form. open the button::mouseClick() and add this code to the end: disableDefault 2. test the form. Nothing appears to have happened. This is because the button "eats" the event anyway, so you can't stop it passing it on. The button still depresses because it isn't the mouseClick (but the mouseDown) that makes it depress. 3. Now add this code as well: passEvent 4. Test the form. You'll see that now the button appears to act like all the other objects. ============================== 1. Events 102 ============================== This part was originally entitled "Where to Put Code, and Why" From the examples, practice, and general fun that you (should) have had with Events 101, you know now the basics of _how_ to watch events happening, and the tools to use to see what events happen when you do something. This is a very important concept - that Paradox and your objects _react_ ! Until the advent of SmallTalk in 1980 nobody had actually worked out how to get a GUI to work properly. Unfortunately, it's also a concept that many people tend to forget, and, as a result, they end up writing a heap of ObjectPAL to do something that is being done by Paradox anyway <g> ! When I first started working with Paradox I was incessantly lectured by my "betters" on the importance of doing things "the Paradox way". However, none of them actually spelled out what that way was, so I made a complete mess of my first app. Oh, it was architecturally superior to anything else they were writing - but it also ran about 300 times slower ! This was because I was doing everything explicitly, instead of just letting Paradox do it. The Paradox Way is, indeed, a Paradox - to get the best from your code, do the least. So, where should you place your code. Well, according to the Gods, Idols, and SoothSayers of OOP, you should place the code in the Object. Great. Lets take a look at a form and I'll describe where the code is, and why. ------------------- 2.1 Setup ------------------- We'll look at a Lookup form. Please see the section on attachments to find the form. This consists of a form, page, tableFrame, and a box with a set of control and navigation buttons. The box of buttons is copied and pasted from another form. We're going to use this form as a prototype for all lookup forms in the app, saving it with a new name whenever we want to use it. In Form::init() we find some SQL that creates a table by obtaining a subset of another table according to some criteria. This is somewhat exceptional, but the code is commented out and left here for later re-use if need be. The table created is the table that the form is based on, so what we're doing is creating a lookup on the fly. This is particularly useful if you want to offer only those values not already in use, or not sold out, or ... This _has_ to go into the Form::init() because that method runs before the form code reads its own data model. Form::open() is the next to run, and its code controls opening libraries for the form, and assigning totally global variables. I usually get a form variable called foGMySelf to attach to the form, so I can refer to the form itself later on, if need be. Obviously, I've commented out the library opening code, but, to satisfy your curiosity, MenuLib handles menus and InfoLib handles information exchange. You can find a cut-down version of InfoLib in another FAQ here. Page::arrive() is pretty much the last method to run before you can start using the form. Here you should do all your last-minute work, like going into edit mode, making the form visible, etc. Some people like to use Form::arrive(), but, IMHO, that method isn't late enough. So, that's the bare essentials for you. Now we have to explore where to trap events ... ------------------- 2.2 Responding to Common Events ------------------- Now we know where to put the start-up code, lets look at common things that might happen on this form. One definition first: the concept of "state". The "state" of an object is the set of values of its variables. Change one or more and its state is altered. In Paradox you can declare variables within a method (between the method() and endMethod statements) and they are strictly transient, being destroyed as soon as the method ends. However, if you place them outside those statements or, better still, in some superior container, it will persist until the container closes. If you place them at the form level, they will persist until the form closes. The user is looking at a list of values and a button bar with navigation buttons and an OK and a Cancel button. Because I use the same button bar for all forms like this, The code for the bar goes into the buttons themselves. There are one or two variables that I want to store for the button bar. If one of the navigation buttons hits the end (record 1, for example) I will remember that until I'm moved off it, and trap the use of navigation that might cause an error because of trying to go past the end of a table. This doesn't appear to happen in Pdx 9, but has in some older versions. These variables are declared in the Var section of the Box that surrounds the button bar, so they will persist and they will be globally available to all the buttons because they are in the container of all the buttons. The above is a good example of placing code in an object. It is also good use of it in Paradox terms, because the object (the toolbar) is cut and pasted as a single unit. On the other hand, the code that moves the cursor on the tableFrame is held within the buttons themselves. I can change the name and table of the tableFrame and I won't have a problem with this because these buttons act on a uiObject variable attached to the tableFrame. The uiObject is attached to the tableFrame in the Form::open() method, but I never have to edit that, because it is attached to a named constant in Form::Const. All I have to do is set the constant when I attach the tableFrame to a new table during form design, and all works like magic. The above is an example of doing less. I open the form, change the data model to accomodate a new table, redefine the tableFrame to show the fields I want, and change one constant accordingly. Navigation now works ! To accomodate OK and Cancel I have a little more work to do. I use a library to transfer values back and forth between forms, so Cancel is always the same - it returns a "False" for a value called "Lookup", indicating that nothing was done. In the code for OK that variable is set to "True", and here I don't even have to return any values ... why not ? Because they're in a table, and the Lookup variable indicates whether or not the waiting code should look for results in the table ! This is an example of doing lots with very little effort ! ------------------- 2.2 Selecting values ------------------- Frequently I need to offer a range of values and allow the user to select 0, 1, or many of them. To do this I very often generate a table filled with a column of all the valid values (usually selected from a master table) and a second column filled with a logical False falue. I then arrange to allow the user to toggle the value of the second column with a mouse click. The calling form uses a simple piece of SQL to get an in-memory tCursor to a table holding just the selected key values (select <keyval> from <tableName> where Selected = True). The code to toggle the value of the "Selected" column is so simple as to be trivial - once you get it to work <g> ! You put this into the Form::mouseClick() method: eventInfo.getTarget(uiTarget) if uiTarget'class = "Field" then if uiTarget'name = "Selected" then doDefault uiTarget.edit() uiTarget'value = not uiTarget'value uiTarget.postRecord() uiTarget.endEdit() endIf endIf The amusing thing is that if you don't have the doDefault then you'll end up with a lock on the table that you have to close Paradox to get rid of :-) You'll notice that I have placed this code at the Form level, and not in the tableFrame. This is because I can make it more generic at this level if I so wish. More importantly, I also find that being able to _find_ people's code is often tougher than finding the bugs. If we all place the code in the same place then we know where to go and look, which will make life much much easier in 3 years' time ! Other famous pieces of code that can best go at Form level are the code to trap for lookups (especially in Paradox 7 & 8 runtimes on NT machines, which crash otherwise), and the code for re-ordering the appearance of the data in a tableFrame by applying filters when the user clicks on the header. It's not because things are hard that we don't dare them; it's because we don't dare them that they are hard - Seneca.events101.exe Paradox Community Newsgroups |
![]() Feedback | Paradox Day | Who Uses Paradox | I Use Paradox | Downloads ![]() |
|
![]() The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation. Paradox is a registered trademark of Corel Corporation. ![]() |
|
![]() Modified: 15 May 2003 Terms of Use / Legal Disclaimer ![]() |
![]() Copyright © 2001- 2003 Paradox Community. All rights reserved. Company and product names are trademarks or registered trademarks of their respective companies. Authors hold the copyrights to their own works. Please contact the author of any article for details. ![]() |
![]() |
|