Years ago when I still use VFP6, I used Timer() object, to re-positioning VFP MessageBox, but it's quite an ugly solution, especially on a slow computer. I can still see the window moving right before my eyes. So the best solution is to use FLL/DLL and use windows hook.
Until recently I still see so many VFP developer out there, still questioning the same old question. Such as re-position the MessageBox, changing the Button caption, etc. For some people it's simply create your own custom form and design the form to look similar with MessageBox. Or you can even design a much more nice looking dialog. However there are some behavior that make it different with MessageBox behavior. So again, FLL/DLL is the best solution.
Now, I'm not going to talk about making the FLL. There are several FLL already exist out there for this purposes. You can find it in UniversalThread download area, or goto Craig Boyd homepage. But here, I will show you how to customize VFP MessageBox using BINDEVENT(). This solution is quite nice actually :)
Let's dig into Windows Messages first. You can use a tools such SPY++ to discover this message. Notice that everytime the MessageBox is called, there is a WM_ACTIVATE message sent to VFP. This message is sent twice, the first one is to let VFP aware, that VFP is about to be deactivated, WPARAM = 0. This is our best chance. So you can Bind the WM_ACTIVATE and look for WParam equal to 0. One more thing is, the HWND parameter is actually the VFP.HWND (or Form.hWnd). But the LPARAM contained the HWND to the new window which just about to be activated. Now we can wrap this up. The class can center other dialog too, such as InputBox(). Just try and experiment with the class. Enjoy!
[CODE]
Local lo_MsgBox
lo_MsgBox = CreateObject( 'cls_MessageBox' )
?lo_MsgBox.ShowMsg( 'Test MessageBox', 64, 'MessageBox Title' )
lo_MsgBox.lChangeButton = .T. && Change MessageBox Button
lo_MsgBox.aButtons[1] = '&Good' && 1st button
?lo_MsgBox.ShowMsg( 'Test MessageBox', 64, 'MessageBox Title' )
**** Uncomment the line below and change myForm to any form you wanted the MessageBox to display
** lo_MsgBox.hWnd = myForm.hWnd && center MessageBox in myForm
lo_MsgBox.lTransparent = .T. && transparent MessageBox
lo_MsgBox.nTransValue = 85 && 85% transparent
lo_MsgBox.aButtons[2] = '&Bad' && 2nd button
lo_MsgBox.aButtons[3] = '&Worst' && 3rd button
?lo_MsgBox.ShowMsg( 'Test MessageBox', 64+2, 'MessageBox Title' )
?lo_MsgBox.ShowMsg( 'Test MessageBox With Timer (5sec)', 64+2, 'MessageBox Title', 5000 )
?lo_MsgBox.ShowMsg( 'Test MessageBox with TimeOut (5sec)', 273, 'MessageBox Title', 5000 )
lo_MsgBox = Null
Release lo_MsgBox*************************
*** Updated : timer is working correctly
*************************
Define class cls_MessageBox as Custom
#Define WM_TIMER 0x0113
#Define IDT_TIMER 1
hWnd = 0
hWnd_MsgBox = 0
pOrgProc = 0
lChangeButton = .F.
lTransparent = .F.
nTimeout = 0
lTimeout = .F.
nTransValue = 100 && in percentage, 100% = opaque
Dimension aButtons[3] = .F.
Procedure Init
Declare Long SetLayeredWindowAttributes in User32 ;
Long nhWnd, Long crKey, Short bAlpha, Long dwFlags
Declare Long GetDesktopWindow in User32
Declare Long GetWindowLong in User32 ;
Long nhWnd, Integer nIndex
Declare Long SetWindowLong in User32 ;
Long nhWnd, Integer nIndex, Long dwNewLong
Declare Long GetWindowRect in User32 ;
Long nhWnd, String @O_lpRect
Declare Long SetWindowPos in User32 ;
Long nhWnd, Long hWndInsertAfter, ;
Integer nX, Integer nY, Integer nWidth, Integer nHeight, Long nFlags
Declare Long CallWindowProc in User32 ;
Long lpPrevWndFunc, Long nhWnd, ;
Long uMsg, Long wParam, Long lParam
Declare Long FindWindowEx in User32 ;
Long hWndParent, Long hWndChildAfter, ;
String lpszClass, String lpszWindow
Declare Long SendMessage in User32 as SendMessageStr ;
Long nhWnd, Long uMsg, Long wParam, String @lParam
Declare Long SetTimer in User32 ;
Long nhWnd, Long nEventId, Long uElapse, Long lpTimerFunc
Declare Long KillTimer in User32 Long nhWnd, Long nEventId
This.hWnd = iif( (VarType( th_Wnd )== 'N'), ;
iif( th_Wnd != 0, th_Wnd, GetDesktopWindow() ), _VFP.hWnd )
This.pOrgProc = GetWindowLong( _VFP.hWnd, -4 )
EndProc
Procedure ShowMsg( tc_Msg, tn_Type, tc_Title, tn_Timeout )
Local ln_Return
If (varType( tn_Timeout ) == 'N')
This.nTimeout = iif( tn_Timeout < 1000, 1000, tn_Timeout )
endif
BindEvent( 0, 0x06, This, 'WndProc' )
If (VarType( tc_Title ) == 'C')
ln_Return = MessageBox( tc_Msg, tn_Type, tc_Title )
else
ln_Return = MessageBox( tc_Msg, tn_Type )
endif
UnBindEvents( 0, 0x06 )
If (This.nTimeout > 0)
If ( This.lTimeout )
ln_Return = -1
KillTimer( This.hWnd_MsgBox, IDT_TIMER )
UnBindEvents( This.hWnd_MsgBox, IDT_TIMER )
else
KillTimer( This.hWnd, IDT_TIMER )
UnBindEvents( This.hWnd, IDT_TIMER )
endif
endif
This.lTimeout = .F.
Return ln_Return
EndProc
Procedure CenterWindow( th_WndParent, th_WndChild )
Local ls_Rect
ls_Rect = space( 16 )
** Get container area (parent)
GetWindowRect( th_WndParent, @ls_Rect )
ln_TargetLeft = CToBin( substr( ls_Rect, 1, 4 ), '4rs' )
ln_TargetTop = CToBin( substr( ls_Rect, 5, 4 ), '4rs' )
ln_Right = CToBin( substr( ls_Rect, 9, 4 ), '4rs' ) + 1
ln_Bottom = CToBin( substr( ls_Rect, 13, 4 ), '4rs' ) + 1
ln_Width = ln_Right - ln_TargetLeft
ln_Height = ln_Bottom - ln_TargetTop
** Get contained area (child)
GetWindowRect( th_WndChild, @ls_Rect )
ln_Left = CToBin( substr( ls_Rect, 1, 4 ), '4rs' )
ln_Top = CToBin( substr( ls_Rect, 5, 4 ), '4rs' )
ln_Right = CToBin( substr( ls_Rect, 9, 4 ), '4rs' ) + 1
ln_Bottom = CToBin( substr( ls_Rect, 13, 4 ), '4rs' ) + 1
** Get Left & Top position (XY coordinate)
ln_Left = ((ln_Width - (ln_Right - ln_Left)) / 2) + ln_TargetLeft
ln_Top = (ln_Height - (ln_Bottom - ln_Top)) / 2 + ln_TargetTop
SetWindowPos( th_WndChild, 0, ln_Left,ln_Top, 0,0, BitOr( 0x1, 0x10, 0x400 ))
EndProc
Procedure WndProc( th_Wnd, tn_Msg, t_wParam, t_lParam )
If (tn_Msg == 0x06) and (t_wParam == 0)
Local ln_X, lh_Wnd, lh_WndChild, ln_OldStyle, ln_Transparent
If (This.nTimeout > 0)
This.hWnd_MsgBox = t_lParam
BindEvent( th_Wnd, WM_TIMER, This, 'TimerProc' )
SetTimer( th_Wnd, IDT_TIMER, This.nTimeOut-60, 0 )
This.nTimeout = 200
endif
.CenterWindow( .hWnd, t_lParam )
If ( .lChangeButton )
lh_WndChild = 0
For ln_X = 1 to 3
lh_WndChild = FindWindowEx( t_lParam, lh_WndChild, 'Button', 0 )
If (lh_WndChild == 0)
ln_X = 4
else
If !empty( .aButtons[ ln_X ] )
SendMessageStr( lh_WndChild, 0x0C, 0, .aButtons[ ln_X ] )
endif
endif
Next
endif
If ( .lTransparent ) and (.nTransValue > 0)
ln_Transparent = int((255 * This.nTransValue) / 100)
SetWindowLong( t_lParam, -20, ;
BitOr( GetWindowLong( t_lParam, -20 ), 0x80000 ))
SetLayeredWindowAttributes( t_lParam, 0, ln_Transparent, 2 )
endif
EndWith
Return 0
endif
Return CallWindowProc( This.pOrgProc, th_Wnd, tn_Msg, t_wParam, t_lParam )
EndProc
Procedure TimerProc( th_Wnd, tn_Msg, t_wParam, t_lParam )
KillTimer( th_Wnd, IDT_TIMER)
UnBindEvents( th_Wnd, IDT_Timer )
SetTimer( This.hWnd_MsgBox, IDT_TIMER, This.nTimeout, 0 )
This.lTimeout = .T.
Return 0
EndProc
Procedure Destroy
Clear DLLs
EndProc
EndDefine
[/CODE]