home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
BUG 4
/
BUGCD1997_05.BIN
/
aplic
/
clip4win
/
clip4win.exe
/
C4W30E.HUF
/
SOURCE
/
DEMOMULT.PRG
< prev
next >
Wrap
Text File
|
1994-06-13
|
68KB
|
2,127 lines
/*
Program: DEMO.PRG
Purpose: Multi-window demonstration of a Clipper 5.0 application
written to run under Microsoft Windows (with Clip-4-Win)
Authors: John Skelton and Greg Lief
Date: November and December 1992
Use "RMAKE DEMOMULT" to compile and link.
This demo is Copyright (c) 1992 by John Skelton and Greg Lief.
Clip-4-Win is Copyright (c) 1992 by John Skelton.
The NTXREC() and NTXPOS() functions are based on Rick Spence's book,
were written in Microsoft C, and are provided by kind courtesy of
Graham McKechnie @ RCM Software Pty. Ltd. At the very least, Robert
DiFalco and Don Caton have also contributed. These functions make
use of an internal function entitled _ntxhandle(), which was written
in Assembler by Ted Means. John Skelton has further modified them.
NOTE: NTXREC() and NTXPOS() are NOT part of Clip-4-Win. They're
just being used as a convenience. Remember the warnings about
free software: you don't get many guarantees! Please don't
expect them to work with e.g. CDX files -- they're called
NTX* for good reasons!
Clip-4-Win is available from the following sources:
*** North America, South America, Asia, and Australia ***
(as well as contact point for dealers/resellers in those areas)
Grumpfish, Inc.
2450 Lancaster Drive, NE
Salem, Oregon 97305
USA
Tel: +1 (503) 588-1815
(USA Toll-free 800-367-7613)
Fax: +1 (503) 588-1980
BBS: +1 (503) 588-7572
CompuServe: 71064,2543
*** United Kingdom ***
QBS Software Ltd.
10 Barley Mow Passage
London W4 4PH
UK
Tel: +44 81 994 4842
Fax: +44 81 994 3441
BBS: +44 81 747 1979
CompuServe: 100016,573
*** Germany ***
dc Soft GmbH
Machtlfinger Str. 26
D-81379 München
Germany
Tel: +49 89 78 58 910
Fax: +49 89 78 58 91 11
CompuServe: 100016,1673
or:
Tobax Software GmbH
Sudermanstrasse 12
D-50670 Köln
Germany
Tel: +49 221 738028
Fax: +49 221 722806
CompuServe: 100113,1131
*** Scandinavia ***
xLib Programs AB
Finnbodavägen 29
S-131 31 NACKA
Sweden
Tel: +46 8 644 31 06
Fax: +46 8 640 61 17
CompuServe: 73354,3430
*** Belgium ***
Belgian (Clipper) User Group
Moerkerkse Steenweg 322
B-8310 BRUGGE 3
Belgium
Tel: +32 50 35 78 66
Fax: +32 50 37 25 05
CompuServe: 100034,56
*** Spain ***
Danysoft Internacional
Paseo de Albacete, 73
28700 San Sebastian de los Reyes
Madrid
Spain
Tel: +34 1 654 62 98
Fax: +34 1 654 63 82
BBS: +34 1 653 41 13
CompuServe: 71774,1614
*** France ***
Aco-ProDucTionS
101, rue de la Justice
91800 Boussy-Saint-Antoine
France
Tel: +33 1 69 00 60 72
Fax: +33 1 69 00 57 33
CompuServe: 100272,2511
PC Tech
24 rue Davoust
93500 Pantin
France
Tel: +33 1 49 42 96 24
Fax: +33 1 49 42 03 10
CompuServe: 70012,2313
*** Italy ***
SixBase srl
Via Giotto 30
20145 Milano
Italy
Tel: +39 2 4819 5421
Fax: +39 2 4801 3348
BBS: +39 2 4813 213
CompuServe: 100023,2415
Italian Software Agency srl
Via Torino, 2
28042 Baveno (NO)
Italy
Tel: +39 323 922066
Fax: +39 323 925208
BBS: +39 323 925428
*** Netherlands ***
DSA Software BV
Kanaalweg 33
2903 LR Capelle aan den Ussel
Holland
Tel: +31 10 458 05 15
Fax: +31 10 458 25 01 or +31 10 442 45 18
BBS: +31 10 451 71 70
CompuServe: 100330,1162
Lemax Company BV
Schipholweg 335 A
1171 PL Badhoevedorp
Holland
Tel: +31 20 659 8701
Fax: +31 20 659 6856
Fax Info-Service: +31 20 659 7196
BBS: +31 3483 4072 and 4335
CompuServe: 100012,1760
*** Israel ***
RDB Systems
18 Rival St.
Tel Aviv 67778
Israel
Tel: 972 3 639 0055
Fax: 972 3 639 0054
CompuServe: 100274,1440 or 100274,600
(including support for Hebrew Windows)
*** "Arabic areas" ***
Please contact Skelton Software
(support for Arabic Windows does exist!)
*** Australia ***
Aeronaut Industries
500 Miller Street
Cammeray NSW 2062
Australia
Tel: +61 2 957 3127
Fax: +61 954 3049
Fax Info-Service: +61 964 9542
CompuServe: 100033,734
or:
RCM Software Pty. Ltd.
1198 Toorak Road
Hartwell, Vic 3124
Australia
Tel: +61 3 889 0580
Fax: +61 3 889 0263
BBS: +61 3 889 0397
CompuServe: 73467,1645
*** Bulgaria ***
Great Bear Technology Bulgaria Inc.
58 Kosta Lulchev Str.
1574 Sofia
Bulgaria
Tel: +359 2 700 120
Fax: +359 2 738 460
*** Contact point for distributors ***
Skelton Software
Kendal Cottage
Hillam
Leeds LS25 5HP
ENGLAND
Tel: +44 977 683 296
Fax: +44 977 681 650
CompuServe: 100112,3102
*/
external descend // for index files that might need it
//───── necessary in the event of pushbutton movement on data entry screen
//───── (see WREADER.PRG for complete structure of get:cargo array)
#define FORCE_FOCUS 6
//───── this one handles fields
#xcommand @ <row>, <col> SAY <prompt> GET [FIELD] <fname> ;
IN WINDOW <w> ;
[GETROW <grow>] ;
[GETCOL <gcol>] ;
[PICTURE <pic>] ;
[BUTTONS <buttons>] ;
[VALID <valid>] ;
[WHEN <when>] ;
[SAYCOLOR <saycolor>] ;
[GETCOLOR <getcolor>] ;
;
=> AAdd( GetList, GetNew( <grow>, <gcol>, fieldblock(<fname>), ;
<"fname">, <pic>, <getcolor> ) ;
) ;
; ATail(GetList):reader := { | g, ev | WGetReader(g, ev, <w>, ;
getlist, ;
<buttons>, BUTTON_HEIGHT ) ;
} ;
; ATail(GetList):cargo := { ;
NIL , <prompt>, <row>, <col>, <saycolor>, .f., .f. ;
} ;
[; ATail(GetList):postBlock := <{valid}>] ;
[; ATail(GetList):preBlock := <{when}>]
//───── this one handles variables
#xcommand @ <row>, <col> SAY <prompt> GET <var> ;
IN WINDOW <w> ;
REDRAW WITH <redraw> ;
[GETROW <grow>] ;
[GETCOL <gcol>] ;
[PICTURE <pic>] ;
[BUTTONS <buttons>] ;
[VALID <valid>] ;
[WHEN <when>] ;
[SAYCOLOR <saycolor>] ;
[GETCOLOR <getcolor>] ;
;
=> AAdd( GetList, GetNew( <grow>, <gcol>, ;
{ | _1 | if(_1 == NIL, <var>, <var> := _1) }, <(var)>, ;
<pic>, <getcolor> ) ;
) ;
; ATail(GetList):reader := { | g, ev | WGetReader(g, ev, <w>, ;
getlist, ;
<buttons>, BUTTON_HEIGHT ) ;
} ;
; ATail(GetList):cargo := { ;
NIL , <prompt>, <row>, <col>, <saycolor>, .f., .f. ;
} ;
[; ATail(GetList):postBlock := <{valid}>] ;
[; ATail(GetList):preBlock := <{when}>]
#xtranslate StripPath( <f> ) => ;
if("\" $ <f>, substr(<f>, rat("\", <f>) + 1), <f>)
#xtranslate StripExt( <fname> ) => ;
if('.' $ <fname>, substr( <fname>, 1, at('.', <fname>) - 1), <fname> )
//───── if you have "Sound Explosion", uncomment the following statement
//───── and make sure that WAVE_DIRECTORY points to the directory where
//───── your Sound Explosion .WAV files reside
// #define SOUND_EXPLOSION
#define WAVE_DIRECTORY "C:\WINDOWS\"
#define WIN_WANT_CLIPBOARD // to make Clipboard directives accessible
#define WIN_WANT_LBS // to make Listbox styles accessible
#define WIN_WANT_ALL
#include "dbstruct.ch"
#include "directry.ch"
#include "error.ch"
#include "getexit.ch"
#include "inkey.ch"
#include "windows.ch"
#include "setcaret.ch"
#include "font.ch"
#define CR chr(13)
//───── remove this if you do not want push buttons on data entry screen
#define BUTTONS
//───── manifest constants for GetClientRect() and GetDIBRect() arrays
//───── note that since Top and Left are always 0, they are unused
#define W_RIGHT 3
#define W_BOTTOM 4
//───── manifest constants for RGB() color combinations
#define C_RED RGB(255,0,0)
#define C_BLUE RGB(0,0,255)
#define C_GREEN RGB(0,255,0)
#define C_MAGENTA RGB(255,0,255)
#define C_BLACK RGB(0,0,0)
#define TB_HEIGHT 30
#define ID_TOOLBAR 1000
#define ID_CLIENT 1001
#define APP_NAME "Clip-4-Win"
static hFrameWnd // top-most (frame) window
static hWnd // main (client) window... must be visible in several places
static cText := "" // text in main window if a file is opened... must be
// visible in several places for cut/copy to Clipboard
static cDIB // handle for bitmapped logo in main window... must be
// visible in several places for copying to Clipboard
static nMainEvId // ID of main event handler... visible throughout
// because it must be reset periodically
/*
Function: Main()
*/
function main
local hMenu, nEvent, hCurrWnd, hTBWnd, aRect
set scoreboard off // don't even THINK about using it in Windows!!
hWnd := WinSetup(APP_NAME, "Clip-4-Win Demo")
/*
* Set up the outermost window as the frame window, with the "main"
* window actually a child sized to fit the area left after the toolbar.
*/
hFrameWnd := hWnd
hMenu := MenuSetup()
HideCaret(hFrameWnd)
hTBWnd := ToolbarSetup(hFrameWnd, hMenu)
aRect := GetClientRect(hFrameWnd)
hWnd := CreateWindow(APP_NAME, "", WS_CHILD + WS_VISIBLE, ;
0, TB_HEIGHT, aRect[W_RIGHT], aRect[W_BOTTOM] - TB_HEIGHT,;
hFrameWnd, ID_CLIENT)
HideCaret(hWnd)
AddHandler(hFrameWnd, {|nEvent| FrameEvent(nEvent, hTBWnd, hWnd)})
nMainEvId := AddHandler(hWnd, {|nEvent| MainEvent(nEvent)})
/*
The C4W_AutoClose() function allows us to make the user confirm their
decision to quit the app, even if they try to close the window
with ALT-F4 (or via the System menu). It will cause the EVENT_CLOSE
event to be generated, which we can then react to (see below)
*/
C4W_AutoClose(.f.)
/*
* SetHandleCount() is needed to tell Windows how many file handles
* this application uses (40 isn't true for this one!).
*/
SetHandleCount(40)
do while .t.
do while (nEvent := ChkEvent()) == EVENT_NONE
// "background" processing could go here
enddo
HandleEvent(nEvent)
do case
case nEvent == EVENT_CLOSE
//───── determine if main window is currently active
hCurrWnd := _LasthWnd()
if hWnd == hCurrWnd
DoExit()
else
DestroyWindow(hCurrWnd)
SetFocus(hWnd)
endif
case nEvent == EVENT_QUIT
quit
endcase
enddo
return nil
/*
Function: ToolbarSetup()
*/
static function ToolbarSetup(hWnd, hMenu)
local aButtons := ;
{ {10, 3, 25, 22, ReadDIB("open1.bmp"), GetMenuId(hMenu, "open")}, ;
{60, 3, 25, 22, ReadDIB("print1.bmp"), GetMenuId(hMenu, "print")}, ;
{110, 3, 25, 22, ReadDIB("browse1.bmp"), GetMenuId(hMenu, "browse")}, ;
{160, 3, 25, 22, ReadDIB("exit1.bmp"), GetMenuId(hMenu, "exit")}, ;
{210, 3, 25, 22, ReadDIB("help1.bmp"), GetMenuId(hMenu, "about")} }
local aRect, hTBWnd
/*
* This sample puts the toolbar at the top of the window's
* client area. A child window is created to fill the area
* left. The user doesn't know the "childclient" exists,
* so just make it fill the area not used by the toolbar. Of
* course, this means changing its size if the main window changes
* size (msg WM_SIZE). The toolbar size needs to change as well
* (just the width in this example).
*
* This same technique can be useful at other times, e.g. the child
* might be a multi-line edit control.
*
* If you have a status bar, the easiest way to handle it is to
* make it another window (with no special border, no title bar,
* etc., and using WS_CHILD + WS_VISIBLE), put it at the bottom
* of the frame window, and reduce the child client's window height
* by the height of the status bar.
* (Yes, you do get extra windows. Don't worry.)
*/
aRect = GetClientRect(hWnd)
hTBWnd = ToolBar(hWnd, 0, 0, aRect[3], TB_HEIGHT, aButtons, ID_TOOLBAR)
return hTBWnd
/*
Function: FrameEvent()
*/
static function FrameEvent(nEvent, hTBWnd, hWnd)
local aRect, nW, nH
do case
case nEvent == EVENT_WINSIZE
aRect := GetClientRect(hFrameWnd)
nW := aRect[W_RIGHT] // same as _LastLolParam()
nH := aRect[W_BOTTOM] // same as _LastHilParam()
MoveWindow(hTBWnd, 0, 0, nW, TB_HEIGHT, .t.)
MoveWindow(hWnd, 0, TB_HEIGHT, nW, nH - TB_HEIGHT, .f.)
endcase
return nil
/*
Function: MainEvent()
*/
static function MainEvent(nEvent)
local hDC
local aDIBRect, aClientRect
do case
case nEvent == EVENT_REDRAW
hDC := GetDC(hWnd)
if cDIB == NIL
cDIB := ReadDIB("clip4win.bmp")
endif
aDIBRect := GetDIBRect(cDIB)
aClientRect := GetClientRect(hWnd)
// centre the bitmap
ShowDIB(hDC, cDIB, (aClientRect[W_RIGHT] - aDIBRect[W_RIGHT]) / 2, ;
(aClientRect[W_BOTTOM] - aDIBRect[W_BOTTOM]) / 2)
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: Credits()
*/
static function credits
MessageBox( , "By John Skelton and Greg Lief (rev 17-June-93)", ;
"About Clip-4-Win Demo", MB_ICONASTERISK + MB_OK)
return nil
/*
Function: DoExit()
*/
static function DoExit()
//───── Note that MessageBox() returns a value based on the I.D. of
//───── the selected item, all of which have corresponding ID* manifest
//───── constants in the WINDOWS.CH header file
if MessageBox(0, "Are you sure you want to exit this demo?", ;
"Leaving so soon?", MB_OKCANCEL + MB_ICONQUESTION) == IDOK
if IsWindow(hWnd)
DestroyWindow(hWnd)
endif
UnregisterClass(APP_NAME)
quit
endif
return nil
/*
Function: DoAudio()
Note: If you have Sound Explosion, use it instead of the
somewhat feeble stock Windows .WAV files
*/
static function DoAudio()
#ifdef SOUND_EXPLOSION
static waves_ := { "WHISTLE1", "SCREAM5", "LAUGH4", "BELCH2" }
#else
static waves_ := { "CHORD", "CHIMES", "TADA", "DING" }
#endif
local hLib, cSound, hWnd, n
hLib := LoadLibrary("MMSYSTEM.DLL")
cSound := GetProcAddress(hLib, "SndPlaySound", "Pascal", "Int", "str, int")
//───── note that Alert() allows you to specify trigger letters by
//───── preceding them with an ampersand
#ifdef SOUND_EXPLOSION
n := alert("Pick a sound", ;
{ "&Whistle", "&Scream", "&Laugh" } )
#else
n := alert("Pick a sound", ;
{ "&Chord", "Chi&mes", "&Tada" } )
#endif
// if they escaped out, use sound #4
if n == 0
n := 4
endif
//───── second parameter to SndPlaySound() is: 1 == return instantly,
//───── 0 == wait until finished playing before returning
n := CallDLL(cSound, WAVE_DIRECTORY + waves_[n] + ".WAV", 1)
if n == 0
MessageBox(, "No audio hardware" + CR + "Or other error", ;
MB_ICONHAND + MB_OK)
endif
return nil
/*
Function: DoColor()
*/
static function DoColor()
static nX := 20, nY := 50
local nColor := ChooseColor(), hWnd
if nColor >= 0 // else user chose cancel/close or hit Esc
hWnd := WinNew("Color", nX += 40, nY += 60, 150, 100)
AddHandler(hWnd, {|nEvent| ColorEvent(nEvent, hWnd, nColor)})
endif
return nil
/*
Function: ColorEvent()
*/
static function ColorEvent(nEvent, hWnd, nColor)
local hDC, hBrush
do case
case nEvent == EVENT_REDRAW
hDC := GetDC(hWnd)
hBrush := CreateSolidBrush(nColor)
FillRect(hDC, 20, 20, 100, 50, hBrush)
DeleteObject(hBrush)
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: DoOpen()
*/
static function DoOpen
local cFile := GetOpenFileName(, "*.txt", "Select a text file")
local hMenu
if cFile <> NIL
if directory(cFile)[1][F_SIZE] < 65535
//───── because we want this text file to be displayed in the
//───── main window, we must first delete the primary event handler
DelHandler(nMainEvId)
cText := memoread(cFile)
nMainEvId := AddHandler(hWnd, ;
{ | nEvent | CBEventText(nEvent, hWnd, cText)})
//───── enable the "Clear" and "Cut" menu items ("Copy" already enabled)
hMenu := GetMenu(hFrameWnd) // retrieve reference to main menu
EnableMenuItem(hMenu, "clear", MF_ENABLED)
EnableMenuItem(hMenu, "cut", MF_ENABLED)
//───── force main window to be redrawn immediately
InvalidateRect(hWnd)
else
MessageBox(hWnd, cFile + " is too large to load", "Error", ;
MB_ICONEXCLAMATION + MB_OK)
endif
endif
return nil
/*
Function: DaDoRunRun()
Note: Long live Phil Spector!
*/
static function DaDoRunRun
local cFile := GetOpenFileName(, "*.exe;*.com;*.bat", "Run", ;
{ {"programs", "*.exe;*.com;*.bat"} } )
local hCursor
local hOldcursor
if cFile <> NIL
hCursor := LoadCursor(, IDC_WAIT)
hOldcursor := SetCursor(hCursor)
WinExec(cFile)
SetCursor(hOldcursor) // restore previous cursor
endif
return nil
/*
Function: DoClear()
*/
static function DoClear
if MessageBox(hWnd, "Are you sure you want to clear the current text?", ;
"Question", MB_OKCANCEL + MB_ICONQUESTION) == IDOK
cText := ''
ResetMainEvent()
endif
return nil
/*
Function: DoCutCopy()
*/
static function DoCutCopy(c)
if OpenClipboard(hWnd)
EmptyClipboard()
//───── if there is no text in the main window, copy the logo instead
if empty(cText)
SetClipboardData(CF_DIB, cDIB)
else
SetClipboardData(CF_TEXT, "*** Pasted From Clip-4-Win ***" + CR + cText)
endif
CloseClipboard()
//───── if we just "cut", clear text and reset to show the logo
if c == "cut"
cText := ""
ResetMainEvent()
endif
else
MessageBox( , "Clipboard not available", "Info", MB_ICONHAND + MB_OK)
endif
return nil
/*
Function: DoPaste()
*/
static function DoPaste()
static nX := 300, nY := 150
local hWnd
local cPasted
if OpenClipboard(hWnd)
//───── retrieve text from Clipboard
cPasted := GetClipbData(CF_TEXT)
//───── if Clipboard contained text, create new window to hold it
if ! empty(cPasted)
hWnd := WinNew("Text from Clipboard", nX += 40, nY += 40, 250, 150)
AddHandler(hWnd, { | nEvent | ;
CBEventText(nEvent, hWnd, cPasted)})
else // Clipboard did not contain text -- maybe it contains a bitmap?
cPasted := GetClipbData(CF_DIB)
if ! empty(cPasted)
hWnd := WinNew("Bitmap from Clipboard", nX += 40, nY += 40, 250, 150)
AddHandler(hWnd, {|nEvent| CBEventDIB(nEvent, hWnd, cPasted)})
endif
endif
CloseClipboard()
if empty(cPasted)
MessageBox( , "Clipboard is either empty" + CR + ;
"or contains unknown data", "Info", MB_ICONHAND + MB_OK)
endif
else
MessageBox( , "Clipboard not available", "Info", MB_ICONHAND + MB_OK)
endif
return nil
/*
Function: CBEventText()
*/
static function CBEventText(nEvent, hWnd, cText)
local hDC
do case
case nEvent == EVENT_REDRAW
hDC := GetDC(hWnd)
DrawText(hDC, cText, GetClientRect(hWnd))
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: CBEventDIB()
*/
static function CBEventDIB(nEvent, hWnd, cDIB)
local hDC
do case
case nEvent == EVENT_REDRAW
hDC := GetDC(hWnd)
ShowDIB(hDC, cDIB, 0, 0)
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: DoDLL()
*/
static function DoDLL()
static nX := 20, nY := 200
local hLib, cRectangle, hWnd
hLib := LoadLibrary("GDI.EXE")
cRectangle := GetProcAddress(hLib, "rectangle", "Pascal", "Int", ;
"int, int, int, int, int")
hWnd := WinNew("DLL", nX += 40, nY += 60, 200, 100)
AddHandler(hWnd, {|nEvent| DLLEvent(nEvent, hWnd, cRectangle)})
return nil
/*
Function: DLLEvent()
*/
static function DLLEvent(nEvent, hWnd, cRectangle)
local hDC, cText
do case
case nEvent == EVENT_REDRAW
hDC := GetDC(hWnd)
cText := "CallDLL(Rectangle, ...) --> " + ;
nstr(CallDLL(cRectangle, hDC, 10, 30, 100, 50))
DrawText(hDC, cText, GetClientRect(hWnd))
ReleaseDC(hWnd, hDC)
case nEvent == EVENT_LCLICK .or. nEvent == EVENT_RCLICK
InvalidateRect(hWnd)
endcase
return nil
/*
Function: DoFont()
*/
static function DoFont()
static nX := 100, nY := 50
local aFont := {40, 40, 450, 0, 400, .t., .f., .f., 1, 0, 0, 0, 0, "Arial"}
local hWnd, nColor := C_RED
aFont := ChooseFont(aFont, , , @nColor)
if aFont <> NIL // else user chose cancel/close or hit Esc
hWnd := WinNew("Font", nX += 40, nY += 60, 300, 200)
AddHandler(hWnd, {|nEvent| FontEvent(nEvent, hWnd, aFont, nColor)})
endif
return nil
/*
Function: FontEvent()
*/
static function FontEvent(nEvent, hWnd, aFont, nColor)
local hDC, hFont, hOldFont, i, j
static msg := "Clip-4-Win"
static aShow := { {200, 200, 300}, ; // {x coord, y coord, angle}
{0, 350, 1800}, ; // angle is in 3600 gradiants
{10, 200, 800}, ; // e.g., 900 points straight up
{20, 0, 2700}, ; // 0 is horizontal left-to-right
{75, 400, 1350}, ;
{100, 20, 0}, ;
{100, 300, 450}, ;
{125, 425, 450}, ; // 1800 is horizontal right-to-left
{200, 50, 3450}, ;
{250, 200, 0}, ;
{300, 15, 2100}, ;
{300,400, 1080}, ;
{375,200, 300}, ;
{470,300, 2700}, ;
{500,200, 1800}, ;
{550,100, 3500}, ;
{400, 50, 3150} }
do case
case nEvent == EVENT_REDRAW
hDC := GetDC(hWnd)
SetTextColor(hDC, nColor)
j := len(aShow)
for i := 1 to j
aFont[LF_Escapement] := aShow[i, 3]
hFont := CreateFont(aFont)
hOldFont := SelectObject(hDC, hFont)
TextOut(hDC, aShow[i, 1], aShow[i, 2], msg)
//───── note that SelectObject() returns a handle to the
//───── prior object, so you can delete it in one fell swoop
DeleteObject( SelectObject(hDC, hOldFont) )
next
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: DoPie()
*/
static function DoPie()
static nX := 400, nY := 50
local hWnd := WinNew("Pie", nX += 40, nY += 60, 100, 100)
AddHandler(hWnd, {|nEvent| PieEvent(nEvent, hWnd)})
return nil
/*
Function: PieEvent()
*/
static function PieEvent(nEvent, hWnd)
local hDC, aRect, hBrush, hOldbrush
do case
case nEvent == EVENT_REDRAW
hBrush := CreateSolidBrush(C_RED)
aRect := GetClientRect(hWnd)
hDC := GetDC(hWnd)
hOldbrush := SelectObject(hDC, hBrush)
pie(hDC, 0, 0, aRect[W_RIGHT], aRect[W_BOTTOM], ;
aRect[W_RIGHT] / 2, 0, aRect[W_RIGHT], aRect[W_BOTTOM])
hBrush := CreateSolidBrush(C_MAGENTA)
//───── Just as it is important to close each open file handle, you
//───── should always delete each object when you are done with it.
//───── Note that this can be done with the following syntax, which
//───── lets you re-use the same variable name for multiple objects.
DeleteObject( SelectObject(hDC, hBrush) )
pie(hDC, 0, 0, aRect[W_RIGHT], aRect[W_BOTTOM], aRect[W_RIGHT], ;
aRect[W_BOTTOM], aRect[W_RIGHT] * .75, aRect[W_BOTTOM] * .25)
hBrush := CreateSolidBrush(C_BLUE)
DeleteObject( SelectObject(hDC, hBrush) )
pie(hDC, 0, 0, aRect[W_RIGHT], aRect[W_BOTTOM], aRect[W_RIGHT], ;
aRect[W_BOTTOM] / 2, aRect[W_RIGHT] * .75, aRect[W_BOTTOM] * .25)
hBrush := CreateSolidBrush(C_GREEN)
DeleteObject( SelectObject(hDC, hBrush) )
pie(hDC, 0, 0, aRect[W_RIGHT], aRect[W_BOTTOM], ;
aRect[W_RIGHT] * .75, aRect[W_BOTTOM] * .25, aRect[W_RIGHT] / 2, 0)
DeleteObject( SelectObject(hDC, hOldbrush) )
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: DoBar()
*/
static function DoBar()
static nX := 150, nY := 175
local hWnd := WinNew("Bar Graph", nX += 40, nY += 60, 250, 125)
AddHandler(hWnd, {|nEvent| BarEvent(nEvent, hWnd)})
return nil
/*
Function: BarEvent()
*/
static function BarEvent(nEvent, hWnd)
local hDC, aRect, hBrush, hOldbrush, nWidth
do case
case nEvent == EVENT_REDRAW
hBrush := CreateHatchBrush(HS_BDIAGONAL, C_RED)
aRect := GetClientRect(hWnd)
nWidth := aRect[W_RIGHT] / 6
hDC := GetDC(hWnd)
hOldbrush := SelectObject(hDC, hBrush)
TextOut(hDC, nWidth + 10, aRect[W_BOTTOM] * .15 - 20, "U.S.A.")
rectangle(hDC, nWidth, aRect[W_BOTTOM] * .15, ;
nWidth * 2, aRect[W_BOTTOM])
hBrush := CreateHatchBrush(HS_VERTICAL, C_MAGENTA)
DeleteObject( SelectObject(hDC, hBrush) )
TextOut(hDC, nWidth * 2 + 10, aRect[W_BOTTOM] * .4 - 20, "England")
rectangle(hDC, nWidth * 2, aRect[W_BOTTOM] * .4, ;
nWidth * 3, aRect[W_BOTTOM])
hBrush := CreateHatchBrush(HS_FDIAGONAL, C_BLUE)
DeleteObject( SelectObject(hDC, hBrush) )
TextOut(hDC, nWidth * 3 + 10, aRect[W_BOTTOM] * .8 - 20, "Australia")
rectangle(hDC, nWidth * 3, aRect[W_BOTTOM] * .8, ;
nWidth * 4, aRect[W_BOTTOM])
hBrush := CreateHatchBrush(HS_CROSS, C_GREEN)
DeleteObject( SelectObject(hDC, hBrush) )
TextOut(hDC, nWidth * 4 + 10, aRect[W_BOTTOM] * .55 - 20, "Germany")
rectangle(hDC, nWidth * 4, aRect[W_BOTTOM] * .55, ;
nWidth * 5, aRect[W_BOTTOM])
DeleteObject( SelectObject(hDC, hOldbrush) )
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: DoPrint()
*/
static function DoPrint()
local hPrintDC, hIcon, hBrush, hOldBrush, hCursor, hOldCursor
local nBlack := C_BLACK
local i, nWidth, nHeight
// display printer dialog box, so the user can choose the settings
hPrintDC := GetPrintDC()
if empty(hPrintDC)
// cancelled by the user
return nil
endif
// print a test page
hCursor := LoadCursor( , IDC_WAIT)
hOldCursor := SetCursor(hCursor)
nWidth := GetDeviceCaps(hPrintDC, HORZRES)
nHeight := GetDeviceCaps(hPrintDC, VERTRES)
StartDoc(hPrintDC, "TestOutput")
StartPage(hPrintDC)
TextOut(hPrintDC, 100, 50, "Clip-4-Win Printer Test Page")
Rectangle(hPrintDC, 0, 0, nWidth, nHeight)
MoveTo(hPrintDC, 0, 0)
LineTo(hPrintDC, nWidth, nHeight)
MoveTo(hPrintDC, nWidth, 0)
LineTo(hPrintDC, 0, nHeight)
Arc(hPrintDC, 1000, 1000, 1300, 1200, 1250, 1190, 1100, 1100)
hBrush := CreateHatchBrush(HS_HORIZONTAL, nBlack)
hOldBrush := SelectObject(hPrintDC, hBrush)
Chord(hPrintDC, 1500, 1200, 2000, 1350, 1550, 1340, 1400, 1200)
DeleteObject( SelectObject(hPrintDC, hOldBrush) )
hBrush := CreateHatchBrush(HS_BDIAGONAL, nBlack)
hOldBrush := SelectObject(hPrintDC, hBrush)
Pie(hPrintDC, 100, 1200, 700, 1500, 650, 1490, 120, 1280)
DeleteObject( SelectObject(hPrintDC, hOldBrush) )
hBrush := CreateHatchBrush(HS_FDIAGONAL, nBlack)
hOldBrush := SelectObject(hPrintDC, hBrush)
Polygon(hPrintDC, { {1000, 250}, {1600, 500}, {1800, 100} })
DeleteObject( SelectObject(hPrintDC, hOldBrush) )
PolyLine(hPrintDC, { {300, 700}, {100, 900}, {500, 1000} })
for i := 100 to 500 step 100
TextOut(hPrintDC, i + 400, i + 100, nstr(i))
next i
EndPage(hPrintDC)
EndDoc(hPrintDC)
DeleteDC(hPrintDC)
SetCursor(hOldCursor)
return nil
/*
Function: DoTimer()
*/
static function DoTimer()
static lTimer := .F., hWnd
if ! lTimer
hWnd := WinNew("Timer", 400, 150, 135, 75)
SetTimer(hWnd, 1, 1000) // every 1000 millisecs (= one second)
lTimer := .T.
AddHandler(hWnd, ;
{|nEvent| TimerEvent(nEvent, hWnd, @lTimer)})
endif
return nil
/*
Function: TimerEvent()
*/
static function TimerEvent(nEvent, hWnd, lTimer)
local hDC, hFont, hOldFont, aRect
static aFont := { -55, -17, 0, 0, FW_SEMIBOLD, .F., .F., .F., 0, 3, 2, 1, 18, ;
"Times New Roman" }
do case
case nEvent == EVENT_TIMER
InvalidateRect(hWnd)
case nEvent == EVENT_CLOSE .or. nEvent == EVENT_DESTROY
if IsWindow(hWnd)
KillTimer(hWnd, 1)
DestroyWindow(hWnd)
endif
lTimer := .F.
case nEvent == EVENT_WINSIZE
//───── adjust font height/width based on new size of this window
aRect := GetClientRect(hWnd)
aFont[LF_Height] := - aRect[W_BOTTOM] * .85
aFont[LF_Width] := - aRect[W_RIGHT] * .11
case nEvent == EVENT_REDRAW
hDC := GetDC(hWnd)
SetTextColor(hDC, C_BLUE)
hFont := CreateFont(aFont)
hOldFont := SelectObject(hDC, hFont)
DrawText(hDC, time(), GetClientRect(hWnd))
DeleteObject( SelectObject(hDC, hOldFont) )
ReleaseDC(hWnd, hDC)
endcase
return nil
/*
Function: NStr()
*/
static function nstr(n)
return alltrim(str(n)) + " "
/*
Function: asString()
*/
static function asString(x)
local v := valtype(x)
do case
case v == "C"
case v == "N"
return nstr(x)
case v == "L"
if x
return ".T."
else
return ".F."
endif
case v == "D"
return "date"
case v == "U"
return "NIL"
case v $ "AOB"
return ""
otherwise
return ""
end case
return x
/*
Function: MenuSetup()
*/
static function MenuSetup()
local hWnd := SelectWindow()
local hMenu
local hPopupMenu
if (hMenu := GetMenu(hWnd)) <> NIL
DestroyMenu(hMenu)
endif
//───── note the grayed out entries (search for MF_GRAYED)
//───── also note that ampersand indicates the trigger letter
hMenu := CreateMenu()
hPopupMenu := CreatePopupMenu()
AppendMenu(hMenu, "file", MF_ENABLED + MF_POPUP, "&File", hPopupMenu)
AppendMenu(hPopupMenu, "open", MF_ENABLED + MF_STRING, "&Open...", {|| DoOpen()})
AppendMenu(hPopupMenu, "clear", MF_GRAYED + MF_STRING, "&Clear", {|| DoClear()})
AppendMenu(hPopupMenu, "run", MF_ENABLED + MF_STRING, "&Run...", {|| DaDoRunRun()})
AppendMenu(hPopupMenu, "save", MF_GRAYED + MF_STRING, "&Save", {|| qout("save")})
AppendMenu(hPopupMenu, "saveas", MF_GRAYED + MF_STRING, "Save &As...", {|| qout("save as")})
AppendMenu(hPopupMenu, "", MF_SEPARATOR)
AppendMenu(hPopupMenu, "print", MF_ENABLED + MF_STRING, "&Print...", {|| DoPrint()})
AppendMenu(hPopupMenu, "", MF_SEPARATOR)
AppendMenu(hPopupMenu, "exit", MF_ENABLED + MF_STRING, "E&xit", {|| DoExit() })
hPopupMenu := CreatePopupMenu()
AppendMenu(hMenu, "edit", MF_ENABLED + MF_POPUP, "&Edit", hPopupMenu)
AppendMenu(hPopupMenu, "cut", MF_GRAYED + MF_STRING, "Cu&t", {|c| DoCutCopy(c)})
AppendMenu(hPopupMenu, "copy", MF_ENABLED + MF_STRING, "&Copy", {|c| DoCutCopy(c)})
AppendMenu(hPopupMenu, "paste", MF_ENABLED + MF_STRING, "&Paste", {|| DoPaste()})
AppendMenu(hPopupMenu, "", MF_SEPARATOR)
AppendMenu(hPopupMenu, "toggle", MF_ENABLED + MF_STRING, "&Toggle", {|c| ToggleItem(c) })
AppendMenu(hPopupMenu, "find", MF_GRAYED + MF_STRING, "&Find...", {|c| qout(c)})
AppendMenu(hPopupMenu, "replace", MF_GRAYED + MF_STRING, "&Replace...", {|c| qout(c)})
hPopupMenu := CreatePopupMenu()
AppendMenu(hMenu, "stuff1", MF_ENABLED + MF_POPUP, "&Stuff", hPopupMenu)
AppendMenu(hPopupMenu, "color", MF_ENABLED + MF_STRING, "&Color...", {|c| DoColor()})
AppendMenu(hPopupMenu, "dll", MF_ENABLED + MF_STRING, "&DLL", {|c| DoDLL()})
AppendMenu(hPopupMenu, "font", MF_ENABLED + MF_STRING, "&Font...", {|c| DoFont()})
AppendMenu(hPopupMenu, "", MF_SEPARATOR)
AppendMenu(hPopupMenu, "pie chart", MF_ENABLED + MF_STRING, "&Pie Chart", {|c| DoPie()})
AppendMenu(hPopupMenu, "bar graph", MF_ENABLED + MF_STRING, "&Bar Graph", {|c| DoBar()})
hPopupMenu := CreatePopupMenu()
AppendMenu(hMenu, "stuff2", MF_ENABLED + MF_POPUP, "&More Stuff", hPopupMenu)
AppendMenu(hPopupMenu, "audio", MF_ENABLED + MF_STRING, "&Audio...", {|c| DoAudio()})
AppendMenu(hPopupMenu, "cursor", MF_ENABLED + MF_STRING, "&Cursor", {|c| DoCursor()})
AppendMenu(hPopupMenu, "icons", MF_ENABLED + MF_STRING, "&Icons", {|c| DoIcons()})
AppendMenu(hPopupMenu, "printer", MF_ENABLED + MF_STRING, "&Printer...", {|c| DoPrint()})
AppendMenu(hPopupMenu, "timer", MF_ENABLED + MF_STRING, "&Timer", {|c| DoTimer()})
hPopupMenu := CreatePopupMenu()
AppendMenu(hMenu, "dataentry", MF_ENABLED + MF_POPUP, "&Data Entry", hPopupMenu)
AppendMenu(hPopupMenu, "modelessgets", MF_ENABLED + MF_STRING, "&Modeless", {|| ModelessGets()})
AppendMenu(hPopupMenu, "valtest", MF_ENABLED + MF_STRING, "&Validation", {|| ValidTest()})
hPopupMenu := CreatePopupMenu()
AppendMenu(hMenu, "browsemenu", MF_ENABLED + MF_POPUP, "&Browses", hPopupMenu)
AppendMenu(hPopupMenu, "browse", MF_ENABLED + MF_STRING, "&Browse...", {|| DoBrowse()})
hPopupMenu := CreatePopupMenu()
AppendMenu(hMenu, "help", MF_ENABLED + MF_POPUP, "&Help", hPopupMenu)
AppendMenu(hPopupMenu, "about", MF_ENABLED + MF_STRING, "&About", {|| credits()})
SetMenu(hWnd, hMenu)
return hMenu
/*
Function: DoCursor()
*/
static function DoCursor()
static cursors_ := { IDC_ARROW, IDC_IBEAM, IDC_WAIT, IDC_CROSS, ;
IDC_UPARROW, IDC_SIZE, IDC_ICON, IDC_SIZENWSE, ;
IDC_SIZENESW, IDC_SIZEWE, IDC_SIZENS }
local y := len(cursors_)
local x
local hCursor
local hOldcursor := SetCursor()
MessageBox(hWnd, "Press spacebar to cycle through the stock cursors", "Info", ;
MB_OK)
for x := 1 to y
hCursor := LoadCursor( , cursors_[x])
if hCursor <> 0
SetCursor(hCursor)
endif
inkey(3)
next
SetCursor(hOldcursor)
return nil
/*
Function: DoIcons()
*/
static function DoIcons()
static nX := 250, nY := 100
local hWnd := WinNew("Icons", nX += 40, nY += 60, 300, 100)
AddHandler(hWnd, {|nEvent| IconEvent(nEvent, hWnd)})
return nil
/*
Function: IconEvent()
*/
static function IconEvent(nEvent, hWnd)
static icons_ := { IDI_EXCLAMATION, IDI_HAND, IDI_ASTERISK, ;
IDI_QUESTION, IDI_APPLICATION }
local hDC
local x
local hIcon
local aRect
do case
case nEvent == EVENT_REDRAW
aRect := GetClientRect(hWnd)
hDC := GetDC(hWnd)
if hDC <> 0
for x := 1 to 5
if ( hIcon := LoadIcon(, icons_[x]) ) <> 0
DrawIcon(hDC, aRect[W_RIGHT] * x / 6, ;
aRect[W_BOTTOM] * .4, hIcon)
endif
next
ReleaseDC(hWnd, hDC)
endif
endcase
return nil
/*
Function: ModelessGets()
*/
static function ModelessGets
local cFile
local getlist := {}
local x
local f
local nFields
local hCWnd
local nHand
local nArea
local nOldarea := select()
local bOldinsert
#ifdef BUTTONS
local nWidth
local aRect
local nY
local hInst
local lContinue := .t.
local aButtons := {}
local nButton
#endif
cFile := GetOpenFileName(, "*.dbf", "Select a database")
if cFile <> NIL
if select(StripPath(StripExt(cFile))) == 0
use (cFile) new
else
// trying to browse/edit a dbf that's already open
// the following would probably just confuse
//dbSelectArea(StripPath(StripExt(cFile)))
MessageBox( , "Already in use", "Error", MB_ICONHAND + MB_OK)
return nil
endif
x := alias()
//───── I realize that this is not particularly graceful, but the
//───── purpose of this demo is not necessarily to show locking
//───── techniques... i.e., puh-leeze feel free to substitute your own!
if ! rlock()
MessageBox(, "Cannot edit " + x + " database at this time", ;
MB_ICONHAND + MB_OK)
dbSelectArea(nOldarea)
return nil
endif
// the WS_CLIPCHILDREN makes sure anything drawn with WS_CHILD is visible
// (e.g. the buttons)
hCWnd := WinNew("Editing " + left(x, 1) + lower(substr(x, 2)) + ;
" Database", 0, 0, 350, 250, ;
WS_OVERLAPPEDWINDOW + WS_CLIPCHILDREN)
#ifdef BUTTONS
#define BUTTON_HEIGHT 20
#define B_NEXT 1
#define B_PREV 2
#define B_FIRST 3
#define B_LAST 4
#define B_ADD 5
#define TOTAL_BUTTONS 5
aRect := GetClientRect(hCWnd)
hInst := _GetInstance()
nWidth := aRect[W_RIGHT] / TOTAL_BUTTONS
nY := aRect[W_BOTTOM] - BUTTON_HEIGHT
aadd(aButtons, CreateWindow("button", ; // button class name
"Next", ; // title text
WS_CHILD ; // child window
+ WS_VISIBLE ; // ... that can be seen
+ BS_PUSHBUTTON, ; // ... a push button
0, nY, ; // x,y position
nWidth, ; // width
BUTTON_HEIGHT, ; // height
hCWnd, ; // parent window
B_NEXT, ; // unique child id
hInst) ) // parent's instance
aadd(aButtons, CreateWindow("button", ; // button class name
"Prev", ; // title text
WS_CHILD ; // child window
+ WS_VISIBLE ; // ... that can be seen
+ BS_PUSHBUTTON, ; // ... a push button
nWidth, nY, ; // x,y position
nWidth, ; // width
BUTTON_HEIGHT, ; // height
hCWnd, ; // parent window
B_PREV, ; // unique child id
hInst) ) // parent's instance
aadd(aButtons, CreateWindow("button", ; // button class name
"First", ; // title text
WS_CHILD ; // child window
+ WS_VISIBLE ; // ... that can be seen
+ BS_PUSHBUTTON, ; // ... a push button
nWidth*2, nY, ; // x,y position
nWidth, ; // width
BUTTON_HEIGHT, ; // height
hCWnd, ; // parent window
B_FIRST, ; // unique child id
hInst) ) // parent's instance
aadd(aButtons, CreateWindow("button", ; // button class name
"Last", ; // title text
WS_CHILD ; // child window
+ WS_VISIBLE ; // ... that can be seen
+ BS_PUSHBUTTON, ; // ... a push button
nWidth*3, nY, ; // x,y position
nWidth, ; // width
BUTTON_HEIGHT, ; // height
hCWnd, ; // parent window
B_LAST, ; // unique child id
hInst) ) // parent's instance
aadd(aButtons, CreateWindow("button", ; // button class name
"Add", ; // title text
WS_CHILD ; // child window
+ WS_VISIBLE ; // ... that can be seen
+ BS_PUSHBUTTON, ; // ... a push button
nWidth*4, nY, ; // x,y position
nWidth, ; // width
BUTTON_HEIGHT, ; // height
hCWnd, ; // parent window
B_ADD, ; // unique child id
hInst) ) // parent's instance
#endif
//───── create Getlist array and load it with GET objects
nFields := fcount()
for x := 1 to nFields
f := field(x)
#ifdef BUTTONS
@ x, 2 say lower(f) get field f in window hCWnd buttons aButtons ;
picture '@K' saycolor 'n/w' getcolor 'n/bg,+w/b'
#else
@ x, 2 say lower(f) get field f in window hCWnd picture '@K' ;
saycolor 'n/w' getcolor 'n/bg,+w/b'
#endif
next
ShowCaret(hCWnd)
// EnableMenuItem(GetMenu(hFrameWnd), "modelessgets", MF_GRAYED) // one at a time
nArea := select()
#ifdef BUTTONS
nHand := AddHandler(hCWnd, {|nEvent| ModelessEvent(GetList, nEvent, hCWnd, nHand, nArea, aButtons)})
#else
nHand := AddHandler(hCWnd, {|nEvent| ModelessEvent(GetList, nEvent, hCWnd, nHand, nArea, {})})
#endif // BUTTONS
endif
dbSelectArea(nOldarea)
return nil
/*
Procedure: ModelessEvent()
*/
static procedure ModelessEvent(GetList, nEvent, hCWnd, nHand, nArea, aButtons)
local nButton, hOldWnd, nOldArea := select()
select (nArea)
if nEvent == EVENT_DESTROY .and. used()
close
endif
if ReadModeless(GetList, nEvent) == NIL
// not yet finished the read
select (nOldArea)
return // wait for next event
endif
// getting here means the read has completed
#ifdef BUTTONS
// maybe a button was pressed
if nEvent == EVENT_CONTROL
nButton := _lastwParam()
do case
case nButton == B_NEXT
dbskip(1)
if eof()
MessageBox( , "You are at the end of the file", ;
"Message", MB_ICONHAND + MB_OK)
dbgobottom()
endif
case nButton == B_PREV
dbskip(-1)
if bof()
MessageBox( , "You are at the start of the file", ;
"Message", MB_ICONHAND + MB_OK)
dbgotop()
endif
case nButton == B_FIRST
dbgotop()
case nButton == B_LAST
dbgobottom()
case nButton == B_ADD
dbappend()
endcase
hOldWnd = SelectWindow(hCWnd)
//───── as we may have changed records, redisplay all GETs
aeval(getlist, { | g | g:display() } )
//───── force first GET to gain focus immediately upon
//───── entering WGetReader() (see notes in WREADER.PRG)
getlist[1]:cargo[FORCE_FOCUS] := .t.
//───── give this window focus again because one of the buttons might
//───── still have it (which would force all keystrokes to be ignored!)
SetFocus(hCWnd)
// restart the GETs
ReadModeless(GetList, EVENT_NONE) // output the GETs
SelectWindow(hOldWnd)
select (nOldArea)
return // wait for next event
endif
#endif // BUTTONS
// getting here means the read has completed (or been abandoned)
if used()
close
endif
select (nOldArea)
if nEvent == EVENT_DESTROY
// abandoned
return
endif
HideCaret(hCWnd)
DestroyWindow(hCWnd)
// the following is not needed, as the buttons are all children of hWnd
// and are thus cleaned up automatically...
//aeval(aButtons, { | b | DestroyWindow(b) } )
DelHandler(nHand)
EnableMenuItem(GetMenu(hFrameWnd), "modelessgets", MF_ENABLED)
return
/*
Function: DoBrowse()
*/
static function DoBrowse
static nX := 10, nY := 10
local b
local hWnd
local x
local f
local c
local e
local nFields
local fields_
local cFile
local cNtxfile
local bOldhandler
local nArea
static lFirsttime
cFile := GetOpenFileName(, "*.dbf", "Select a database")
if cFile <> NIL
if select(StripPath(StripExt(cFile))) == 0
cNtxfile := GetOpenFileName(, "*.ntx", "Select an index file")
use (cFile) new shared
if cNtxfile != nil .and. !(".ntx" $ lower(cNtxfile))
// you can't use ntxpos()/ntxrec(), and may well need to be
// using a different RDD (this is a sample, not the browser from hell!)
MessageBox( , "Index ignored - only NTX supported", APP_NAME, MB_ICONSTOP)
cNtxfile := nil
endif
if cNtxfile <> NIL
bOldhandler := errorblock( { | e | bogusindex(e, bOldhandler) } )
dbsetindex(cNtxfile)
//───── verify the validity of this index key
begin sequence
x := eval( &("{ || " + indexkey(0) + "}") )
recover using e
MessageBox(, "Invalid index (missing " + e:operation + " " + ;
if(e:genCode == EG_NOVAR, "field", "") + ;
if(e:genCode == EG_NOFUNC, "function", "") + ;
if(e:genCode == EG_NOALIAS, "alias", "") + ;
")" + CR + "Browsing " + StripPath(cFile) + ;
" in natural order", "Bogus Index", ;
MB_ICONHAND + MB_OK)
dbclearindex()
end sequence
errorblock(bOldhandler)
endif
else
// trying to browse a dbf that's already open
// the following would probably just confuse
//dbSelectArea(StripPath(StripExt(cFile)))
MessageBox( , "Already in use", "Error", MB_ICONHAND + MB_OK)
return nil
endif
x := alias()
hWnd := WinNew("Browsing " + left(x, 1) + lower(substr(x, 2)) + ;
" Database", nX += 20, nY += 20, 525, 200, ;
WS_OVERLAPPEDWINDOW + WS_VSCROLL + WS_HSCROLL)
b := TBrowseDB(0, 0, maxrow(), maxcol())
b:headSep := "─┬─"
b:colSep := " │ "
b:colorSpec := "+w/b, +gr/n, +w/r, +r/w"
nFields := fcount()
#define B_FIELDNAMES 1
#define B_RECNO 2
#define B_ALIAS 3
#define B_CARGO_LEN 3
b:cargo := array(B_CARGO_LEN)
//───── we are going to store an array of fieldnames in the TBrowse
//───── cargo instance variable (to be used when inserting new columns)
b:cargo[B_FIELDNAMES] := array(nFields)
b:cargo[B_RECNO] := recno() // record pointer for this browse window
b:cargo[B_ALIAS] := alias() // needed when editing specific cells
fields_ := dbstruct()
for x := 1 to nFields
b:cargo[B_FIELDNAMES][x] := field(x)
//───── memos must be treated differently than "regular" fields
if fields_[x][DBS_TYPE] == "M"
c := TBColumnNew(b:cargo[B_FIELDNAMES][x], &("{ || if(empty(" + ;
b:cargo[B_FIELDNAMES][x] + "), '<memo>', '<MEMO>') }"))
c:cargo := { || ShowMemo(b) }
else
c := TBColumnNew(b:cargo[B_FIELDNAMES][x], fieldwblock(b:cargo[B_FIELDNAMES][x], select()))
endif
//───── set color block to highlight negative numbers
//───── dumb example, I know, but it will have to do for now...
if valtype(eval(c:block)) == "N"
c:colorBlock := { | x | if( x < 0, { 3, 4 }, { 1, 2 } ) }
endif
b:AddColumn(c)
next
//───── set range of horizontal scrollbar to match # of columns
//───── note that if you later decide to add or delete columns,
//───── you should do this again to set the new range
SetScrollRange(hWnd, SB_HORZ, 1, b:colCount, .f.)
nArea := select()
lFirsttime := .t.
AddHandler(hWnd, {|nEvent| BrowseEvent(nEvent, hWnd, nArea, b, @lFirsttime)})
//InvalidateRect(hWnd)
endif
return nil
/*
Function: BogusIndex()
Purpose: To catch any errors due to invalid indexes
*/
static function bogusindex(oError, bOldhandler)
if oError:genCode == EG_NOFUNC ;
.or. oError:genCode == EG_NOVAR ;
.or. oError:genCode == EG_NOALIAS
break oError
endif
return eval(bOldhandler, oError)
/*
Function: BrowseEvent()
*/
static function BrowseEvent(nEvent, hWnd, nArea, b, lFirsttime)
local nKey
local nColumn
local nWidth
local nTemprow
local nTopclear
local nBottomclear
local x
local hCursor
local hOldcursor
local hOldWnd := SelectWindow(hWnd)
local nScrollcmd
local nNewrec
local nOldrec
local nSkipcnt
local oColumn
local nOldArea := select()
//───── note: these statics should eventually be encapsulated within
//───── the appropriate TBrowse object
static lKeypressed := .f.
static lWait := .f.
static nHPos := 1
static nVPos := 0
//──────────────────────────────────────────────────────────────────
select (nArea)
do case
case nEvent == EVENT_DESTROY .and. used()
close
//───── if the browse window has just regained focus, force a
//───── refresh because it is possible that the user has edited
//───── data on this screen elsewhere (e.g. via "Data Entry" option)
//───── NOTE: could be reworked with the PostMessage() function...
case nEvent == EVENT_SETFOCUS .and. ! lFirsttime
InvalidateRect(hWnd)
//───── move record pointer to previously saved position if necessary
if recno() <> b:cargo[B_RECNO]
dbgoto(b:cargo[B_RECNO])
endif
b:refreshAll()
case nEvent == EVENT_KILLFOCUS
b:cargo[B_RECNO] := recno()
case nEvent == EVENT_REDRAW
if ! lKeypressed
//───── try to determine the minimum number of rows that
//───── must be redisplayed... much faster than refreshAll()!
if ! lFirsttime
nTopclear := _RedrawTop() - 1
//───── if any part of the column header and/or heading
//───── separator were zapped, we must refreshAll()!
if nTopclear < if(! empty(b:headSep), 2, 1)
b:refreshAll()
lWait := .t.
else
nBottomclear := _RedrawBottom() - 1
nTemprow := b:rowPos
for x := nTopclear to nBottomclear
b:rowPos := x
b:refreshCurrent()
next
b:rowPos := nTemprow
endif
else
lFirsttime := .f.
b:refreshAll()
lWait := .t.
endif
else
lKeypressed := .f.
endif
if lWait
//───── a refreshAll() is somewhat time-consuming,
//───── so we will switch to the hourglass cursor
hCursor := LoadCursor(, IDC_WAIT)
hOldcursor := SetCursor(hCursor)
lWait := .f.
endif
do while ! b:stabilize()
enddo
if hOldcursor <> NIL
SetCursor(hOldcursor) // restore previous cursor
endif
UpdateScrollbar(hWnd, b, @nVPos, @nHPos)
case nEvent == EVENT_KEY
nKey := inkey(0)
lKeypressed := .t.
do case
case nKey == K_UP
b:up()
case nKey == K_DOWN
b:down()
case nKey == K_PGUP
b:pageUp()
case nKey == K_PGDN
b:pageDown()
case nKey == K_CTRL_PGUP
b:goTop()
case nKey == K_CTRL_PGDN
b:goBottom()
case nKey == K_LEFT
b:left()
case nKey == K_RIGHT
b:right()
case nKey == K_ENTER
if editcell(b, hWnd)
if b:getColumn(b:colPos):heading $ upper(indexkey(0))
b:refreshAll()
lKeypressed := .f.
lWait := .t.
else
b:refreshCurrent()
endif
endif
case chr(nKey) == "+" // expand current column
oColumn := b:getColumn(b:colPos)
if oColumn:width == NIL
oColumn:width := b:colWidth(b:colPos)
endif
oColumn:width++
b:configure()
case chr(nKey) == "-" // shrink current column
oColumn := b:getColumn(b:colPos)
if oColumn:width == NIL
oColumn:width := b:colWidth(b:colPos)
endif
if oColumn:width > 1
oColumn:width--
b:configure()
endif
case nKey == K_INS // insert new column
if ! empty(x := pickfield(b))
b:insColumn(b:colPos, TBColumnNew(x, fieldblock(x)))
//───── must reset range of horizontal scrollbar to match
//───── new number of columns
SetScrollRange(hWnd, SB_HORZ, 1, b:colCount, .f.)
//───── force horizontal scrollbar display to be updated
//───── in UpdateScrollbar()
nHPos := 0
endif
case nKey == K_DEL // delete current column
b:delColumn(b:colPos)
//───── must reset range of horizontal scrollbar to match
//───── new number of columns
SetScrollRange(hWnd, SB_HORZ, 1, b:colCount, .f.)
//───── force horizontal scrollbar display to be updated
//───── in UpdateScrollbar()
nHPos := 0
case nKey == K_ESC
lKeypressed := .f.
DestroyWindow(hWnd) // close the browse
case nKey == K_ALT_F4
// ignore (will close the browse)
lKeypressed := .f.
otherwise
lKeypressed := .f.
MessageBeep(MB_ICONEXCLAMATION)
endcase
if lKeypressed
do while ! b:stabilize()
enddo
UpdateScrollbar(hWnd, b, @nVPos, @nHPos)
endif
case nEvent == EVENT_WINSIZE
b:nBottom := maxrow()
b:nRight := maxcol()
case nEvent == EVENT_LDBLCLK // treat left double-click as ENTER
if editcell(b, hWnd)
if b:getColumn(b:colPos):heading $ upper(indexkey(0))
b:refreshAll()
lKeypressed := .f.
lWait := .t.
else
b:refreshCurrent()
do while ! b:stabilize()
enddo
endif
else
b:refreshCurrent()
do while ! b:stabilize()
enddo
endif
case nEvent == EVENT_LCLICK
//───── determine number of visible columns (with separators)
nColumn := b:leftVisible - 1
nWidth := 0
do while ++nColumn <= b:rightVisible
nWidth += b:colWidth(nColumn) + 1
enddo
//───── since the TBrowse is always centered, find the left padding
nWidth := int((b:nRight - b:nLeft + 1 - nWidth) / 2)
//───── now locate column in which mouse cursor sits
nColumn := b:leftVisible - 1
do while ++nColumn <= b:colCount .and. ;
(nWidth += b:colWidth(nColumn) + 1) < MouseCol()
enddo
b:refreshCurrent() // get rid of old highlight on previous row
b:colPos := nColumn
//───── the next line is purely temporary and will have to be revamped
//───── because it makes the somewhat dangerous assumption that there
//───── will always be a total of two lines for the column headings and
//───── heading separator
b:rowPos := MouseRow() - if(! empty(b:headSep), 1, 0)
do while ! b:stabilize()
enddo
UpdateScrollbar(hWnd, b, @nVPos, @nHPos)
case nEvent == EVENT_HSCROLL
nScrollCmd = _lastwParam() // from Windows
do case
case nScrollCmd == SB_LEFT
x := 1
case nScrollCmd == SB_RIGHT
x := b:colCount
case nScrollCmd == SB_LINELEFT
x := max(b:colPos - 1, 1)
case nScrollCmd == SB_LINERIGHT
x := min(b:colPos + 1, b:colCount)
case nScrollCmd == SB_PAGELEFT .and. b:leftVisible > 1
b:panLeft()
do while ! b:stabilize()
enddo
x := b:colPos
case nScrollCmd == SB_PAGERIGHT .and. b:rightVisible < b:colCount
b:panRight()
do while ! b:stabilize()
enddo
x := b:colPos
case nScrollCmd == SB_THUMBPOSITION .or. nScrollCmd == SB_THUMBTRACK
// ignore
x := _lastlolParam() // from Windows
case nScrollCmd == SB_ENDSCROLL
x := NIL
endcase
if x <> NIL .and. x <> nHPos
nHPos := x
//───── if the target column position is already on screen,
//───── there is no need to refresh the entire screen
if x >= b:leftVisible .and. x <= b:rightVisible
b:refreshCurrent()
else
b:refreshAll()
endif
b:colPos := x
SetScrollPos(hWnd, SB_HORZ, nHPos, .t.)
do while ! b:stabilize()
enddo
endif
case nEvent == EVENT_VSCROLL
nScrollCmd = _lastwParam()
do case
case nScrollCmd == SB_TOP
x := 0
case nScrollCmd == SB_BOTTOM
x := 100
case nScrollCmd == SB_LINEDOWN
x := min(nVPos + 1, 100)
case nScrollCmd == SB_PAGEDOWN
x := min(nVPos + 10, 100)
case nScrollCmd == SB_LINEUP
x := max(nVPos - 1, 0)
case nScrollCmd == SB_PAGEUP
x := max(nVPos - 10, 0)
case nScrollCmd == SB_THUMBPOSITION .or. nScrollCmd == SB_THUMBTRACK
x := _lastlolParam() // from Windows
case nScrollCmd == SB_ENDSCROLL
x := nVPos
endcase
nVPos := x
//───── this IF test speeds up the "thumb" movement
if nScrollCmd <> SB_THUMBTRACK
SetScrollPos(hWnd, SB_VERT, x, .t.)
//───── update record pointer accordingly (if necessary)
//───── note the use of Ntxrec() if there is an active index
if indexord() == 0 .or. empty(indexkey(0))
nOldrec := recno()
else
nOldrec := ntxpos(indexord(), recno())
endif
nNewrec := int(x * lastrec() / 100)
if nOldrec <> nNewrec
nSkipcnt := nNewrec - nOldrec
if nSkipcnt > 0 // moving downward
//───── see if movement can be made without updating the screen
if nSkipcnt < b:rowCount - b:rowPos + 1
for x := 1 to nSkipcnt
b:down()
next
else
if indexord() == 0 .or. empty(indexkey(0))
dbgoto(nNewrec)
else
dbgoto(ntxrec(indexord(), nNewrec))
endif
b:refreshAll()
endif
else // moving upward
//───── see if movement can be made without updating the screen
if nSkipcnt < b:rowPos + 1
for x := -1 to nSkipcnt step -1
b:up()
next
else
if indexord() == 0 .or. empty(indexkey(0))
dbgoto(nNewrec)
else
dbgoto(ntxrec(indexord(), nNewrec))
endif
b:refreshAll()
endif
endif
do while ! b:stabilize()
enddo
endif
endif
endcase
select (nOldArea)
SelectWindow(hOldWnd)
return nil
/*
Function: ShowMemo()
*/
static function showmemo(b)
MessageBox( , "Showing Memo", ;
b:cargo[B_FIELDNAMES][b:colPos], MB_ICONASTERISK + MB_OK)
return nil
/*
Function: PickField()
*/
static function pickfield(b)
local ret_val
static aDlg
if aDlg == NIL
aDlg := CreateDialog("Available Fields", ;
WS_CAPTION + WS_SYSMENU + WS_GROUP + WS_TABSTOP ;
+ WS_THICKFRAME + WS_VISIBLE + WS_POPUP, ;
100, 30, 100, 100)
aDlg := AppendDialog(aDlg, "listbox", DLG_LISTBOX, ;
LBS_STANDARD + WS_CHILD + WS_VISIBLE + WS_TABSTOP, ;
10, 10, 80, 60, ;
b:cargo[B_FIELDNAMES])
aDlg := AppendDialog(aDlg, "ok", DLG_BUTTON, ;
BS_DEFPUSHBUTTON + WS_TABSTOP + WS_CHILD + WS_VISIBLE,;
10, 75, 35, 15, ;
"&Ok")
aDlg := AppendDialog(aDlg, "cancel", DLG_BUTTON, ;
BS_PUSHBUTTON + WS_TABSTOP + WS_CHILD + WS_VISIBLE, ;
55, 75, 35, 15, ;
"&Cancel")
endif
if ModalDialog(aDlg, _GetInstance(), SelectWindow()) <> 0 .and. ;
! GetDialogResult(aDlg, "cancel")
ret_val := GetDialogResult(aDlg, "listbox")
endif
return ret_val
/*
Function: UpdateScrollBar()
*/
static function UpdateScrollbar(hWnd, b, nVPos, nHPos)
local ele
local nNewpos
//───── determine relative position
if indexord() == 0 .or. empty(indexkey(0))
ele := recno()
else
ele := ntxpos(indexord(), recno())
endif
//───── determine if vertical scrollbar indicator has changed
nNewpos := (ele / lastrec() * 100)
if nVPos <> nNewpos
nVPos := nNewpos
SetScrollPos(hWnd, SB_VERT, nVPos, .t.)
endif
//───── determine if horizontal scrollbar indicator has changed
if nHPos <> b:colPos
nHPos := b:colPos
SetScrollPos(hWnd, SB_HORZ, nHPos, .t.)
endif
return nil
/*
Function: EditCell()
*/
static function editcell(b, hWnd)
local c := b:getColumn(b:colPos)
local oldval := eval(c:block)
if rlock()
ShowCaret(hWnd)
// this is modal because you're changing a record, which should (in general)
// be locked -- so you should not spend too long over it!
// however, it could be modeless with a time-out just by using code like
// that in ValidTest(), and DoTimer()
readmodal( { getnew(Row(), Col(), c:block, ;
c:heading, '@K', b:colorSpec) } )
dbcommit()
dbunlock()
HideCaret(hWnd)
endif
return ( eval(c:block) <> oldval )
/*
Function: WinNew()
*/
static function WinNew(cTitle, nX, nY, nWidth, nHeight, nStyle)
local hInst := _GetInstance()
local nCmdShow := _GetnCmdShow()
local hWin
if nStyle == NIL
nStyle := WS_OVERLAPPEDWINDOW
endif
hWin := CreateWindow(APP_NAME, ; // window class
cTitle, ; // caption for title bar
nStyle, ; // window style
nX, ; // x co-ordinate
nY, ; // y co-ordinate
nWidth, ; // width
nHeight, ; // height
hFrameWnd, ; // hWnd of parent
0, ; // hMenu of menu (none yet)
hInst) // our own app instance
if hWin == 0
// probably out of resources
MessageBox( , "Can't create window", "Error", MB_ICONEXCLAMATION + MB_OK)
return nil
endif
HideCaret(hWin)
// make sure it's displayed ...
ShowWindow(hWin, nCmdShow)
// ... and up to date
UpdateWindow(hWin)
return hWin
/*
Function: ResetMainEvent()
Purpose: Reset event handler for main window (i.e., delete the
primary event handler, which had been changed in DoOpen())
*/
static function ResetMainEvent
local hMenu
DelHandler(nMainEvId)
nMainEvId := AddHandler(hWnd, {|nEvent| MainEvent(nEvent)})
//───── disable the "Clear" and "Cut" menu items
hMenu := GetMenu(hFrameWnd) // retrieve reference to main menu
EnableMenuItem(hMenu, "clear", MF_GRAYED)
EnableMenuItem(hMenu, "cut", MF_GRAYED)
//───── force main window to be redrawn immediately
InvalidateRect(hWnd)
return nil
/*
Function: ValidTest()
*/
static function ValidTest
local cFruit := space(10)
local hWnd := WinNew("Sample Listbox Validation", 0, 0, 300, 100)
local getlist := {}
local nHand
ShowCaret(hWnd)
@ 2,5 say "Type in a fruit" color 'n/w' ;
get cFruit color '+w/b,+w/b' valid FruitLook()
nHand := AddHandler(hWnd, {|nEvent| ValidEvent(GetList, nEvent, hWnd, nHand, cFruit)})
return nil
/*
Procedure: ValidEvent()
*/
static procedure ValidEvent(GetList, nEvent, hWnd, nHand, cFruit)
local hOldWnd
local lDone := (GetList[1] == nil) // only do the read until it finishes
if !lDone
if nEvent == EVENT_REDRAW
hOldWnd = SelectWindow(hWnd)
@ 2,5 say "Type in a fruit" color 'n/w'
SelectWindow(hOldWnd)
endif
if ReadModeless(GetList, nEvent) == NIL
// not yet done
return // wait for next event
endif
endif
// getting here means the read has completed (or been abandoned)
if nEvent == EVENT_DESTROY
// abandoned
return
endif
if !lDone
// get here exactly once
// (immediately after ReadModeless() returns non-NIL)
GetList[1] := nil // cancel the GET
HideCaret(hWnd)
InvalidateRect(hWnd) // trigger a re-draw (below)
endif
do case
case nEvent == EVENT_REDRAW
hOldWnd = SelectWindow(hWnd)
@ 2,5 say "Fruit selected: " + cFruit color '+r/w'
SelectWindow(hOldWnd)
endcase
return
/*
Function: FruitLook()
*/
static function FruitLook
static aChoices := {"Apple", "Banana", "Coconut", "Grape", "Grapefuit", ;
"Lemon", "Lime", "Orange", "Papaya", "Pineapple", ;
"Raisin", "Raspberry", "Strawberry" }
local g := getactive()
local v := upper( g:varGet() )
static aDlg
if ascan(aChoices, { | f | upper(f) = upper(v) } ) == 0
if aDlg == NIL
aDlg := CreateDialog("Fruit List", ;
WS_CAPTION + WS_SYSMENU + WS_GROUP + WS_TABSTOP ;
+ WS_THICKFRAME + WS_VISIBLE + WS_POPUP, ;
100, 30, 100, 100)
aDlg := AppendDialog(aDlg, "listbox", DLG_LISTBOX, ;
LBS_STANDARD + WS_TABSTOP + WS_CHILD + WS_VISIBLE, ;
10, 10, 80, 60, ;
aChoices)
aDlg := AppendDialog(aDlg, "ok", DLG_BUTTON, ;
BS_DEFPUSHBUTTON + WS_TABSTOP + WS_CHILD + WS_VISIBLE,;
10, 75, 35, 15, ;
"&Ok")
aDlg := AppendDialog(aDlg, "cancel", DLG_BUTTON, ;
BS_PUSHBUTTON + WS_TABSTOP + WS_CHILD + WS_VISIBLE, ;
55, 75, 35, 15, ;
"&Cancel")
endif
if ModalDialog(aDlg) <> 0 .and. ! GetDialogResult(aDlg, "cancel")
g:varPut( padr(GetDialogResult(aDlg, "listbox"), len(g:buffer)) )
endif
endif
return .t.
/*
Function: ToggleItem()
Purpose: Demonstrate how to check and uncheck any menu item
*/
static function ToggleItem(cItem)
local hMenu := GetMenu(hFrameWnd) // retrieve reference to main menu
local nCheck := CheckMenuItem(hMenu, cItem)
CheckMenuItem(hMenu, cItem, if(nCheck == MF_CHECKED, MF_UNCHECKED, MF_CHECKED))
return nil
//───── end of file DEMO.PRG