Click here to Skip to main content
15,886,091 members
Articles / Desktop Programming / Windows Forms

Owner Drawn Component - Extended MenuItem

Rate me:
Please Sign up or sign in to vote.
4.50/5 (8 votes)
18 Mar 2007CPOL5 min read 43.8K   1.1K   46  
This is the second installment in the Owner Drawn Controls' series of articles
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.ComponentModel;
using System.Collections;

//================================================================
//
//  Created: 3/17/07
//
//  Namespace: MenuTest
//  
//  Component: XMenuItemExtender
//
//  Author: Mike Hankey
//
//  Special Thanks: The IExtenderProvider concept was provided by
//  a CodeProject article:
//      "Getting to know IExtenderProvider" by James T. Johnson
//
//  Definition:
//
//================================================================

namespace MenuTest
{
    #region Extender stuff

    /// <summary>
    /// Properties provided by extender
    /// </summary>
    [ProvideProperty("XPMenuItem", typeof(MenuItem))]
    [ProvideProperty("XPMenuItemImage", typeof(MenuItem))]
    [ProvideProperty("XPHeaderText", typeof(MenuItem))]
    [ProvideProperty("XPHeaderImage", typeof(MenuItem))]
    class XMenuItemExtender : Component, IExtenderProvider  
    {
        //Required Hash Table to cache MenuItem components
        private Hashtable properties;

        //The checkBox image displayed when item checked
        private Image _checkImage = null;

        public XMenuItemExtender()
        {
            properties = new Hashtable();
            _checkImage = global::MenuTest.Properties.Resources.OK_CheckBox;
        }

        public XMenuItemExtender(IContainer parent) : this()
        {
            parent.Add(this);
            _checkImage = global::MenuTest.Properties.Resources.OK_CheckBox;
        }

        bool IExtenderProvider.CanExtend(object o)
        {
            if (o is MenuItem)
                return true;

            return false;
        }

        private Properties EnsurePropertyExists(object key)
        {
            Properties p = (Properties) properties[key];

            if (p == null)
            {
                p = new Properties();
                //After creating the property we assign a reference to the 
                //  MenuItem associated with it.  If we didn't do this we would
                //  have to manually assign the item to the extender in the designer.
                p._menuItem = (MenuItem) key;
                properties[key] = p;
            }
            return p;
        }

        /// <summary>
        /// Class containing provider fields
        /// </summary>
        internal class Properties
        {
            public MenuItem _menuItem = null;
            public Image _menuImage = null;
            public string _headerText = string.Empty;
            public Image _headerImage = null;
            public Properties()
            {
            }
        }

        /// <summary>
        /// XPMenuItem Property
        /// </summary>
        /// <param name="mi"></param>
        /// <returns></returns>
        public MenuItem GetXPMenuItem(MenuItem mi)
        {
            return EnsurePropertyExists(mi)._menuItem;
        }

        public void SetXPMenuItem(MenuItem mis, MenuItem mid)
        {
            EnsurePropertyExists(mis)._menuItem = mid;

            //Add or remove event handlers for the required events
            if (mid != null)
            {
                mis.MeasureItem += new MeasureItemEventHandler(OnMeasureItem);
                mis.DrawItem += new DrawItemEventHandler(OnDrawItem);
                mis.Click += new EventHandler(OnClick);
            }
            else
            {
                mis.MeasureItem -= new MeasureItemEventHandler(OnMeasureItem);
                mis.DrawItem -= new DrawItemEventHandler(OnDrawItem);
                mis.Click -= new EventHandler(OnClick);
            }
        }

        /// <summary>
        /// XPMenuItemImage Property
        /// </summary>
        /// <param name="mi"></param>
        /// <returns></returns>
        public Image GetXPMenuItemImage(MenuItem mi)
        {
            return EnsurePropertyExists(mi)._menuImage;
        }

        public void SetXPMenuItemImage(MenuItem mi, Image img)
        {
            EnsurePropertyExists(mi)._menuImage = img;
        }

        /// <summary>
        /// XPHeaderText Property
        /// </summary>
        /// <param name="mi"></param>
        /// <returns></returns>
        public string GetXPHeaderText(MenuItem mi)
        {
            return EnsurePropertyExists(mi)._headerText;
        }

        public void SetXPHeaderText(MenuItem mi, string str)
        {
            EnsurePropertyExists(mi)._headerText = str;
        }

        /// <summary>
        /// XPHeaderImage Property
        /// </summary>
        /// <param name="mi"></param>
        /// <returns></returns>
        public Image GetXPHeaderImage(MenuItem mi)
        {
            return EnsurePropertyExists(mi)._headerImage;
        }

        public void SetXPHeaderImage(MenuItem mi, Image img)
        {
            EnsurePropertyExists(mi)._headerImage = img;
        }

    #endregion

        #region Owner Drawn Event Handlers

        /// <summary>
        /// Handles any custom Mouse click handling for the item.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnClick(object sender, EventArgs e)
        {
            MenuItem mi = (MenuItem)sender;
            if (mi.Checked)
                mi.Checked = false;
        }

