Extended Messagebox() function reviewed

Updated version - now without the need of external FLL

This is just an update for the original article. Originally this function needed Craig Boyd’s VFPEX.FLL to be able to update the Messagebox dialog. In this new version, I’m using VFP9 BINDEVENTS function, to bind to Windows events directly.

MSGBOXEX() is a simple function that allows us to customize the captions of buttons of the MESSAGEBOX(), dialog window, like in the images shown below:

lnOption = MsgboxEx( ;
   "You have entered a wrong password for 5 times.
" + chr(13) + ;
   "For security reasons this login was suspended.", ; && main message
   0, ; && default icon info
   "Password incorrect", ; && title bar caption
   "\&Retry,&Exit,Get &new pwd", ; && new button captions
   "41.ico") && icon file

lnOption = MsgboxEx(;
   "An unexpected error has occurred and the system needs to be restarted." + ;
   chr(13) + chr(13) + "What do you want to do ?", ;
   "X", ;
   "MsgboxEx sample", ;
   "Restart &Now,Restart &later,&Never restart")

lnOption = MsgboxEx( ;
   "Could not find the file 'Import.csv' in the selected folder.", ;
   0, ;
   "File not found", ;
   "&Abort,\&Retry,Change folder", ;

function: MsgboxEx.prg

description: Modifies the captions of messagebox buttons


  • tcCaption - the text that appears in the dialog box.

  • tnIcon - the icon sign - can be numeric, using the default messagebox() icon values or character:
       - stop sign: 16 or "x"
       - question mark: 32 or "?"
       - exclamation point: 48 or "!"
       - information: 64 or "i"

  • tcTitle - the text that appears in the title bar of the dialog box

  • tcButtons - the captions to be used in the buttons using the comma "," delimiter
    use the "&" character to determine the hotkeys to be used - eg: "option&1,option&2,option&3" - the "\" backslash can be used to show the current button disabled.

  • tcIconFile - (optional) the icon image file

returns: The index number according to the option selected - eg. returns the value 3 if the 3rd button was selected.

use the same dialog interface from the messagebox() function, allowing us to easilly customize existing dialog boxes.

almost the same structure of parameters of the original function

behaves correctly in all operating systems.

allows hotkeys (thanks to thiago takehana for reminding about this possibility)

up to 3 buttons are allowed

size of buttons is limited to the original messagebox() buttons size, not allowing big sized buttons.

LPARAMETERS tcCaption, tnIcon, tcTitle, tcButtons, tcIconFile
* msgboxex.prg
* description: modifies the Captions of messagebox Buttons
* Parameters:
* tcCaption - the text that appears in the dialog box.
* tnIcon - the Icon sign
* tcTitle - the text that appears in the Title bar of the dialog box
* tcButtons - the Captions to be used in the Buttons using the comma "," delimiter
* use the "&" character to determine the hotkeys to be used - eg: "option&1,option&2,option&3"
* use a "\" to disable the Button
* tcIconFile - the Icon File to replace the default from messagebox()
* returns: the index number according to the option selected - eg. returns the value 3 if the 3rd Button was selected.
* sample:
* =MsgBoxEx("This is a common text", "!", "Window Title", "Option 1,Option 2,Option 3")

* Special thanks to:
* Herman Tan - Article: 'Centering VFP MessageBox in any Form'
* http://hermantan.blogspot.com/2008/07/centering-vfp-messagebox-in-any-form.html
* Craig boyd - Article: 'BindEvents on Steroids'
* http://www.sweetpotatosoftware.com/spsblog/2005/08/07/bindeventonsteroids.aspx

LOCAL loMsgB, lnOption
loMsgB = CREATEOBJECT("xmbMsgBoxEx")
lnOption = loMsgB.SendMessage(tcCaption, tnIcon, tcTitle, tcButtons, tcIconFile)
loMsgB = NULL
RETURN lnOption

    nButtonCnt = 0
    cButtons   = ""
    nbutttype  = 0
    cIconFile  = ""
    hIcon      = 0

    PROCEDURE SendMessage
        LPARAMETERS tcCaption, tnIcon, tcTitle, tcButtons, tcIconFile
        IF VARTYPE(tntimeout) = "C" AND (PCOUNT() = 4)
            tcButtons = tntimeout
            tntimeout = 0

        PRIVATE pnButtonCnt, pcButtons, pnbutttype, pcIconFile, phIcon
        This.cIconFile = IIF(EMPTY(tcIconFile),"", tcIconFile)
        This.nButtonCnt = GETWORDCOUNT(tcButtons, ",")
        This.cButtons = tcButtons
        *!* stop 16
        *!* question 32
        *!* exclamation 48
        *!* info 64
        IF (NOT EMPTY(m.tcIconFile)) OR INLIST(TRANSFORM(tnIcon), "X", "?", "!", "I")
         IF VARTYPE(tnIcon) = "C"
             tnIcon = UPPER(tnIcon)
             DO CASE
                 CASE tnIcon = "X"
                     tnIcon = 16
                 CASE tnIcon = "?"
                     tnIcon = 32
                 CASE tnIcon = "!"
                     tnIcon = 48
                 CASE tnIcon = "I"
                     tnIcon = 64
                     tnIcon = 0
   tnIcon = 0
        * check if an Icon will be shown
        * if an Icon File was passed, we need to ensure that messagebox() will
        * show an Icon, that will be changed further.

        #DEFINE image_bitmap 0
        #DEFINE image_Icon 1
        #DEFINE lr_loadfromFile 0x0010
        #DEFINE lr_defaultsize 0x0040
        This.hIcon = 0
        IF NOT EMPTY(This.cIconFile) AND ;
                (NOT (BITTEST(tnIcon, 4) OR BITTEST(tnIcon, 5) OR BITTEST(tnIcon, 6)))
            tnIcon = tnIcon + 16
            This.hIcon = xmbLoadImage(0, FULLPATH(This.cIconFile), image_Icon,;
                0,0, lr_loadfromFile + lr_defaultsize)

        * this messagebox will be modified before it is shown
        LOCAL lnoption, lnIndex
        DO CASE
            CASE This.nButtonCnt = 1
                This.nbutttype = 0 && ok
            CASE This.nButtonCnt = 2
                This.nbutttype = 4 && yes / no
            CASE This.nButtonCnt = 3
                This.nbutttype = 2 && abort / retry / ignore

        BINDEVENT( 0, 0x06, THIS, 'WndProc' )
        lnoption = MESSAGEBOX(tcCaption, tnIcon + This.nbutttype, tcTitle)
        UNBINDEVENTS( 0, 0x06 )

        LOCAL lnOffset
        lnOffset = ICASE(This.nButtonCnt = 3, 2, This.nButtonCnt = 2, 5 , 0)
        lnIndex = lnoption - lnOffset

        IF This.hIcon <> 0
            =xmbdeleteobject(This.hIcon) && clear Icon handle

        RETURN lnIndex


    * Windows event handler procedure
    * MSDN WindowProc callback function
    * http://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
    * http://hermantan.blogspot.com/2008/07/centering-vfp-messagebox-in-any-form.html
    * Here we will make all the modifications in the Windows dialog
    PROCEDURE WndProc( th_Wnd, tn_Msg, t_wParam, t_lParam )

        IF (tn_Msg == 0x06) AND (t_wParam == 0) AND (t_lParam <> 0)

            wParam = t_lParam

            #DEFINE dlg_ctrlid_Icon 0x0014
            #DEFINE stm_setIcon 0x0170
            #DEFINE stm_setimage 0x0172
            IF NOT EMPTY(This.hIcon)
                * changing the dialog Icon
                LOCAL lhIconwindow
                lhIconwindow = xmbGetDlgItem(wParam, dlg_ctrlid_Icon)
                IF lhIconwindow <> 0
                    IF This.hIcon <> 0
                        =xmbSendMessage(lhIconwindow, stm_setIcon, This.hIcon, 0)

            * Set tansparency
            IF VARTYPE(_Screen.xmbMessageboxTransp) = "N"
               LOCAL lnTransp
               lnTransp = _Screen.xmbMessageboxTransp
               IF lnTransp > 30 AND lnTransp < 255 && values lower than 30 generate an almost invisible dialog!!!
                    lnTransp = MIN(INT(lnTransp), 254)
                    =xmbSetWindowLong( wParam, -20, ;
                        BITOR( xmbGetWindowLong( wParam, -20 ), 0x80000 ))
                    =xmbSetLayeredWindowAttributes( wParam, 0, lnTransp, 2 )

            * change Button attributes
            LOCAL N, lnOffset, lcCaption
            lnOffset = ICASE(This.nButtonCnt = 3, 2, This.nButtonCnt = 2, 5 , 0)
            LOCAL lnBtnhWnd
            FOR N = 1 TO This.nButtonCnt
                lcCaption = GETWORDNUM(This.cButtons, N, ",") + CHR(0)
                * disable current Button
                IF LEFT(lcCaption, 1) = "\"
                    lcCaption = SUBSTR(lcCaption, 2) && get the rest of the string
                    lnBtnhWnd = xmbGetDlgItem(wParam, lnOffset + N)
                    =xmbEnableWindow(lnBtnhWnd, 0)

                * change the Caption
                =xmbSetDlgItemtext(wParam, lnOffset + N, lcCaption)


        LOCAL pOrgProc
        pOrgProc = xmbGetWindowLong( _VFP.hWnd, -4 )
        = xmbCallWindowProc( pOrgProc, th_Wnd, tn_Msg, t_wParam, t_lParam )


    FUNCTION xmbSetDlgItemtext(hdlg, nidDlgItem, lpString)
        DECLARE INTEGER SetDlgItemText IN user32 AS xmbsetDlgItemtext ;
            LONG hdlg,;
            LONG nidDlgItem,;
            STRING lpString
        RETURN xmbSetDlgItemtext(hdlg, nidDlgItem, lpString)

    FUNCTION xmbCallNextHookEx(hhook, ncode, wParam, LParam)
        DECLARE LONG callnexthookex IN user32 AS xmbcallnexthookex ;
            LONG hhook, LONG ncode, LONG wParam, LONG LParam
        RETURN xmbcallnexthookex(hhook, ncode, wParam, LParam)

    FUNCTION xmbGetDlgItem(hdlg, nidDlgItem)
        * hdlg in handle to the dialog box that contains the control.
        * nidDlgItem in specifies the identifier of the control to be retrieved
        * http://msdn.microsoft.com/en-us/library/ms645481(vs.85).aspx
        DECLARE INTEGER GetDlgItem IN user32 AS xmbgetDlgItem ;
            LONG hdlg,;
            LONG nidDlgItem
        RETURN xmbGetDlgItem(hdlg, nidDlgItem)

    FUNCTION xmbEnableWindow(hWnd, fEnable)
        DECLARE INTEGER EnableWindow IN user32 AS xmbEnablewindow INTEGER hWnd, INTEGER fEnable
        RETURN xmbEnableWindow(hWnd, fEnable)

    FUNCTION xmbSendMessage(hwindow, msg, wParam, LParam)
        * http://msdn.microsoft.com/en-us/library/bb760780(vs.85).aspx
        * http://www.news2news.com/vfp/?group=-1&function=312
        DECLARE INTEGER SendMessage IN user32 AS xmbsendmessage;
            INTEGER hwindow, INTEGER msg,;
            INTEGER wParam, INTEGER LParam
        RETURN xmbSendMessage(hwindow, msg, wParam, LParam)

    FUNCTION xmbLoadImage(hinst, lpszname, utype, cxdesired, cydesired, fuload)
        DECLARE INTEGER LoadImage IN user32 AS xmbloadimage;
            INTEGER hinst,;
            STRING lpszname,;
            INTEGER utype,;
            INTEGER cxdesired,;
            INTEGER cydesired,;
            INTEGER fuload
        RETURN xmbLoadImage(hinst, lpszname, uType, cxdesired, cydesired, fuload)

    FUNCTION xmbDeleteObject(hobject)
        DECLARE INTEGER DeleteObject IN gdi32 AS xmbdeleteobject INTEGER hobject
        RETURN xmbdeleteobject(hobject)

    FUNCTION xmbCallWindowProc(lpPrevWndFunc, nhWnd, uMsg, wParam, LParam)
        DECLARE LONG CallWindowProc IN User32 ;
            AS xmbCallWindowProc ;
            LONG lpPrevWndFunc, LONG nhWnd, ;
            LONG uMsg, LONG wParam, LONG LParam

        RETURN xmbCallWindowProc(lpPrevWndFunc, nhWnd, uMsg, wParam, LParam)

    FUNCTION xmbGetWindowLong(nhWnd, nIndex)
        DECLARE LONG GetWindowLong IN User32 ;
            AS xmbGetWindowLong ;
            LONG nhWnd, INTEGER nIndex

        RETURN xmbGetWindowLong(nhWnd, nIndex)

    FUNCTION xmbSetWindowLong(nHWnd, nIndex, nNewVal)
        DECLARE INTEGER SetWindowLong In Win32Api ; 
            AS xmbSetWindowLong ;
            INTEGER nHWnd, INTEGER nIndex, INTEGER nNewVal
        RETURN xmbSetWindowLong(nHWnd, nIndex, nNewVal)

    FUNCTION xmbSetLayeredWindowAttributes(nHWnd, cColorKey, nOpacity, nFlags)
        DECLARE INTEGER SetLayeredWindowAttributes In Win32Api ; 
            AS xmbSetLayeredWindowAttributes ;
            INTEGER nHWnd, STRING cColorKey, ; 
            INTEGER nOpacity, INTEGER nFlags
        RETURN xmbSetLayeredWindowAttributes(nHWnd, cColorKey, nOpacity, nFlags)

2009-10-19 Extended Messagebox function original version (change captions)
2009-10-23 Extended Messagebox function updated, allowing icon change and disabling buttons
2012-01-30 Updated, no need of external FLL library

2014-03-14 Updated, fix that was not showing the custom icon in some situations


  1. Thanks Cesar, good job.

    Spanish Translation / Traducción al español

    -- Función MessageBox() Extendida - Revisada --

  2. Any chance you will be extending this to have more than 3 buttons? I use Doug Hennig's Getchoic function in those cases. But it is from 1994, so needs a little update and your function here would be a great replacement if it did any number of buttons.

    Other than that, this looks great! Thank you!


  3. Thanks Mathew,
    In fact, I am not interested in enhancing this function. This is meant to work with simple dialogs only.
    If you need more options, I recommend that you simply create your own dialog form !

  4. Muchas gracias Luis Maria!

  5. Using the extended messagebox function with Craig Boyd’s VFPEX.FLL was a help under "XP Professional", until I found out that it dosn't work under "XP 4POS" (a downgraded American bastard version, trimmed to be used in Germany). So I was very happy to find the same tool that uses a native VFP9 function without the need of external FLL help.
    Now it works fine. Greetings + many thanks: Gerhard

  6. Thanks Gerhard,
    I'm glad to know it was helpful :-D