TxtMessagebox

  A command line interface (CLI), fixed pitch (mono-spaced) messagebox to display numeric information. Proportional fonts are not good for columns of numbers because the characters have different widths. This makes a column "wiggle". Used with TXT.WINDOW, GRAPHIC WINDOW or console the CLI would be consistant. It can be compiled in PBCC 6 or PBWin 10. It is named TxtMessagebox because it uses a TXT.WINDOW instead of a DIALOG.

The project started when a user on the PowerBASIC forum requested an output that needed right justification and fixed pitch font. Another user then requested a messagebox that to use with PBCC. The API Messagebox function can be called with PBCC code but is not designed to easily change fonts. In (at: post 20 (new tab or window) I posted a crude messagebox for PBCC. This replaces that and has improvements.

    Features -
  1. The width adjusts so caption, message line(s) and exit labels will fit. The quantity of exit choices is limited to three, the same as MSGBOX or API Messagebox.
  2. Height is set by number of message lines and exit labels.
  3. Exit choices (labels ) set by you, not styles. This also means styles for default choice are not needed. It is not limited to OK, YesNo, YesNoCancel, etcetera; you name the items in the exit list.
    Downside -
  1. No icons. Though Unicode symbol characters (new tab or window), for example, may be embedded in the message string. (note: characters having hexadecimal values of U+1Fxxx are 17 bit, and can not be used.)
  2. Must have PBWin 10 or PBCC 6 to compile. See "Download" below if you have older PB or a compiler for a different language.
  3. (Can't think of another right now.☻)

PB user Stuart McLachlan created a GUI messagebox (new tab or window) in response to the same forum thread. For users having only PBCC (or another language) he compiled it to DLL (new tab or window). It has selectable font so columns of numbers will line up. Being GUI, its IO style is not consistant with console, TXT.WINDOW or GRAPHIC WINDOW.

Download.

Download compiled TxtMessagebox.zip. Contains TxtMessageBox function compiled in both a DLL and a SLL, and an #INCLUDE file with sample DECLAREs and #LINK.

Parameters

The caption string, message string and position of window called from are not optional. The Message string is not "wrapped" by TxtMessageBox function, you must embed $$CRLFs for reasonable box width and multiple lines. The function does put a blank line between last message line and first exit choice line.

TMBCaption
  (Text MessageBox Caption)
A single line string. Embedded CRLFs are ignored.
String length used for minimum messagebox width. There is no checking that string length forces messagebox width beyond screen edge.
TMBMessage
  (Text MessageBox Message)
Embed CRLF for multiple lines.
Message box width increased as needed for longest line.
The number of lines, plus one blank line, are part of the messagebox height.
ParentPosX, ParentPosY
   (Parent Positions X and Y)
Sets the on screen position , in pixels, of the messagebox. The horizontal position X has 30 added to it, and the verticle position Y has 80 added to it. "Parent" here means the console, graphic, text or dialog the messagebox will appear "over" (not necessarily the same relationship as hParent to hDlg).
By using functions CON.LOC.X, CON.LOC.Y or GRAPHIC(LOC.X), GRAPHIC(LOC.Y) in the TxtMessagebox call it will have a consistant offset location.
DIALOG GET LOC does not have a function form, so two variables are needed. There is no "LOC" for text window, use location of "parent" TXT.WINDOW in TxtMessagebox call.
Opt1Str, Opt2Str, Opt3Str
  (First through third Optional exit menu item Strings.)
The default (if none of these are used) is the <Enter> key.
Each exit menu item is a single line string. Embedded CRLFs are ignored.
Messagebox width will increase if needed for item length.
Messagebox increased by one line for each item.
Return LONG
On default exit or if Opt1Str is used alone = 0
On Opt1Str exit if Opt2Str, or Opt2Str and Opt3Str are used = 1
On Opt2Str exit = 2
On Opt3Str exit = 3

Alternate Uses

If %Demo = 0 the source code may be used as a #INCLUDE "filename"

To compile to SLL:
Change #COMPILE line, and add Descriptor "COMMON" as shown in bold below.
#compile sll "TxtMessagebox.sll"
' . . .
function TxtMessageBox (TxtCaption as wstring, _
                        TMBMessage as wstring, _
                  byval ParentPosX as long, _
                  byval ParentPosY as long, _
                    opt Opt1Str as wstring, _
                    opt Opt2Str as wstring, _
                    opt Opt3Str as wstring) COMMON as long 

To compile as DLL:
Change #COMPILE line, and add ALIAS "alias name" and Descriptor "EXPORT" as shown in bold below.
#compile dll "TxtMessagebox.dll"
' . . . 
function TxtMessageBox ALIAS "TxtMessageBox" _
                       (TMBCaption as wstring, _
                        TMBMessage as wstring, _
                  byval ParentPosX as long, _
                  byval ParentPosY as long, _
                    opt Opt1Str as wstring, _
                    opt Opt2Str as wstring, _
                    opt Opt3Str as wstring) EXPORT as long 


Source Code

Full copyleft (ɔ), 2023 by Dale Yarker in source or compiled form. Complete license in new tab or window.

The Text Message Box Function and PBCC test/demo.

When the constant %Demo is undefined, or set to "0", the whole file may be used as a #INCLUDE. The PBMAIN function will have no effect on operation or size of the compiled code. If %Demo is set to "1" the file compiles to a console executable that tests with no optional exit key, one, two and three optional exit keys. It also uses different numbers of lines in the message boxes.

With changes discussed above the TXTMessageBox can be compiled to SLL or DLL (with %Demo undefined or "0"). Or, just copy and paste the function where you need it.

' File TxtMessagebox.bas
'Full copyleft chr$$("(",&h0254,")"), 2023 Dale Yarker.
' (lower case open "o" with not display in either PB editor)
'########################### mono-spaced messagebox ############################
#compile exe
#dim all
%UNICODE = 1
%Demo = 0 'Non-zero to also compile for demonstration in PBCC 6.
declare function SetForegroundWindow lib "User32.dll" _
    alias "SetForegroundWindow" (byval hWnd as dword) as long
declare function GetParent lib "User32.dll" alias "GetParent" _
    (byval hWnd as dword) as dword
function TMBMessageBox (TMBCaption as wstring, _
                        TMBMessage as wstring, _
                  byval ParentPosX as long, _
                  byval ParentPosY as long, _
                    opt Opt1Str as wstring, _
                    opt Opt2Str as wstring, _
                    opt Opt3Str as wstring) as long
  local hTWin as dword
  local NumOfLines, Counter, TmpLen, MaxOpt as long
  local A_Key, TmpStr(), Item1, Item2, Item3, MatchStr as wstring
  '
  NumOfLines = parsecount(TMBMessage, $$crlf)
  dim TmpStr(1 to NumOfLines)
  TmpLen = len(TMBCaption)  + 2
  for Counter = 1 to NumOfLines
    TmpStr(Counter) = $$spc + parse$(TMBMessage, $$crlf, Counter)
    if len(TmpStr(Counter)) > TmpLen then
      TmpLen = len(TmpStr(Counter))
    end if
  next
  if varptr(Opt1Str) = 0 then '· · · · · · · · · · · · · · No optional items · ·
    Item1 = "  Press "
    if len(Item1) > TmpLen then
      TmpLen = len(Item1) + 1
    end if
    MaxOpt = 2 'set 1 greater than number of optionals used so ...
  else
    if varptr(Opt2Str) = 0 then '· · · · · · · · · · · · · · 1 optional item · ·
      Item1 = "  " + Opt1Str
      if len(Item1) > TmpLen then
        TmpLen = len(Item1) + 1
      end if
      MaxOpt = 2 'that it can be added to NumOfLines in ...
    else ' · · · · · · · · · · · · · · · · · · · · · · · ·  2 optional items · ·
      Item1 = "  Press 1 for " + Opt1Str
      if len(Item1) > TmpLen then
        TmpLen = len(Item1) + 1
      end if
      Item2 = "  Press 2 for " + Opt2Str
      if len(Item2) > TmpLen then
        TmpLen = len(Item2) + 1
      end if
      MaxOpt = 3 'TXT.WINDOW without a "+ 1" for the height.
      if varptr(Opt3Str) then '· · · · · · · · · · · · · ·  3 optional items · ·
        Item3 = "  Press 3 for " + Opt3Str
        if len(Item3) > TmpLen then
          TmpLen = len(Item3) + 1
        end if
        MaxOpt = 4
      end if
    end if
  end if                      '
  'Create the TXT.WINDOW
  txt.window (TMBCaption, ParentPosX + 30, ParentPosY + 80, _
                          NumOfLines + MaxOpt, TmpLen + 1) to hTWin
  SetForegroundWindow(GetParent(hTWin)) 'Thanx Borje and Dave
  'Show the message in the TXT.WINDOW.
  for Counter = 1 to NumOfLines
    txt.print TmpStr(Counter)
  next
  'Show exit choices.
  txt.print
  txt.print Item1;
  if MaxOpt > 2 then
    txt.print : txt.print Item2;
  end if
  if MaxOpt > 3 then
    txt.print : txt.print Item3;
  end if
  'Process exit choice.
  if MaxOpt = 2 then '(replaces larger amount of code inside the DO/LOOP)
    MatchStr = chr$$(13)
  elseif MaxOpt = 3 then
    MatchStr = "12"
  elseif MaxOpt = 4 then
    MatchStr = "123"
  end if
  do
    A_Key = txt.waitkey$
    if instr(A_Key, any MatchStr) then
      function = val(A_Key)
      exit do
    end if
  loop
  txt.end
end function
'======= brute force to demo the messagebox with right justified numbers =======
#if %Demo
function pbmain () as long
  local MyNumber as ext
  local MyMessage, dummy as wstring
  local LenLbl, X as long
  con.print "Right justification in messagebox with default exit key."
  con.print "(needs mono-spaced (fixed pitch) font to ""line-up"" columns.)"
  con.print "Press any key to show messagebox:"
  con.waitkey$
  MyMessage = "Formatted number entry: "
  LenLbl = len(MyMessage)
  MyNumber = 160000.65##
  MyMessage += rset$(format$(myNumber,"$ #,#0.00"), 18) + $$crlf
  MyNumber = 16000.65##
  MyMessage += rset$(format$(myNumber,"$ #,#0.00"), lenLbl + 18) + $$crlf
  MyNumber = 9876543210.21##
  MyMessage += rset$(format$(myNumber,"$ #,#0.00"), lenLbl + 18)
  '
  X = TMBMessageBox("Show right justified in a messagebox.", MyMessage, _
     con.loc.x, con.loc.y)
  con.print "Returned value is: " + dec$(X)
  '
  con.print
  con.print "Single choice nondefault exit."
  con.print "(note auto-size.)
  con.print "Press any key to show messagebox:"
  con.waitkey$
  '
  MyMessage = "Five line message, line 1." + $$crlf + "Line 2." + $$crlf + _
              "Line 3." + $$crlf + "Line 4." + $$crlf + "Line 5."
  X = TMBMessageBox( "Show one optional menu item.", MyMessage, _
     con.loc.x, con.loc.y, "OK")
  con.print "Returned value is: " + dec$(X)
  '
  con.print
  con.print "Two choices to exit, choice number returned."
  con.print "Press any key to show messagebox:"
  con.waitkey$
  MyMessage = "The water is cold." + $$crlf + "Do you want to swim?"
  X = TMBMessageBox("Swim Decision", MyMessage, con.loc.x, con.loc.y, _
     "Yes, cold or not.", "No, I'm sick.")
  select case as long X
    case 1
      con.print "Get your suit. No skinny dipping here."
    case 2
      con.print "Go home, you're contagious!"
  end select
  con.print
  con.print "Three exit choices after a long line."
  con.print "Press any key to show messagebox:"
  con.waitkey$
  MyMessage = "A Llooonnnnggggg line, abc defg hijkl mnopqrs tuvwxyz 0123456789"
  X = TMBMessageBox("Set your own menu items.", MyMessage, con.loc.x, _
     con.loc.y, "ABORT", "RETRY", "IGNORE")
  if X = 1 then
    con.print "Oops!"
  elseif X = 2 then
    con.print "Wheee!"
  else 'can only be 3, invalid responses blocked in TMBMessageBox
    con.print "Don't do that."
  end if
  con.print
  con.print "Any key to exit demo."
  con.waitkey$
end function
#endif '

Demonstration of TxtMessagebox And SLL Use.

For PBWin 10 use of the SLL (Static Link Library) TxtMessagebox. The TxtMessagebox function does have any PBWin 10 or PBCC 06 specific code so the SLL maybe compiled by either and used in the other. The #LINK line will be the same for either. #COMPILE SLL and #LINK were new in PBWin 10 and PBCC 6, so the SLL is not usable in older PB versions (see DLL demonstration below).

#compile exe
#dim all
%UNICODE = 1
%IsPixels = 0 '<=== set to 1 if DIALOG NEW PIXELS (UNITS is default so 0 then)
#link "TxtMessagebox.sll"
%ID_Test_1st_TMB_Btn = 1001
%ID_Test_2nd_TMB_Btn = 1002
%ID_ExitBtn = 1000
'
callback function DlgCB() as long
  local MessageStr as wstring
  local PosX, PosY as long
  if (cb.msg = %wm_command) and cb.ctlmsg = %bn_clicked then
    select case as long cb.ctl
      case %ID_ExitBtn
        dialog end cb.hndl
      case %ID_Test_1st_TMB_Btn
        MessageStr = "This messagebox was compiled to an SLL in PBCC 6 and " + _
                     $$crlf + _
                     "works here in PBWin 10." + $$crlf + $$crlf
        MessageStr +="After closing this messagebox move the dialog before " + _
                     "pressing button for 2nd messagebox.
        gosub X_and_Y
        TxtMessageBox ("Messagebox test 1.", MessageStr, PosX, PosY)
      case %ID_Test_2nd_TMB_Btn
        MessageStr = "Should be in same place ON THE DIALOG as it was in test 1."
        gosub X_and_Y
        TxtMessageBox ("Messagebox test 2.", MessageStr, PosX, PosY)
    end select
  end if
  exit function
  X_and_Y: 'GOSUB because needed in multiple places.
    dialog get loc cb.hndl to PosX, PosY 'may be units or pixels
    if %def(%IsPixels) and (%IsPixels = 1) then 'DIALOG NEW PIXELS, do nothing
      return
    else                      'DIALOG NEW or DIALOG NEW UNITS, units to pixels
      #if %pb_revision = &h1004 'version has known error
        dialog units cb.hndl, PosX, PosY to pixels PosY, PosX
      #else                     'other versions are correct
        dialog units cb.hndl, PosX, PosY to pixels PosX, PosY
      #endif
    end if
  return
end function
'
function pbmain () as long
  local hDlg as dword
  dialog new 0, "Dialog to test TxtMessagebox.", 50, 50, 200, 100 to hDlg
  control add button, hDlg, %ID_Test_1st_TMB_Btn, "Show &1st Messagebox", _
     5, 5, 80, 12
  control add button, hDlg, %ID_Test_2nd_TMB_Btn, "Show &2nd Messagebox", _
     5, 22, 80, 12
  control add button, hDlg, 1000, "E&xit", 5, 54, 25, 12
  dialog show modal hDlg call DlgCB
end function  
Demonstration of TxtMessagebox in DLL by Explicite Load.

For PBWin 9 and PBCC 5, or older, and other languages, use the API functions LoadLibrary, GetProcAddress and FreeLibrary. PBWin 10 and PBCC 6 added IMPORT ADDR and IMPORT CLOSE for the purpose. This demo uses IMPORT, but the choice is yours for the more recent ("newer" didn't seem appropriate anymore ☹) compilers.

There is no demo for Implicite loading, i.e.:
DECLARE FUNCTION function_name LIB "library_name.dll" ALIAS "alias_name" (parameters) AS type
Use IMPORT instead of LIB in PBWin 10 and PBCC 6 (LIB is still allowed). An advantage of implicite is "normal" calling in source code (no CALL DWORD USING). A disadvantage is program failure at startup without any notification of why if there is a problem loading.

' Fle name  "demoExplicitLoadTMB.bas"
#compile exe
#dim all
%UNICODE = 1
declare function TxtMessageBox (TMBCaption as wstring, _
                                TMBMessage as wstring, _
                          byval ParentPosX as long, _
                          byval ParentPosY as long, _
                            opt Opt1Str as wstring, _
                            opt Opt2Str as wstring, _
                            opt Opt3Str as wstring) as long
function pbmain () as long
  local hLib, pTMB as dword
  local X as long
  import addr "TxtMessagebox", "TxtMessagebox.dll" to pTMB, hLib
  con.color 10, 0
  con.print "Any key to show messagebox."
  con.waitkey$
  call dword pTMB using TxtMessageBox ("B Title", "Some message", 200, 100, _
                 "1 to return 1 on exit.", "2 to return 2 on exit.") to X
  con.print "TxtMessagebox returned " + dec$(X)
  con.color 14, 0
  con.print "CALL DWORD USING isn't that difficult, is it?"
  con.color 7, 0
  con.print
  con.print "Any key to exit."
  con.waitkey$
end function

Created on 21 May 2023, last edit 12 Jun 2023.

To Domain Home.

home
To Dale's Notebook index.
notebook
To Programs index.
programs