TXBrowse
The most powerful and versatile data grid control in FiveWin
Fuente: source/classes/xbrowse.prg | Parent: TControl | Header: XBrowse.ch
Overview
TXBrowse is the flagship data grid control of FiveWin. It renders tabular data from virtually any source -- DBF tables, in-memory arrays, ADO RecordSets, ODBC cursors, or custom data -- through a unified block-based navigation architecture. It virtualizes rendering so that only visible rows are processed, enabling it to handle millions of records without excessive memory consumption.
Key capabilities include:
- Data source abstraction -- one control, many back-ends (DBF/RDD, Array, ADO, ODBC, custom)
- Rich column objects -- each column (
TXBrwCol) controls its own header, data formatting, colors, editing, and width - In-cell editing -- GET, ComboBox, CheckBox, DatePicker, and Spinner editors per column
- Incremental seek / filter -- built-in
lIncrFilterseek bar with per-column search - Multi-select, freeze columns, record selector, marquee styles
- Export to Excel, CSV, clipboard, and printer
Architecture
TXBrowse decouples the visual grid from the underlying data through a set of navigation code blocks. Helper methods like SetRDD(), SetArray(), and SetAdo() simply populate these blocks with the appropriate logic for each data source type.
(SetRDD)"] ARR["Array
(SetArray)"] ADO["ADO RecordSet
(SetAdo)"] ODBC["ODBC Cursor
(SetODBC)"] CUSTOM["Custom
(manual blocks)"] end subgraph NavBlocks["Navigation Blocks"] bGoTop bGoBottom bSkip bBof bEof bKeyNo bKeyCount bBookMark bSeek end subgraph TXBrowseObj["TXBrowse Object"] GRID["Grid Engine
paint, scroll, keys"] COLS["aCols<br/>TXBrwCol array"] GRID --- COLS end DBF --> bGoTop ARR --> bGoTop ADO --> bGoTop ODBC --> bGoTop CUSTOM --> bGoTop bGoTop --> GRID bGoBottom --> GRID bSkip --> GRID bKeyCount --> GRID bBookMark --> GRID style TXBrowseObj fill:#1c2129,stroke:#58a6ff,stroke-width:2px,color:#e6edf3 style NavBlocks fill:#1c2129,stroke:#3fb950,stroke-width:1px,color:#e6edf3 style DataSources fill:#1c2129,stroke:#d29922,stroke-width:1px,color:#e6edf3
Column Object: TXBrwCol
Each column in the browse is a TXBrwCol object stored in the aCols array. Columns define what data to display and how to display it.
Key Properties (DATA)
| Property | Type | Description |
|---|---|---|
aCols | Array | Array of TXBrwCol column objects defining every column in the grid |
cAlias | String | Work area alias when using SetRDD() |
nFreeze | Numeric | Number of left-hand columns that remain frozen during horizontal scroll |
lRecordSelector | Logical | When .T., displays a row-indicator column on the left edge |
nMarqueeStyle | Numeric | Visual style of the selection highlight (see XBrowse.ch constants: MARQSTYLE_HIGHLROW, MARQSTYLE_HIGHLCELL, MARQSTYLE_DOTEDCELL, etc.) |
lFastEdit | Logical | When .T., typing a character immediately opens the cell editor -- no Enter key required. Caution: this disables single-key navigation shortcuts. |
lIncrFilter | Logical | When .T., a seek/filter bar appears below the headers for incremental column searching |
nDataType | Numeric | Internal flag indicating the data source type (RDD, Array, ADO, etc.) |
lMultiSelect | Logical | Enables multi-row selection with Ctrl+Click and Shift+Click |
nArrayAt | Numeric | Current row index when browsing an array data source |
nRowHeight | Numeric | Pixel height of each data row |
nHeaderHeight | Numeric | Pixel height of the column header row |
Navigation Blocks
These code blocks form the data abstraction layer. They are set automatically by SetRDD(), SetArray(), SetAdo(), etc., but you can supply custom blocks for specialized data sources.
| Block | Signature | Description |
|---|---|---|
bGoTop | {|| ... } | Position data source at first record |
bGoBottom | {|| ... } | Position data source at last record |
bSkip | {|n| ... } | Move n records (positive=forward, negative=backward). Returns actual records moved. |
bBof | {|| ... } | Returns .T. if before first record |
bEof | {|| ... } | Returns .T. if past last record |
bKeyNo | {|n| ... } | Get/set the current logical position (for scrollbar) |
bKeyCount | {|| ... } | Returns total number of records (for scrollbar) |
bBookMark | {|u| ... } | If u is NIL, returns a bookmark for the current row. Otherwise, repositions to bookmark u. |
bSeek | {|c| ... } | Incremental seek -- find the first record matching the string c |
Key Methods
| Method | Description |
|---|---|
New( oWnd ) | Constructor. Creates a TXBrowse instance associated with parent window/dialog oWnd. |
SetRDD( [cAlias] ) | Configure navigation blocks for a DBF/RDD work area. |
SetArray( aData, [lAutoCol] ) | Configure navigation blocks for an in-memory array. |
SetAdo( oRs ) | Configure navigation blocks for an ADO RecordSet object. |
SetODBC( oQry ) | Configure navigation blocks for an ODBC result set. |
AddColumn( cHead, bData, [cPict], ... ) | Add a column with header, data block, optional picture, and more. |
CreateFromCode() | Finalize control creation after configuring data source and columns. |
Adjust() | Recalculate header, row, and footer dimensions. Sometimes needed after font changes. |
Refresh() | Repaint all visible rows. |
SelectedCol() | Returns the currently selected TXBrwCol object. |
Report( [cTitle] ) | Quick-print the browse contents via the FWH print engine. |
ToExcel() | Export visible data to Microsoft Excel. |
ToClip() | Copy visible data to the clipboard. |
bLClicked y BookMark
Un clic izquierdo en una celda de datos primero mueve el browse a esa
fila (Skip() actualiza BookMark) y después
evalúa oBrw:bLClicked una sola vez. El código que lee
oBrw:BookMark dentro del bloque — típico de una columna
SetCheck() tipo radio — ve el registro clicado, no la fila
actual anterior. El bloque se nilea durante
Super:LButtonDown() para que TControl / TWindow no lo
disparen otra vez.
Sample: samples/system/xbrwin7c.prg. Ver
Novedades 26.08.
Commands (XBrowse.ch)
FiveWin provides two command styles for creating TXBrowse controls:
Positioned Command
@ nRow, nCol XBROWSE oBrw ;
[ OF oWnd ] ;
[ SIZE nWidth, nHeight ] ;
[ COLUMNS cCol1, cCol2, ... ] ;
[ HEADERS cHead1, cHead2, ... ] ;
[ ALIAS cAlias ] ;
[ ON DBLCLICK bAction ] ;
[ FONT oFont ] ;
[ NOBORDER ]
Quick Browser Command
XBROWSER <cAlias> // browse a DBF alias
XBROWSER <aArray> // browse an array
XBROWSER <oRs> // browse an ADO RecordSet
Code Examples
Example 1: Browsing a DBF Table
#include "FiveWin.ch"
#include "XBrowse.ch"
FUNCTION Main()
LOCAL oWnd, oBrw
USE Customer VIA "DBFCDX" NEW
DEFINE WINDOW oWnd TITLE "Customers" FROM 1, 1 TO 24, 80
oBrw := TXBrowse():New( oWnd )
oBrw:nTop := 0
oBrw:nLeft := 0
oBrw:nRight := -1 // stretch to right edge
oBrw:nBottom := -1 // stretch to bottom edge
oBrw:SetRDD() // bind to current work area
// Configure individual columns
WITH OBJECT oBrw:AddColumn( "ID", FieldBlock( "CUST_ID" ), "99999" )
:nWidth := 80
:nDataStrAlign := AL_RIGHT
END
WITH OBJECT oBrw:AddColumn( "First Name", FieldBlock( "FIRSTNAME" ), "@!" )
:nWidth := 140
:lEdit := .T. // allow in-cell editing
END
oBrw:AddColumn( "Last Name", FieldBlock( "LASTNAME" ), "@!" )
oBrw:AddColumn( "City", FieldBlock( "CITY" ) )
oBrw:AddColumn( "Balance", FieldBlock( "BALANCE" ), "@E 999,999.99" )
oBrw:nFreeze := 1 // freeze ID column
oBrw:lRecordSelector := .T. // show row indicator
oBrw:nMarqueeStyle := MARQSTYLE_HIGHLROW
oBrw:CreateFromCode()
oWnd:oClient := oBrw // fill the window client area
ACTIVATE WINDOW oWnd
CLOSE Customer
RETURN NIL
Example 2: Browsing an Array
#include "FiveWin.ch"
#include "XBrowse.ch"
FUNCTION Main()
LOCAL oWnd, oBrw
LOCAL aData := { ;
{ "Alice", "Smith", 28, .T. }, ;
{ "Bob", "Johnson", 34, .F. }, ;
{ "Charlie", "Williams", 45, .T. }, ;
{ "Diana", "Brown", 31, .T. }, ;
{ "Edward", "Jones", 52, .F. } ;
}
DEFINE WINDOW oWnd TITLE "Array Browse" FROM 2, 2 TO 20, 70
oBrw := TXBrowse():New( oWnd )
oBrw:SetArray( aData, .F. ) // .F. = do not auto-create columns
oBrw:aCols := {}
oBrw:AddColumn( "First", {|| aData[ oBrw:nArrayAt ][ 1 ] } )
oBrw:AddColumn( "Last", {|| aData[ oBrw:nArrayAt ][ 2 ] } )
oBrw:AddColumn( "Age", {|| aData[ oBrw:nArrayAt ][ 3 ] }, "999" )
oBrw:AddColumn( "Active", {|| aData[ oBrw:nArrayAt ][ 4 ] } )
// Make "Active" column render as a checkbox
oBrw:aCols[ 4 ]:SetCheck( "CHECK1", "UNCHECK1" )
oBrw:nMarqueeStyle := MARQSTYLE_HIGHLCELL
oBrw:CreateFromCode()
oWnd:oClient := oBrw
ACTIVATE WINDOW oWnd
RETURN NIL
Example 3: Editable Browse with Column Configuration
#include "FiveWin.ch"
#include "XBrowse.ch"
FUNCTION Main()
LOCAL oWnd, oBrw, oCol
USE Invoices VIA "DBFCDX" NEW
DEFINE WINDOW oWnd TITLE "Editable Invoices"
@ 0, 0 XBROWSE oBrw OF oWnd ;
COLUMNS "INVNO", "CUSTNAME", "AMOUNT", "INVDATE", "PAID" ;
HEADERS "Invoice #", "Customer", "Amount", "Date", "Paid?"
oBrw:SetRDD()
// Configure the Amount column
oCol := oBrw:aCols[ 3 ]
oCol:cPicture := "@E 999,999.99"
oCol:lEdit := .T.
oCol:nEditType := EDIT_GET // edit with a GET control
oCol:bClrStd := {|| { CLR_BLACK, CLR_WHITE } }
// Configure the Date column with a date picker editor
oCol := oBrw:aCols[ 4 ]
oCol:lEdit := .T.
oCol:nEditType := EDIT_DATE
// Configure the Paid column as a checkbox
oCol := oBrw:aCols[ 5 ]
oCol:SetCheck( "CHECK1", "UNCHECK1" )
oCol:lEdit := .T.
oBrw:lFastEdit := .T. // typing goes straight to edit mode
oBrw:CreateFromCode()
oWnd:oClient := oBrw
ACTIVATE WINDOW oWnd
CLOSE Invoices
RETURN NIL
Example 4: Incremental Seek / Filter Bar
#include "FiveWin.ch"
#include "XBrowse.ch"
FUNCTION Main()
LOCAL oWnd, oBrw
USE Customer INDEX Customer VIA "DBFCDX" NEW
DEFINE WINDOW oWnd TITLE "Customer Search"
oBrw := TXBrowse():New( oWnd )
oBrw:SetRDD()
oBrw:AddColumn( "Name", FieldBlock( "NAME" ) )
oBrw:AddColumn( "City", FieldBlock( "CITY" ) )
oBrw:AddColumn( "Phone", FieldBlock( "PHONE" ) )
oBrw:AddColumn( "Balance", FieldBlock( "BALANCE" ), "@E 999,999.99" )
// Enable the incremental filter bar
oBrw:lIncrFilter := .T.
// Custom seek block -- performs an indexed seek on the NAME field
oBrw:bSeek := {|cSeek| ( oBrw:cAlias )->( DbSeek( cSeek, .T. ) ) }
oBrw:nMarqueeStyle := MARQSTYLE_HIGHLROW
oBrw:lRecordSelector := .T.
oBrw:CreateFromCode()
oWnd:oClient := oBrw
ACTIVATE WINDOW oWnd
CLOSE Customer
RETURN NIL
Example 5: Flicker-Free Runtime Resize
To enlarge or reduce a browse at runtime without flickering, freeze the parent
dialog with WM_SETREDRAW while moving the control, then repaint dialog
and browse once. The parent must also have the WS_CLIPCHILDREN style --
FiveWin dialogs do not set it by default -- otherwise the dialog background erase
paints over the browse area before the rows are redrawn, which is visible as a
flash when the browse grows. See samples/testrsz.prg.
#include "FiveWin.ch"
#include "XBrowse.ch"
#define WM_SETREDRAW 11
#define GWL_STYLE -16
#define WS_CLIPCHILDREN 0x2000000
FUNCTION Main()
LOCAL oDlg, oBrw
LOCAL nHeightIni := 350
USE Customer NEW SHARED
DEFINE DIALOG oDlg SIZE 800,500 PIXEL TRUEPIXEL TITLE "Resize without flicker"
@ 10,10 XBROWSE oBrw OF oDlg SIZE 760,nHeightIni PIXEL AUTOCOLS ALIAS "CUSTOMER"
oBrw:CreateFromCode()
@ 420,10 BUTTON "Up" SIZE 80,25 PIXEL OF oDlg ACTION SetBrwHeight( oDlg, oBrw, nHeightIni - 60 )
@ 420,100 BUTTON "Down" SIZE 80,25 PIXEL OF oDlg ACTION SetBrwHeight( oDlg, oBrw, nHeightIni )
ACTIVATE DIALOG oDlg CENTERED ;
ON INIT SetWindowLong( oDlg:hWnd, GWL_STYLE, ;
nOr( GetWindowLong( oDlg:hWnd, GWL_STYLE ), WS_CLIPCHILDREN ) )
RETURN NIL
STATIC FUNCTION SetBrwHeight( oDlg, oBrw, nHeight )
oDlg:SendMsg( WM_SETREDRAW, 0, 0 ) // freeze painting
oBrw:nHeight := nHeight // resize: nothing reaches the screen
oDlg:SendMsg( WM_SETREDRAW, 1, 0 ) // re-enable
oDlg:Refresh() // single clean repaint...
oBrw:Refresh() // ...Refresh() does not cascade to children
RETURN NIL
Edición multilínea en celda con longitud máxima
Con nDataLines >= 2 (browse o columna) y valor carácter, XBrowse
abre un TMultiGet en la celda
solo si cEditPicture está vacío. Un picture no vacío
fuerza TGet de una línea y se pierde el wrap al escribir.
#define MAX_MNEMO 250
oBrw:nDataLines := 3
oCol := oBrw:aCols[ 2 ]
oCol:nEditType := EDIT_GET
// NO pongas oCol:cEditPicture := Replicate( "X", MAX_MNEMO )
oCol:bOnPreEdit := {| oCol |
local oGet := oCol:oEditGet, bOrig, c
if oGet != nil .and. oGet:IsKindOf( "TMULTIGET" )
c := RTrim( oGet:cText ) // quitar relleno antes de LimitText
oGet:cText := c
oGet:LimitText( MAX_MNEMO )
oGet:SetPos( Len( c ) )
// Intro termina; Ctrl/Shift+Intro = un salto de línea real
bOrig := oGet:bKeyDown
oGet:bKeyDown := {| nKey |
If( nKey == VK_RETURN .and. ;
( GetKeyState( VK_CONTROL ) .or. GetKeyState( VK_SHIFT ) ), ;
nil, ;
If( bOrig != nil, Eval( bOrig, nKey ), nil ) ) }
endif
return nil }
oCol:bOnPostEdit := {| o, x, n |
If( n != VK_ESCAPE .and. x != nil, o:Value := Left( x, MAX_MNEMO ), ) }
- RTrim antes de LimitText — campos rellenados
(
PadR( ..., 250 )/Space(250)) ya tienen longitud 250;LimitText(250)bloquea cada tecla.TMultiGetno hace SelectAll comoTGet. - Intro termina la celda (comportamiento XBrowse).
Ctrl+Intro o Shift+Intro insertan un
salto vía el EDIT multilínea. No llames a
Replace( CRLF )o obtendrás dos saltos. - El wrap al borde de la celda es solo visual; los saltos reales son
CRLFen la cadena guardada. - Sample de referencia:
samples/ui/xbrowse/xbrml250.prg.
Gráficas en celda: BAR, LINE y GAUGE
Una columna puede pintar sus valores numéricos como una pequeña gráfica dentro de cada celda. Se configuran con estos DATAs de la columna:
oCol:aChartCols— array de series; cada elemento es un nombre de columna, un objeto columna, un nombre de cabecera de grupo, o un code block que devuelve el valor numérico.oCol:cChartType— "BAR" (por defecto), "LINE" o "GAUGE".oCol:aChartColors— un color por serie (si se omite se asignan colores METRO automáticamente).oCol:nChartMaxVal— máximo fijo opcional de la escala; sube automáticamente al mayor valor encontrado.
"GAUGE" es un modo aditivo pensado para celdas con un solo valor, donde el aspecto clásico "BAR" (varias series lado a lado) se ve como un bloque sólido llenando toda la celda. GAUGE pinta una pista gris fina con padding lateral centrada verticalmente, más una barra proporcional al valor en el color de la serie y una etiqueta "NN.N%" encima. Las celdas "LINE" y "BAR" conservan su renderizado original.
oCol:aChartCols := { {|| nPct } } // una serie desde un block...
oCol:aChartCols := { "nDone", "nTotal" } // ...o varias columnas por nombre
oCol:cChartType := "GAUGE"
oCol:aChartColors := { CLR_HBLUE }
oCol:nChartMaxVal := 100 // escala fija opcional
Tips and Best Practices
- Always call
CreateFromCode()after configuring data source, columns, and visual properties. - Use
oWnd:oClient := oBrwto make the browse auto-resize with the window. - Avoid
lFastEditwith navigation-heavy UIs -- it intercepts character keys that would otherwise trigger seeks or keyboard shortcuts. - For incremental seek on a DBF, ensure the table has an active index on the column you are searching.
- Column
bClrEditcontrols the colors of the cell while it is being edited (foreground/background block). This is separate frombClrStdwhich controls display-mode colors. - Double-buffer painting: TXBrowse paints to an off-screen bitmap, then blits to screen. This eliminates flicker but means you should call
Refresh()-- notInvalidate()-- to force a redraw. - For very large arrays, consider using
SetRDD()with a temporary DBF instead, as array navigation does not virtualize seeks. - Resizing the browse at runtime: a plain
oBrw:nHeight :=assignment repaints twice (immediateMoveWindow()repaint plus the laterRefresh()) and flickers. Wrap the resize inWM_SETREDRAW0/1 on the parent dialog and addWS_CLIPCHILDRENto the dialog style -- see Example 5. - Celdas de texto largo multilínea — ver Edición multilínea en celda con longitud máxima.
Ver También
- TControl -- parent class
- TDatabase -- high-level DBF wrapper often used with TXBrowse
- TRecordSet (ADO) -- ADO data source for
SetAdo() - TGet -- editor de celda de una línea
- TMultiGet -- editor multilínea cuando
nDataLines >= 2