OpenEdge Development: Progress 4GL Handbook
![]() ![]() ![]()
|
Using the Browse ObjectThis chapter describes how to design and interact with static browse objects and the queries they represent. In "Creating and Using Dynamic Temp-tables and Browses,"you’ll learn how to create a dynamic browse at run time.
Like most other Progress graphical objects, the browse is also supported on character terminals, though with somewhat reduced functionality. Although this book concentrates on graphical interfaces, this chapter includes notes, along with a section at the end, that describe some of the differences between using the browse in a Windows GUI and in character mode.
A browse is a visual representation of a query. You already know from "Using Queries," that one of the principal uses of a query in Progress is to define a set of rows that you can navigate visually using a browse. The browse displays data from the query in rows and columns. A row represents the data of a single row in the query’s result list. A column represents the value of a particular field for each row. A row and column intersection, called a cell, represents the value of the field (column name) in that particular row. A user can scroll up and down the rows, and left and right through the columns.
You’ve already learned how to use the AppBuilder to generate a browse definition for you, and also how to define a browse on your own. In this chapter, you’ll learn about some of the many features that make the browse the most flexible and powerful visual object the 4GL supports. Even an entire chapter isn’t sufficient to cover every aspect of using the browse. For a description of all browse functionality, including attributes and methods not covered here, see the
DEFINE BROWSEStatement reference entry and the Browse Widget reference entry, in OpenEdge Development: Progress 4GL Reference or the online help.This chapter includes the following sections:
Defining a query for a browse
By default, a query against database tables uses
SHARE-LOCK, so that other users can see the same records but not update them. A query is not scrollable by default. That is, the query does not have the ability to move backward as well as forward in the query. When you associate a browse with a query (by way of theDEFINE BROWSEstatement), Progress automatically changes the query to use theNO-LOCKandSCROLLABLEoptions. You can also associate a query with a browse at run time. In this case, you should make the querySCROLLABLEwhen you define it. Since it’s important for you to begin to separate user interface procedures from database access procedures even in these first exercises, this chapter extends the temp-table-based OrderLine browse from "Defining and Using Temp-tables," so that you think in terms of browsing temp-tables in a client procedure that might be in a separate session from where the database is located. In this case, locking the temp-table records is not an issue since those records are always used only within your local session.Once you define the query, define the browse, and open the query, the browse and the query become tightly bound. The currently selected row and the result list cursor are in sync and remain so. When the user manipulates the browse, the user is also performing the same manipulation on the cursor of the result list. Many programmatic actions performed on the result list or the browse automatically update the other, although this is not universally true. You could say that learning all the subtleties of the browse involves learning what occurs by default, what you have to manage, and what behaviors you can override.
As a rule, a query associated with a browse should be used exclusively by that browse. While you can use
GETstatements on the query to manipulate the query’s cursor, the result list, and the associated buffers, you run the risk of putting the browse out of sync with the query. If you do mix browse widgets andGETstatements with the same query, you must use theREPOSITIONstatement to manually keep the browse in sync with the query.Planning for the size of the result set
The number of records in your result list can have a serious impact on the performance of your browse, especially when you consider that you typically have to load those records into a temp-table and ship them across a network connection before they are seen in the browse. You might want to optimize your query definition to take advantage of features that work well with small and large sets of data, and design your user interface to encourage or require users to provide selection criteria to reduce the number of records that need to go into the data they’re browsing.
Defining a browse
This section describes some of the major functionality and style choices available when you define a browse, and the issues involved with those choices.
You can browse records by defining a browse for the query and opening the query. If you do not specifically enable the browse columns, the result is a read-only browse. Once the user finds and selects a record, your application can use the selected record, which the associated query puts in one or more associated buffers. This is the general syntax for a browse definition:
DEFINE BROWSEbrowse-nameQUERYquery-name[ SHARE-LOCK | EXCLUSIVE-LOCK | NO-LOCK ]DISPLAY {column-list|record[ EXCEPTfield... ] }[browse-enable-phrase]browse-options-phrase.
As with all objects you define, you first give the browse a name. Next, you associate it with a query that you have previously defined. You can later associate the browse with a different query at run time by setting its
QUERYattribute, but the query it’s defined for is the source of initial information about the buffers and fields you use to define columns in the browse.Once again,
NO-LOCKis the only reasonable locking option even for a browse on a database table and the only possible one for a browse on a temp-table, since temp-table don’t support record locking. It’s also the default, so you can leave out theNO-LOCKkeyword altogether.The
DISPLAYphrase tells Progress which fields from the buffers in the query to display as columns in the browse. You can specify either an explicit space-delimitedcolumn-listor a list of one or more record buffers from the query. In the latter case, you can use theEXCEPTphrase to remove one or more fields from eachrecordwhen it is displayed. The default is not to display any fields at all, so you must always include aDISPLAYphrase in your browse definition unless you want to define all the columns at run time, as you will learn to do in "Creating and Using Dynamic Temp-tables and Browses."If you just specify the
DISPLAYlist, the browse is read-only. None of its cells are enabled for input. Thebrowse-enable-phraselets you enable one or more cells for input:
Each column in the
ENABLEphrase must be a column in theDISPLAYlist. If you want to enable all or almost all the columns, you can use theALLkeyword optionally followed by anEXCEPTlist.There are many options you can specify in the
browse-options-phraseto customize the appearance and behavior of your browse. The phrase begins with theWITHkeyword. Here are some of the more important options:
rows DOWN[WIDTHwidth] — You can specify how many rows to display in the viewport of the browse and, optionally, the width of the browse in characters. If the result of the query contains more rows than can be displayed at once, then the browse has a vertical scrollbar to let the user see them. If you use theDOWNphrase but don’t specify aWIDTH, Progress allocates enough horizontal space to display all the browse columns. Otherwise, you can use a horizontal scrollbar to let the user scroll left and right through the columns.- {
SIZE|SIZE-PIXELS}widthBYheight— As an alternative, you can define the size of the browse in both dimensions. This is the outer size of the browse including its border. When you use this option, Progress determines how many rows can be displayed at once, based on theheight. This might result in a partial row being displayed at the bottom of the browse.Note: You must specify either aDOWNphrase or aSIZEphrase in the browse options.MULTIPLE|SINGLE— You can let the user select only a single row at a time or multiple rows. The default isSINGLE.SEPARATORS|NO-SEPARATORS— Separators are vertical and horizontal lines between columns and rows. The default isNO-SEPARATORS, but you will probably find that your browses look better withSEPARATORS.NO-ROW-MARKERS— By default, an updateable browse displays row markers, which let the user select currently displayed rows in an updateable browse widget without selecting any particular cell to update. This option prevents row markers from being displayed.NO-LABELS— This option suppresses the display of column labels for the columns.TITLEstring— You can optionally display a title bar across the top of the browse.NO-ASSIGN— If this option is not specified, data entered into an updateable browse is assigned on any action that results in aROW-LEAVEevent. TheNO-ASSIGNoption is intended for use with user-defined triggers on theROW-LEAVEevent. Essentially, when you specify this option, all data assignments by way of the updateable browse are up to you, the 4GL programmer. If you are defining a browse on a temp-table, then it is likely that the default support for automatic assigns back to the temp-table is appropriate because there are no transaction semantics to worry about. If you were to define an updateable browse directly against a database table, it is unlikely that the default assignment would be appropriate in any but the most trivial situations, so you would specifyNO-ASSIGNto take control of the update process yourself.NO-SCROLLBAR-VERTICAL|SCROLLBAR-VERTICAL— By default, a browse gets a vertical scrollbar, and this scrollbar is enabled whenever the size of the browse is not sufficient to display all the rows in the result set. You can suppress this default with theNO-SCROLLBAR-VERTICALkeyword. If you don’t have a vertical scrollbar, then the user must use the up and down arrow keys or other keys to navigate the browse. A browse always gets a horizontal scrollbar if the width of the browse is not sufficient to display all the columns.ROW-HEIGHT-CHARS|ROW-HEIGHT-PIXELS— (GUI only) By default, Progress assigns a row height appropriate for the font size used in the browse. You can change this default by specifying the row height in either characters or pixels.FIT-LAST-COLUMN— (GUI only) This option allows the browse to display so that there is no empty space to the right and no horizontal scroll bar, by widening or shrinking the last browse column’s width. When this attribute is specified, and the last browse column can be fully or partially displayed in the browse’s viewport, then the last browse column’s width is adjusted so that it fits within the viewport with no empty space to its right and no horizontal scroll bar. If the last browse column is fully contained in the viewport with empty space to its right, it grows so that its right edge is adjacent to the vertical scroll bar. If the last browse column extends outside the viewport, it shrinks so its right edge is adjacent to the vertical scroll bar and the horizontal scroll bar is not needed.Changing the test window for the OrderLine browse
You can try out some of the browse options using the browse you defined for a temp-table based on the OrderLine table in "Defining and Using Temp-tables."
To change the test window:
- Open the
CustBrowseWin4.wprocedure and save it asCustBrowseWin5.w.- Remove the Save Position and Restore Position buttons, and the New State and Number of Matches fill-ins.
- Remove these lines from the Main Block:
ASSIGN cState = Customer.StateiMatches = NUM-RESULTS("CustQuery").DISPLAY cState iMatches WITH FRAME CustQuery.The default position for the OrderLine browse was to the right of the Order browse.- To keep the window from being so wide, change its position to be explicitly below the OrderBrowse in the
VALUE-CHANGEDtrigger block for the OrderBrowse:
- Resize the window so that it is somewhat narrower but taller than it was before. Experiment with it so that it is large enough to display the OrderLine browse when you run it:
![]()
- Now that you better understand the complete syntax for defining a browse, take another look at the browse definition you created for Order Lines in the Definitions section of your window procedure:
The definition has the necessary browse name and query name, and the optionalNO-LOCKkeyword, which could have been left off because it’s the default. This is followed by the list of temp-table fields to display as browse columns. A browse column definition has a long list of available display options, including theLABELandFORMATthat you specified for some of them. Because there is noENABLEphrase, the browse is read-only.The browse options phrase specifies:
NO-ROW-MARKERS— Provides the default when there are no enabled columns.SEPARATORS— Provides the lines between columns and rows.7 DOWN— Provides the height of the browse in terms of rows displayed (because there is noWIDTHphrase all columns are displayed in full).ROW-HEIGHT-CHARS— Specifies the precise height of each row. The value 57 is the same value Progress would provide for the default font if you left this option out.You can experiment with changing some of these options to see how they affect the appearance of the browse.Enabling columns in the browse
In this section, you experiment with browse columns.
To enable some of the columns in the OrderLine browse:
- Add this
ENABLEphrase to the browse definition:
DEFINE BROWSE OlineBrowseQUERY OlineQuery NO-LOCK DISPLAYttOline.Ordernum...ENABLE ttOline.Price ttOline.Qty ttOline.DiscountWITH SEPARATORS 7 DOWN ROW-HEIGHT-CHARS .57.- You can also remove the
NO-ROW-MARKERSkeyword to see the effect of row markers in your updateable browse:
![]()
Now that the browse is updateable there is a row marker at the beginning of each row, if you don’t specify the
NO-ROW-MARKERSkeyword, to indicate which row is selected. This is useful because the way a row is highlighted is very different depending on whether the user happens to click in an enabled or read-only column:
- If the user selects a row by clicking on a column that isn’t enabled, the entire row is highlighted to indicate the selection (as in the first row of the Order browse above), just as if the browse had no enabled columns.
- If the user selects a row by clicking on an enabled column, then that cell is highlighted and a cursor appears in it to let the user change its value, as in the selected row of the OrderLine browse above.
The user should always click on the row marker to select a row to avoid inadvertently selecting an enabled column when this is not the user’s intent.
If the user selects the Price column for an OrderLine, or the Qty or Discount, which are all enabled, the browse cell for that column behaves in much the same manner as a fill-in field. Tabbing or clicking moves the user from one updateable cell to the next. If the user changes the value, then that value is assigned when the user leaves the row. But assigned where? Because this is a browse of a temp-table, the value is assigned only back to the row in the temp-table. If the user browses an actual database table, then the update goes back to the database unless you included the
NO-ASSIGNkeyword in your browse definition. You’ll learn more about updating records in "Updating Your Database and Writing Triggers," so you won’t do any more with enabled columns for now.Note: In character interfaces, a key sequence (ESC-R) toggles between two modes, to select rows or edit cells. By default, the selected row is the highlighted row that you change withCURSOR-DOWNandCURSOR-UP. Character row markers appear as an asterisk (*). Also, theEDITOR-TABandBACK-TABkey functions (usually CTRL-G and CTRL-U) tab from cell to cell.One very important fact to remember is that you cannot enable, or turn on, a read-only browse after you have defined it. If you expect to need any of the capabilities of the updateable browser, enable the appropriate fields in the definition and then use the
READ-ONLYlogical attribute to temporarily turn them off at run time.Defining a single- or multiple-select browse
A browse can support single and multiple selections of rows. By default, a browse is single-select. The
MULTIPLEoption sets up a multiple-select browse. Multiple-selection lets you select any combination of rows from the browse, which you can then access using the appropriate browse attributes and methods.In a multiple-select browse, the user first clicks in a row to select it. To select additional rows without deselecting the first, the user holds down the CTRL key and clicks in each row. Alternatively, the user can click in one row, hold down the mouse key, and drag the cursor through multiple rows to select them all. Clicking on a selected row while pressing CTRL deselects it. Clicking on a row without pressing CTRL selects it and deselects all other rows. In a multiple-select updateable browse, the user must click on one row marker, hold down the mouse button and drag down through the row markers to select multiple contiguous rows.
Note that an updateable browse can only have one selected row when it is in edit mode. So, if the user selects five rows and then an updateable cell, the four rows that do not contain the active cell are deselected.
Note: For a multiple-select browse in character interfaces, selection of each row is switched by the space bar. The close angle bracket (>) indicates each selected row. The same technique applies to both read-only and updateable multiple-select browses.There are several attributes and methods you can use in conjunction with selected rows in a single-selection or multiple-selection browse. You can learn about these in OpenEdge Development: Progress 4GL Reference.
Next, you modify your test procedure to use the
NUM-SELECTED-ROWSattribute and theFETCH-SELECTED-ROWmethod to gather information about a set of selected rows in a multiple-selection browseTo modify your test procedure:
The
NUM-SELECTED-ROWSattribute returns the number of rows the user has selected in the browse. You can then use theFETCH-SELECTED-ROWmethod, which takes the sequence within the list of selected rows as an argument, to position the query to each selected row in turn, and add up the ExtendedPrice for each of those rows.One question you might have is why is it necessary to define a handle variable to hold the handle of the browse, rather than just referring to it in each statement as
BROWSE OlineBrowse? The browse is not part of the initial frame definition for the window’s frame. It is displayed after the window is initialized. If you refer toBROWSE OlineBrowsein the statements with theNUM-SELECTED-ROWSattribute or theFETCH-SELECTED-ROWmethod, Progress insists that you qualify the reference with a frame name so that it can identify the browse. Normally, you would qualify each reference to make it explicit or else simply put the entire set of references inside aDOblock that defines the context:
But because the browse is not part of the frame at compile time, Progress gives you an error message when you compile the procedure (or when you run it, which compiles it first) to the effect that it can’t find OlineBrowse in frame CustQuery. To get around this, you need to fool Progress into accepting the reference at compile time. To do this, you first set the handle
hBrowsevariable to theHANDLEattribute of the browse, and then use the handle in place of the name. Progress accepts this because handle references are always difficult or impossible to check at compile time, so Progress has to assume that the reference will be valid when the code is executed, which it is.To see a total displayed, Run the window, select some OrderLines, and then choose the Total button:
![]()
Browse selection and query interaction
When a browse is initialized, the buffer for its query contains the first record in the query, since the user has not yet selected a record. To keep the browse in sync with the query, the buffer contains the first record in the browse viewport while there is no selected record. Whenever the user selects a row, that row becomes the current row in the query’s buffer.
In a multiple-select browse, if you deselect the current row, the query is repositioned to the previously selected row. If no rows are selected, the query is repositioned to the first record in the viewport.
Table 13–1 summarizes how actions on the browse affect the associated query.
Using the
GETstatement (such asGET NEXT) to navigate within the result list of the query has no effect on the browse. However, theREPOSITIONstatement does update the current position of the browse. If you useGETstatements for a query on which a browse is defined, you should use theREPOSITIONstatement to keep the browse synchronized with the query. Also, when you open or reopen the query with theOPEN QUERYstatement, the browse is automatically refreshed and positioned to the first record.Using calculated columns
A browse can show calculated results involving other browse columns and other available data values as separate columns in the browse. You accomplish this by adding the expression for each calculated column to the
DEFINE BROWSEstatement. All valid browse format phrase options are legal extensions to the expression (for example, aLABELoption). When the browse is opened, the calculated columns are displayed for each row.For an updateable browse, however, you need to refresh the calculation if one of the values in the expression changes. The browse displays the appropriate calculations only when the query refreshes the data. It is more appropriate to refresh the calculated data programmatically when the user finishes editing the affected row. To accomplish this, use a base field as a placeholder for the expression. You then can reference the calculation and refresh the result as needed.
To see an example, you can add a column to the Order browse in your test procedure to display the number of days between the OrderDate and the PromiseDate
To add a column to the Order browse that displays the number of days between the OrderDate and the PromiseDate:
- Open
h-CustOrderWin4.wand save it ash-CustOrderWin5.w.- Define a variable in the Definitions section to act as the placeholder for the calculated field. Call this Integer variable iPromiseDays:
- Define the calculation as a column in the browse that is effectively displayed at the placeholder variable. You do this by including the expression for the calculation in the
DISPLAYlist followed by the at-sign (@) followed by the name of the placeholder variable that is used to store the value and represent its display format. Give the calculated field aCOLUMN-LABELof“Promise!Days”to see the effect of creating a stacked label:
- Run
h-CustOrderWin5.w. You see the calculation along with the other columns (it just happens that the number of days is always 5 in the test data in the Sports2000 database):
Note: When there are calculated fields in theDISPLAYphrase, you cannot use theENABLEALLoption unless you use theEXCEPToption to exclude the calculated field, since calculated fields cannot be enabled.Sizing a browse and browse columns
Normally, the browse calculates its size based on the column width of each field and the number of display rows requested with the
DOWNoption. If the resultant browse is too big for the frame, Progress displays an error message at compile time.Use the
DOWNoption to specify the number of rows to display and theWIDTHoption to specify the width of the browse. If the total column width is greater than the specified browse width, the browse displays a horizontal scrollbar to access the columns that don’t display initially and you avoid the compilation error.Note: In character interfaces, there is no horizontal scrollbar for a wide browse but the user can scroll the browse one column at a time, usingCURSOR-LEFTandCURSOR-RIGHT.The horizontal scrollbar can work in two different ways. By default, the browse scrolls whole columns in and out of the viewport. To change this behavior to pixel scrolling, specify the
NO-COLUMN-SCROLLINGoption of theDEFINE BROWSEstatement or set theCOLUMN-SCROLLINGattribute to No at run time.You can also use the
SIZEphrase to set an absolute outer size of the browse in pixel or character units, and Progress determines how many rows and columns to display. The AppBuilder always uses theSIZEphrase to specify the size of a browse that you lay out visually.The browse also supports a
WIDTHattribute for each column. Use this attribute to set the width of individual columns. When a columnWIDTHsets the width of an updateable column smaller than the size specified by theFORMATstring, the browse cell scrolls to accommodate extra input up to the size specified by theFORMAT. You can take advantage of this behavior to allow the display of longer character fields. For example, you can use it where theWIDTHlimits the display space the column takes up, but the user can scroll left and right through the entire text of the string up to the size of theFORMAT, using the left and right arrow keys.The width of each column label is also a determining factor in the width of the displayed column. If the column label is wider than the data for that column, then the column is made wide enough to accommodate the label. To reduce this width, you can specify a shorter label in the browse definition (as you did in the OrderLine browse, for example). As you’ve seen, you can also create stacked labels by inserting an exclamation mark (!) when the label should be divided between two lines. For more details, see the "Using stacked labels" .
Programming with the browse
The following sections discuss important programming techniques involving the browse, including browse events, managing rows in the browse, and manipulating the browse as a visual object.
Browse events
This section covers the essential events supported by the browse, including:
- Basic events — Occur when the user selects a row in the browse or scrolls through the data in the browse.
- Row events — Occur when the user enters or leaves a particular browse row or when the browse displays data values in a row.
- Column events — Occur when the user enters or leaves an enabled cell for a browse column.
Basic events
The basic browse events include:
VALUE-CHANGED— Occurs each time the user selects or deselects a row. Note that the event name is slightly misleading in that it is not meant to imply that the value of the data in the row has been modified, only that a different row has been selected.HOME— Occurs when the user repositions the browse to the beginning of the query’s result set by pressing the HOME key.END— Occurs when the user repositions the browse to the end of the query’s result set by pressing the END key.OFF-ENDandOFF-HOME— Occur when the user uses the vertical scrollbar or arrow keys to scroll all the way to the end or the top of the browse.DEFAULT-ACTION— Occurs when the user presses RETURN or ENTER or when the user double-click a row.DEFAULT-ACTIONalso has the side effect of selecting the row that is currently highlighted.SCROLL-NOTIFY— Occurs when the user adjusts the scrollbar.Typically, you use the
VALUE-CHANGEDandDEFAULT-ACTIONevents to link your browse to other parts of your application. Your current test window contains an example of the use ofVALUE-CHANGED. Whenever the current row in the Order browse is changed, theVALUE-CHANGEDevent occurs and the trigger executes to retrieve its OrderLines and to display them in the second browse.The
DEFAULT-ACTIONevent occurs when the user explicitly selects a row not just by positioning to it, but by either double-clicking it or pressing ENTER.To define a
DEFAULT-ACTIONtrigger for the Order browse:
- In the Section Editor, select the Triggers section and the OrderBrowse object.
- Choose the New button and select DEFAULT-ACTION from the list of browse events.
- Enter this
MESSAGEstatement for the event:
DO:MESSAGE "This Order's SalesRep is " Order.SalesRep SKIP"and the terms are " Order.Terms VIEW-AS ALERT-BOX.END.- Run the window procedure and double-click on one of the Order rows to see the message:
![]()
The
SCROLL-NOTIFYevent is important if you plan to overlay other botches on browse cells, because it notifies you whenever the position of the rows of data within the browse viewport is changed by a scrolling action.SCROLL-NOTIFYgives you the ability to move the overlay widget to keep pace with the user’s scrolling. See the "Overlaying objects on browse cells" for more information on this technique.You could use the
OFF-ENDorOFF-HOMEevent to retrieve more data to all the rows in the browse’s query, for example, when there are too many rows to retrieve at once. Note that if the user scrolls by clicking and holding the up or down arrows at the bottom and top of the vertical scrollbar, theOFF-HOMEorOFF-ENDevent does not occur until the user releases the mouse key.Row events
The browse supports three row-specific events:
ROW-ENTRY— Occurs when a user enters edit mode on a selected row. This occurs when the user clicks on an enabled cell within a row.ROW-LEAVE— Occurs when the user leaves edit mode. This occurs when the user leaves the enabled cells within a row, either by selecting a different row or selecting a nonenabled cell within the same row.ROW-DISPLAY— Occurs when a row becomes visible in the browse viewport.You can find examples of how
ROW-ENTRYandROW-LEAVEwork in the sections on updating and creating browse rows later in the "Manipulating rows in the browse" .The
ROW-DISPLAYevent lets you change the color and font attributes of a row or individual cells or to reference field values in the row. The event also lets you change the format of a browse-cell by changing the value of itsFORMATattribute. You can also use theROW-DISPLAYevent to change theSCREEN-VALUEof one or more cells within the row.For example, you can create a
ROW-DISPLAYevent for the OrderBrowse in your test window with this code:
DO:Order.PO:SCREEN-VALUE IN BROWSE orderBrowse ="PO" + STRING (Order.OrderNum).IF Order.ShipDate = ? THENShipDate:BGCOLOR IN BROWSE OrderBrowse = 12.END.
This changes the displayed value for the PO column and also checks the value of the ShipDate and changes its background color to signal that the ShipDate has not been entered, as shown in Figure 13–1.
Figure 13–1: Result of ROW-DISPLAY event example
Notes: If you want to use this technique to change the colors of an array extent field, you must do so by directly referencing each member.
In character interfaces, theDCOLORattribute specifies the color of an individual cell.Column events
There are
LEAVEandENTRYevents that reference the browse object itself. For example:
You can also write
LEAVEandENTRYtriggers for individual columns. These triggers can refer to fields in the current row and can set and get attributes of the current cell by using either the name of the column in the browse or theSELFkeyword. For example, you can enable the PO column in the OrderBrowse and then define thisLEAVEtrigger for it:
DO:IF Order.PO:SCREEN-VALUE IN BROWSE OrderBrowse BEGINS "X" thenOrder.PO:BGCOLOR IN BROWSE OrderBrowse = 12. /* RED */END.
Or more simply, you can use the
SELFkeyword to refer to the cell:
Either way, you see the result shown in Figure 13–2 when you run the procedure.
Figure 13–2: Result of column event example
![]()
Searching columns
There are also two events that let you interact with search mode.
START-SEARCHoccurs when the user chooses a column label.END-SEARCHoccurs when the user enters edit mode on a cell or selects a row. You can apply both of these events to columns to force the start and end of search mode.When a read-only browse has focus, typing any printable key (an alphanumeric character) forces Progress to reposition the browse to the first row where the first character of the first browse column matches the typed character. Thus, the read-only browse allows searching, but based only on the data in the first column. If the browse finds no match, it wraps to the top of the column and continues searching.
In an updateable browse, the user can select the column that is to be the basis of the character search. First, the user clicks a column label. The column label depresses. When the user types any printable key, Progress searches for the first row in that column that matches and focuses that row. Column searches also wrap to the top if necessary.
Note: A column search on a browse associated with a query with theINDEXED-REPOSITIONoption does not wrap to the top of the column if it cannot find a record to satisfy the search. This behavior is a side effect of the reposition optimization. To work around this, you can applyHOMEto the query before starting the search.There are two ways to extend this basic behavior.:
- You can configure an updateable browse to look like a read-only browse. This technique gives you selectable columns and searching on those columns even when you don’t want to allow rows in the browse to be modified.
- You can use the
START-SEARCHandEND-SEARCHbrowse events to trap the beginning and end of search mode and write to your own search routines.As noted, the column-searching feature is exclusive to the updateable browse. The read-only browse can do one-character deep searches on the initial column of the browse. You can work around this by defining your browse with one field enabled for input and the
NO-ROW-MARKERSoption specified. After the definition, disable the enabled column by setting the column’sREAD-ONLYattribute to false. This provides an updateable browse that looks like a read-only browse. The updateable browse retains the ability for the user to select individual columns and perform searches on them.Note: The column searching capability is not available in character interfaces.Manipulating rows in the browse
This section describes different ways you can change browse rows.
Refreshing browse rows
If you use a read-only browse and a user updates a record in some way outside the browse, you need a way to refresh the currently focused browse row with the new data. Use the following statement form at the end of the update code to refresh the browse:
Repositioning focus
The browse and its query must remain in sync. At times, you might have to do some behind-the-scenes manipulation of the result set to keep them in sync. Generally speaking, whenever the user repositions the browse by selecting a new row, its query repositions automatically to that same row. Likewise, if the query is programmatically repositioned to a different row, the browse automatically coordinates with it and the new current row within the query becomes the currently selected row in the browse. However, if you simply
FINDa row using the query’s buffer, this does not reposition either the query or its browse. This is why you might need to resync the query in your code in order to properly refresh the browse. To do this, you can use theREPOSITIONstatement. TheREPOSITIONstatement moves the database cursor to the specified position and adjusts the browse viewport to display the new row.To avoid display flashing when doing programmatic repositions, you can set the
REFRESHABLEbrowse attribute toFALSE, do theREPOSITION, and then setREFRESHABLEtoTRUE. This suspends any redisplay of the browse until after the operation is complete.In addition, the
SET-REPOSITIONED-ROW( )method gives you control over the position in the viewport where the browse displays the repositioned row. The method takes two arguments:
- Its first Integer argument tells Progress which row (that is within the browse viewport) to position to. For example, if your browse displays seven rows at a time, you could use the
SET-REPOSITIONED-ROWmethod to show a newly positioned row in the middle of the viewport by using an argument value of 4.- The second Character argument to the method can be
ALWAYSorCONDITIONAL. If you specifyALWAYS, the browse is always adjusted to show the repositioned row in the specified position. If you specifyCONDITIONAL, then the browse adjusts only if the repositioned row is not already in the viewport.Note that normally you set
SET-REPOSITIONED-ROW( )once for the session for a browse to establish its behavior wherever it is used. You can also use theGET-REPOSITIONED-ROW()method to return as an Integer the current target viewport row for repositions.To reposition the query and the browse along with it:
When you enter a value into the field and press TAB, the query and the browse are both repositioned to that row. If you enter a row that Progress can’t find or that isn’t an Order for the current Customer, Progress suppresses these errors and nothing changes in the browse. The
SET-REPOSITIONED-ROWmethod makes the new row the third row in the viewport unless it’s already displayed.For a multiple-select browse, the concepts of focus and selection separate. Selection indicates that the user has selected a record. The user can select many records. The last selected record is the one in the record buffer. Focus indicates that the record has input focus. There can be only one focused row, and it might or might not be a selected row. In a single-select browse, selection and focus are one and the same.
Updating browse rows
By default, Progress handles the process of updating data in the records managed by an updateable browse’s query. Because you should normally create applications that do not have a client-side dependency on a direct database connection, it is not advisable for you to define browses directly against database tables or to update database tables directly through a browse. Therefore, the default update behavior is described here to make you aware of what Progress does when you are browsing a database table, not to recommend that you take advantage of it. Some of these steps might not be completely clear to you until you read "Managing Transactions," but it gives you an overview of the steps.
Assuming that the browse starts with the record in
NO-LOCKstate, Progress follows these steps:
- On any user action that modifies data in an editable browse cell, Progress again gets the record with a
SHARE-LOCK, which means that no other user can change the record. If the data has changed, Progress warns the user and redisplays the row with the new data.- When the user leaves the row and has made changes to the row, Progress starts a transaction (or subtransaction) and gets the record from the database with
EXCLUSIVE-LOCK NO-WAIT, which means that no other user can lock the record in any way. If no changes were made, Progress does not start a transaction.- If the
GETwithEXCLUSIVE-LOCKis successful, Progress updates the record, disconnects it (removes the lock), ends the transaction, and downgrades the lock to its original status.- If the
GETwithEXCLUSIVE-LOCKfails, Progress backs out the transaction, displays an error message, keeps the focus on the edited row, and retains the edited data.You also have the option to disable this default behavior and programmatically commit the changes by way of a trigger on the
ROW-LEAVEevent. To do this, you must supply theNO-ASSIGNoption in theDEFINE BROWSEstatement.If you define your browses against temp-tables, as you are doing in the OlineBrowse of the test procedure, then there are no record locks and no possibility of contention with other users for the records in the temp-table. Therefore, you can let Progress update the temp-table by defining the browse without the
NO-ASSIGNkeyword and then take care of updating the database from the temp-table yourself. You learn how to do this in "Creating and Using Dynamic Temp-tables and Browses."Creating browse rows
In an updateable browser, you might want to allow the user to add new records to the underlying temp-table or database table. Programmatically, this requires three separate steps:
- Create a blank line in the browse viewport with the
INSERT-ROW()method and populate it with new data.INSERT-ROWtakes a single optional argument, which is the string“BEFORE”or“AFTER”. This argument tells Progress whether to insert the new row before or after the currently selected row in the browse. The default is“BEFORE”. You can use theINSERT-ROW()browse method in an empty browse. It places a new row at the top of the viewport.- Use the
CREATEstatement andASSIGNstatement to update the database or the underlying temp-table.- Add a reference to the result list with the
CREATE-RESULT-LIST-ENTRY( )method. This is the list of record identifiers that Progress uses to keep track of the set of rows in the query. This step is only necessary if you do not plan to reopen the query after the update, because reopening the query completely refreshes the list. However, this method makes reopening the query unnecessary for most applications.All three steps are required to create the record and keep the database, query, and browse in sync. Also, there are several possible side effects to allowing the user to add a record through an updateable browse. They include placing new records out of order and adding records that do not match the query. To eliminate these side effects, you can reopen the query after each new record is added.
To add a button to the test window that lets you add new OrderLines to the temp-table through the OlineBrowse:
- In the Definitions section of the
h-CustOrderWin5.wprocedure, beneath the browse define for OlineBrowse, define a handle variable calledhBrowse. You will use this variable to hold the handle of the OrderLine browse because it is needed in several places.- Define a new buffer for the OrderLine temp-table called
ttOline2. You’ll use this buffer to hold onto the values in the current temp-table record as you create a new record, and to copy some of the values from the old record to the new one.- Assign the OlineBrowse handle to the new
hBrowsevariable:
DEFINE VARIABLE hBrowse AS HANDLE NO-UNDO.DEFINE BUFFER ttOline2 FOR ttOline.hBrowse = BROWSE OlineBrowse:HANDLE.- Drop a new button onto the window where it will not be overwritten by the OrderLine browse when it is displayed.
- Name the new button btnNew and give it a label of New OrderLine.
- Define a
CHOOSEtrigger for the button:
DO:IF hBrowse:NUM-SELECTED-ROWS = 0 THENDO:APPLY "END" TO hBrowse.hBrowse:SELECT-FOCUSED-ROW().END.hBrowse:INSERT-ROW("AFTER").END.This trigger code uses the handle of the OlineBrowse that you just assigned up in the Definitions section and inserts a new row in the browse after the currently selected row. First, it checks to make sure that there is a selected row using the browse’sNUM-SELECTED-ROWSattribute. If the value of this attribute is zero, then the user has not selected any row. In this case, the trigger positions to the end of the browse by applying theENDevent to it and then selects that row using theSELECT-FOCUSED-ROWmethod.- Back in the procedure’s Definitions section, add this
ROW-LEAVEtrigger block for the OlineBrowse following the statement that assigns its handle tohBrowse:
This code first checks theNEW-ROWattribute of the browse to see if the row the user left is one newly created by the New OrderLine button.Because you’re not prepared to do a full-fledged database update from the browse yet, the example simply creates a new row in the temp-table. To simplify the example, you start by copying the fields from the previous record into the new one. To do this, youFINDtheLASTrecord in the temp-table using the second bufferttOline2. This gives you the highest LineNum value so that you can increment it for the new record. Note that this means that other initial field values are also assigned from the last record regardless of where in the viewport the user inserted the new record.You create a new temp-table record andBUFFER-COPYthe last record into the new one, incrementing the LineNum field to give it a distinct value. This is important because the temp-table has the same unique index on the OrderNum and LineNum fields as the underlying OrderLine table in the database, so changing the LineNum has to happen in theBUFFER-COPYstatement to avoid a unique index violation.Next, the code assigns values to those columns that are enabled for input. TheINPUTkeyword on theASSIGNstatement tells Progress to take the values from the screen buffer for each column. Then the new values are displayed in the browse.Finally, you create a result list entry for the new record. If you didn’t create a result list entry the row would be lost if you were to scroll it out of the browse’s viewport. Reopening the query would also rebuild the result list, and also assure that all rows are in the proper order, so you can use either technique for getting the browse and the query back in sync.- Run the window. Now you can add a record to the OrderLine browse:
Once again, you’ll learn how to get new records into the database in "Updating Your Database and Writing Triggers."Deleting browse rows
Deleting a record by way of a browse is a two-step process. First, you need to delete the record from the underlying temp-table or database table, and then you need to remove the record from the browse itself, along with the query’s result list. If you are browsing a database table directly and the user indicates a deletion, you should again get the records by
EXCLUSIVE-LOCKNO-WAITand then use theDELETEstatement to remove the records from the database. In the case of a temp-table, you can simply use theDELETEstatement to remove the records.Next, you use the
DELETE-SELECTED-ROWS( )method to delete one or more selected records from both the browse widget and the associated query result list.To add a Delete button to the test window and use it to remove rows from the OlineBrowse and its temp-table:
Because the OlineBrowse is a multiple-selection browse, you can use it to delete one or more rows at once. This code walks through the set of selected rows and retrieves each one in turn using the
FETCH-SELECTED-ROWmethod. This method repositions the temp-table query to that row, so that it can be deleted. The code then uses theDELETE-SELECTED-ROWSmethod to delete all the rows from the browse itself, along with the query’s result list entries for them.If you run the test window, you can now delete one or more rows from the list of OrderLines for an Order. Remember that these records are deleted only from the temp-table. You would have to make a call to a procedure connected to the database to pass the list of rows to delete from the database table.
Manipulating the browse itself
There are various ways in which you can allow users to change the appearance of the browse at run time. In some cases, you can provide for these changes programmatically so that they are fully under your control. In other cases, you can just let users use the mouse to change the size and shape of the browse to their liking. This section describes some of those capabilities.
Setting the query attribute for the browse
When you define a browse, you must associate it with a previously defined query. This association allows Progress to identify the source for the browse columns and other information. However, at run time you can associate a browse with any query that supplies the columns the browse needs by setting the browse’s
QUERYattribute to the handle of the query. The query must supply the same columns as the one you defined the browse with.When you set the
QUERYattribute, Progress immediately refreshes the browse with the results of the new query. If the query is not open then Progress empties the browse.You can use this capability to define a placeholder query for a browse simply to satisfy the definition, and then to associate the actual query with it at run time. Or you can use this to alternate between two or more queries to display different useful sets of data in the same browse.
To define a different query on the Order table to alternate with the display of Customers of the current Order:
(This alternative shows all the Orders in the database that have the same OrderDate as the currently displayed Order.)
- In the Definitions section of
h-CustOrderWin5.w, add these lines:
DEFINE BUFFER bOrder2 FOR Order.DEFINE QUERY qOrderDate FOR bOrder2 SCROLLING.DEFINE VARIABLE lOrderDate AS LOGICAL NO-UNDO.Buffer bOrder2 is a second buffer for the Order table and query qOrderDate is a query on this buffer. You’ll use this to define a query for other Orders that have the same OrderDate as the currently displayed Order. The lOrderDate flag tells the program whether the user is currently seeing the results of the query on the OrderDate or not.- Add a button to the window named btnOrderDate with the label Show all on this Date.
- Define this
CHOOSEtrigger for the button:
If the lOrderDate flag is not set, then the user sees the default query of Orders for the current Customer. In this case the code:
- Opens the other query for all Orders matching the current OrderDate.
- Assigns this to be the query for the OrderBrowse.
- Reverses the value of the flag variable.
- Hides the OrderLine browse because it’s not relevant to this display.
- Switches the label of the button to allow the user to change back to the original query.
If the flag is set, then the trigger reverts to the original query and label, resets the flag, and views the OrderLine browse.There’s one more step you need to take, so that the procedure always reverts back to the original query if the user chooses one of the First, Next, Last, or Prev buttons.- Create a local copy of the
h-ButtonTrig1.iinclude file, call ith-ButtonTrig2.i, and add these lines to it:
This code runs the trigger block if the flag is true, so that it reverts to theOrders OF Customerquery.- Run the procedure now and choose the Show all on this Date button. You can see the effect of switching queries for the browse:
![]()
Accessing the browse columns
In "Creating and Using Dynamic Temp-tables and Browses," you will learn how to create a dynamic browse. Even with a static browse it is often useful to get at some of its attributes through its handle. You have already seen how you can use the handle of a browse rather than the
BROWSEbrowse-namestatic language construct to refer to a browse and access its attributes. The individual columns of the browse also have handles. You can get the handle of any browse column by walking through the list of browse columns from left to right. This section introduces you to this concept, and in later examples you’ll use this technique to act on the columns of a browse. These are the basic attributes you need to identify any column in a browse:
NUM-COLUMNS— This browse attribute returns the number of columns in the browse. You can use this as a limit, for example, in aDOstatement that looks at every column.CURRENT-COLUMN— This browse attribute returns the handle of the currently selected column in the browse, if the user has clicked on a column.FIRST-COLUMN— This browse attribute returns the handle of the first (leftmost) column in the browse. Use this attribute to get started walking through the columns.NEXT-COLUMN— This is an attribute of each browse column, not of the browse itself. After retrieving the handle of the first column using theFIRST-COLUMNattribute, you then retrieve the column’sNEXT-COLUMNattribute to walk through the columns.PREV-COLUMN— This column attribute returns the handle of the previous column, that is, the one to the current column’s left in the browse.Browse columns have various useful attributes that you can get and, in some cases, set (such as its
NAMEorLABEL). You can learn about all of these attributes in the online help and in the third volume of OpenEdge Development: Progress 4GL Reference. Later examples, such as the one in the "Moving columns" , show how to use a few of these.Locking columns
You can use the
NUM-LOCKED-COLUMNSattribute to prevent one or more browse columns from scrolling out of the browse viewport when the horizontal scrollbar is used. A nonhorizontal-scrolling column is referred to as a locked column.Locked columns are always the leftmost columns in the browse. In other words, if you set
NUM-LOCKED-COLUMNSto 2, the first two columns listed in theDEFINE BROWSEstatement are locked. In the next example, the Order Number and Order Date never move out of the browse viewport, no matter which of the remaining fields the user accesses with the horizontal scrollbar.To experiment with locking columns:
- Double-click on the OrderBrowse to go into its property sheet.
- Choose the Fields button to edit the field list.
- Choose the Add button and add all the rest of the Order fields to the browse. Don’t add any Customer fields, as they just repeat all the values from the Customer record you’re already displaying above the browse.
- Back in the property sheet, set Locked Columns to 2.
The AppBuilder generates a statement from the Locked Columns setting to set theNUM-LOCKED-COLUMNSattribute at run time, because this attribute cannot be set using a keyword in theDEFINE BROWSEstatement:
- Run the window again. You can scroll through all the rest of the Order columns, but the first two columns are always displayed:
![]()
Moving columns
You can use the
MOVE-COLUMN( )method to rearrange the columns within a browse. For example, rather than forcing the users to scroll horizontally to see additional columns, you might allow them to reorder the columns.MOVE-COLUMNtakes two arguments:The following simple example shows how to use the
MOVE-COLUMNmethod along with theSTART-SEARCHevent and the column attributes introduced earlier.The
START-SEARCHevent occurs when the user clicks on the column header for a browse with enabled columns. You can use this event to sort by column or for other purposes. In this case, you want to identify which column position was selected and move this column one position to the left.To rearrange the columns you see first in the viewport without scrolling, define this
START-SEARCHtrigger block for the OrderBrowse:
Whenever you enter a trigger block, the
SELFkeyword evaluates to the handle of the object that initiated the event. In this case, this is the browse itself, not the browse column. TheCURRENT-COLUMNattribute returns the handle of the column the user clicked on.The code initializes the
hColumnhandle variable to the first column in the browse and then walks through all the columns, looking for the one with the same handle as the browse’sCURRENT-COLUMN. This identifies the sequential position of the one selected. TheMOVE-COLUMNmethod moves this column one position to the left, unless the user selected the first column.You can also let the user simply drag columns left or right by setting the browse
COLUMN-MOVABLEattribute to true or an individual column’sMOVABLEattribute to true, as described in the "Moving the browse" .Overlaying objects on browse cells
One useful way to extend the behavior of an updateable browse in a Windows GUI is to overlay an enabled browse cell with another object when the user enters the cell to edit it. For example, on
ENTRYof a cell, you could display a combo box or a toggle box. The user selects an entry from the combo box, or checks the toggle box, and you use those values to update theSCREEN-VALUEattribute of the browse cell. The values get committed or undone along with the rest of the row.There are two problems to overcome:
- The first is in calculating the geometry to let you precisely position the overlay widget. You do this by adding the X and Y attributes of the browse cell to the X and Y attributes of the browse and assigning the result to the X and Y attributes of the overlay object.
- You might need to move the overlay object if the user accesses the scrollbar or manipulates the browse in some other way. A trigger on the
SCROLL-NOTIFYevent notifies you when the user scrolls, and you can update the X and Y positioning accordingly.Note: It is not possible in every instance to capture every manipulation of a browse to reposition an overlayed object, especially if you have made browse columns resizable and so forth. Thus, this technique is not completely foolproof. However, it can be useful in many cases for extending the browse to have visual behavior that users can expect and that is not natively available with the browse.To overlay the CreditCard field in the Order browse with a combo box:
(Not only is this convenient, but it assures that the user selects only a valid choice for the field.)
- Go into the OrderBrowse property sheet, click on the Fields button and move the CreditCard field up toward the head of the list, so that it is visible without horizontal scrolling.
- Enable the CreditCard column.
- Select the Combo Box icon
from the AppBuilder palette and drop it onto any empty spot on the window. It is initially hidden and then moved to the proper location when it’s needed, so it doesn’t matter where it starts out.
- In its property sheet, give the new object a name of cCreditCard, check the No-Label toggle box, and provide the three choices shown for the List-Items:
![]()
- Make the Format X(16) and check the Hidden toggle box from the set of Other Settings at the bottom.
- Choose OK to accept these settings.
Now you have two objects that will interact: the browse column called CreditCard representing the CreditCard field in the Order table and the combo box called cCreditCard that you will overlay on it.- You might also have to add a line to the procedure’s main block to make sure that the combo box is hidden when the window first comes up:
Now you need to define a series of trigger blocks to handle the following events on the browse and on the combo box:
- You want the combo box to appear in the right place when the user selects the CreditCard field.
- Then, when the user selects one of its values, you need to transfer that selection from the combo box to the browse cell.
- You also need to adjust the placement of the combo if the user scrolls the browse or moves the CreditCard column.
To create these trigger blocks:
- Define an internal procedure called placeCombo to position and view the combo box on top of the selected browse cell:
This procedure gets the handle to the CreditCard cell in the browse and checks its X and Y coordinates (which are in pixels). If they are not both greater than zero, then the column is not currently displayed and no action is taken. This might happen in response to a browse event that has scrolled the contents of the browse, for instance. If the column has been scrolled out of the viewport, then the column can’t be updated.Otherwise, the code totals the X and Y positions of the browse itself and the CreditCard cell. This is because the X and Y coordinates of the browse are relative to its frame, and the X and Y coordinates of the cell are relative to the browse. Since the combo box coordinates are relative to the frame, you need to add the browse position and the cell position together to get the cell’s position relative to the frame, which is where the combo box will go.Next, the code initializes the cCreditCard combo box to theSCREEN-VALUEof the selected cell. Finally, the code makes the combo box visible and uses theMOVE-TO-TOPmethod to make sure that it’s displayed on top of the browse and not hidden underneath it.- Define an
ENTRYtrigger for the CreditCard browse column to run the procedure when the user clicks in the column:
- Define a
LEAVEtrigger for the CreditCard column:
You want this trigger to execute when the user really leaves this column, for example, by selecting another column or another row. The overlay combo has finished its usefulness at that point and you should hide it so that the simple value in the cell itself is shown. However, there is one wrinkle to this.As soon as the user clicks in the combo box to change its value, this action leaves the browse cell and then enters the combo box. TheLEAVEtrigger you just defined therefore hides the combo box, which is not at all what you want! So you have to define anENTRYtrigger for the combo box to make sure it is visible whenever it is being used.- Define an
ENTRYtrigger for the cCreditCard combo box to make sure it displays whenever it is selected:
After the user has selected a value from the combo box, you need to hide the combo box, apply the new value to the browse cell, and move focus back into the browse cell.- To do this, write this
VALUE-CHANGEDtrigger for cCreditCard:
DO:cCreditCard:VISIBLE IN FRAME CustQuery = FALSE.CreditCard:SCREEN-VALUE IN BROWSE OrderBrowse =cCreditCard:SCREEN-VALUE.APPLY "ENTRY" TO CreditCard IN BROWSE OrderBrowse.END.Now your changes basically work, but the placement of the combo box is messed up if the user scrolls the browse when the combo box is visible.- Write this
SCROLL-NOTIFYtrigger for the OrderBrowse to reposition the overlay combo box properly:
Because you enabled column moving, you need to do the same thing on theSTART-SEARCHevent so that in case the user moves the CreditCard column, the combo box moves with it.- Add this line to the
START-SEARCHtrigger for the browse:
To test your changes:
- Run the window and select a Credit Card cell. The combo box overlays the cell:
![]()
- If necessary, edit your code to adjust the width of the Credit Card column and the row height of the browse so that the combo box looks as though it really fits into the cell.
- Select the combo box, then select one of the valid choices:
When you leave the field, this becomes the new value for the cell:
![]()
Browse style options
This section offers a few ideas for changing the look of your browse.
Using stacked labels
To use more than one line for your column labels, use the stacked label syntax. Here is an example:
DEFINE BROWSE CustBrowse QUERY CustQuery DISPLAYCustNum COLUMN-LABEL "Customer!Number"Name COLUMN-LABEL "Customer!Name" WITH 10 DOWN.
You must use the
COLUMN-LABELoption instead of theLABELoption. The exclamation point character indicates the line breaks.Justifying labels
Column labels in the browse are left-justified by default. You can use the C, L, and R options (Center, Left, Right) of the
LABELattribute to modify the justification of column labels:
Note the colon (:) syntax. To use this option, you must attach the justification option to the end of the
LABELoption. So, even if you want to use the default labels, you need to re-enter them here in order to append the justification option.Using color to distinguish updateable columns
You can make the updateable columns in your browse a different color. For example, you can make the read-only columns gray with black text and the updateable columns blue with yellow text. This code fragment assumes you defined variables to hold the standard color values:
Note: In character interfaces, theCOLUMN-DCOLORattribute specifies the column color.Using color and font to distinguish cells
On top of your basic color scheme, you may want individual cells that have key values to display in a different color or font. For example, you might want to color overdue accounts in red. This kind of cell manipulation is only valid while the cell is in the viewport. For this reason, you need to use the special
ROW-DISPLAYevent to check each new row as it is scrolled into the viewport. See the "Browse events" for examples and implementation notes.Establishing ToolTip information
The
DEFINE BROWSEstatement supports theTOOLTIPoption. You can elect to specify a ToolTip, a brief text message string that automatically displays when the mouse pointer pauses over a browse widget for which a ToolTip value is defined. You can set a ToolTip value for a variety of field-level widgets. However, they are most commonly defined for button widgets.Using a disabled updateable browse as a read-only browse
A browse with no enabled columns is considered a read-only browse. A read-only browse has certain limitations that you might want to circumvent by defining one or more enabled columns in the browse definition. You then disable those columns at run time by setting their
READ-ONLYattribute toTRUE. Using an updateable browse that has had its enabled columns turned off by way of theREAD-ONLYattribute provides these benefits:Resizable browse objects
In graphical interfaces, you (the programmer) and the user can:
Resizing the browse
To let the user resize a browse, you set the browse's
RESIZABLEandSELECTABLEattributes toTRUE, as the following code fragment shows:
To resize a browse through direct manipulation, the user clicks on the browse to display the resize handles, then drags a resize handle as desired.
To resize a browse programmatically, you set the browse’s
WIDTH-CHARSorWIDTH-PIXELS,HEIGHT-CHARSorHEIGHT-PIXELS, orDOWNattributes as desired.The following code fragment programmatically resizes a browse to 50 characters wide by 40 characters high:
Moving the browse
To let the user move a browse, you set the browse’s
MOVABLEattribute toTRUE, as the following code fragment shows:
To move a browse at run time, the user drags the browse as desired. To move a browse programmatically, you set the browse’s X and Y attributes as desired.
The following code fragment programmatically moves a browse to the point (50,50) (in pixels) relative to the parent frame:
Resizing the browse column
To let the user resize a browse column, use one of the following techniques:
- To let the user resize any column of a browse, you set the browse's
COLUMN-RESIZABLEattribute toTRUE.- To let the user resize a single column of a browse, you set the column's
RESIZABLEattribute toTRUE.- To resize a column through direct manipulation, the user drags a column separator horizontally.
- To resize a column programmatically, you set the column's
WIDTH-CHARSorWIDTH-PIXELSattribute.Moving the browse column
To let the user move the columns of a browse, use one of the following techniques:
- To let the user move any column of a browse, you set the browse’s
COLUMN-MOVABLEattribute toTRUE.- To let the user move a single column, you set the column’s
MOVABLEattribute toTRUE.- To move a column at run time, the user drags a column label horizontally. (If the user drags a column label to either edge of the viewport, Progress scrolls the viewport.)
To move a browse column programmatically, you use the
MOVE-COLUMNmethod of the browse, as shown earlier.Changing the row height of the browse
To let the user change the row height of a browse, you set the browse’s
ROW-RESIZABLEattribute toTRUE.To change the row height of a browse, the user vertically drags a row separator that appears at run time.
To change the row height programmatically, you set the browse’s
ROW-HEIGHT-CHARSorROW-HEIGHT-PIXELSattribute.Additional attributes
When you set up a resizable browse, whether for user or programmatic manipulation, you can set additional attributes.
When setting up a browse for user manipulation, you can set the following attributes:
When setting up a browse to resize or otherwise manipulate programmatically, you can set the following attributes:
EXPANDABLE(attribute of the browse) — Indicates whether Progress extends the rightmost browse column to the right edge of the browse, covering any white space that might appear.AUTO-RESIZE(attribute of the browse column) — Indicates whether Progress automatically resizes the browse column if a change occurs in its font or its label’s font or text.User manipulation events
When the user moves or resizes a browse or one of its components, Progress fires one or more of the following events:
These events do not fire when you manipulate the browse programmatically.
You do not have to monitor these events. This chapter includes them only for the your reference. For example, you might want to write trigger code for one or more of these events.
For more information on these events, see the Events Reference section of OpenEdge Development: Progress 4GL Reference.
Using browse objects in character interfaces
Browse objects in character interfaces operate in two distinct modes: row mode and edit mode.
In row mode, the user can select and change focus among rows, as well as navigate (tab) to widgets outside the browse. In edit mode, the user can edit and tab between enabled cells within the browse. A user can only enter edit mode in an updateable browse, but can operate in row mode in either a read-only or an updateable browse.
Character browse modes
Figure 13–3 shows a character browse in row mode.
Figure 13–3: Character browse in row mode
![]()
The asterisks (*) are row markers that indicate editable rows in an updateable browse. Row markers do not appear:
In row mode, the highlight indicates the row that has focus and the close angle brackets (>) are tick marks that indicate selected rows. In a single selection browse, the tick mark follows the highlight because focus and selection are the same. In a multiple selection browse, focus is independent of selection, and tick marks indicate the selected rows. Figure 13–4 shows a character browse in edit mode.
Figure 13–4: Character browse in edit mode
![]()
In edit mode, all rows become deselected except the edited row. The highlight indicates the edited cell within the edited row. As the user moves from row to row during editing, the tick mark and highlight both move to indicate the row and cell being edited.
Control keys
To control operation of a character browse, Progress provides a set of dedicated control keys. These control keys apply differently in row mode or edit mode. However, in both modes, any control key that changes (or attempts to change) the selected row fires the
VALUE-CHANGEDevent.These control keys correspond to Progress key functions that you can redefine. On Windows, you can redefine them in the registry or in an initialization file. On UNIX, you can redefine them in the
PROTERMCAPfile. For more information on redefining control key functions, see the chapter on user interface environments in OpenEdge Deployment: Managing 4GL Applications.Table 13–2 describes the key functions, default key labels, and operation of the row mode control keys.
Table 13–2: Row mode control keys Key functions Default key labels Description" "1 SPACE BAR Fires theVALUE-CHANGEDevent. In multiple selection mode, this key also selects and deselects the row that has focus, alternately displaying and erasing the tick mark.CURSOR-LEFT CURSOR-LEFT Scrolls the browse horizontally one column to the left.CURSOR-RIGHT CURSOR-RIGHT Scrolls the browse horizontally one column to the right.CURSOR-DOWN CURSOR-DOWN Moves focus down one row.2CURSOR-UP CURSOR-UP Moves focus up one row.2END ESC-. Moves focus to the last row in the browse.2HOME ESC-, Moves focus to the first row in the browse.2PAGE-DOWN ESC-CURSOR-DOWN Pages down one full page of data.2PAGE-UP ESC-CURSOR-UP Pages up one full page of data.2REPLACE ESC-R Changes the browse to edit mode, places focus in the first enabled cell of the currently focused row, and fires theVALUE-CHANGEDevent. In multiple selection mode, this key also selects the focused row and deselects any other selected rows. If you specifyNO-ROW-MARKERSin the browse definition or set theROW-MARKERSattribute toFALSE,REPLACEhas no effect. RETURN RETURN Fires theDEFAULT-ACTIONevent. TAB TAB Leaves the browse and sets input focus to the next sibling of the browse in the tab order.
1 Note that Progress has no key function identifier for the space bar. In code, you reference a character string containing a single space (" ").2 In single selection mode, this also fires theVALUE-CHANGEDevent because selection follows focus.Table 13–3 describes the key functions, default key labels, and operation of the edit mode control keys.
Table 13–3: Edit mode control keys Key function Default key label Description" "1 SPACE BAR Enters a space or zeroes numeric data in the focused cell. CURSOR-LEFT CURSOR-LEFT Moves the cursor one character to the left in the cell. (Does not leave the cell.) CURSOR-RIGHT CURSOR-RIGHT Moves the cursor one character to the right in the cell. (Does not leave the cell.) CURSOR-DOWN CURSOR-DOWN Moves focus down to the next cell in the column and fires theVALUE-CHANGEDevent. CURSOR-UP CURSOR-UP Moves focus up to the previous cell in the column and fires theVALUE-CHANGEDevent. BACK-TAB CTRL-U Moves focus to the previous enabled cell in the browse (right to left, bottom to top).2 When focus is on the first cell,BACK-TABdoes not function. EDITOR-TAB CTRL-G Moves focus to the next enabled cell in the browse (left to right, top to bottom).2 When focus is on the last cell, theEDITOR-TABdoes not function. END ESC-. Moves focus to the last cell in the current column and fires theVALUE-CHANGEDevent. HOME ESC-, Moves focus to the first cell in the current column and fires theVALUE-CHANGEDevent. PAGE-DOWN ESC-CURSOR-DOWN Pages down one full page of data and fires theVALUE-CHANGEDevent. PAGE-UP ESC-CURSOR-UP Pages up one full page of data and fires theVALUE-CHANGEDevent. REPLACE ESC-R Changes the browse to row mode, with selection set to the currently focused row. RETURN RETURN Moves focus to the next cell in the current column and fires theVALUE-CHANGEDevent. TAB TAB Leaves the browse and sets input focus to the next sibling widget of the browse in the tab order.
1 Note that Progress has no key function identifier for the space bar. In code, you reference a character string containing a single space (" ").2 If this action changes or attempts to change the selected row, it also fires theVALUE-CHANGEDevent.Functional differences from the Windows graphical browse
The character browse shares most of the same functional capabilities of the Windows graphical browse, including all methods and most attributes and events.
However, there are differences from the Windows graphical browse in:
Font management
Because there is no font management within character interfaces, all font attributes are inactive for the character browse.
Color management
For color terminals, the character browse supports the following attributes to manage its color:
LABEL-DCOLOR— Specifies the color of a column label (likeLABEL-FGCOLORfor the Windows browse).COLUMN-DCOLOR— Specifies the color of a single column (likeCOLUMN-FGCOLORfor the Windows browse).DCOLOR— Specifies the color of an individual cell (likeFGCOLORfor the Windows browse). You can only specify the color of an individual cell as it appears in the view port. For more information on specifying individual cell color, see the "Browse events" .COLUMN-PFCOLOR— Specifies the color of the enabled (updateable) column with input focus (unsupported for the Windows browse).PFCOLOR— Specifies the color of the updateable cell with input focus (handled by default processing for the Windows browse).By default, the
COLUMN-PFCOLORandPFCOLORvalues are both set from theINPUTcolor value. On Windows, you can specify this value in the registry or in an initialization file. On UNIX, you can specify this value in thePROTERMCAPfile.Row and cell navigation
Unlike the Windows graphical browse, the character browse does not support column searching (positioning to a row or cell by typing a character contained in that row or cell).
Unlike the Windows graphical browse, the character browse uses a different set of key functions to tab between updateable cells than between the browse and other sibling widgets. Tabbing between cells occurs only in the edit mode of the character browse using the
EDIT-TABandBACK-TABkey functions.Tabbing forward from the character browse to a sibling object occurs in either row mode or edit mode using the
TABkey function. However, tabbing backward from the character browse to a sibling object occurs only in row mode using theBACK-TABkey function.Conclusion
This chapter has introduced you to some of the ways in which you can use the browse and customize its behavior. The browse is a complex object, and one that you can manipulate in many ways—more than can be completely documented in a single chapter. To explore the complete capabilities of the browse, you should consult the Browse Widget section of OpenEdge Development: Progress 4GL Reference or the online help. These provide both a list of all attributes and methods, and an explanation of each. Just by way of example of how extensive the browse capabilities are, Figure 13–5 shows some of the browse methods in the online Help.
Figure 13–5: Browse widget online help topic
![]()
|
Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095 |
![]() ![]() ![]()
|