Click here to Skip to main content
15,886,783 members
Articles / Multimedia / GDI+

Custom windows using .NET 2.0 and the WinAPI

Rate me:
Please Sign up or sign in to vote.
4.44/5 (11 votes)
19 Jul 2009CPOL 40.5K   484   16   5
An article about windows with custom shapes.

menu.png

Introduction

This article shows how to create custom windows using GDI+.

Background

Some time ago, I had a task to create a menu for the MS PPTViewer application that allows the user to switch slides using menu items. I decided to create a menu using the WinAPI function "UpdateLayeredWindow" from the User32.dll library. This functions allow to draw any 32-bit image on the screen. For this menu, I implemented a library that I want to share.

Using the code

To create a custom window, you should inherit the TransparetWindow class and override the DrawPolygons method. This method should return a collection of IPolygons. This is done in order to draw the polygons after all the calculations.

C#
public interface IPolygon
{
    void Draw(Graphics g);
}

The TransparentWindow class includes some events and properties from the System.Windows.Forms namespace:

C#
public event EventHandler LocationChanged;
public event EventHandler Move;
public event MouseEventHandler MouseDown;
public event MouseEventHandler MouseUp;
public event MouseEventHandler MouseMove;
public event EventHandler MouseEnter;
public event EventHandler MouseLeave;
public Brush Background { get; set; }
public CornerRadius CornerRadius{ get; set; }
public int AlphaChannel{ get; set; }
public Size Size{ get; set; }
public Point Location{ get; set; }

The most interesting property is CorenerRadius. If you set this property, you will get a window with round corners. Here is the code:

C#
TransparentWindow window = new TransparentWindow();
window.Border = new Pen(Brushes.Black);
window.CornerRadius = new CornerRadius(15);
window.Size = new Size(400,400);
window.Background = Brushes.White;
window.Location = new Point
            (
                Screen.PrimaryScreen.WorkingArea.Left + 300,
                Screen.PrimaryScreen.WorkingArea.Height / 2 - 140
            );
window.Show();

If you run this code, you will get a window like this:

CorenerRadius.png

Here is the code for the menu (see the first image) implementation:

C#
using System;
using System.Collections.Generic;
using System.Drawing;
using CustomMenuLibrary.CustomPolygons;
using CustomMenuLibrary.Menu;

namespace CustomMenuLibrary
{
    /// <summary>
    /// There are three state of window:
    /// 1 - Collapsed: when mouse is not in menu window
    /// 2 - Expanded: when mouse is in window
    /// 3 - Select: Expnaded + submenu windows is shown
    /// This class displays all these states in one transparency window 
    /// The window size and location are fixed.
    /// Size and location claculated in constructor  
    /// in compliance with submenu window and menu widht.
    /// </summary>
    public class MainMenuWindow : TransparentWindow
    {
        private readonly MainMenu mainMenu;
        private Size expandedWindowSize;
        private Size selectWindowSize;
        private Size menuItemsSize;

        private bool isMouseIn;

        private Dictionary<Rectangle, SubMenuItem> positionOfSubMenuItems;
        private SelectedSubMenuItem lastSelectedSubMenuItem;
        private Dictionary<Rectangle, MenuItem> positionOfMenuItems;
        private SelectedMenuItem lastSelectedMenuItem;

        private readonly Brush itemTextColor;
        private readonly Font itemFont;
        private readonly Pen subMenuBorder;
        private readonly Brush subMenuBackground;
        private readonly CornerRadius submenuCornerRadius;
        private readonly Brush submenuTextColor;
        private readonly Font submenuFont;

        private Rectangle submenuRectangle;
 