        private void OnMeasureItem(object sender, MeasureItemEventArgs e)
        {
            MenuItem mi = (MenuItem)sender;
            
            if (mi.Text == "-")
                e.ItemHeight = 3;
            else
            {
                if (mi.Parent == mi.Parent.GetMainMenu())
                {
                    e.ItemHeight = 16;
                    e.ItemWidth = 50;
                }
                else
                {
                    e.ItemHeight = 20;
                    e.ItemWidth = 100;
                }
            }
        }

        private void OnDrawItem(object sender, DrawItemEventArgs e)
        {
            MenuItem mi = (MenuItem)sender;

            Rectangle rct = new Rectangle(e.Bounds.X + 20, e.Bounds.Y, e.Bounds.Width - 20, e.Bounds.Height);

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

            if (mi.Parent == mi.Parent.GetMainMenu())
                DrawRootMenuItem(e, mi);
            else
            {
                if (e.Index == 0)
                    DrawMenuHeader(e, mi);

                DrawMenuImage(e, mi);
                if (mi.Text == "-")
                    DrawSeparator(e);
                else
                    DrawMenuItem(e, rct, mi);
            }
        }

        #endregion

        #region Helper methods

        private void DrawSeparator(DrawItemEventArgs e)
        {
            Color clr2a = Color.FromArgb(172, 175, 137);
            Rectangle rct = new Rectangle(
                new Point(e.Bounds.X + 42, e.Bounds.Y),
                new Size(e.Bounds.Width - 44, e.Bounds.Height));
            SolidBrush br = new SolidBrush(clr2a);

            e.Graphics.FillRectangle(br, rct);

            br.Dispose();
        }

        private void DrawRootMenuItem(DrawItemEventArgs e, MenuItem mi)
        {
            GraphicsPath gp = null;
            Rectangle rct = e.Bounds;

            Color clr1 = Color.White;
            Color clr2 = Color.FromArgb(230, 225, 204);
            Rectangle txtRct = new Rectangle(rct.X + 20,
                rct.Y,
                rct.Width - 20,
                rct.Height);
            LinearGradientBrush txtLgb = new LinearGradientBrush(txtRct, clr1, clr2, 90f);

            Color clr1a = Color.FromArgb(120, Color.White);
            Color clr2a = Color.FromArgb(140, 172, 175, 137);
            Rectangle selRct = new Rectangle(rct.X + 2,
                rct.Y + 2,
                rct.Width - 4,
                rct.Height - 4);
            LinearGradientBrush selLgb = new LinearGradientBrush(selRct, clr1a, clr2a, 90f);

            gp = DrawBorder(e, rct, BorderTypes.rounded);
            DrawItemState state = e.State & DrawItemState.Selected;
            if (state == DrawItemState.Selected)
                e.Graphics.FillRectangle(selLgb, rct);
            else
                e.Graphics.FillPath(txtLgb, gp);
            
           Image img = ((Properties) properties[mi])._menuImage;
           string str = mi.Text;
           Font fnt = new Font("Arial", 8, FontStyle.Bold | FontStyle.Italic);
           SolidBrush br = new SolidBrush(Color.DimGray);
           SizeF sz = e.Graphics.MeasureString(str, fnt);

           if (img != null)
           {
               rct.Size = new Size(16, 16);
               rct.Offset(2, 2);
               e.Graphics.DrawImage(img, rct);
           }

            txtRct.Y = e.Bounds.Y + (int)(txtRct.Height - sz.Height) / 2;
            e.Graphics.DrawString(str, fnt, br, txtRct);

            txtLgb.Dispose();
            selLgb.Dispose();
            fnt.Dispose();
            br.Dispose();
        }

        private void DrawMenuImage(DrawItemEventArgs e, MenuItem mi)
        {
            Rectangle rct = new Rectangle(e.Bounds.X + 20,
                e.Bounds.Y, 20, e.Bounds.Height + 1);

            SolidBrush br = new SolidBrush(Color.FromArgb(230, 225, 204));

            e.Graphics.FillRectangle(br, rct);

            Image img = null;
            if (mi.Checked)
                img = _checkImage;
            else
                img = ((Properties)properties[mi])._menuImage;
           
            if (img != null)
                e.Graphics.DrawImage(img, new Rectangle(rct.X + 2, rct.Y + 2, 16, 16));

            br.Dispose();
        }

