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 IPolygon
s. 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:
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
{
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);
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;
}
MenuItem mouseSelectedItem = positionOfMenuItems[selectedRect];
SelectedMenuItem selectedMenuItem =
new SelectedMenuItem(mouseSelectedItem, selectedRect);
if (LastSelectedMenuItem == selectedMenuItem) return;
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];
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);
if (LastSelectedMenuItem != null &&
LastSelectedMenuItem.MenuItem == mi)
{
ImagePolygon selectedItem =
new ImagePolygon(LastSelectedMenuItem.MenuItem.ImageOn, rect);
retValue.Add(selectedItem);
positionOfSubMenuItems = null;
if (mi.SubItems.Count > 0)
{
retValue.AddRange(DrawSubMenu(clientRect));
}
}
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)
{
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; }
}
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;
}
}
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;
}
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);
}
}
}
}