        public MainMenuWindow(MainMenu mainMenu)
        {
            if (mainMenu == null)
            {
                throw new ArgumentNullException();
            }

            this.mainMenu = mainMenu;

            submenuFont = new Font("Arial", 10);
            submenuCornerRadius = new CornerRadius(Constants.SubMenu.CORNER_RADIUS);
            subMenuBorder = Pens.Black;
            submenuTextColor = Brushes.Blue;
            subMenuBackground = Brushes.White;
            itemTextColor = Brushes.White;
            itemFont = new Font("Arial", 10);

            //Calculate window location and  windows size 
            //in compliance with submenu window and menu widht
            Border = null;
            Size = SelectWindowSize;
            Location = new Point(mainMenu.Position.X,
                                 mainMenu.Position.Y - Size.Height + mainMenu.Size.Height);
           
            MouseEnter += OnMouseEnter;
            MouseLeave += OnMouseLeave;
            MouseMove += OnMouseMove;
            MouseUp += OnMouseClick;

        }

        
        #region Mouse handlers
        void OnMouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (LastSelectedSubMenuItem != null && 
                LastSelectedSubMenuItem.Rectangle.Contains(e.Location))
            {
                RaiseSelectMenuClick(new SelectMenuEventArgs(
                  LastSelectedMenuItem.MenuItem,LastSelectedSubMenuItem.SubItem));
            }
        }

        void OnMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            Rectangle selectedRect = Rectangle.Empty;
            if (positionOfMenuItems == null) return;

            foreach (KeyValuePair<Rectangle, MenuItem> item in positionOfMenuItems)
            {
                if (item.Key.Contains(e.Location))
                {
                    selectedRect = item.Key;
                    break;
                }
            }

