![]()
Last updated 12/08/04.
This is a table of contents for tips within this document... You can just scroll through it if you prefer.
![]()
First, thanks is due to Victor Pei for his investigation on this matter.
A frequent question of many Delphi programmers is, "How do I create an MDI child form in a DLL?" Like many Delphi programmers, I long believed this was impossible.
It turns out that the heart of the MDI application is in the Application variable, of which one instance exists for each application (.dpr file). The related Screen variable (also one instance per application) is also important for control of the cursor in any kind of application.
By passing the Application and Screen variables into your DLL's "startup" procedure or function, you can access the calling application, and treat it as though it is part of your DLL. Since the DLL has its own Application and Screen variables, it is important to save these before replacing their values.
The procedure is simple. Create global variables for your Dll, for example:
var PrevApplication: TApplication; PrevScreen: TScreen;
In the Initialization part of the DLL, save the DLL's Screen and Application variables; in the Finalization, restore them to their original values. (If you fail to restore the DLL's Application variable before the DLL closes, the calling application will close as well.) For example:
Initialization PrevApplication := Application; PrevScreen := Screen; Finalization Application := PrevApplication; Screen := PrevScreen;
Say that your calling procedure is called CallMe, and that it displays a form called Form1 (of type TForm1). CallMe might read something like this:
procedure CallMe(A: TApplication; S: TScreen); begin Application := A; Screen := S; Form1 := TForm1.Create(Application); end;
In the above example, if Form1 is a fsMDIChild, it should now be a "regular" MDI child of your application, and any access to the Application and Screen variables should behave as it does within the calling application. Note that I say should because I have not actually used this technique to create an MDI child, only to access the calling Application and Screen from a dialog box. However, by all reports (and logic), it should work quite smoothly.
![]()
Before I launch into the discussion, let me give a little history and credit where credit is due. (Unlike some people I have known, if I get help answering a question, I don't pretend I figured it out all by myself. But that's another story entirely.)
Back in July or so, I originally did think up this line of code all by myself, and then (for long and boring reasons) I deleted it, thinking that I would never forget it, since it was so clever. Naturally, I forgot it within hours! (Imagine how I kicked myself!) So, I went to Compuserve and posted my question there. It took everybody a little while to figure out what I was asking, but eventually I was lead to the right path by the following kind folks (listed in alphabetical order): Ian Barker, Harley Pebley, Steve Schafer, Bob Villiers and Mark Wilsdorf. Thanks, guys!
For brevity, "Cut" will refer to Cut, Copy and Paste.
There are a number of ways to use the Windows clipboard in Delphi. Most of the components for which this behavior is valid (TEdit, for example), have CutToClipboard method. Using this method accomplishes the same thing as:
SendMessage(MyEditControl.Handle, WM_CUT, 0, 0);
Unfortunately, the DBGrid component doesn't have a CutToClipboard method, nor will the following line of code work:
SendMessage(MyDBGrid.Handle, WM_CUT, 0, 0); {nothing happens}
This is because the Grid itself doesn't know what to do with the message. All the data in a grid is actually just "painted" there, and a little edit box (TDBGridInplaceEdit in Delphi 2.0, TInplaceEdit in Delphi 1.0, which are descended from TCustomMaskEdit, which is descended from TCustomEdit.) just follows the cursor around, providing editing capability in the current cell only. (This is an over-simplification.)
It turns out that all descendants of TCustomEdit inherits a CutToClipboard method. So, in order to Cut to Clipboard from the DBGrid, you have to iterate through the grid's Controls until you find the InplaceEdit, which you can typecast as a TCustomEdit and handle generically with all of the other descendants of TCustomEdit.
This actually works quite well; it is fast, efficient, concise and easy to code.
But, you will notice that somehow (unless you have overridden it), Windows can Cut with its context menu or by pressing Ctrl+X (no code required, but you must select text, not an entire cell). This functionality is derived from Windows itself, which obviously can't be expected to know about TDBGrid.
If you test ActiveControl when the cursor is resting on a DBGrid, you are returned TDBGrid as a type, not TInplaceEdit. However, if you use the GetFocus API call, you will be returned the handle of the currently focused control, and it turns out that this is the handle of the Inplace editor, and not the grid.
So, since Windows messages require a handle, GetFocus gives you all the information you need to generically Cut to the clipboard:
SendMessage(GetFocus, WM_CUT, 0, 0); {Cut selection to clipboard}
And here are some others you will find handy:
SendMessage(GetFocus, WM_COPY, 0, 0); {Copy selection to clipboard}
SendMessage(GetFocus, WM_PASTE, 0, 0); {Replace selection with clipboard contents}
SendMessage(GetFocus, WM_CLEAR, 0, 0); {Clear selection}
SendMessage(GetFocus, EM_UNDO, 0, 0); {Undo last operation}
For your application to be compliant with Windows standards, you must have an Edit menu with Cut, Copy, Paste and perhaps also SelectAll, Clear (or Delete), Undo and Redo. (If you know me, you know I'm a stickler for compliant behavior.) The menu should display the HotKey, and as soon as you assign a ShortCutKey to a menu item, the default behavior of the keypress is overridden and the attached procedure (if any) is executed instead. (If there is no event-handling procedure attached, nothing happens.) So you must write the above code to re-enable the default behavior. Of course, you could try making the HotKey part of the menu item's Caption, but it would be very hard to line them up so that they look like "regular" HotKeys, imparting a non-standard appearance (about which I am also a stickler).
I hope you found this tip especially helpful. One person I know deleted several hunderd lines of code already! (And isn't deleting code so much more satisfying than writing it?)
![]()
If you want to create a resource file without using the resource workshop, you can make a resource script, which is just a plain ASCII text file with a .rc extension. Type in the names of your bitmaps (each saved in a separate file) in this format:
Bitmap_Name (the name your bitmap should have within the res file) BITMAP (identifies it as a bitmap) File_name (the file name where the bitmap is stored). Or, Icon_Name ICON File_name.
Just type in your stringtables as you normally would in an rc file. When you're done, compile it using BRCC (for 16-bit applications and components) or BRCC32 (for 32- bit applications and components). The result is a res file of the same name. BRCC and BRCC32 are found in the BIN subdirectory of the appropriate Delphi version. These programs run at the DOS prompt. You can learn about the syntax and switches by typing BRCC /? or BRCC32 /?.
Below is the contents of the rc file I used to make my DBNavigator's .res file.
STRINGTABLE BEGIN 50000, "Lookup help" 50001, "Locate field value" 50002, "Locate next field value" 50003, "Set bookmark" 50004, "Clear bookmark" 50005, "Goto bookmark" 50006, "Prior set" 50007, "Next set" 50008, "Cancel changes to record?" END MODBN_BKCLEAR BITMAP BKCLEAR.BMP MODBN_BKGOTO BITMAP BKGOTO.BMP MODBN_BKSET BITMAP BKSET.BMP MODBN_CANCEL BITMAP CANCEL.BMP MODBN_DELETE BITMAP DELETE.BMP MODBN_EDIT BITMAP EDIT.BMP MODBN_FIRST BITMAP FIRST.BMP MODBN_INSERT BITMAP INSERT.BMP MODBN_LAST BITMAP LAST.BMP MODBN_LOCATE BITMAP LOCATE.BMP MODBN_LOCATENEXT BITMAP LOCATENE.BMP MODBN_LOOKUPHELP BITMAP LOOKUP.BMP MODBN_NEXT BITMAP NEXT.BMP MODBN_NEXTSET BITMAP NEXTSET.BMP MODBN_POST BITMAP POST.BMP MODBN_PRIOR BITMAP PRIOR.BMP MODBN_PRIORSET BITMAP PRIORSET.BMP MODBN_REFRESH BITMAP REFRESH.BMP TMODBNAV BITMAP DBNAVNEW.BMPBack to the Table of Contents
![]()
If you want to change the bitmap of a BitBtn or SpeedButton at runtime, without loading it from an external file (reduce the redistributables!), you can store the bitmap in a resource file and get it at runtime with a couple of simple API calls. Don't forget to include the resource file in your code using {$R MYRES.RES} in the Implementation part of the appropriate unit. (MYRES.RES is the resource file containing your bitmap.)
var
MyBtnGlyph: HBitmap;
MyBtnBmp: TBitmap;
begin
MyBtnBmp := TBitmap.Create;
MyBtnGlyph := LoadBitmap(HINSTANCE, 'MYBMP');
{MYBMP is the name of the bitmap in the resource file.}
MyBtnBmp.Handle := MyBtnGlyph;
MyBtn.Glyph := MyBtnBmp;
MyBtn.NumGlyphs := 1; {Obviously depends on the bitmap.}
MyBtnBmp.Free;
end;
Back to the Table of Contents
![]()
©1996-97 Maggie Owens.