Hi! Welcome back! This time, we'll learn on how to make dialog boxes in TV. Just to remind you to turn on the extended syntax in Option menu, or just add {$X+} option at the top of your program. Well, at this point I assume that you at least know some easy terminologies in GUI, like check box, radio button, and so on. I definitely don't want to explain those terms. You should have known it.
Making message boxes are easy. There is a MessageBox method that takes 3 parameters: The string to display, the owner pointer (simply pass nil to it), and the button code constant. For example:
MessageBox(#3"My First Message Box"#13#3"By: Me", nil, mfInformation or mfOKButton);
Wow, that's easy! The #3 is to center the message and the #13 is to provide the line break. For the button, it's self explanatory.
To make a dialog box, you should first create a class that inherit TDialog. Then, override its Init constructor to fill the components. It can be done as follows:
type PMyDlgBox = ^TMyDlgBox TMyDlgBox = object(TDialog) constructor Init; procedure HandleEvent(var Event: TEvent); virtual; end;
Don't forget to add a pointer in your main program. It can be done as follows:
type TMyApp = object(TApplication) ClipboardWin: PEditWindow; MyDlg: PMyDlgBox; { <-- the pointer } : : { and so on } end;
Overriding the TDialog's Init constructor is done as follows:
constructor TMyDlgBox.Init; var R: TRect; { often used in TV-style programs :-) } Field: PInputLine; Cluster: PCluster; Memo: PMemo; begin R.Assign(0, 0, 60, 17); { Define the dialog box dimension } inherited Init(R,'Orders'); { Initialize it with the dimension and a title } Options := Options or ofCentered { Center the dialog box } HelpCtx := $F000; { Set the help context number } { Add a text field with a label next to it } R.Assign(13, 2, 23, 3); { Text box dimension } { Create a text box with limit 8 chars to it } Field := New(PInputLine, Init(R, 8)); Insert(Field); R.Assign(2, 2, 12, 3); { Create the dimension of label next to it } { Attach the label next to text box } Insert(New(PLabel, Init(R, '~O~rder #:', Field))); { Add a text field with a label next to it } R.Assign(43, 2, 53, 3); Field := New(PInputLine, Init(R, 8)); Insert(Field); R.Assign(26, 2, 41, 3); Insert(New(PLabel, Init(R, '~D~ate of order:', Field))); { Add a text field with a label next to it } R.Assign(13, 4, 23, 5); Field := New(PInputLine, Init(R, 8)); Insert(Field); R.Assign(2, 4, 12, 5); Insert(New(PLabel, Init(R, '~S~tock #:', Field))); { Add a text field with a label next to it } R.Assign(46, 4, 53, 5); Field := New(PInputLine, Init(R, 5)); { limit 5 chars } Insert(Field); R.Assign(26, 4, 44, 5); Insert(New(PLabel, Init(R, '~Q~uantity: ', Field))); { Add a set of radio buttons with a label next to them } R.Assign(3, 7, 57, 8); Cluster := New(PRadioButtons, Init(R, { Initialize the radio buttons } NewSItem('Cash ', NewSItem('Check ', NewSItem('P.O. ', NewSItem('Account',nil)))))); Insert(Cluster); R.Assign(2, 6, 21, 7); { Create the dimension of label next to it } { Attach the label next to radio buttons } Insert(New(PLabel, Init(R, '~P~ayment Method:', Cluster))); { Add a check box without a label } R.Assign(22, 8, 37, 9); Cluster := New(PCheckBoxes, Init(R, NewSItem('~R~eceived', nil))); Insert(Cluster); { Add a memo field with a label } R.Assign(3, 10, 57, 13); Memo := New(PMemo, Init(R, nil, nil, nil, 255)); { max: 255 chars memo } Insert(Memo); R.Assign(2, 9, 9, 10); Insert(New(PLabel, Init(R, 'Notes: ', Memo))); { Add a set of buttons and associate command constants to each of them } R.Assign(2, 14, 12, 16); Insert(New(PButton, Init(R, '~N~ew', cmOrderNew, bfNormal))); R.Assign(13, 14, 23, 16); Insert(New(PButton, Init(R, '~S~ave', cmOrderSave, bfDefault))); R.Assign(24, 14, 34, 16); Insert(New(PButton, Init(R, 'Re~v~ert', cmOrderCancel, bfNormal))); R.Assign(35, 14, 45, 16); Insert(New(PButton, Init(R, '~N~ext', cmOrderNext, bfNormal))); R.Assign(46, 14, 56, 16); Insert(New(PButton, Init(R, '~P~rev', cmOrderPrev, bfNormal))); end;
Wow. I think that adding each element is clearly explained in the code above. The buttons simply associated with a command constant each. The bfDefault simply says that this is the default button. Of course you may not define more than one default button. The rest should be filled with bfNormal
Now how can I detect the request to open the dialog box? First of all, we have to detect whether the dialog box is already open or not. If it is, then simply bring it to the front. Otherwise, create a new dialog box and show it. So, where should I put the code for that. Well to both of the class: the dialog box class (TMyDlgBox) and the application class (TMyApp). First of all, the application send a broadcast message to all existing windows to find whether the intended dialog box is open. If it is, then the HandleEvent in the dialog box class should respond with ClearEvent. It is done as follows:
procedure TMyApp.OpenOrderWindow; begin if Message(Desktop, evBroadCast, cmFindOrderWindow, nil) = nil then begin { If we can't find the Dialog Box, then create anew } MyDlg := New(PMyDlg, Init); Application^.InsertWindow(MyDlg); end else begin { If the Dialog Box is already there, just show it } if PView(OrderWindow) <> Desktop^.TopView then OrderWindow^.Select; end; end;
This shows that the main application send a broadcast message throughout the desktop. If it doesn't have a reply, it means that the intended dialog box is not yet there, so we have to create it as shown on the then part. If we have a reply, then the dialog box is already there. We just need to bring it on. So, how should we respond to that broadcast message so that our main application can act accordingly? Hmm... good question. This will show how:
procedure TMyDlgBox.HandleEvent(var Event: TEvent); begin inherited HandleEvent(Event); if (Event.What = evBroadCast) and (Event.Command = cmFindOrderWindow) then ClearEvent(Event); end;
We respond it by using ClearEvent after we know the message. That's it. :-) Easy right?
Of course we create a dialog box so that the user can fill in the data. The data should later be processed. The next question is how can we extract the data out of the dialog box. The first step here is to create a data structure that matches the dialog box lay out. You should first remember the order on how you put the elements in. In the above example, we put the order as follows:
Field | Control | Declaration |
---|---|---|
Order # | Input Line | String[8] |
Date | Input Line | String[8] |
Stock # | Input Line | String[8] |
Quantity | Input Line | String[5] |
Payment Method | Radio Buttons | Word |
Received | Check Boxes | Word |
Notes | Memo | Word and array of char |
Note that in Order #, Date, and Stock #, we set the limit 8, so we declare the string of length 8. In Quantity, the limit is 5, so the declaration is string of length 5. Payment method and Received are both declared as words. The notes field is defined as memo need both word and an array of chars. So, the data structure to hold such data is:
POrder = ^TOrder TOrder = record OrderNum: String[8]; OrderDate: String[8]; StockNum: String[8]; Quantity: String[5]; Payment, Received: Word; MemoLen: Word; MemoText: array[0..255] of char; end;
Reading the dialog data out is as follows: (This code is in TMyApp)
var MyOrder: TOrder; begin : : MyDlg^.GetData(MyOrder); : : end;
Setting the values is just similar. You can just first fill in MyOrder with the values you want, then use MyDlg^.SetData(MyOrder); to set them.
Actually there is a way just to extract one data out, however, it will require you to store the pointer of each elements in the dialog box class (i.e. in TMyDlgBox). Then one of the field of each will contain the data.
Control | Class | Field that Contains Data | Type |
---|---|---|---|
Input Line | TInputLine | Data | String |
Radio Buttons | TRadioButton | Value | Word |
Check Boxes | TCheckBoxes | Value | Word |
Memo | TMemo | (Leave it alone) | (Leave it alone) |
Frankly, I forgot on where Memo store its data. But I think that you should only use GetData and SetData rather than do it separately. It's cleaner. :-)
TV enables us to check the input lines whether it contains a valid or expected input or not by providing validator objects. Two validators are discussed here: PRangeValidator and PPXPictureValidator. PRangeValidator checks whether the input is an integer within the specified range. PPXPictureValidator is more general. If you are quite familiar with COBOL or FoxPro, you may consider PPXPictureValidator as the PICTURE or PIC statement. For example '&&&&' denotes that the input line must be 4 letters, while '&&&-####' denotes that the input line must be 3 letters followed by a dash, then followed by 4 numbers. '{#[#]}-&&&&' tells that we can have one or two numbers, followed by a dash and 4 letters. The code below will show you on how we can use those validators:
: : R.Assign(13, 2, 23, 3); Field := New(PInputLine, Init(R, 8)); { Set it within 1 to 99999 } Field^.SetValidator(New(PRangeValidator, Init(1, 99999))); Insert(Field); : : R.Assign(43, 2, 53, 3); Field := New(PInputLine, Init(R, 8)); Field^.SetValidator(New(PPXPictureValidator, Init('{#[#]}/{#[#]}/{##[##]}', True))); Insert(Field); : : R.Assign(13, 4, 23, 5); Field := New(PInputLine, Init(R, 8)); Field^.SetValidator(New(PPXPictureValidator, Init('&&&-####', True))); Insert(Field); : :
Easy right? Just SetValidator before inserting the Field.
OK, I think that's all for now. See you at the next chapter about collections.
Chapter 11
News Page
Pascal Lesson 3 index
Contacting Me
Roby Joehanes © 2001