Extend the test window to use a buffer handle
- Last Updated: May 6, 2024
- 7 minute read
- OpenEdge
- Version 13.0
- Documentation
In this section, you extend the h-CustOrderWin8.w procedure beyond the
changes you already made 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 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:
- Open h-CustOrderWin8.w in AppBuilder and save it as h-CustOrderWin9.w.
- 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 define next.
- 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.
- Extend the existing
CHOOSEtrigger onbtnFilterto fire for the other buttons, just as you did for the Customer field’sLEAVEtrigger before:
- 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? */ - Go into the
LEAVEtrigger 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. - After the statement that assigns the query handle and
PREPARE-STRING, start a block based on the value of the newcBtnChoicevariable. 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: - End the
DOblock for the Filter choice (this will be done later) and start anELSEblock for the Reposition choice. Save off the Customer buffer handle and execute aFIND-FIRSTmethod on it that once again uses theBEGINSkeyword for CHARACTER fields and the=comparison otherwise. Execute theFIND-FIRSTmethod asNO-ERRORin case there is 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. - Use the
AVAILABLEattribute to check whether there was a matching record. If there was, use theREPOSITION-TO-ROWIDmethod on the query handle to position the query to that record using its RowID. Do this operationNO-ERROR. ApplyCHOOSEto 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-FIRSTmethod, and notFIND-UNIQUE, the AVM retrieves a record into the buffer even if there is more than one match. TheAMBIGUOUSattribute 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. Is not it already there for you to see? Yes, it is. If you leave out the
REPOSITIONmethod on the query and just display the contents of the buffer, you see the recordFIND-FIRSTretrieved. But if you then click the Next button, you do not see the next record following the oneFIND-FIRSTretrieved. You just see the next record in the query as it was before you ever did theFIND-FIRST. The reason for this is that theFIND-FIRSTmethod on the buffer handle is effectively reusing the buffer for a purpose completely separate from the query. There is no connection between theFINDmethod and the records in the query. That is really the purpose of theFINDmethods, that they allow you to fetch a specific record without using a query at all. To use theFINDmethod 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
NEXToperation is required. If you use one of theFINDmethods 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’sREPOSITIONmethods, the query cursor is effectively placed immediately before the record you are repositioning to, so you need theGET NEXTstatement to actually bring that record into the buffer.And finally, consider the
NO-ERRORqualifier on theFIND-FIRSTmethod. It is 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 is in the Customer table (and therefore found by theFIND-FIRSTmethod 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. the AVM successfully retrieves the first Customer Name starting with B from the database, but then theREPOSITIONmethod 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. - Check whether there is 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.
- Define a block of code to support the Find button. Because
the Find button is identifying a single record, the code uses
the
FIND-UNIQUEbuffer 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 are trying to find just one matching record, the comparison operator in the
WHEREclause is simply "=".As before, you need to invoke the method with the
NO-ERRORqualifier, in case the selection either does not yield a record or yields more than one matching record. Next, you need to check for those conditions. - Add code to check the
AMBIGUOUSandAVAILABLEattributes 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.". - Write the code to handle the successful case.
- Close the Customer query.
- Disable the navigation buttons and the Reposition button because they do not apply if you only have one record.
- Display the record that the
FIND-UNIQUEmethod retrieved. - 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 are 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 get an error if you allowed the user to choose them.
You can copy the
DISPLAYstatement 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.Do not forget to end all your blocks properly, with a comment on each
ENDstatement 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.
Add the statements in bold to the block of code for the Filter button:
IF cBtnChoice = "Filter" THEN DO: hQuery:QUERY-OPEN(). ENABLE BtnFirst BtnNext BtnPrev BtnLast BtnRepos WITH FRAME CustQuery. APPLY "CHOOSE" TO btnFirst. END. /* END DO IF "Filter" */ - 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.