用C#和VB.NET实现VS.NET或Office XP风格的菜单(三)

类别:.NET开发 点击:0 评论:0 推荐:

用C#和VB.NET实现VS.NET或Office XP风格的菜单

小气的神 2001.08.18

3. “MenuItemStyle”接口和VS.NET风格的菜单项

 

这个Project又将切换到C#语言。我是这样想的:先针对普通菜单、Office200风格、VS.NET风格三种情况定义一个统一的接口(interface),其中包括画IconDrawIcon)、画分割条(DrawSeparator)、画菜单背景(DrawBackground)、写菜单项的文字(DrawMenuText)等功能;普通、Office2000VS.NET根据各自不同的情况实现这个接口的Drawxxx的功能。然后从MenuItem继承一个子类,象第二部分讲的那样Overrides 菜单项的两个函数:OnMeasureItemOnDrawItem,根据不同的风格调用上面实现的接口中的DrawXXX函数就可以了。最后我把这部分都分隔出来放在一个.CS文件中,单独编译成一个VSNET.Menu.DLL,你只用using VSNET.Menu ; 然后就可以象在第一部分那样象使用普通的MenuItem那样来用了,Demo源代码中你还可以看到我定义了IconMenuItem的类,它有一个方法:MenuItemCreatorVSNET.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风格都要实现自己方式的接口,这个接口包括DrawCheckmarkDrawIconDrawMenuTextDrawBackgroundDrawSeparator等函数,可以实现菜单项需要的各种函数。完成这部分后可以从MenuItem继承一个子类来象第二部分一样处理了。看下面的代码,具体考察一下熟悉的OnMeasureItemOnDrawItem

        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或是VSNET的风格。这部分和我们第二部分看到的没有什么不同。

            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.NETC#完成一个有VS.NETOffice XP风格的菜单。三个部分是渐进的,如果你以前进行或实验过第二部分讨论的问题,那么第三部分唯一让人感兴趣的是MenuItemStyleDrawer接口的思路。对于整个新的.NET的编程方式上说,原来的VB用户可能会经历一个痛苦的过程,他们的第一反应是Sub ClassHook或是终极的API,而接触过C++、MFCDELPHI甚至VJ++的用户会很容易想到继承,特别时DELPHIVJ的用户入手应当最快了。想想会开始怀念以前VB的时光,因为对于这样的问题,VB用户总是拿着大锤,直接敲个大洞,然后拿到结果;而C++、MFCDEPHI用户则拿着一本说明书,一步一步按指示找到结果;结果可能一样,但两者的方式是截然不同的。好了,这些是题外话了。

 

本文地址:http://com.8s8s.com/it/it46304.htm