TControl
Core Class Inherits TWindow source/classes/control.prg
TControl is the abstract base class for all UI controls in FiveWin. It inherits from TWindow and adds control-specific behavior: data binding via bSetGet, change notifications, drag-and-drop support, docking/anchoring within parent windows, design-mode resizing, and the REDEFINE pattern for resource-based dialogs. You never instantiate TControl directly; instead, you use its concrete subclasses like TButton, TGet, TXBrowse, etc.
Control Hierarchy
TControl is the common ancestor of every visual control. Here is the inheritance tree showing the major subclasses:
Key DATA Members
| DATA | Type | Default | Description |
|---|---|---|---|
oWnd | TWindow/TDialog | nil | Parent window or dialog that contains this control. |
nId | Numeric | auto | Win32 control ID. Auto-assigned from CLASSDATA nInitID (starts at 100) or explicitly set for resource dialogs. |
bSetGet | Block | nil | Data-binding block. When called with no args, returns the current value; with one arg, sets it. Created automatically by the GET / VAR command clauses. |
bChange | Block | nil | Evaluated whenever the control's value changes (typed text, selection change, etc.). |
lUpdate | Logical | .F. | If .T., the control participates in the parent's update cycle (oWnd:Update()). Used for data-bound controls that must refresh when the record pointer moves. |
lDrag | Logical | .F. | Enable mouse drag-and-drop for this control. Used in design mode and for user-initiated dragging. |
lTransparent | Logical | .F. | Paint the control with a transparent background, letting the parent's brush/gradient show through. |
lCaptured | Logical | .F. | Whether the control currently has mouse capture. |
lMouseDown | Logical | .F. | Tracks whether the left mouse button is pressed. |
nDlgCode | Numeric | nil | Value returned by GetDlgCode(). Controls tab/arrow key navigation behavior. |
oJump | TControl | nil | After VALID succeeds, focus jumps to this control instead of the next tab-order control. |
nClientBevel | Numeric | nil | Bevel/border offset for AdjClient() docking. |
aSizeRect | Array | nil | Stores original size rectangle for proportional resizing. |
ResizeType | Numeric | nil | Resize behavior flag set by SetResizeType(). Controls how the control scales when its parent is resized. |
uOriginalValue | Any | nil | Original value at control creation. Used by lChanged access method. |
LastValidValue | Any | nil | Stores the last value that passed validation. |
Access / Assign Methods
| Access | Description |
|---|---|
lChanged | Read-only. Returns .T. if the current value (from VarGet()) differs from uOriginalValue. |
nTop( nNewTop ) | SETGET — gets or sets the top coordinate, moving the Win32 window if it exists. |
nLeft( nNewLeft ) | SETGET — gets or sets the left coordinate. |
Key Methods
Refresh()
Inherited from TWindow. Forces the control to repaint. For data-bound controls (those with bSetGet), it also re-reads the value from the bound variable.
SetFocus()
Gives keyboard focus to this control. Internally sends FM_CHANGEFOCUS and calls the Win32 SetFocus() API.
VarGet() / VarPut()
METHOD VarGet() INLINE If( ValType( ::bSetGet ) == "B", Eval( ::bSetGet ), )
METHOD VarPut( uVal ) INLINE If( ValType( ::bSetGet ) == "B", Eval( ::bSetGet, uVal ), )
Read or write the control's bound variable through the bSetGet block. This is the standard way to programmatically get/set a control's value.
cToChar()
METHOD cToChar( cCtrlClass )
Generates source code that recreates this control. Used by the visual designer to serialize dialogs.
Move()
METHOD Move( nTop, nLeft, nWidth, nHeight, lRepaint )
Repositions and/or resizes the control within its parent.
End()
Destroys the control's Win32 window and removes it from the parent's aControls array.
Initiate()
METHOD Initiate( hWnd )
Called automatically by the parent after the control's Win32 window is created. It subclasses the window procedure to intercept control messages and completes the control's initialization. Not typically called directly.
CheckDots() / ShowDots() / HideDots()
METHOD CheckDots( hWnd, nId )
METHOD ShowDots()
METHOD HideDots()
Design-mode helper methods that manage the eight resize handles (dots) around the control border. ShowDots() displays the handles; HideDots() removes them; CheckDots() is called during MResize() to adjust handle positions. These are used by the visual dialog designer.
Docking and Anchoring
TControl provides docking methods that automatically position a control to fill specific regions of the parent window. These are essential for building resizable layouts:
Docked to top edge"] L["oLeft — AdjLeft()
Docked to left edge"] C["oClient — AdjClient()
Fills remaining space"] R["oRight — AdjRight()
Docked to right edge"] B["oBottom — AdjBottom()
Docked to bottom edge"] end T --- C L --- C C --- R C --- B
AdjClient()
METHOD AdjClient()
Resizes the control to fill the entire client area of the parent, minus any space taken by docked controls (oTop, oBottom, oLeft, oRight) and the message bar. This is the most commonly used docking method.
Internally, AdjClient() inspects the parent's oTop, oBar, oBottom, oMsgBar, oLeft, and oRight properties to calculate the remaining rectangle, then calls WndAdjClient() to position the control.
AdjTop() / AdjBottom()
Dock the control to the top or bottom edge of the parent. The control stretches the full width, and its height remains as defined.
AdjLeft() / AdjRight()
Dock the control to the left or right edge. The control stretches the full height (between any top/bottom docked controls), and its width remains as defined.
Docking in Practice
#include "FiveWin.ch"
function Main()
local oWnd, oTree, oBrw, oBottom
DEFINE WINDOW oWnd TITLE "Docking Layout"
// Toolbar docks automatically to top via oWnd:oBar
DEFINE BUTTONBAR oBar OF oWnd SIZE 48, 48 2007
DEFINE BUTTON OF oBar RESOURCE "OPEN" ACTION nil
// Status bar docks automatically to bottom via oWnd:oMsgBar
SET MESSAGE OF oWnd TO "Ready" CENTERED
// Left panel: tree view
oTree := TTreeView():New( 0, 0, oWnd, , 220, 400 )
oWnd:oLeft := oTree
oTree:AdjLeft()
// Client area: browse control fills the rest
oBrw := TXBrowse():New( oWnd )
oBrw:SetArray( GetCustomerData() )
oBrw:CreateFromCode()
oWnd:oClient := oBrw
oBrw:AdjClient()
ACTIVATE WINDOW oWnd MAXIMIZED ;
ON RESIZE ( oTree:AdjLeft(), oBrw:AdjClient() )
return nil
The key pattern: assign the control to the parent's docking slot (oWnd:oLeft, oWnd:oClient), then call the corresponding Adj*() method. In the ON RESIZE handler, call them again so the layout adapts.
Data Binding with bSetGet
The bSetGet code block is the heart of FiveWin's data binding. It follows the xBase getter/setter convention:
// When creating a GET, FiveWin generates this block automatically:
local cName := Space( 30 )
@ 10, 10 GET oGet VAR cName OF oDlg // Internally creates:
// oGet:bSetGet := { |u| If( u == nil, cName, cName := u ) }
// Reading the value:
? oGet:VarGet() // returns cName
// Writing a value:
oGet:VarPut( "John" ) // sets cName := "John"
oGet:Refresh() // updates the visual display
For database-bound controls, bSetGet references a field:
@ 10, 10 GET oGet VAR Customers->Name OF oDlg
// bSetGet := { |u| If( u == nil, Customers->Name, Customers->Name := u ) }
Example: Creating a Custom Control
You can subclass TControl to create custom controls with specialized painting and behavior:
#include "FiveWin.ch"
// A simple LED indicator control
CLASS TLed FROM TControl
DATA lOn INIT .F. // LED state
DATA nClrOn INIT CLR_GREEN
DATA nClrOff INIT CLR_RED
DATA nRadius INIT 12
METHOD New( nTop, nLeft, oWnd, nWidth, nHeight, lOn )
METHOD Paint()
METHOD Toggle() INLINE ::lOn := !::lOn, ::Refresh()
ENDCLASS
METHOD New( nTop, nLeft, oWnd, nWidth, nHeight, lOn ) CLASS TLed
DEFAULT nWidth := 30, nHeight := 30, lOn := .F.
::nTop := nTop
::nLeft := nLeft
::nBottom := nTop + nHeight
::nRight := nLeft + nWidth
::oWnd := oWnd
::lOn := lOn
::nStyle := nOR( WS_CHILD, WS_VISIBLE )
if oWnd != nil
::Create( "TLed" )
oWnd:AddControl( Self )
endif
return Self
METHOD Paint() CLASS TLed
local hDC := ::BeginPaint()
local aRect := ::GetCliRect()
local nCx := ( aRect[4] - aRect[2] ) / 2
local nCy := ( aRect[3] - aRect[1] ) / 2
local nClr := If( ::lOn, ::nClrOn, ::nClrOff )
// Fill background
FillSolidRect( hDC, aRect, GetSysColor( COLOR_BTNFACE ) )
// Draw the LED circle
FW_FillCircle( hDC, nCy, nCx, ::nRadius, nClr )
// Draw border
FW_Circle( hDC, nCy, nCx, ::nRadius + 1, CLR_BLACK )
::EndPaint()
return 0
Usage:
function TestLed()
local oDlg, oLed1, oLed2
DEFINE DIALOG oDlg TITLE "LED Demo" SIZE 300, 200 TRUEPIXEL
oLed1 := TLed():New( 30, 40, oDlg, 36, 36, .T. ) // ON
oLed2 := TLed():New( 30, 90, oDlg, 36, 36, .F. ) // OFF
@ 30, 140 SAY "Server" OF oDlg SIZE 80, 22 TRUEPIXEL
@ 30, 190 SAY "Offline" OF oDlg SIZE 80, 22 TRUEPIXEL
@ 100, 40 BUTTON "Toggle" OF oDlg SIZE 100, 30 TRUEPIXEL ;
ACTION ( oLed1:Toggle(), oLed2:Toggle() )
ACTIVATE DIALOG oDlg CENTER
return nil
Design Mode
TControl includes built-in support for visual design mode (used by FiveWin's dialog editor). When lDrag is set to .T.:
- The control can be moved by dragging with the mouse.
- Resize handles (dots) appear around the control when focused (
ShowDots()/HideDots()). - The
MResize()method handles the 8 resize directions (NW, N, NE, E, SE, S, SW, W). bDragBegin,bDrag,bPreDrag, andbPostDragblocks fire at different drag stages.bPreDelControlandbPostDelControlfire before/after a control is deleted in the designer.
The REDEFINE Pattern
For resource-based dialogs, controls are created by the dialog resource template. FiveWin's REDEFINE commands bind FiveWin objects to those existing Win32 controls by their numeric ID:
METHOD Redefine( nId, oWnd ) CONSTRUCTOR
DEFINE DIALOG oDlg RESOURCE "MYDIALOG"
REDEFINE GET oGet VAR cName ID 101 OF oDlg
REDEFINE BUTTON oBtn ID 102 OF oDlg ACTION SaveData()
REDEFINE COMBOBOX oCbx VAR cCity ID 103 OF oDlg ;
ITEMS { "NYC", "London", "Tokyo" }
ACTIVATE DIALOG oDlg CENTER
Control Lifecycle
Object created"] --> B["Parent Activate()
Win32 controls created"] B --> C["Initiate()
Subclass window proc"] C --> D["Default()
Set initial value"] D --> E["User interaction
bChange, bValid, bWhen"] E --> F{"Dialog closes
or End()"} F --> G["Destroy()
Win32 window destroyed"]
Usage Tips
- Use
lUpdate := .T.for controls that should refresh when navigating database records. CalloWnd:Update()oroDlg:Update()after aSKIPto refresh alllUpdate-flagged controls. lChangedaccess — CheckoGet:lChangedto see if the user modified a field before saving.- Tab order is determined by the order controls are added to
aControls(i.e., the order of your@commands). - Transparent controls — Set
lTransparent := .T.(orTRANSPARENTclause) when placing controls over gradient or bitmap backgrounds. oJump— SetoGet1:oJump := oGet3to skip a field in the tab sequence conditionally.- Proportional resizing — The
CalcSize()andSetResizeType()methods (added 2024) enable relative coordinate layout where controls scale proportionally with the parent. - Colors — The
Colors( hDC )method handlesWM_CTLCOLORmessages. Override it in subclasses for custom color behavior.
See Also
- TWindow — Parent class providing the base window API
- TDialog — Dialog boxes that host controls
- TXBrowse — The most feature-rich grid/browse control
- TGet / TMGet — Single-line and multi-line input controls
- TButton / TBtnBmp — Push buttons and bitmap buttons