Release 10.1A: OpenEdge Development:
Progress 4GL Handbook

> ## Extending the test window to use a buffer handle

> In this section, you’ll extend the `h-CustOrderWin8.w` procedure beyond the changes you already made in this chapter to illustrate the use and the value of some of the buffer handle attributes and methods. To preserve the first set of changes separately, this variant of the procedure is saved as `h-CustOrderWin9.w`. You’ll add buttons to the window to reposition within the current query to a particular record, and also to use a dynamic `FIND` method to locate and display a single record without using the query at all.

> | To extend the sample window to use a buffer handle:

> 1. Define another variable in the Definitions section to record which of several new buttons the user selected:

>     DEFINE VARIABLE cBtnChoice AS CHARACTER    NO-UNDO.

> This variable keeps track of whether the user chose the Filter button or one of the new buttons labeled Reposition and Find that you’ll define next.

> 2. Drop two new buttons onto the window beneath the Filter button. Call them btnRepos and btnFind and give them the labels Reposition and Find, respectively.

> 3. Extend the existing `CHOOSE` trigger on `btnFilter` to fire for the other buttons, just as you did for the Customer field’s `LEAVE` trigger before:

> 4. At the end of the trigger, add a line to save off which button was chosen:

>     cBtnChoice = SELF:LABEL. /* Was this a Filter, Reposition, or Find? */

> 5. Go into the `LEAVE` trigger for CustNum (which of course applies to all the Customer fields). Define a variable to hold the buffer handle:

>     DEFINE VARIABLE hBuffer  AS HANDLE      NO-UNDO.

> 6. After the statement that assigns the query handle and `PREPARE-STRING`, start a block based on the value of the new `cBtnChoice` variable. Execute the existing code only if the button chosen was Filter:

>     ASSIGN hQuery = QUERY CustQuery:HANDLE
>            cPrepare = hQuery:PREPARE-STRING.
>       IF cBtnChoice = "Filter" THEN
>       DO:

> 7. End the `DO` block for the Filter choice and start an `ELSE` block for the Reposition choice. Save off the Customer buffer handle and execute a `FIND-FIRST` method on it that once again uses the `BEGINS` keyword for `CHARACTER` fields and the = comparison otherwise. Execute the `FIND-FIRST` method as `NO-ERROR` in case there’s no such record:

>     END.      /* END DO IF "Filter" */
>     ELSE IF cBtnChoice = "Reposition" THEN
>     DO:
>         hBuffer = BUFFER Customer:HANDLE.
>         hBuffer:FIND-FIRST("WHERE " + SELF:NAME +
>                         (IF SELF:DATA-TYPE = "CHARACTER" THEN " BEGINS "
>                                 ELSE " = ") +
>                             QUOTER(SELF:SCREEN-VALUE)) NO-ERROR.

> 8. Use the `AVAILABLE` attribute to check whether there was a matching record. If there was, use the `REPOSITION-TO-ROWID` method on the query handle to position the query to that record using its RowID. Do this operation `NO-ERROR`. Apply `CHOOSE` to the Next button to position onto the record itself, display it, and retrieve its Orders:

>     IF hBuffer:AVAILABLE THEN
>         DO:
>           hQuery:REPOSITION-TO-ROWID(hBuffer:ROWID) NO-ERROR.
>           APPLY "CHOOSE" TO BtnNext.
>         END.  /* END DO IF AVAILABLE Customer */

> There are a few things worth examining here. First, remember that because you are using the `FIND-FIRST` method, and not `FIND-UNIQUE`, Progress retrieves a record into the buffer even if there’s more than one match. The `AMBIGUOUS` attribute is never true in this case.

> Second, you might be a little confused about why you have to reposition the query to the record you just retrieved in the buffer. Isn’t it already there for you to see? Yes, it is. If you leave out the `REPOSITION` method on the query and just display the contents of the buffer, you see the record `FIND-FIRST` retrieved. But if you then click the Next button, you don’t see the next record following the one `FIND-FIRST` retrieved. You just see the next record in the query as it was before you ever did the `FIND-FIRST`. The reason for this is that the `FIND-FIRST` method on the buffer handle is effectively reusing the buffer for a purpose completely separate from the query. There is no connection between the `FIND` method and the records in the query. That’s really the purpose of the `FIND` methods, that they allow you to fetch a specific record without using a query at all. To use the `FIND` method to reposition the query, you need code similar to what you just wrote, which uses the RowId to reposition the query to the same record.