        private void DrawMenuItem(DrawItemEventArgs e, Rectangle rct, MenuItem mi)
        {
            Color clr1 = Color.White;
            Color clr2 = Color.FromArgb(230, 225, 204);
            Rectangle txtRct = new Rectangle(rct.X + 20,
                rct.Y,
                rct.Width - 20,
                rct.Height);
            LinearGradientBrush txtLgb = new LinearGradientBrush(txtRct, clr1, clr2, 90f);

            Color clr1a = Color.FromArgb(140, Color.White);
            Color clr2a = Color.FromArgb(140, 172, 175, 137);
            Rectangle selRct = new Rectangle(rct.X + 2,
                rct.Y + 2,
                rct.Width - 4,
                rct.Height - 4);
            LinearGradientBrush selLgb = new LinearGradientBrush(selRct, clr1a, clr2a, 90f);

            DrawItemState state = e.State & DrawItemState.Selected;

            SolidBrush br = null;
            Font fnt = null;
            if (state == DrawItemState.Selected)
            {
                e.Graphics.DrawRectangle(new Pen(Color.Black, 1), selRct);
                e.Graphics.FillRectangle(selLgb, selRct);

                fnt = new Font("Arial", 8, FontStyle.Bold | FontStyle.Italic);
                br = new SolidBrush(Color.Black);
            }
            else
            {
                e.Graphics.FillRectangle(txtLgb, txtRct);

                fnt = new Font("Arial", 8, FontStyle.Bold | FontStyle.Italic);
                br = new SolidBrush(Color.SlateGray);
            }

            string str = mi.Text;
            SizeF sz = e.Graphics.MeasureString(str, fnt);

            txtRct.X += 2;
            txtRct.Y += (int)(rct.Height - sz.Height) / 2;
            e.Graphics.DrawString(str, fnt, br, txtRct);

            fnt.Dispose();
            br.Dispose();
            selLgb.Dispose();
            txtLgb.Dispose();

        }

        private void DrawMenuHeader(DrawItemEventArgs e, MenuItem mi)
        {
            Rectangle hrct = new Rectangle(0, 0, 20, mi.Parent.MenuItems.Count * 20);
            Color clr1 = Color.White;
            Color clr3 = Color.FromArgb(194, 196, 176);
            LinearGradientBrush hlgb = new LinearGradientBrush(hrct, clr1, clr3, 0f);
            GraphicsPath gp = null;

            gp = DrawBorder(e, hrct, BorderTypes.none);
            e.Graphics.FillPath(hlgb, gp);

            if (((Properties)properties[mi])._headerImage != null)
            {
                hrct.Size = new Size(16, 16);
                Image img = ((Properties)properties[mi])._headerImage;
                e.Graphics.DrawImage(img, hrct);
            }

            StringFormat strFmt = new StringFormat();
            strFmt.FormatFlags = StringFormatFlags.DirectionVertical;
            Font fnt = new Font("Arial", 8, FontStyle.Bold | FontStyle.Italic);
            SolidBrush br = new SolidBrush(Color.DarkSlateGray);

            string str = ((Properties)properties[mi])._headerText;
            hrct.Y += 16;
            hrct.Height -= 20;
            e.Graphics.DrawString(str, fnt, br, hrct.Location, strFmt);

            br.Dispose();
            fnt.Dispose();
            gp.Dispose();
            hlgb.Dispose();
        }

        private const int ArcWidth = 12;
        public enum BorderTypes
        {
            none,
            square,
            rounded
        };

        /// <summary>
        /// Draw the Border around the item
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        protected GraphicsPath DrawBorder(DrawItemEventArgs e, Rectangle rct, BorderTypes _borderType)
        {
            GraphicsPath gp = new GraphicsPath();

            //rct.Offset(1, 1);
            rct.Width -= 1;
            rct.Height -= 1;

            if (_borderType == BorderTypes.none)
                gp.AddRectangle(rct);

            if (_borderType == BorderTypes.square)
            {
                gp.AddRectangle(rct);
                e.Graphics.DrawRectangle(new Pen(Color.Black, 1), rct);
            }

            if (_borderType == BorderTypes.rounded)
            {
                Rectangle arcRct = new Rectangle(rct.X, rct.Y, ArcWidth, ArcWidth);
                Point pt1 = new Point(rct.X + ArcWidth, rct.Y);
                Point pt2 = new Point(rct.X + rct.Width - ArcWidth, rct.Y);

                gp.AddArc(arcRct, 180, 90);
                gp.AddLine(pt1, pt2);

                arcRct.Location = pt2;
                gp.AddArc(arcRct, 270, 90);

                pt1 = new Point(rct.X + rct.Width, rct.Y + ArcWidth);
                pt2 = new Point(rct.X + rct.Width, rct.Y + rct.Height - ArcWidth);
                gp.AddLine(pt1, pt2);

                arcRct.Y = pt2.Y;
                gp.AddArc(arcRct, 0, 90);

                pt1 = new Point(rct.X + rct.Width - ArcWidth, rct.Y + rct.Height);
                pt2 = new Point(rct.X + ArcWidth, rct.Y + rct.Height);
                gp.AddLine(pt1, pt2);

                arcRct.X = rct.X;
                gp.AddArc(arcRct, 90, 90);

                gp.CloseFigure();

                e.Graphics.DrawPath(new Pen(Color.Black, 1), gp);
            }
            return gp;
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Retired
United States United States
Currently enjoying retirement and working on projects without pressure, deadlines or any kind of management.

Comments and Discussions