Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom windows using .NET 2.0 and the WinAPI

0.00/5 (No votes)
19 Jul 2009 1  
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.

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

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

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:

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:

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here