为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

用C#和VB¸NET实现Office XP风格的菜单

2018-04-08 17页 doc 45KB 21阅读

用户头像

is_215732

暂无简介

举报
用C#和VB¸NET实现Office XP风格的菜单用C#和VB¸NET实现Office XP风格的菜单 用C#和VB.NET实现Office XP风格的菜单 www.chinacs.net 2001-8-28 中文C#技术站 VS.NET或Office XP中的菜单都是非常漂亮的,反正我很喜欢。可惜VS.NET没有带制作这种菜单的控件或组件,不知正式版本会不会提供一个模板和向导。至今还记得刚学计算机语言时自己用Turbo C制作菜单的感受,那些矩形框函数和象素操作的确很迷人,况且那时是如此的流行菜单。 这篇文章中我会介绍有关在Framework SDK Beta 2 中...
用C#和VB¸NET实现Office XP风格的菜单
用C#和VB¸NET实现Office XP风格的菜单 用C#和VB.NET实现Office XP风格的菜单 www.chinacs.net 2001-8-28 中文C#技术站 VS.NET或Office XP中的菜单都是非常漂亮的,反正我很喜欢。可惜VS.NET没有带制作这种菜单的控件或组件,不知正式版本会不会提供一个模板和向导。至今还记得刚学计算机语言时自己用Turbo C制作菜单的感受,那些矩形框函数和象素操作的确很迷人,况且那时是如此的流行菜单。 这篇文章中我会介绍有关在Framework SDK Beta 2 中制作自己风格的菜单,所以你最好已安装了Framework SDK Beta 2,VS.NET Beta 2 不一定是必须的。附带的Zip包中的例子都是VS.NET Project的。 整个的文章包括三部分, 开始我会涉及到在Winform中最基本的一些菜单的概念。 然后会有一个以前接触过的有关菜单的例子,它是For Beta 1的。老实说我没有想到Beta1 到Beta2有许多函数和命名空间发生了变化,以前我在Beta1中测试过这个例子,很顺利。这次在Beta2中会有许多错误,我提供了两个Project,一个是原来的Project的,一个是我修改后For Beta 2的。这种移植很枯燥,但可以很快熟悉新的Beta2的类库和函数,Show出来的菜单还不错,感觉是Office2000风格的菜单。如果你有兴趣可以试一试这个过程,会获益非浅的,这个例子还包括按钮的,原来的作者其实是在Demo控件的“Owner-drawn menus”技术,不过我只对菜单部分感兴趣。 最后一部分是制作VS.NET或XP风格的例子,上面那个例子的效果不能使我完全满意,然后我重新写了另外一个,不过我对最后的结果还不是很满意,因为我的没有上一个例子那么完整,例子中我只显示了这个风格的菜单,对于事件响应、状态栏更新、tooltips、菜单的状态,enabled state,等处理都没有考虑,我把这些归结为时间问题,并承诺自己下次把它做得更好。 1,Framework SDK Beta 2中菜单分成两类一类是普通的菜单叫,MainMenu,在VS.NET的Toolsbox中有这样一个对应的菜单控件,拖下它到你的窗体中,设置一下属性就可以所见所得了,这个版本的比VS.Studio98 系列的要好用和漂亮的多。另一类叫,ContextMenu菜单,也就是常用的弹出菜单。对于VB6来说所有的普通菜单在VS.NET中是可以兼容和自动升级成MainMenu类型的菜单,但对于PopMenu的菜单是不能转换成ContextMenu类型的菜单,你必须自己重新修改代码实现。这里我们主要是针对MainMenu的,其实原理一样。 最简单的菜单你可以这样做, using System; using System.Windows.Forms; public class frmVB6 : Form { private MainMenu muMain ; // MainMenu public static int Main(string[] Args) { Application.Run(new frmVB6()); return 0; } public frmVB6() { // The following code sets up the form properties. this.Text = "Form1"; this.Height = 213 + SystemInformation.CaptionHeight; this.Width = 312; this.StartPosition = FormStartPosition.WindowsDefaultLocation; MenuItem mItemFile = new MenuItem() ; mItemFile.Text = "&File" ; MenuItem mItemExit = new MenuItem() ; mItemExit.Text = "E&xit" ; muMain = new MainMenu() ; muMain.MenuItems.Add( mItemFile ) ; muMain.MenuItems.Add( mItemExit) ; this.Menu = muMain ; } } 手工方式保存它为一个.cs文件然后在编译它, csc /t:winexe /r:System.dll /r:System.Windows.Forms.Dll /r:System.Drawing.Dll Form1Menu.cs VS.NET下只用New一个新的WinForm项目,然后在默认窗体中放入 MainMenu控件,然后设置完属性,F5就可以了,完全不用一行代码。 如果要生成一个主菜单和一个菜单的子菜单项目,主要是 MenuItems.AddRange的方法,看下面的代码, this.mainMenu1 = new System.Windows.Forms.MainMenu(); this.menuItem1 = new System.Windows.Forms.MenuItem(); this.menuItem2 = new System.Windows.Forms.MenuItem(); this.menuItem3 = new System.Windows.Forms.MenuItem(); this.menuItem4 = new System.Windows.Forms.MenuItem(); // mainMenu1 this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.menuItem1, this.menuItem2}); // menuItem1 this.menuItem1.Index = 0; this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { this.menuItem3, this.menuItem4}); this.menuItem1.Index = 0 ; this.menuItem1.Text = "&File"; // menuItem2 this.menuItem2.Index = 1; this.menuItem2.Text = "Help"; // menuItem3 this.menuItem3.Index = 0; this.menuItem3.Text = "Open"; this.menuItem3.Click += new System.EventHandler(this.menuItem3_Click); // menuItem4 this.menuItem4.Index = 1; this.menuItem4.Text = "Exit"; this.menuItem4.Click += new System.EventHandler(this.menuItem4_Click); this.Menu = this.mainMenu1; 如代码所示MenuItem1(File)和MenuItem2(Help)被AddRange到MainMenu1中成为了顶级的菜单,MenuItem3(Open)和 MenuItem4(Exit)被AddRange到MenuItem1(File)中成为了File菜单下的子菜单项。 this.menuItem3.Click += new System.EventHandler(this.menuItem3_Click);表明MenuItem3点击时激发的事件处理程序,一般的事件处理程序象下面这样, private void menuItem3_Click(object sender, System.EventArgs e) { MessageBox.Show ( " My Click Open" ) ; } 我们关心的其实是MenuItem,让它用我们的方式画出有VS.NET或XP风格的菜单就可以了,更简单的说就是实现一个MenuItem的继承类,扩展它Draw的部分。好吧,让我们深入一点看看第二部分。 2,“Owner-drawn menus”技术 这个例子是VB.NET语法的.我去掉了和Menu无关的Class,原因是错误太多,你会遇到类库和命名空间的移植性的问题, 最多的是Beta1 System.WinForms 和Beta 2 的 System.Windows.Froms的命名空间问题, 然后是Beta1中的BitAnd 、BitOR等等Bitxxx的函数在Beta2中已去掉了Bit又和VB中一样了,据说Beta1的这项改动遭到了总多VB Fans的投诉,说不能把VB也C,化,Bit是什么东东,,这样你需要把这类函数改掉, 然后是NameObjectCollectionBase从原来的system.collections中删除了,Beta2放在system.collections.specialized 中,真的有些昏倒,开始我还以为Beta2中删除了这个类。 最后是一些Overrides和 Overloads的问题,具体的看VS.NET或Framework SDK Beta 2编译时的提示就可以了,这方面MS做得不错,Task list中告诉你具体得建议,照做就是了。 具体一点你可以在Framework SDK Beta 2安装目录的Doc目录中找到这两个文件,这是从Beta1移植到Beta2上不错的指导文件,APIChangesBeta1toBeta2.htm 和Change List - Beta1 to Beta2.doc 特别是这个doc文件洋洋洒洒90多页,但很有帮助。 希望你还能在排除所有的错误之后保持清醒,找到最核心有用的代码,来。主要是CActionMenu.vb,焦点在OnMeasureItem和OnDrawItem这两个函数或说事件处理程序上。OnMeasureItem主要是处理 MenuItem的ItemHeight和ItemWidth的,从它传的 MeasureItemEventArgs参数数就知道。OnDrawItem主要是如何画菜单的问题。关键字Overrides表明我们要在子类中重新定义MenuItem中的这两个方法。 从56行到58行是OnMeasureItem函数, Protected Overrides Sub OnMeasureItem(ByVal e As System.Windows.Forms.MeasureItemEventArgs) If Me.Action.Caption = "-" Then e.ItemHeight = 5 Else e.ItemHeight = 20 End If Dim fs As FontStyle If Me.DefaultItem = True Then fs = fs Or FontStyle.Bold Dim fnt As New Font("Tahoma", 8, fs) Dim sf As SizeF = e.Graphics.MeasureString(Me.Action.Caption, fnt) fnt.Dispose() e.ItemWidth = CInt(sf.Width) + 20 End Sub MeasureItemEventArgs提供4个属性Graphis、Index、ItemHeight和ItemWidth。Me相当于C,或Java的this关键字。fnt.Dispose()中 Dispose是一个很有意思的函数调用,在以往的Windows编程中象字体、 画笔等许多资源都希望快使用快释放,这个语句是用来控制GC,garbage collection,的,意思是我已使用完了这个设备或资源,GC你可以收回了。 从70到146行是有关OnItemDraw函数的, Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs) ' colors, fonts Dim clrBgIcon, clrBgText, clrText As Color, fs As FontStyle, fnt As Font Dim b As SolidBrush, p As Pen Dim fEnabled As Boolean = Not CType(e.State And DrawItemState.Disabled, Boolean) Dim fSelected As Boolean = CType(e.State And DrawItemState.Selected, Boolean) Dim fDefault As Boolean = CType(e.State And DrawItemState.Default, Boolean) Dim fBreak As Boolean = (Me.Action.Caption = "-") If fEnabled And fSelected And Not fBreak Then clrBgIcon = Color.Silver clrBgText = Color.White clrText = Color.Blue fs = fs Or FontStyle.Underline Else clrBgIcon = Color.Gray clrBgText = Color.Silver clrText = Color.Black End If If Not fEnabled Then clrText = Color.White End If If fDefault Then fs = fs Or FontStyle.Bold End If fnt = New Font("Tahoma", 8, fs) ' total background (partly to remain for icon) b = New SolidBrush(clrBgIcon) e.Graphics.FillRegion(b, New [Region](e.Bounds)) b.Dispose() ' icon? If Not Me.Action.ActionList Is Nothing Then Dim il As ImageList = Me.Action.ActionList.ImageList If Not il Is Nothing Then Dim index As Integer = Me.Action.Image If index > -1 And index < il.Images.Count Then Dim rect As Rectangle = e.Bounds With rect .X += 2 .Y += 2 .Width = 16 .Height = 16 End With e.Graphics.DrawImage(il.Images.Item(index), rect) End If End If End If ' text background Dim rf As RectangleF With rf .X = 18 .Y = e.Bounds.Y .Width = e.Bounds.Width - .X .Height = e.Bounds.Height End With b = New SolidBrush(clrBgText) e.Graphics.FillRegion(b, New [Region](rf)) b.Dispose() ' text/line rf.Y += 3 : rf.Height -= 3 If Not fBreak Then b = New SolidBrush(clrText) e.Graphics.DrawString(Me.Action.Caption, fnt, b, rf) fnt.Dispose() b.Dispose() Else p = New Pen(Color.Black) rf.Y -= 1 e.Graphics.DrawLine(p, rf.X, rf.Y, rf.Right, rf.Y) p.Dispose() End If ' border If fEnabled And fSelected And Not fBreak Then p = New Pen(Color.Black) e.Graphics.DrawRectangle(p, e.Bounds) p.Dispose() End If End Sub DrawItemEventArgs参数给了你和菜单相关的所有环境和信息,它包括6个属性,Bounds、Font、ForeColor、Graphics、Index、States。如果你以前用过Windows下的GDI函数,那一定很熟悉这些函数,不是很复杂只需要你一点点算术知识和美术观点就可以了,如果你是第一次那么在纸上画几个矩形块就可以了理解和做的很好,比起以前TC下的菜单编程容易得多。主要是作者是如何把Icon画在菜单上的,然后是根据不同的States表现一下菜单的ForeColor, Bounds就是菜单项最前面的表示选中等等的小方块。 好了第二部分涉及到了大部分技术细节了,这里你需要关注的是,如何画出来,下一部分我们来看如何画的好看些,象VS.NET或Office XP那样子。 3. “MenuItemStyle”接口和VS.NET风格的菜单项 这个Project又将切换到C,语言。我是这样想的,先针对普通菜单、Office200风格、VS.NET风格三种情况定义一个统一的接口,interface,,其中包括画Icon,DrawIcon,、画分割条,DrawSeparator,、画菜单背景,DrawBackground,、写菜单项的文字,DrawMenuText,等功能,普通、Office2000和VS.NET根据各自不同的情况实现这个接口的Drawxxx的功能。然后从MenuItem继承一个子类,象第二部分讲的那样Overrides 菜单项的两个函数,OnMeasureItem和OnDrawItem,根据不同的风格调用上面实现的接口中的DrawXXX函数就可以了。最后我把这部分都分隔出来放在一个.CS文件中,单独编译成一个 VSNET.Menu.DLL,你只用using VSNET.Menu ; 然后就可以象在第一部分那样象使用普通的MenuItem那样来用了,Demo源代码中你还可以看到我定义了IconMenuItem的类,它有一个方法,MenuItemCreator,VSNET.Menu.IconMenuStyle sType , String sText , Bitmap bmp , System.EventHandler eh,可以完成生成需要的MenuItem。本来我想用资源文件或将图片Icon等资源放在一个专门的文件中,然后由这个类来负责从资源文件或外部的类中获得资源CreateMenuItem。但是是第一版,你会看到例程中我仍然用原始的New Bitmap,,的方式直接从硬盘拿资源。当我看到它show出来时,先是很开心,然后发现还有许多要改进,想想其实做一个专业的菜单也需要花许多心思。 好吧让我们看一下有关VS.NET风格菜单项这部分主要的实现代码, public class VSNetStyle : MenuItemStyleDrawer { static Color bgcolor = Color.FromArgb(246, 246, 246); static Color ibgcolor = Color.FromArgb(202, 202, 202); static Color sbcolor = Color.FromArgb(173, 173, 209); static Color sbbcolor = Color.FromArgb( 0, 0, 128); static int TEXTSTART = 20; public void DrawCheckmark(Graphics g, Rectangle bounds, bool selected) { ControlPaint.DrawMenuGlyph(g, new Rectangle(bounds.X + 2, bounds.Y + 2, 14, 14), MenuGlyph.Checkmark); } public void DrawIcon(Graphics g, Image icon, Rectangle bounds, bool selected, bool enabled, bool ischecked) { if (enabled) { if (selected) { ControlPaint.DrawImageDisabled(g, icon, bounds.Left + 2, bounds.Top + 2, Color.Black); g.DrawImage(icon, bounds.Left + 1, bounds.Top + 1); } else { g.DrawImage(icon, bounds.Left + 2, bounds.Top + 2); } } else ControlPaint.DrawImageDisabled(g, icon, bounds.Left + 2, bounds.Top + 2, SystemColors.HighlightText); } public void DrawSeparator(Graphics g, Rectangle bounds) { int y = bounds.Y + bounds.Height / 2; g.DrawLine(new Pen(SystemColors.ControlDark), bounds.X + SystemInformation.SmallIconSize.Width + 7, y, bounds.X + bounds.Width - 2, y); } public void DrawBackground(Graphics g, Rectangle bounds, DrawItemState state, bool toplevel, bool hasicon) { bool selected = (state & DrawItemState.Selected) > 0; if (selected || ((state & DrawItemState.HotLight) > 0)) { if (toplevel && selected) { // draw toplevel, selected menuitem g.FillRectangle(new SolidBrush(ibgcolor), bounds); ControlPaint.DrawBorder3D(g, bounds.Left, bounds.Top, bounds.Width, bounds.Height, Border3DStyle.Flat, Border3DSide.Top | Border3DSide.Left | Border3DSide.Right); } else { // draw menuitem, selected OR toplevel, hotlighted g.FillRectangle(new SolidBrush(sbcolor), bounds); g.DrawRectangle(new Pen(sbbcolor), bounds.X, bounds.Y, bounds.Width - 1, bounds.Height - 1); } } else { if (!toplevel) { // draw menuitem, unselected g.FillRectangle(new SolidBrush(ibgcolor), bounds); bounds.X += SystemInformation.SmallIconSize.Width + 5; bounds.Width -= SystemInformation.SmallIconSize.Width + 5; g.FillRectangle(new SolidBrush(bgcolor), bounds); } else { // draw toplevel, unselected menuitem g.FillRectangle(SystemBrushes.Menu, bounds); } } } public void DrawMenuText(Graphics g, Rectangle bounds, string text, string shortcut, bool enabled, bool toplevel, DrawItemState state) { StringFormat stringformat = new StringFormat(); stringformat.HotkeyPrefix = ((state & DrawItemState.NoAccelerator) > 0) ? HotkeyPrefix.Hide : HotkeyPrefix.Show; int textwidth = (int)(g.MeasureString(text, SystemInformation.MenuFont).Width); int x = toplevel ? bounds.Left + (bounds.Width - textwidth) / 2: bounds.Left + TEXTSTART; int y = bounds.Top + 2; Brush brush = null; if (!enabled) brush = new SolidBrush(Color.FromArgb(120, SystemColors.MenuText)); else brush = new SolidBrush(Color.Black); g.DrawString(text, SystemInformation.MenuFont, brush, x, y, stringformat); g.DrawString(shortcut, SystemInformation.MenuFont, brush, bounds.Left + 130, bounds.Top + 2, stringformat); } } MenuItemStyleDrawer就是那个公用的接口类,无论普通风格、 Office2000还是VS.NET风格都要实现自己方式的接口,这个接口包括 DrawCheckmark、DrawIcon、DrawMenuText、DrawBackground、 DrawSeparator等函数,可以实现菜单项需要的各种函数。完成这部分后 可以从MenuItem继承一个子类来象第二部分一样处理了。看下面的代码, 具体考察一下熟悉的OnMeasureItem和OnDrawItem, protected override void OnMeasureItem(MeasureItemEventArgs e) { base.OnMeasureItem(e); // make shortcut text 省略这部分代码。 if (menustyle != IconMenuStyle.Standard) { if (Text == "-") { e.ItemHeight = 8; e.ItemWidth = 4; return; } int textwidth = (int)(e.Graphics.MeasureString(Text + shortcuttext, SystemInformation.MenuFont).Width); e.ItemHeight = SystemInformation.MenuHeight; if (Parent == Parent.GetMainMenu()) e.ItemWidth = textwidth - 5; // 5 is a magic number else e.ItemWidth = Math.Max(160, textwidth + 50); } } IconMenuStyle.Standard是个enum表明是普通风格、Office2000或 是VS。NET的风格。这部分和我们第二部分看到的没有什么不同。 protected override void OnDrawItem(DrawItemEventArgs e) { base.OnDrawItem(e); Graphics g = e.Graphics; Rectangle bounds = e.Bounds; bool selected = (e.State & DrawItemState.Selected) > 0; bool toplevel = (Parent == Parent.GetMainMenu()); bool hasicon = Icon != null; style.DrawBackground(g, bounds, e.State, toplevel, hasicon); if (hasicon) style.DrawIcon(g, Icon, bounds, selected, Enabled, Checked); else if (Checked) style.DrawCheckmark(g, bounds, selected); if (Text == "-") { style.DrawSeparator(g, bounds); } else { style.DrawMenuText(g, bounds, Text, shortcuttext, Enabled, toplevel, e.State); } } 刚刚我们说的MenuItemStyleDrawer接口的好处在这里显示出来,整个 过程显得简单明了,具体实现得代码不是很多。当这个类完成后,剩下来的就是使用了它了,这部分象第一部分所述,你可以在一个顶级菜单项的子菜单项声明成IconMenu类型的也就是我们实现的继承MenuItem的类,简单的代码象下面这样, private System.Windows.Forms.MenuItem mItems1 ; System.Drawing.Bitmap Bitmap1 = new Bitmap( BMPPATHSTR + "Open.bmp") ; mItems1 = iMenuItem.MenuItemCreator( MenuStyle , "&Open" , Bitmap1, 这个mItem1就是一个VS.NET风格的菜单项了。具体的可以看附带的 Project和屏幕截图。 至此我们完成了用VB.NET或C#完成一个有VS.NET或Office XP风格 的菜单。三个部分是渐进的,如果你以前进行或实验过第二部分讨论的问题,那么第三部分唯一让人感兴趣的是MenuItemStyleDrawer接口的思路。 对于整个新的.NET的编程方式上说,原来的VB用户可能会经历一个痛苦 的过程,他们的第一反应是Sub Class、Hook或是终极的API,而接触过C,,、MFC、DELPHI甚至VJ,,的用户会很容易想到继承,特别时DELPHI和VJ的用户入手应当最快了。想想会开始怀念以前VB的时光,因为对于这样的问题,VB用户总是拿着大锤,直接敲个大洞,然后拿到结果,而C,,、MFC、DEPHI用户则拿着一本说明书,一步一步按指示找到结果,结果可能一样,但两者的方式是截然不同的。好了,这些是题外话了。
/
本文档为【用C#和VB&cedil;NET实现Office XP风格的菜单】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索