Delphi的奇异菜单的编写
翻译者: 李均宇 email: e271828@163.net,okMyDelphi@163.net
CustomMenus,Text,Lines/Delphi4,5
自定义菜单,文本,线/Delphi4,5
FancyMenus,etc.
奇异菜单,等等
CustomMenus,RotatedText,andSpecialLines
自定义菜单,旋转文本,和特殊的线条
BeforeDelphi4,itwasdifficulttocustomizeamenu(addabitmap,changeafont,etc.),becauseownerdrawing(i.e.customdrawing)-althoughimplementedbyWindows-wasnotexposedbytheTMainMenuclass.SinceDelphi4,however,thissituationhasbeenrectified,andwecanhaveourwaywithmenus.
在Delphi4之前,要想自定义一个菜单是困难的(例如加上一个BMP图像,改变字体等),因为ownerdrawing事件(也就是customdrawing事件)-虽然是由Windows来执行,但是却并不在TMainMenuclass中出现.自从Delphi4开始后,
这种情况有了改变,我们于是有了可以自定义菜单的功能了.
ThisarticlewillhighlightsometechniquesyoucanusetocustomizetheappearanceofmenusinyourDelphiapplications.We'lldiscusstextplacement,menusizing,fontassignment,andusingbitmapsandshapestoenhanceamenu'sappearance.Justforfun,thisarticlealsofeaturestechniquesforcreatingrotatedtextandcustomlines.Allofthetechniquesdiscussedinthisarticlearedemonstratedinprojectsavailablefordownload。
这篇文章将主要着重论述可以用来自定义你的DELPHI应用程序中的菜单的外形的一些技术巧.我们将论述文本的放置,菜单的大小,字体的设置,以及用BMP文件和SHAPE控件来加强菜单的显示效果。仅仅出于娱乐的目的,这篇文章也将对旋转的文本和自定义线条的技巧进行特写。这篇文章所论述到的所有技巧都已在工程文件中通过了调试并且可以到网上下载这些工程文件。
CustomFontsandSizes
设置字体和大小
Tocreateacustommenu,settheOwnerDrawpropertyofthemenucomponent-TMainMenuorTPopupMenu-toTrue,andprovideeventhandlersforitsOnDrawItemandOnMeasureItemevents.Forexample,anOnMeasureItemeventhandlerisdeclaredlikethis:
为了创建一个自定义的菜单,将TmainMenu或TpopupMenu组件的OwnerDraw属性设为TRUE,并且创建它的OnDrawItem和OnMeasureItem的事件过程。例如,一个OnMeasureItem事件过程可以声明如下:
procedureTForm1.Option1MeasureItem(Sender:TObject;
ACanvas:TCanvas;varWidth,Height:Integer);
SettheWidthandHeightvariablestoadjustthesizeofthemenuitem.TheOnDrawItemeventhandleriswhereallthehardworkisdone;it'swhereyoudrawyourmenuandmakeanyspecialsettings.TodrawthemenuoptionwithTimesNewRomanfont,forexample,youshoulddosomethinglikethis:
设置上面事件过程中的菜单项的Width和Height变量到合适的大小.所有主要的事情都要由OnDrawItem事件来触发;它是你要重画菜单和作任何特殊设置的地方。举例,为了用TimesNewRoman字体来重画菜单项,你可以如下面这样做:
procedureTForm1.Times1DrawItem(Sender:TObject;
ACanvas:TCanvas;ARect:TRect;Selected:Boolean);
begin
ACanvas.Font.Name:='TimesNewRoman';
ACanvas.TextOut(ARect.Left
1,ARect.Top 1,
(SenderasTMenuItem).Caption);
end;
Thiscodeisflawed,however.Ifit'srun,themenucaptionwillbedrawnalignedwiththeleftborderofthemenu.Thisisn'tdefaultWindowsbehavior;usually,there'saspacetoputbitmapsandcheckmarksinthemenu.Therefore,youshouldcalculatethespaceneededforthischeckmarkwithcodelikethatshowninFigure1.Figure2showstheresultingmenu.
-
然而这段代码是有缺陷的。如果运行这段代码,菜单项的标题(caption)会在菜单项中靠左对齐.这并不是Windows的默认行为,通常,在菜单左边那儿有一个空间用来放置BMP图像和选择标志的。因此,你应该用代码计算要多少空间来放置这个选择标志的,就象Figure1中显示的那样。Figure2显示的是菜单的运行效果。
procedureTForm1.Times2DrawItem(Sender:TObject;
ACanvas:TCanvas;ARect:TRect;Selected:Boolean);
var
dwCheck:Integer;
MenuCaption:string;
begin
//Getthecheckmarkdimensions.
获取选择标志所需的像素数
dwCheck:=GetSystemMetrics(SM_CXMENUCHECK);
//Adjustleftposition.
调整左边位置
ARect.Left:=ARect.Left
LoWord(dwCheck)
1;
MenuCaption:=(SenderasTMenuItem).Caption;
//Thefontnameisthemenucaption.
ACanvas.Font.Name:='TimesNewRoman';
//Drawthetext.
画文本
DrawText(ACanvas.Handle,PChar(MenuCaption),
Length(MenuCaption),ARect,0);
end;
Figure1:ThisOnDrawItemeventhandlerplacesmenuitemtextcorrectly.
[译者省略掉所有的FigureS,以下同样]
Figure2:Amenudrawnwithcustomfonts.
Ifthetextistoolargetobedrawninthemenu,Windowswillcutittofit.Therefore,youshouldsetthemenuitemsizesoallthetextcanbedrawn.ThisistheroleoftheOnMeasureItemeventhandlershowninFigure3.
如果文本太长,Windows会自动裁剪长度来合适。因此,你应该设置菜单大小使所有的文本都可以显示出来。在OnMeasureItem事件中也应如此,这在Figure3可以看到。
procedureTForm1.Times2MeasureItem(Sender:TObject;
ACanvas:TCanvas;varWidth,Height:Integer);
begin
ACanvas.Font.Name:='TimesNewRoman';
ACanvas.Font.Style:=[];
//Thewidthisthespaceofthemenucheck
这个长度是菜单的选择标志的长度
//plusthewidthoftheitemtext.
再加上菜单项的长度
Width:=GetSystemMetrics(SM_CXMENUCHECK)
ACanvas.TextWidth((SenderasTMenuItem).Caption)
2;
Height:=ACanvas.TextHeight(
(SenderasTMenuItem).Caption) 2;
end;
Figure3:ThisOnMeasureItemeventhandlerinsuresthatanitemfitsinitsmenu.
CustomShapesandBitmaps
设置图形和位图
It'salsopossibletocustomizemenuitemsbyincludingbitmapsorothershapes.Toaddabitmap,simplyassignabitmapfiletotheTMenuItem.Bitmapproperty-withtheObjectInspectoratdesigntime,orwithcodeatruntime.Todrawcoloredrectanglesasthecaptionofamenuitem,youcouldusetheOnDrawItemeventhandlershowninFigure4.Figure5showstheresult.
用位图和其它图形来设置菜单是可能的事.要想添加一个位图,只需在设计时简单地在ObjectInspector中把一个BMP文件赋给TmenuItem的Bitmap属性即可,或者运行时用代码赋值也可以。要想用一个有颜色的矩形来代替菜单标题,你可以使用OnDrawItem事件,例如在Figure4中显示的那样。在Figure5中显示的是结果。
procedureTForm1.ColorDrawItem(Sender:TObject;
ACanvas:TCanvas;ARect:TRect;Selected:Boolean);
var
dwCheck:Integer;
MenuColor:TColor;
begin
//Getthecheckmarkdimensions.
dwCheck:=GetSystemMetrics(SM_CXMENUCHECK);
ARect.Left:=ARect.Left
LoWord(dwCheck);
//Convertthecaptionofthemenuitemtoacolor.
将菜单项的标题转换为颜色
MenuColor:=
StringToColor((SenderasTMenuItem).Caption);
//Changethecanvasbrushcolor.
改变画布canvas的画笔颜色ACanvas.Brush.Color:=MenuColor;
//Drawstherectangle.Iftheitemisselected,
画矩形,如果菜单项是被选择的
//drawaborder.
画边框
ifSelectedthen
ACanvas.Pen.Style:=psSolid
else
ACanvas.Pen.Style:=psClear;
ACanvas.Rectangle(ARect.Left,ARect.Top,
ARect.Right,ARect.Bottom);
end;
Figure4:UsingtheOnDrawItemeventtodrawcoloredrectanglesonmenuitems.
Figure5:Amenufeaturingcoloredrectanglesasitems.
There'sjustonecatch.Ifyou'reusingDelphi5,youmustsetthemenu'sAutoHotkeyspropertytomaManual.Ifyouleaveitasthedefault,maAutomatic,Delphiwilladdanampersandcharacter(&)tothecaption,whichwillbreakthiscode.AnothersolutionistoremovetheampersandwiththeStripHotKeyfunction.
比较流行的做法是,如果你用的是Delphi5,你应设置菜单的AutoHotkeys属性为maManual。如果你不这样做,而让缺省值maAutomatic留着,Delphi会自动添加一个&号给标题,这将破坏这些代码。另一个解决办法是用StripHotKey函数来移去&号。
AnotherwaytousetheOnDrawItemandOnMeasureItemeventsistowritetextverticallyonamenu(asshowninFigure7).Todothis,youmustcreatearotatedfont.ThisisonlypossibleusingtheWindowsAPIfunctionCreateFontorCreateLogFont(seethe"RotatedText"tiplaterinthisarticle).ThenyoumustdrawitintheOnDrawItemeventhandler.Thiseventisfiredeverytimeamenuitemisdrawn,soifamenuhas20items,itwillbedrawn20times.Tomakeitfaster,theverticaltextwillbedrawnonlywhenthemenuitemisselected(sincethere'sisonlyonemenuitemselectedatatime).Figure6showshowthisisimplementedwithcode,andFigure7showstherun-timeresult.
OnDrawItem和OnMeasureItem事件的另一个用途是用来在菜单侧旁写垂直的文字(例如在Figure7显示的那样)。为了做到这样,你必须创建一个旋转的字体。唯一办法是用WindowsAPI的CreateFont或者CreateLogFont函数(稍后看本文中的“旋转的文字”技巧)。于是你必须在OnDrawItem事件中重画它。这个事件在菜单项被拉出时执行,所以如果一个菜单有20项,那么它将被执行20次。为了使它快些,这垂直的文字可以在菜单项被选择时才重画一次(虽然每次只有一个菜单项被选择)。Figure6显示的是代码如何执行,而Figure7显示的是运行结果。
procedureTForm1.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);
//Thiswillbedoneonce,whentheitemisselected.
当菜单项被选中时,这将被执行
ifSelectedthenbegin
//Createarotatedfont.
创建一个旋转的字体
FillChar(lf,SizeOf(lf),0);
lf.lfHeight:=-14;
lf.lfEscapement:=900;
lf.lfOrientation:=900;
lf.lfWeight:=Fw_Bold;
StrPCopy(lf.lfFaceName,'Arial');
//Selectthisfonttodraw.
选取这个字体来画
OldFont:=SelectObject(ACanvas.Handle,
CreateFontIndirect(lf));
//Changeforegroundandbackgroundcolors.
改变前景色和背景色
clFore:=SetTextColor(ACanvas.Handle,clSilver);
clBack:=SetBkColor(ACanvas.Handle,clBlack);
//Getthemenuheight.
获取菜单高度
MenuHeight:=(ARect.Bottom-ARect.Top)*
((SenderasTMenuItem).ParentasTMenuItem).Count;Rectang:=Rect(-1,0,dwCheck-1,MenuHeight);
//Drawthetext.
画文本
ExtTextOut(ACanvas.Handle,-1,MenuHeight,Eto_Clipped,
@Rectang,'MadeinBorland',15,nil);
//Returnstotheoriginalstate.
返回到最初状态
DeleteObject(SelectObject(ACanvas.Handle,OldFont));
SetTextColor(ACanvas.Handle,clFore);
SetBkColor(ACanvas.Handle,clBack);
end;
//Drawtherealmenutext.
画真实的菜单项文本
ARect.Left:=ARect.Left
LoWord(dwCheck)
2;
DrawText(ACanvas.Handle,
PChar((SenderasTMenuItem).Caption),
Length((SenderasTMenuItem).Caption),ARect,0);
end;
Figure6:UsingOnDrawItemtodrawverticaltextonamenu.
Figure7:Menuwithverticaltext.
Onetrickydetailisknowingwheretobegindrawingthetext.Itshouldbeginatthebottomofthelastitemonthemenu.Togetitsposition,wegettheheightofthemenuitem,using:
从哪儿开始画文本是应该知道的。它应该在菜单的最后一项的底部开始。为了得到这个位置,我们如下这样要获取菜单项的高度:
ARect.Top-ARect.Bottom
andmultiplyitbythenumberofitemsinthemenu:
并且乘上菜单项的数目:
(((SenderasTMenuItem).ParentasTMenuItem).Count)
RotatedText
旋转的文本
TheWindowsAPIallowsyoutodrawtextatanyangle.TodothisinDelphi,youmustusetheAPIfunctionCreateFontorCreateFontIndirect.CreateFontisdeclaredasshowninFigure8.
WindowsAPI可以让你用任何角度来画文本。为了在Delphi中做到这点,你必须用到CreateFont或者CreateFontIndirect这两个API函数。Figure8显示了如何声明CreateFont。
functionCreateFont(
nHeight,
//Logicalheightoffont.字体的逻辑高度
nWidth,
//Logicalaveragecharacterwidth.字符的逻辑平均宽度
nEscapement,
//Angleofescapement.旋转的角度
nOrientation,
//Base-lineorientationangle.底线的定位角度
fnWeight:Integer;
//Fontweight.字体的weight子属性
fdwItalic,
//Italicattributeflag.是否斜体
fdwUnderline,
//Underlineattributeflag.是否下划线
fdwStrikeOut,
//Strikeoutattributeflag.是否Strikeout属性
fdwCharSet
//Charactersetidentifier.字符集
fdwOutputPrecision,//Outputprecision.
fdwClipPrecision,
//Clippingprecision.
fdwQuality, //Outputquality.
fdwPitchAndFamily:DWORD;
//Pitchandfamily.
lpszFace:PChar
//Pointertotypefacenamestring.
):HFONT;stdcall;
Figure8:TheObjectPascaldeclarationfortheCreateFontWindowsAPIfunction.
Whilethisfunctionhasmanyparameters,youwillusuallywantonlytochangeoneortwoattributesofthetext.Insuchcases,youshouldusetheCreateFontIndirectfunctioninstead.Ittakesonlyoneargument-arecordoftypeTLogFont,asshowninFigure9.
虽然这函数有很多参数,但你通常只须改变文本的一个或两个属性。在这种情形下,你将使用CreateFontIndirect函数来代替。它只须一个参数----一个TlogFont的记录类型的参数,在Figure9可以看到。
tagLOGFONTA=packedrecord
lfHeight:Longint;
lfWidth:Longint;
lfEscapement:Longint;
lfOrientation:Longint;
lfWeight:Longint;
lfItalic:Byte;
lfUnderline:Byte;
lfStrikeOut:Byte;
lfCharSet:Byte;
lfOutPrecision:Byte;
lfClipPrecision:Byte;
lfQuality:Byte;
lfPitchAndFamily:Byte;
lfFaceName:array[0..LF_FACESIZE-1]ofAnsiChar;
end;
TLogFontA=tagLOGFONTA;
TLogFont=TLogFontA;
Figure9:TheTLogFontrecord.
Lookingatthisrecord,you'llnoticeitsmembersmatchtheparametersfortheCreateFontfunction.Theadvantageofusingthisfunction/recordcombinationisthatyoucanfilltherecord'smemberswithaknownfontusingtheGetObjectAPIfunction,changethemembersyouwant,andcreatethenewfont.
仔细看下这个记录类型,你会发现它的成员与CreateFont函数的参数十分相似。使用这个函数/记录的联合体的好处是,你可以用GetObject这个API函数来将一个已知的字体来填满这个记录的成员值,然后改变你想改变的成员值来产生一个新字体。
Todrawrotatedtext,theonlymemberyoumustchangeislfEscapement,whichsetsthetextangleintenthsofdegrees.So,ifyouwanttextdrawnat45degrees,youmustsetlfEscapementto450.
为了画出旋转的文字,你仅仅只须改变的成员值是lfEscapement,它可以用十分之一度的单位来设置字体的角度。所以,如果你想字符旋转45度,你必须设置
lfEscapement为450。
Noticethatthereareflagstodrawitalic,underline,andstrikeouttext,butthereisnoflagtodrawboldtext.ThisisdonewiththelfWeightmember,anumberbetween0and1000.400isnormaltext,valuesabovethisdrawboldtext,andvaluesbelowitdrawlighttext.
注意到这里有不少标记来选取斜体,下划线,凸出文字,但是却没有标记来画粗体。这是因为用lfWeight成员来代替了,这个成员的数值介于0与1000之间。400是正常值,高于这个值的是粗体,低于这个值的是细体。
ThecodeinFigure10drawstextatanglesrangingfrom0degreesto360degrees,at20-degreeintervals.It'stheform'sOnPainteventhandler,sothetextisredrawneachtimetheformispainted.Figure11showstheresult.
Figure10中的代码从0度到360度每隔20度就画一次字符。这是在窗体的OnPaint事件中触发的,所以文字在窗体每次描绘时重画。在Figure11可以看到效果。
procedureTForm1.FormPaint(Sender:TObject);
var
OldFont,NewFont:hFont;
LogFont:TLogFont;
i:Integer;
begin
//Gethandleofcanvasfont.
获取窗体字体对象的句柄
OldFont:=Canvas.Font.Handle;
i:=0;
//Transparentdrawing.
设置透明属性
SetBkMode(Canvas.Handle,Transparent);
//FillLogFontstructurewithinformation
用信息填写LogFont结构
//fromcurrentfont.
从当前字体
GetObject(OldFont,Sizeof(LogFont),@LogFont);
//Anglesrangefrom0to360.
从0到360度
whilei<3600dobegin
//Setescapementtonewangle.
设置文字方向到新的角度
LogFont.lfEscapement:=i;
//Createnewfont.
创建新字体
NewFont:=CreateFontIndirect(LogFont);
//Selectthefonttodraw.
选取字体来输出
SelectObject(Canvas.Handle,NewFont);
//Drawtextatthemiddleoftheform.
在窗体中间输出文字
TextOut(Canvas.Handle,ClientWidthdiv2,
ClientHeightdiv2,'RotatedText',21);
//Cleanup.
清空
DeleteObject(SelectObject(Canvas.Handle,OldFont));
//Incrementangleby20degrees.
每隔20度递增
Inc(i,200);
end;
end;
Figure10:Codetodrawtextrotatedin20-degreeintervals.
Figure11:Textrotated360degrees.
Theform'sfontissettoArial,aTrueTypefont.ThiscodeworksonlywithTrueTypefonts;otherkindsoffontsdon'tsupporttextrotation.TogetcurrentfontsettingsandfilltheTLogFontstructure,youmustusetheGetObjectAPIfunction.ThecodeinFigure12showshowtofillanddisplaytheTLogFontsettingsfortheform'sfont.
-
这个窗体的字体设置成Arial,一种TrueType字体。这段代码仅仅在TrueType字体下才能运行;其它字体不支持文字旋转。为了获取当前字体设置和填写TlogFont结构体,你必须用到GetObject这个API函数。在Figure12中的代码中可以看到如何填写和显示窗体中TlogFont的设置。
procedureTForm1.Info1Click(Sender:TObject);
var
LogFont:TLogFont;
begin
//FillLogFontstructurewithinformation
填写LogFont结构体的成员值
//fromcurrentfont.
从当前字体
GetObject(Canvas.Font.Handle,Sizeof(LogFont),@LogFont);
//Displayfontinformation.
显示字体信息
withLogFontdoShowMessage(
'lfHeight:'
IntToStr(lfHeight) #13
'lfWidth:' IntToStr(lfWidth) #13
'lfEscapement:'
IntToStr(lfEscapement) #13
'lfOrientation:' IntToStr(lfOrientation) #13
'lfWeight:' IntToStr(lfWeight) #13
'lfItalic:' IntToStr(lfItalic) #13
'lfUnderline:' IntToStr(lfUnderline) #13
'lfStrikeOut:'
IntToStr(lfStrikeOut) #13
'lfCharSet:' IntToStr(lfCharSet) #13
'lfOutPrecision:' IntToStr(lfOutPrecision) #13
'lfClipPrecision:'
IntToStr(lfClipPrecision) #13
'lfQuality:' IntToStr(lfQuality) #13
'lfPitchAndFamily:' IntToStr(lfPitchAndFamily) #13
'lfFaceName:'
string(lfFaceName));
end;
Figure12:Gettinganddisplayingfontattributes.
OnceyouhavethesettingsinaTLogFontstructure,theonlychangeleftistosetlfEscapementtothedesiredangleandcreateanewfontwithCreateFontIndirect.Beforeusingthisnewfont,itmustbeselectedwithSelectObject.Anotherwayistoassignthehandleofthisnewfonttothehandleofthecanvas'sfont,beforedrawingthetext.Afterdrawingthetext,thisworkmustbereversed;theoldfontmustbeselected,andthenewfontdeleted.Ifthenewfontisn'tdeleted,therewillbeamemoryleak,and-iftheroutineisexecutedmanytimes-Windows(especially95/98)willrunoutofresources,andcrash.
一旦你已设置好了TlogFont结构体,剩下唯一要做的事是改变lfEscapement的值为目的值并且用CreateFontIndirect来产生一个新字体。在使用这个新字体之前,必须用SelectObject来选择它。另一种方法是在描绘文字之前用这个新字体对象的句柄赋给窗体的canvas的字体对象的句柄。在描绘完文字后,这个过程要巅倒;旧字体必须被选中,新字体被删除。如果新字体没有被删除,会造成内存泄漏,并且-----如果程序被执行多次------Windows(尤其是95/98)会耗尽资源,并且
死机。
StylishLines
流行的线条
Whenyoudrawlines,theindividualpixelsusuallydon'tmatter;yousimplysetthelinestyle,andit'sdrawnbyWindows.Sometimeshowever,youneedtodosomethingspecialanddrawalinestylenotprovidedbyWindows.ThiscanbedoneusingaWindowsAPIfunctionnamedLineDDA,definedinFigure13.
当你描绘线条时,单独的象素通常是不重要的;你只须简单地设置线条的类型,它将交给Windows来描绘。然而有时你想要做一些特殊的并且Windows没有提供的线条类型。这可以用一个名叫LineDDA的API函数来实现,在Figure13中可以看到它的定义。
functionLineDDA(
nXStart,
//x-coordinateofline'sstartingpoint.
X坐标起点
nYStart,
//y-coordinateofline'sstartingpoint.
Y坐标起点
nXEnd,
//x-coordinateofline'sendingpoint.
X坐标终点
YEnd:Integer;//y-coordinateofline'sendingpoint.
Y坐标终点
//Addressofapplication-definedcallbackfunction.
应用程序定义的回调函数的地址
lpLineFunc:TFNLineDDAProc;
lpData:LPARAM//Addressofapplication-defineddata.
应用程序定义的数据的地址
):BOOL;stdcall;
Figure13:ObjectPascaldeclarationfortheWindowsAPIfunction,LineDDA.
Thefirstfourparametersarethestartingandendingpointsoftheline.Thefifthparameterisacallbackfunctionthatwillbecalledeverytimeapixelshouldbedrawn.Youputyourdrawingroutinesthere.Thelastparameterisauserparameterthatwillbepassedtothecallbackfunction.YoucanpassanyIntegerorpointertothefunction,becauseitisanLParam(inWin32,itistranslatedtoaLongint).Thecallbackfunctionmusttaketheformshownhere:
这开始的四个参数是线条的开始和结束点。第五个参数是一个回调函数,每次像素被描绘时都将被调用到。你可以将你的描绘过程写在这里。最后一个参数是用户定义的可以传给回调函数。你可以传递任何整数或指针给这个函数,因为它是
一个Lparam型(在WIN32,它是被解释成Longint型)。这个回调函数必须使用象如下的形式:
procedureCallBackDDA(x,y:Integer;
UsERParam:LParam);stdcall;
wherexandyarethecoordinatesofthedrawnpoint,andUserParamisaparameterthatispassedtothefunction.Thisfunctionmustbedeclaredasstdcall.TheroutineinFigure14drawsalineofbitmaps,andFigure15showstheresult.
这里X和Y都是被描绘的坐标点,而UserParam是一个参数。这个函数必须被子定义为stdcall。Figure14中的程序描绘了一个BMP线条,而Figure15则显示结果。
type
TForm1=class(TForm)
ImageList1:TImageList;
procedureFormPaint(Sender:TObject);
procedureFormResize(Sender:TObject);
end;
var
Form1:TForm1;
procedureCallDDA(x,y:Integer;Form:TForm1);stdcall;
implementation
{$R*.DFM}
procedureCallDDA(x,y:Integer;Form:TForm1);
begin
ifxmod13=0then
Form.ImageList1.Draw(Form.Canvas,x,y,0);
end;
procedureTForm1.FormPaint(Sender:TObject);
begin
LineDDA(0,0,ClientWidth,ClientHeight,
@CallDDA,Integer(Self));
end;
procedureTForm1.FormResize(Sender:TObject);
begin
Invalidate;
end;
Figure14:Codetodrawalineofbitmaps.
Figure15:Windowwithacustomline.
Thisroutinehandlestheform'sOnPaintevent,callingLineDDA,soeverytimetheformmustbepainted,itredrawstheline.AnothereventthatishandledisOnResize,whichinvalidatestheformclientarea,sothelinemustberedrawnwhensomeonechangesitssize.TheLineDDAcallbackfunction,CallDDA,isverysimple.Atevery13thpointitiscalled,itdrawsthebitmapstoredintheImageList.Asyoumaynotice,Selfispassedasthelastparametertothecallbackfunction,soitcanAccesstheinstancedata.
这个程序处理窗体的OnPaint事件,调用LineDDA,所以每次窗体被描绘时,它将重画这条线。另一个事件是OnResize,它使窗体的客户区无效,所以当有人改变它的大小时线条亦将重画。LineDDA回调函数,CallDDA都是非常简单的。每当被调用了13次后,它将描绘存贮在ImageList中的位图。也许你注意到,SELF被作为最后一个参数传递给回调函数,所以它可以存取程序的数据。
Conclusion
结论
SinceownerdrawingwasexposedonTMainMenuinDelphi4,therehavebeenmanywaystoaugmentyourmenus.Usingthetechniqueswe'vediscussedhere,youcaneasilyenhanceyourDelphiapplication'smenuswithcustomtext,bitmaps,andcolors.
既然ownerdrawing在Delphi4的TmainMenu中已出现了,它就可以有很多方法来扩展你的菜单功能。使用我们在上面讨论过的技巧,你能够轻易地用自定义文字,位图,和颜色来加强你的DELPHI应用程序的菜单功能。