Delphi's strange menu writing
Translator: Li Junyu Email: E271828 @ 163.NET, OkmyDelphi @ 163.net
Custom Menus, Text, Lines / Delphi 4, 5 Custom Menu, Text, Line / Delphi 4, 5fancy Menus, ETC. Singular Menu, etc. Custom Menus, Rotated Text, And Special Lines Custom menu, rotating text, and special Line
Before Delphi 4, it was difficult to customize a menu (add a bitmap, change a font, etc.), because owner drawing (ie custom drawing) - although implemented by Windows -. Was not exposed by the TMainMenu class Since Delphi 4, However, this Situation Has Been Rect, And We can have, before Delphi 4, want to customize a menu is difficult (for example, plus a BMP image, changing font, etc.) because Owner Drawing events (also It is Custom Drawing event) - although it is executed by Windows, it is not in the TMAINMENU Class. Since Delphi 4 starts, this situation has changed, and we have the ability to customize the menu.
This article will highlight some techniques you can use to customize the appearance of menus in your Delphi applications. We'll discuss text placement, menu sizing, font assignment, and using bitmaps and shapes to enhance a menu's appearance. Just for fun, this article Also Features Techniques for Creating Rotated TEXT AND CUSTOM LINES. All of the techniques Discussed in This Article Are Demonstrated in Projects Available for Download. This article will focus mainly discuss some techniques that can be used from the shape of the menu in your Delphi application. We will discuss the placement of the text, the size of the menu, the font settings, and strengthen the use of the BMP file and the Shape control. The display effect of the menu. This article will also perform close-up for the purpose of the rotating text and custom lines. All techniques discussed in this article have passed debugging in the project document and can be downloaded online. Custom Fonts and Sizes and set the font size To create a custom menu, set the OwnerDraw property of the menu component -TMainMenu or TPopupMenu -. To True, and provide event handlers for its OnDrawItem and OnMeasureItem events For example, an OnMeasureItem event handler is declared Like this: In order to create a custom menu, set the OwnerDraw property of the TMAINMENU or TPOPUPMENU component to TRUE and create its OndraWItem and ONMeasureItem event procedures. For example, an OnMeasureItem event process can be declared as follows: Procedure TFORM1.Option1measureItem (Sender: Tobject; Acanvas: Tcanvas; Var Width, Height: Integer);
. Set the Width and Height variables to adjust the size of the menu item The OnDrawItem event handler is where all the hard work is done; it's where you draw your menu and make any special settings To draw the menu option with Times New Roman font. For Example, You Should Do Something Like this: Sets the width and height variables of the menu items during the above event to the appropriate size. All major things are triggered by the onDrawItem event; it is you want to redo the menu and Any special setting place. For example, in order to re-paint menu items with Times New Roman font, you can do this below:
procedure TForm1.Times1DrawItem (Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean); begin ACanvas.Font.Name: = 'Times New Roman'; ACanvas.TextOut (ARect.Left 1, ARect.Top 1, (Sender as TMenuItem) .Caption); end;. This code is flawed, however If it's run, the menu caption will be drawn aligned with the left border of the menu This is not default Windows behavior;. usually, there's a space to put bitmaps and checkmarks in the menu. Therefore, you should calculate the space needed for this checkmark with code like that shown in Figure 1. Figure 2 shows the resulting menu. However, this code is defective. If you run this code, the topic of the menu item is aligned in the menu item. This is not the default behavior of Windows, usually, with a space on the left side of the menu to place BMP images and selection flags. Therefore, you should use the code to calculate how much space is to place this selection flag, just like it is shown in Figure 1. Figure 2 shows the running effect of the menu.
procedure TForm1.Times2DrawItem (Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean); var dwCheck: Integer; MenuCaption: string; begin // Get the checkmark dimensions to obtain the required number of pixels selectable marker dwCheck: =. GetSystemMetrics (SM_CXMENUCHECK); // adjust left position to adjust the left position ARect.Left:. = ARect.Left LoWord (dwCheck) 1; MenuCaption: = (Sender as TMenuItem) .Caption; // The font name is the menu caption .
Acanvas.font.name: = 'Times New Roman'; // Draw The text. Drawing text DrawText (Acanvas.Handle, Pchar (MenuCaption), Length (MenuCaption), all, 0);
Figure 1: This OndrawItem Event Handler Places Menu Item Text Correctly. [The following equivalent] Figure 2: a menu Drawn With Custom Fonts.
If the text is too large to be drawn in the menu, Windows will cut it to fit. Therefore, you should set the menu item size so all the text can be drawn. This is the role of the OnMeasureItem event handler shown in Figure 3 If text is too long, Windows will automatically cut the length. Therefore, you should set the menu size to make all texts can be displayed. The same is true in the OnMeasureItem event, which can be seen in Figure 3. Procedure TForm1.Times2MeasureItem (Sender: Tobject; Acanvas: Tcanvas; Var Width, Height: Integer; begin acanvas.font.name: = 'Times new Roman'; acanvas.font.style: = []; // the width IS The Space of the Menu Check This length is the length of the selection flag of the menu // Plus the width of the item text. Add the length of the menu item width: = getSystemMetrics Acanvas.TextWidth (Sender as tMenuItem). Caption) 2; Height: = acanvas.textheight (sender as tmenuitem) .caption 2; End;
Figure 3: This OnMeasureItem Event Handler Insures That An Item Fits In Its Menu.
Custom Shapes and Bitmaps settings and bitmap graphics It's also possible to customize menu items by including bitmaps or other shapes To add a bitmap, simply assign a bitmap file to the TMenuItem.Bitmap property -. With the Object Inspector at design time, or with Code At Run Time. To Draw Colored Rectangles As The Caption of a Menu Item Event Handler Shown In Figure 4. Figure 5 Shows The Result. To set the menu and other graphs to set the menu. To To add a bitmap, just simply assign a BITMAP attribute that a BMP file to the TMenuItem in the Object Inspector, or the code is assigned by the code. To replace the menu title with a colorful rectangle, you can use the onDrawItem event, such as displayed in Figure 4. The result is shown in Figure 5.
procedure TForm1.ColorDrawItem (Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean); var dwCheck: Integer; MenuColor: TColor; begin // Get the checkmark dimensions dwCheck: = GetSystemMetrics (SM_CXMENUCHECK); ARect.Left. : = All, lyd loword (dwcheck); // Convert the CAPTION OF THE MENU ITEM TO A Color Converts the title of the menu item to Color MenuColor: = StringTocolor (Sender as TMenuItem); // Change THE Canvas Brush Color. Change the brush color of the canvas Canvas Acanvas.brush.Color: = Menucolor; // Draws the Rectangle. If the item is selected, painting rectangle, if the menu item is selected // Draw A Border. Section Box IF SELECTED THENVAS.PEN.Style: = pssolid else acanvas.pen.Style: = psclear; acanvas.Rectangle (all); end; figure 4: useing the onDrawItem Event To Draw Colored Rectangles on Menu Items. Figure 5: a menu Featuring Colored Rectangles As Items.
There's just one catch. If you're using Delphi 5, you must set the menu's AutoHotkeys property to maManual. If you leave it as the default, maAutomatic, Delphi will add an ampersand character (&) to the caption, which will break this Code. Another Solution Is To Remove The Ampersand with The StriphotKey Function. The popular practice is that if you use Delphi 5, you should set the autohotKeys property of the menu to Mamanual. If you don't do this, let the default value maautomatic leave, Delphi will automatically add a & number to the title, which will destroy these code. Another solution is to remove the & number with the StriphotKey function. Another way to use the OnDrawItem and OnMeasureItem events is to write text vertically on a menu (as shown in Figure 7). To do this, you must create a rotated font. This is only possible using the Windows API function CreateFont or CreateLogFont (see The "Rotated Text" Tip Later in this article ". The You Must Draw It in The OnDrawItem Event Handler. This Event IS Fired Every Time A Menu Item Is Drawn, SO IF A Menu HAS 20 Items, IT Will Be Drawn 20 Times. To make it faster, the vertical text will be drawn only when the menu item is selected (since there's is only one menu item selected at a time). Figure 6 shows how this is implemented with code, and Figure 7 shows the run-time Another use of Result. OnDrawItem is used to write vertical texts on the menu side (for example, in Figure 7). To do this, you have to create a rotating font. The only way is to use the CreateFont or CreateLogFont function of the Windows API (a "rotating text" technique later). So you must call it in the OnDrawItem event. This event is executed when the menu item is pulled out, so if a menu has 20 items, it will be executed 20 times. In order to make it fast, this vertical text can be redrawn once at the menu item (although only one menu item is selected each time). Figure 6 shows how the code is executed, and Figure 7 shows the run results.
procedure TForm1.VerticalDrawItem (Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean); var lf: TLogFont; OldFont: HFont; clFore, clBack: LongInt; Rectang: TRect; dwCheck: LongInt; MenuHeight: Integer; begin DWCHECK: = GetSystemMetrics (SM_CXMENUCHECK); // this will be done overce, when the item is selected. This will be executed if the menu item is selected, which will be executed if SELECTED FONT. Creating a rotating font Fillchar (LF, SIZEOF (LF), 0); lf.lfheight: = -14; lf.LFescape: = 900; lf.lforientation: = 900; lf.lfweight: = fw_bold; strpcopy (lf.lffacename, 'arial') // select this font to draw. Select this font to paint Oldfont: = selectObject (Acanvas.Handle, CreateFontIndirect (LF)); // Change Foreground and Background Colors. Change the foreground color and background color CLFORE: = SetTextColor (Acanvas. Handle, Clsilver; CLBACK: = setBkcolor (acanvas.handle, clb); // get the menu height. Get menu Height MenuHeiGHT: = (all) * (Sender as tMenuItem) .pa Rent As Tmenuitem) .count; Rectang: = Rect (-1, 0, dwcheck-1, menuheight); // Draw the text. Drawing text EXTTEXTOUT (Acanvas.Handle, -1, MenuHEight, Eto_Clipped, @Rectang, 'MADE In borland ', 15, nil); // Returns to the Original State. Return to the initial state deleteObject (selectObject (acanvas.handle, oldfont)); setTextColor (Acanvas.Handle, ClFore); setbkcolor (acanvas.handle, clck) End; // Draw The Real Menu Text. Draw real menu item text.Left: = all; DrawText (disnvas.handle, pchar (sender as tmenuitem), Length (Sender As Tmenuitem) .caption, all, 0);
Figure 6: Using OnDrawItem to draw vertical text on a menu Figure 7:. Menu with vertical text.One tricky detail is knowing where to begin drawing the text It should begin at the bottom of the last item on the menu To get its.. Position, We get the height of the menu item, using: Where to start drawing text should be known. It should start at the bottom of the last item of the menu. In order to get this position, we will get the height of the menu item as follows: all the menu: and multiply the number of menu items: ((Sender as tMenuItem) .parent as tMenuitem) .count)
Rotated Text rotated text The Windows API allows you to draw text at any angle. To do this in Delphi, you must use the API function CreateFont or CreateFontIndirect. CreateFont is declared as shown in Figure 8. Windows API allows you to use any angle Come draw text. In order to do this in Delphi, you must use the two API functions of CreateFont or CreateFontIndirect. Figure 8 shows how to declare CREATEFONT. function CreateFont (nHeight, // Logical height of font. logical font height nWidth, // Logical average character width. nEscapement logic average width of the characters, // Angle of escapement. the angle of rotation nOrientation, // Base-line orientation angle the positioning angle bottom line fnWeight: Integer; // Font weight font weight sub-attributes fdwItalic, // Italic attribute flag italic fdwUnderline, // underline attribute flag whether underlined fdwStrikeOut, // Strikeout attribute flag whether Strikeout property.... fdwCharSet // character set identifier character set fdwOutputPrecision, // Output precision fdwClipPrecision, // Clipping precision fdwQuality, // Output quality fdwPitchAndFamily: DWORD; // Pitch and family lpszFace:..... PChar // Pointer to typeface name string : Hfont; stdcall;
Figure 8: The Object Pascal Declaration for the createfont Windows API Function.
While this function has many parameters, you will usually want only to change one or two attributes of the text In such cases, you should use the CreateFontIndirect function instead It takes only one argument -.. A record of type TLogFont, as shown in Figure 9. Although this function has many parameters, you usually only change one or two properties of the text. In this case, you will use the CREATEFONTINDIRECT function instead. It only has a parameter ---- a TLogFont record type parameter, which can be seen in Figure 9. tagLOGFONTA = packed record lfHeight: Longint; lfWidth: Longint; lfEscapement: Longint; lfOrientation: Longint; lfWeight: Longint; lfItalic: Byte; lfUnderline: Byte; lfStrikeOut: Byte; lfCharSet: Byte; lfOutPrecision: Byte; lfClipPrecision: Byte; lfQuality: Byte; lffpitchandfamily: byte; lffacename: array [0..lf_facesize - 1] of ansichar; end; tlogfonta = taglogfonta; tlogfont = tlogfonta; Figure 9: The Tlogfont Record.
Looking at this record, you'll notice its members match the parameters for the CreateFont function. The advantage of using this function / record combination is that you can fill the record's members with a known font using the GetObject API function, change the members you Want, and create the new font. Take a closer look, you will find that its member is very similar to the CreateFont function parameters. The advantage of using this function / record is that you can use the getObject this API function to fill a known font to the member value of this record, then change the member value you want to change to generate a new font.
To draw rotated text, the only member you must change is lfEscapement, which sets the text angle in tenths of degrees. So, if you want text drawn at 45 degrees, you must set lfEscapement to 450. In order to draw rotated text, you It is only necessary to change the member value of LFESCAPEMENT, which can be used to set the angle of the font with one tenth unit. So if you want the character to rotate 45 degrees, you have to set the LFESCAPEMENT to 450. Notice that there are flags to draw italic, underline, and strikeout text, but there is no flag to draw bold text. This is done with the lfWeight member, a number between 0 and 1000. 400 is normal text, values above this draw bold TEXT, AND VALUES BELOW IT DRAW LIGHT. Note that there are many markers here to choose the slope, underscore, protrude, but there is no marker to draw bold. This is because it is replaced with LFWEIGHT members, and the value of this member is between 0 and 1000. 400 is a normal value, which is a bold that is lower than this value is fine. The code in Figure 10 draws text at angles ranging from 0 degrees to 360 degrees, at 20-degree intervals. It's the form's OnPaint event handler, so the text is redrawn each time the form is painted. Figure 11 shows the result. Figure 10 The code draws characters every 20 degrees from 0 degrees to 360 degrees. This is triggered in the form's onpaint event, so the text will redraw while the form is drawn. The effect can be seen in Figure 11.
Procedure TForm1.FormPaint (Sender: TOBJECT); VAR Oldfont, NewFont: HFont; Logfont: Tlogfont; i: integer; begin // Get Handle of Canvas font. Get the handle of the form font object: = canvas.font.handle; I: = 0; // Transparent Drawing. Setting transparent attribute setbkmode (canvas.handle, transparent); // Fill Logfont Structure with information Fill in the logfont structure // from Current Font. from the current font GetObject (Oldfont, Sizeof (Logfont ), @Logfont); // Angles Range from 0 to 360. From 0 to 360 degrees WHILE I <3600 DO BEGIN / / SET ESCAPEMENT TO New Angle. Setting the text direction to a new angle logfont.lfescapement: = i; // Create New Font. Create a new font newfont: = createFontIndirect (logfont); // select the font to draw. Select the font to output SelectObject (canvas.handle, newfont); // Draw Text At the Middle of the form. In the form Intermediate Output Textout (Canvas.Handle, ClientWidth Div 2, ClientHeight Div 2, 'Rotated Text', 21); // Clean Up. Clear DeleteObject (SelectObject (canvas.Handle, Oldfont)); // increment Angle By 20 DegRees Increment INC (I, 200) every 20 degrees; End; End; Figure 10: Code to Draw Text Rotated in 20-Degree Interval 360 Degrees.
The form's font is set to Arial, a TrueType font This code works only with TrueType fonts;. Other kinds of fonts do not support text rotation To get current font settings and fill the TLogFont structure, you must use the GetObject API function.. The Code in Figure 12 shows how to Fill and Display The Tlogfont Settings for the form's font. This form is set to Arial, a TrueType font. This code is only running under the TrueType font; other fonts do not support text rotation. To get the current font settings and fill in the TLogFont structure, you must use the getObject this API function. In the code in Figure 12, you can see how to fill in and display the settings in the form.
procedure TForm1.Info1Click (Sender: TObject); var LogFont:. TLogFont; begin // Fill LogFont structure with information fill out the member value LogFont structure // from current font from the current font GetObject (Canvas.Font.Handle, Sizeof (LogFont ), @Logfont); // Display font information. Display font information with logfont do showmessage ('Lfheight:' INTOSTR (LFHEIGHT) # 13 'LFWidth:' INTOSTR (LFWidth) # 13 'LFEScapement:' INTOSTR (LFESCAPEMENT) # 13 'Lirtient:' INTOSTR (LFORIATION) # 13 'LFWeight:' INTOSTR (LFWEight) # 13 'lfitalic:' INTOSTR (LFITALIC) # 13 'LFunderline : ' INTOSTR (LFunderline) # 13 ' LFSTRIKEOUT: ' INTOSTR (LFSTRIKEOUT) # 13 ' LFCHARSET: ' INTOSTR (LFCharset) # 13 ' LFOUTPRECISION: ' INTOSTOSTR (LFoutPrecision) # 13 'lfClipPrecision:' IntToStr (lfClipPrecision) # 13 'lfQuality:' IntToStr (lfQuality) # 13 'lfPitchAndFamily:' IntToStr (lfPitchAndFamily) # 13 'lfFaceName:' string (lfFaceName)); End; figu RE 12: Getting and Displaying Font Attributes.
Once you have the settings in a TLogFont structure, the only change left is to set lfEscapement to the desired angle and create a new font with CreateFontIndirect. Before using this new font, it must be selected with SelectObject. Another way is to assign the handle . of this new font to the handle of the canvas's font, before drawing the text After drawing the text, this work must be reversed; the old font must be selected, and the new font deleted If the new font is not deleted,. There Will Be a Memory Leak, And - IF The Routine Is Executed Many Times - Windows (especial 95/98) WILL Run OUT OF RESOURES, AND CRASH. Once you have already set the TLogFont structure, the only thing to do It is the value of the value to change the value of LFESCAPEMENT and generates a new font with CreateFontIndirect. Before using this new font, you must use SelectObject to select it. Another method is the handle of the font object of the canvas of the form with this new font object before depicting the text. After depicting the text, this process is in turn; the old font must be selected, the new font is deleted. If the new font is not deleted, it will cause memory leaks, and ----- If the program is executed multiple times ------ Windows (especially 95/98) will exhaust resources, and crash. Stylish Lines popular lines When you draw lines, the individual pixels usually do not matter; you simply set the line style, and it's drawn by Windows Sometimes however, you need to do something special and draw a line style not provided by Windows. .................................................. ... Depict. However, sometimes you want to do some special and WINDOWS no line types provided. This can be implemented with a LINEDDA's API function, and it can be seen in Figure 13.
Function Linedda (NxStart, // X-Coordinate Of Line's Starting Point. x Coordinate Starting NYSTART, // Y-Coordinate Of Line's Starting Point. Y Coordinate Starting NXEND, // X-Coordinate Of Line's Ending Point. x Coordinate end Yend: Integer; // y-coordinate of line's ending point Y coordinate of the end point // address of application-defined callback function address lpLineFunc application-defined callback function: TFNLineDDAProc; lpData:.. LPARAM // address of application-defined data applications. The address of the program defined data: bool; stdcall; Figure 13: Object Pascal Declaration for the Windows API Function, Linedda.
The first four parameters are the starting and ending points of the line. The fifth parameter is a callback function that will be called every time a pixel should be drawn. You put your drawing routines there. The last parameter is a user parameter that will be . passed to the callback function You can pass any Integer or pointer to the function, because it is an LParam (in Win32, it is translated to a Longint) the callback function must take the form shown here:. this is the beginning of the four parameters It is the beginning and end point of the line. The fifth parameter is a callback function, and each time the pixel is depicted, it will be called. You can write your drawing process here. The last parameter is the user-defined to pass to the callback function. You can pass any integers or pointers to this function because it is a LPARAM type (in Win32, which is explained as a longint). This callback function must use the following form: Procedure CallbackDDa (X, Y: Integer; UserParam: LPARAM); stdcall;
where x and y are the coordinates of the drawn point, and UserParam is a parameter that is passed to the function. This function must be declared as stdcall. The routine in Figure 14 draws a line of bitmaps, and Figure 15 shows the result. Here, x and y are all depicted, but userparam is a parameter. This function must be defined as stdcall. The program in Figure 14 depicts a BMP line, while Figure 15 shows the result.
type TForm1 = class (TForm) ImageList1: TImageList; procedure FormPaint (Sender: TObject); procedure FormResize (Sender: TObject); end; var Form1: TForm1; procedure CallDDA (x, y: Integer; Form: TForm1); stdcall; Implementation {$ r * .dfm} Procedure Calldda (x, y: integer; form: tform1); begin if x mod 13 = 0 Then form.Imagelist1.draw (form.canvas, x, y, 0); end; procedure TForm1.FormPaint (Sender: TObject); begin LineDDA (0, 0, ClientWidth, ClientHeight, @CallDDA, Integer (Self)); end; procedure TForm1.FormResize (Sender: TObject); begin Invalidate; end; Figure 14: Code to draw a line of bitmaps Figure 15:. Window with a custom line.This routine handles the form's OnPaint event, calling LineDDA, so every time the form must be painted, it redraws the line Another event that is handled is OnResize, which. Invalidates The Form Client Area, So The Line Must Be RedRawn When Someone Changes Its Size. The Linedda Callback Function, Calldda, Is Very Simple. At Every 13th point it is called, it draws the bitmap stored in the ImageList. As you may notice, Self is passed as the last parameter to the callback function, so it can access the instance data. This routine handles OnPaint event of the form, call LineDDA So each form is depicted, it will redraw this line. Another event is OnResize, which causes the form's client area to be invalid, so when someone change its large hour, the line will also be heavy. Linedda callback function, CallDDA is very simple. Whenever it is called 13 times, it will depict a bitmap stored in ImageList. Maybe you noticed that SELF is passed to the callback function as the last parameter, so it can access the data of the program.