// xbrml250.prg
// Verifies XBrowse multi-line cell edit with a hard 250-character limit.
//
// Why picture fails for multi-line:
// cEditPicture forces TGet (single line) instead of TMultiGet.
// Correct approach:
// nDataLines >= 2 + no picture -> TMultiGet (auto word-wrap)
// bOnPreEdit -> RTrim + LimitText( 250 )
//
// IMPORTANT:
// If the array cell is already PadR(..., 250) / Space(250), Len() is already
// 250. LimitText(250) then blocks every keystroke because the control is full
// (and TMultiGet does NOT SelectAll like TGet). Always RTrim before LimitText.
#include "FiveWin.ch"
#include "XBrowse.ch"
#define MAX_MNEMO 250
//----------------------------------------------------------------------------//
function Main()
local oWnd, oBar, oBrw, oMsg
local aData := SampleData()
local cInfo := ""
DEFINE WINDOW oWnd TITLE "XBrowse multi-line + LimitText(250)" ;
FROM 1, 1 TO 28, 100
DEFINE BUTTONBAR oBar OF oWnd SIZE 70, 22
DEFINE BUTTON OF oBar PROMPT "Check" ;
TOOLTIP "Show Len() of current MNEMO cell" ;
ACTION ShowLen( oBrw )
DEFINE BUTTON OF oBar PROMPT "Fill 250" ;
TOOLTIP "Put exactly 250 real chars (no trailing pad)" ;
ACTION FillMnemo( oBrw, aData, MAX_MNEMO, .F. )
DEFINE BUTTON OF oBar PROMPT "Pad 250" ;
TOOLTIP "PadR to 250 spaces 脭脟枚 shows the classic 'cannot type' bug if you drop RTrim" ;
ACTION FillMnemo( oBrw, aData, 0, .T. )
DEFINE BUTTON OF oBar PROMPT "Help" ;
ACTION MsgInfo( ;
"1. Click MNEMO and type 脭脟枚 text wraps at the cell edge (soft wrap)." + CRLF + ;
"2. You cannot type/paste more than 250 characters." + CRLF + ;
"3. ENTER = end cell edit (save)." + CRLF + ;
"4. Ctrl+ENTER or Shift+ENTER = one real line break." + CRLF + ;
"5. ESC = cancel edit." + CRLF + ;
"6. 'Check' shows Len() of stored value." + CRLF + CRLF + ;
"Soft wrap is only visual. Real newlines need Ctrl/Shift+ENTER.", ;
"How to test" )
@ 4.5, 0.5 XBROWSE oBrw OF oWnd ;
ARRAY aData ;
COLUMNS 1, 2, 3, 4 ;
COLSIZES 80, 480, 70, 100 ;
HEADERS "Contacto", "MNEMO DE GESTION", "Aplazado", "Usuario" ;
LINES CELL FASTEDIT ;
SIZE 700, 340 PIXEL
oWnd:oClient = oBrw
oBrw:lVScroll := .T.
oBrw:nDataLines := 3
oBrw:lAutoAppend := .T.
AEval( oBrw:aCols, {| o | o:nEditType := EDIT_GET } )
// Column 2: multi-line + max 250 chars
// Leave cEditPicture empty so XBrowse creates TMultiGet
oBrw:aCols[ 2 ]:bOnPreEdit := {| oCol | LimitMnemoEdit( oCol ) }
// Keep stored value at most 250; optional PadR if you need fixed length
oBrw:aCols[ 2 ]:bOnPostEdit := {| oCol, xVal, nKey | ;
If( nKey != VK_ESCAPE .and. xVal != nil, ;
oCol:Value := Left( xVal, MAX_MNEMO ), ) }
oBrw:bOnChange := {|| ;
cInfo := "Row " + LTrim( Str( oBrw:nArrayAt ) ) + ;
" | Len = " + LTrim( Str( Len( oBrw:aRow[ 2 ] ) ) ) + ;
" RTrim = " + LTrim( Str( Len( RTrim( oBrw:aRow[ 2 ] ) ) ) ) + ;
" / " + LTrim( Str( MAX_MNEMO ) ), ;
If( oMsg != nil, oMsg:SetText( cInfo ), ) }
oBrw:CreateFromCode()
DEFINE MSGBAR oMsg PROMPT "Edit MNEMO: wrap + max 250 (RTrim before LimitText)" OF oWnd
ACTIVATE WINDOW oWnd ;
ON INIT ( oBrw:SetFocus(), oBrw:GoTop() )
return nil
//----------------------------------------------------------------------------//
// Called when the in-cell editor is ready (after TMultiGet is created).
static function LimitMnemoEdit( oCol )
local oGet := oCol:oEditGet
local bOrig
local c
if oGet == nil
return nil
endif
// Must free trailing spaces BEFORE LimitText, or Len is already 250
// and the user cannot type a single character.
if oGet:IsKindOf( "TMULTIGET" )
c := RTrim( oGet:cText )
oGet:cText := c
oGet:LimitText( MAX_MNEMO )
oGet:SetPos( Len( c ) ) // caret at end
// XBrowse: plain ENTER ends edit (EditGetkeyDown).
// Ctrl+ENTER / Shift+ENTER must NOT end edit and must insert ONE newline.
// Do NOT call Replace(CRLF): the multiline EDIT already inserts a line
// break via WM_CHAR (ES_WANTRETURN). Doing both produces two breaks.
bOrig := oGet:bKeyDown
oGet:bKeyDown := {| nKey | ;
If( nKey == VK_RETURN .and. ;
( GetKeyState( VK_CONTROL ) .or. GetKeyState( VK_SHIFT ) ), ;
nil, ; // keep editing; let TMultiGet insert a single newline
If( bOrig != nil, Eval( bOrig, nKey ), nil ) ) }
elseif oGet:IsKindOf( "TGET" )
oGet:LimitText( MAX_MNEMO )
endif
return nil
//----------------------------------------------------------------------------//
static function SampleData()
local a := {}
// Store only real text (no PadR to 250). Pad only if your backend needs it,
// and always RTrim in bOnPreEdit before LimitText.
AAdd( a, { Date(), "Short note", .F., "ANA" } )
AAdd( a, { Date() - 1, ;
"This cell already holds a longer text so you can see multi-line display " + ;
"with nDataLines = 3. Click to edit: typing wraps at the cell edge and " + ;
"cannot exceed 250 characters thanks to LimitText in bOnPreEdit.", ;
.T., "PEPE" } )
AAdd( a, { Date() - 3, "", .F., "LUIS" } )
// Row that mimics a fixed-length field (250 spaces / padded) 脭脟枚 still editable
AAdd( a, { Date() - 5, PadR( "Padded field", MAX_MNEMO ), .F., "TEST" } )
return a
//----------------------------------------------------------------------------//
static function ShowLen( oBrw )
local cText, nLen, nAt
if oBrw:nLen < 1
MsgInfo( "Browse is empty" )
return nil
endif
nAt := oBrw:nArrayAt
cText := cValToChar( oBrw:aRow[ 2 ] )
nLen := Len( cText )
MsgInfo( ;
"Row: " + LTrim( Str( nAt ) ) + CRLF + ;
"Len( MNEMO ) = " + LTrim( Str( nLen ) ) + CRLF + ;
"Len( RTrim(MNEMO)) = " + LTrim( Str( Len( RTrim( cText ) ) ) ) + CRLF + CRLF + ;
"First 80 chars:" + CRLF + Left( cText, 80 ), ;
"Stored length" )
return nil
//----------------------------------------------------------------------------//
static function FillMnemo( oBrw, aData, nLen, lPadOnly )
local c
DEFAULT lPadOnly := .F.
if oBrw:nLen < 1
return nil
endif
if lPadOnly
c := PadR( "Padded to 250", MAX_MNEMO )
else
c := Replicate( "X", nLen )
endif
aData[ oBrw:nArrayAt, 2 ] := c
oBrw:RefreshCurrent()
MsgInfo( "Assigned Len=" + LTrim( Str( Len( c ) ) ) + ;
" RTrim=" + LTrim( Str( Len( RTrim( c ) ) ) ) + CRLF + ;
"Click the cell and type 脭脟枚 should accept input up to 250.", ;
"Fill MNEMO" )
return nil
//----------------------------------------------------------------------------//