> Also, remember why the `NEXT` operation is required. If you use one of the `FIND` methods on a buffer, the record you want is placed in the buffer, and you can use it immediately. But if you use one of the query handle’s `REPOSITION` methods, the query cursor is effectively placed immediately before the record you are repositioning to, so you need the `GET NEXT` statement to actually bring that record into the buffer.

> And finally, consider the `NO-ERROR` qualifier on the `FIND-FIRST` method. It’s entirely possible that the user might click the Filter button and filter the query before clicking the Reposition button, and then enter a value to reposition to that’s in the Customer table (and therefore found by the `FIND-FIRST` method on the buffer) but not in the query’s result set as it has been filtered. For example, the user could filter on Customer Names beginning with A, and then reposition to the first Customer Name beginning with B. Progress successfully retrieves the first Customer Name starting with B from the database, but then the `REPOSITION` method on the query fails because that record is not in the query’s result list. This is important to keep in mind as you use these objects and their methods.

> 9. Check whether there’s a record available and display a message if there is not:

>          IF NOT hBuffer:AVAILABLE THEN
>            MESSAGE "No record matches that value. Try again. ".
>     END.        /* END ELSE DO (IF "Reposition" ) */

> In such a case, a record might not be available either because there was no matching record in the database or because that record was not in the query.

> 10. Define a block of code to support the Find button. Because the Find button is identifying a single record, the code uses the `FIND-UNIQUE` buffer method:

>     ELSE IF cBtnChoice = "Find" THEN
>         DO:
>             hBuffer = BUFFER Customer:HANDLE.
>             hBuffer:FIND-UNIQUE("WHERE " + SELF:NAME + " = " +
>                                     QUOTER(SELF:SCREEN-VALUE)) NO-ERROR.

> Because you’re trying to find just one matching record, the comparison operator in the `WHERE` clause is simply "`=`".

> As before, you need to invoke the method with the `NO-ERROR` qualifier, in case the selection either doesn’t yield a record or yields more than one matching record. Next, you need to check for those conditions.

> 11. Add code to check the `AMBIGUOUS` and `AVAILABLE` attributes to make sure you got exactly one match:

>     IF hBuffer:AMBIGUOUS THEN
>        MESSAGE "This choice returns more than one row. Try again.".
>      ELSE IF NOT hBuffer:AVAILABLE THEN
>         MESSAGE "This choice does not match any row. Try again.".

> 12. Write the code to handle the successful case.

> 1. Close the Customer query.
> 2. Disable the navigation buttons and the Reposition button because they don’t apply if you’ve just got one record.
> 3. Display the record that the `FIND-UNIQUE` method retrieved.
> 4. Reopen the Order Browse:

>              ELSE DO:
>                 CLOSE QUERY CustQuery.
>                 DISABLE BtnFirst BtnNext btnPrev BtnLast BtnRepos
>                     WITH FRAME CustQuery.
>                 DISPLAY Customer.CustNum Customer.Name Customer.Address
>                     Customer.City Customer.State
>                 WITH FRAME CustQuery IN WINDOW CustWin.
>                 {&OPEN-BROWSERS-IN-QUERY-CustQuery}
>             END.    /* END DO IF "Find" */
>         END.        /* END ELSE DO IF no errors */
>       END.          /* END DO IF SCREEN-VALUE NOT "" */
>     END.            /* END DO for the trigger block */

> Why close the query? Because you’re not using it. You found a record using the same buffer the query uses, but that record is not in any way related to the query.

> Why disable the buttons? Because they expect to be able to navigate through the query or reposition within it. You’d get an error if you allowed the user to choose them.

> You can copy the `DISPLAY` statement and the `{&OPEN-BROWSERS-IN-QUERY-CustQuery}` preprocessor from any of the navigation triggers. As you did in an earlier exercise, you could clean this up by factoring out the repeated code into an include file or internal procedure.

> Don’t forget to end all your blocks properly, with a comment on each `END` statement to help you verify the block structure as things get more complex.

> Because the Find button disables the navigation and Reposition buttons, the block of code in the same trigger for the Filter button needs to re-enable them.

> 13. Add this `ENABLE` statement to that block, after it reopens the query:

>       hQuery:QUERY-OPEN().
>       ENABLE BtnFirst BtnNext BtnPrev BtnLast BtnRepos
>             WITH FRAME CustQuery.
>       APPLY "CHOOSE" TO btnFirst.

> 14. Try out the procedure with various combinations of actions. When you click the Find button, for instance, it should enable the fields, accept input, and then disable the fields along with the navigation and Reposition buttons and display the one matching record:

> Using various other Filter, Find, and Reposition requests, you can test the various error conditions that you wrote code to handle.

Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095