            if (selectedRect == Rectangle.Empty)
            {
                if (!submenuRectangle.Contains(e.Location))
                {
                    LastSelectedMenuItem = null;
                    Invalidate();
                }
                else
                {
                    if (positionOfSubMenuItems == null) return;


                    Rectangle selectedSubItemRect = Rectangle.Empty;
                    foreach (KeyValuePair<Rectangle, 
                             SubMenuItem> item in positionOfSubMenuItems)
                    {
                        if (item.Key.Contains(e.Location))
                        {
                            selectedSubItemRect = item.Key;
                            break;
                        }
                    }

                    if (selectedSubItemRect != Rectangle.Empty)
                    {
                        SubMenuItem subMenuItem = positionOfSubMenuItems[selectedSubItemRect];
                        if (LastSelectedSubMenuItem != 
                              new SelectedSubMenuItem(subMenuItem, selectedSubItemRect))
                        {
                            LastSelectedSubMenuItem = 
                              new SelectedSubMenuItem(subMenuItem, selectedSubItemRect);
                            Invalidate();
                        }
                    }
                    else
                    {
                        LastSelectedSubMenuItem = null;
                        Invalidate();
                    }

                }
                return; //No menu items with such rectangle
            }
            MenuItem mouseSelectedItem = positionOfMenuItems[selectedRect];
            SelectedMenuItem selectedMenuItem = 
                new SelectedMenuItem(mouseSelectedItem, selectedRect);
            if (LastSelectedMenuItem == selectedMenuItem) return; //this is the same item
            LastSelectedMenuItem = selectedMenuItem;
            Invalidate();
        }

        void OnMouseLeave(object sender, EventArgs e)
        {
            isMouseIn = false;
            Invalidate();
        }

        void OnMouseEnter(object sender, EventArgs e)
        {
            isMouseIn = true;
            Invalidate();
        } 
        #endregion

        public IEnumerable<IPolygon> Draw(Rectangle clientRect)
        {
            return isMouseIn ? DrawExpanded(clientRect) : DrawCollapsed(clientRect);
        }
        
        private IEnumerable<IPolygon> DrawCollapsed(Rectangle clientRect)
        {
            List<IPolygon> retValue = DrawMenuRectangle(clientRect);
            return retValue;
        }

        private List<IPolygon> DrawMenuRectangle(Rectangle clientRect)
        {
            List<IPolygon> retValue = new List<IPolygon>();
            Rectangle rect = new Rectangle(clientRect.X,
                                           SelectWindowSize.Height - MainMenu.Size.Height,
                                           MainMenu.Size.Width,
                                           MainMenu.Size.Height);

            ImagePolygon menuImage = new ImagePolygon(mainMenu.Image,rect);
            retValue.Add(menuImage);
            return retValue;
        }

        private IEnumerable<IPolygon> DrawExpanded(Rectangle clientRect)
        {
            List<IPolygon> retValue = new List<IPolygon>();
            int drawHeight = clientRect.Height - ExpandedWinwowSize.Height + 1;

            bool initializeMenuItemsPosition = false;
            if (positionOfMenuItems == null)
            {
                initializeMenuItemsPosition = true;
                positionOfMenuItems = new Dictionary<Rectangle, MenuItem>();
            }
            
            for (int i = MainMenu.Items.Count - 1; i >= 0; --i)
            {
                MenuItem mi = MainMenu.Items[i];
                //calculate rectangle for menu item
                Rectangle rect = new Rectangle(clientRect.X, drawHeight, 
                                               Constants.MainMenu.ITEM_WIDHT,
                                               Constants.MainMenu.ITEM_HEIGH);

                ImagePolygon menuItem = new ImagePolygon(mi.ImageOff,rect);
                retValue.Add(menuItem);

                if (initializeMenuItemsPosition) positionOfMenuItems.Add(rect, mi);
                
                //hight light selected item
                if (LastSelectedMenuItem != null && 
                    LastSelectedMenuItem.MenuItem == mi)
                {
                    ImagePolygon selectedItem = 
                      new ImagePolygon(LastSelectedMenuItem.MenuItem.ImageOn, rect);
                    retValue.Add(selectedItem);

                    positionOfSubMenuItems = null;

                    if (mi.SubItems.Count > 0)
                    {
                        //Draw subitems window
                        retValue.AddRange(DrawSubMenu(clientRect));    
                    }
                }

                //Draw menu item text
                SizeF stringSize = Global.GetStringSize(mi.Text, itemFont);
                StringPolygon menuItemText = new StringPolygon(
                    itemTextColor,
                    itemFont,
                    mi.Text,
                    new Point((int)((rect.Width - stringSize.Width) / 2),
                              rect.Top + 5));
                retValue.Add(menuItemText);
                drawHeight += Constants.MainMenu.ITEM_HEIGH;
            }
            retValue.AddRange(DrawMenuRectangle(clientRect));
            return retValue;
        }

        private List<IPolygon> DrawSubMenu(Rectangle clientRect)
        {
            //calculate size of submenu window and draw it
            List<IPolygon> retValue = new List<IPolygon>();

            int stringHeigh = (int)Global.GetStringSize(@"Place here any string " + 
              @"that you want, we should only calculate string heigh",submenuFont).Height;
            int heigh = Constants.SubMenu.START_HEIGH_SUBMENU_ITEMS * 2 + 
                        stringHeigh * LastSelectedMenuItem.MenuItem.SubItems.Count;

            int y = LastSelectedMenuItem.Rectangle.Y - heigh + 
                    Constants.MainMenu.ITEM_HEIGH * 2 - 10;

            int widht = GetSubmenuWindowWidth(LastSelectedMenuItem.MenuItem);

            submenuRectangle = new Rectangle(clientRect.X + Constants.MainMenu.ITEM_WIDHT,
                                             y,
                                             widht,
                                             heigh
                );
            retValue.Add(new FillRoundRectanglePolygon(subMenuBorder, 
               subMenuBackground, submenuRectangle, submenuCornerRadius));
            int drawHeight = Constants.SubMenu.START_HEIGH_SUBMENU_ITEMS;
            positionOfSubMenuItems = new Dictionary<Rectangle, SubMenuItem>();
            foreach (SubMenuItem subitem in LastSelectedMenuItem.MenuItem.SubItems)
            {
                SizeF stringSize = Global.GetStringSize(subitem.Text, submenuFont);
                Rectangle rect = new Rectangle(new Point(submenuRectangle.Left + 
                  Constants.SubMenu.START_WIDTH_SUBMENU_ITEMS, 
                  submenuRectangle.Top + drawHeight),
                  new Size((int)stringSize.Width,(int)stringSize.Height));
                positionOfSubMenuItems.Add(rect, subitem);
                IPolygon subItemText = LastSelectedSubMenuItem != null && 
                     LastSelectedSubMenuItem.SubItem == subitem
                     ? (IPolygon)(new UnderlineStringPolygon(submenuTextColor, submenuFont, 
                                                             subitem.Text, rect.Location))
                           : new StringPolygon(submenuTextColor, submenuFont, subitem.Text,
                                               rect.Location);
                retValue.Add(subItemText);
                drawHeight += Constants.SubMenu.DELTA_HEIGH_SUBMENU_ITEMS;
            }
            return retValue;
        }

        protected SelectedSubMenuItem LastSelectedSubMenuItem
        {
            get { return lastSelectedSubMenuItem; }
            set { lastSelectedSubMenuItem = value; }
        }


        protected SelectedMenuItem LastSelectedMenuItem
        {
            get { return lastSelectedMenuItem; }
            set { lastSelectedMenuItem = value; }
        }

        /// <summary>
        /// Gets expanded menu size
        /// </summary>
        protected Size ExpandedWinwowSize
        {
            get
            {
                if (expandedWindowSize == Size.Empty)
                {
                    expandedWindowSize = new Size();
                    expandedWindowSize.Width = MenuItemsSize.Width > mainMenu.Size.Width ? 
                                               MenuItemsSize.Width : mainMenu.Size.Width;
                    expandedWindowSize.Height = MenuItemsSize.Height + mainMenu.Size.Height;
                }
                return expandedWindowSize;
            }
        }

        /// <summary>
        /// Gets whole window size
        /// </summary>
        protected Size SelectWindowSize
        {
            get
            {
                if (selectWindowSize == Size.Empty)
                {
                    int maxWidht = 0;
                    foreach (MenuItem menuItem in mainMenu.Items)
                    {
                        int widht = GetSubmenuWindowWidth(menuItem);
                        if (maxWidht < widht)
                        {
                            maxWidht = widht;
                        }
                    }

                    selectWindowSize = new Size();
                    selectWindowSize.Width = 
                      ExpandedWinwowSize.Width + maxWidht + (int)subMenuBorder.Width;
                    selectWindowSize.Height = ExpandedWinwowSize.Height + 
                      Constants.SubMenu.MAX_HEIGH - Constants.MainMenu.ITEM_HEIGH + 2;
                }
                return selectWindowSize;
            }
        }

        protected int GetSubmenuWindowWidth(MenuItem menuItem)
        {
            int maxWidht = 0;
            foreach (SubMenuItem item in menuItem.SubItems)
            {
                SizeF stringSize = Global.GetStringSize(item.Text, submenuFont);
                if (maxWidht < stringSize.Width)
                {
                    maxWidht = (int)stringSize.Width;
                }
            }

            maxWidht += 2*Constants.SubMenu.START_WIDTH_SUBMENU_ITEMS;

            if (maxWidht < Constants.SubMenu.MIN_WIDHT)
                maxWidht = Constants.SubMenu.MIN_WIDHT;
            return maxWidht;
        }

        /// <summary>
        /// Gets menu items size (without menu button)
        /// </summary>
        protected Size MenuItemsSize
        {
            get
            {
                if (menuItemsSize == Size.Empty)
                {
                    menuItemsSize = new Size();
                    menuItemsSize.Width = Constants.MainMenu.ITEM_WIDHT;
                    menuItemsSize.Height = 
                      mainMenu.Items.Count*Constants.MainMenu.ITEM_HEIGH;
                }
                return menuItemsSize;
            }
        }

        protected MainMenu MainMenu
        {
            get { return mainMenu; }
        }

        protected override List<IPolygon> DrawPolygons(Rectangle clientRect)
        {
            List<IPolygon> polygons = 
              new List<IPolygon>(base.DrawPolygons(clientRect));
            polygons.AddRange(Draw(clientRect));
            return polygons;
        }

        public event SelectMenuEventHandler SelectMenuClick;
        protected void RaiseSelectMenuClick(SelectMenuEventArgs e)
        {
            if (SelectMenuClick != null)
            {
                SelectMenuClick(this, e);
            }
        }
    }
}

License

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



Comments and Discussions

 
GeneralMy vote of 5 Pin
kkirusha21-Nov-10 11:38
kkirusha21-Nov-10 11:38 
GeneralMy vote of 1 Pin
Bishoy Demian20-Jul-09 9:54
Bishoy Demian20-Jul-09 9:54 
GeneralRe: My vote of 1 Pin
tobywf20-Jul-09 10:36
tobywf20-Jul-09 10:36 
GeneralRe: My vote of 1 Pin
Md. Marufuzzaman20-Jul-09 23:24
professionalMd. Marufuzzaman20-Jul-09 23:24 
GeneralRe: My vote of 1 Pin
NeoPunk9-Aug-09 4:12
NeoPunk9-Aug-09 4:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.