using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace Mesnac.UI.ToolBox
{
public delegate void ItemChangedEventHandler(Object sender, EventArgs e);
public class ToolBox : Control
{
#region fields
private VScrollBar vScrollBar1;
private ToolBoxCategoryCollection _categories = new ToolBoxCategoryCollection();
private Int32 _itemHeight = 18;
private Int32 _categoryHeight = 16;
private Int32 _itemSpace = 2;
private ToolBoxItem _mouseHoverItem = null;
private ToolBoxItem _selectedItem = null;
private Color _borderColor = Color.Black;
private Pen _borderPen = null;
private Color _categoryBackColor = Color.WhiteSmoke;
#endregion
#region Properties
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ToolBoxCategoryCollection Categories
{
get
{
return _categories;
}
set
{
_categories = value;
}
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ToolBoxItem SelectedItem
{
get
{
return _selectedItem;
}
}
[Browsable(true)]
public Color BorderColor
{
get
{
return _borderColor;
}
set
{
if (_borderColor == value) return;
_borderColor = value;
_borderPen = new Pen(_borderColor);
Invalidate();
}
}
[Browsable(true)]
public Color CategoryBackColor
{
get
{
return _categoryBackColor;
}
set
{
_categoryBackColor = value;
Invalidate();
}
}
#endregion
#region Events
public event ItemChangedEventHandler ItemChanged;
public delegate void ItemSelectedHandler(object sender, ToolBoxItem newItem);
public event ItemSelectedHandler SelectedItemChanged;
public event ItemSelectedHandler ItemDoubleClicked;
public event ItemSelectedHandler ItemDragStart;
#endregion
public void ResetSelection()
{
_selectedItem = null;
Invalidate();
}
#region WINAPI functions/structures
[StructLayout(LayoutKind.Sequential)]
public struct WinAPI_RECT
{
public Int32 Left;
public Int32 Top;
public Int32 Right;
public Int32 Bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct WinAPI_NCCALCSIZE_PARAMS
{
public WinAPI_RECT rgrc0, rgrc1, rgrc2;
public IntPtr lppos;
}
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWINFO
{
public uint cbSize;
public WinAPI_RECT rcWindow;
public WinAPI_RECT rcClient;
public uint dwStyle;
public uint dwExStyle;
public uint dwWindowStatus;
public uint cxWindowBorders;
public uint cyWindowBorders;
public Int32 atomWindowType;
public Int32 wCreatorVersion;
}
public struct WinAPI_HT
{
public const uint HTERROR = unchecked((uint)-2);
public const uint HTTRANSPARENT = unchecked((uint)-1);
public const int HTNOWHERE = 0;
public const int HTCLIENT = 1;
public const int HTCAPTION = 2;
public const int HTSYSMENU = 3;
public const int HTGROWBOX = 4;
public const int HTSIZE = HTGROWBOX;
public const int HTMENU = 5;
public const int HTHSCROLL = 6;
public const int HTVSCROLL = 7;
public const int HTMINBUTTON = 8;
public const int HTMAXBUTTON = 9;
public const int HTLEFT = 10;
public const int HTRIGHT = 11;
public const int HTTOP = 12;
public const int HTTOPLEFT = 13;
public const int HTTOPRIGHT = 14;
public const int HTBOTTOM = 15;
public const int HTBOTTOMLEFT = 16;
public const int HTBOTTOMRIGHT = 17;
public const int HTBORDER = 18;
public const int HTREDUCE = HTMINBUTTON;
public const int HTZOOM = HTMAXBUTTON;
public const int HTSIZEFIRST = HTLEFT;
public const int HTSIZELAST = HTBOTTOMRIGHT;
public const int HTOBJECT = 19;
}
public struct WinAPI_SWP
{
public const int SWP_NOSIZE = 0x0001;
public const int SWP_NOMOVE = 0x0002;
public const int SWP_NOZORDER = 0x0004;
public const int SWP_NOREDRAW = 0x0008;
public const int SWP_NOACTIVATE = 0x0010;
public const int SWP_FRAMECHANGED = 0x0020; // The frame changed: send WM_NCCALCSIZE
public const int SWP_DRAWFRAME = SWP_FRAMECHANGED;
public const int SWP_SHOWWINDOW = 0x0040;
public const int SWP_HIDEWINDOW = 0x0080;
public const int SWP_NOCOPYBITS = 0x0100;
public const int SWP_NOOWNERZORDER = 0x0200; // Don't do owner Z ordering
public const int SWP_NOREPOSITION = SWP_NOOWNERZORDER;
public const int SWP_NOSENDCHANGING = 0x0400; // Don't send WM_WINDOWPOSCHANGING
}
public enum WinAPI_WM
{
WM_NCCALCSIZE = 0x0083,
WM_NCHITTEST = 0x0084,
WM_NCLBUTTONDOWN = 0x00A1,
WM_NCLBUTTONUP = 0x00A2,
WM_NCMOUSEMOVE = 0x00A0,
WM_NCPAINT = 0x0085,
WM_LBUTTONDOWN = 0x0201,
WM_MOUSEMOVE = 0x0200
}
[DllImport("User32.dll")]
public extern static IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("User32.dll")]
public extern static int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern Boolean GetWindowInfo(IntPtr hwnd, ref WINDOWINFO pwi);
#endregion
#region 非客户区的计算和绘制
///
/// 添加非客户区的计算和绘制
///
///
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case (int)WinAPI_WM.WM_NCCALCSIZE:
if (m.WParam.ToInt32() == 0)
{
WinAPI_RECT rc = (WinAPI_RECT)m.GetLParam(typeof(WinAPI_RECT));
rc.Left += 1;
rc.Top += 1;
rc.Right -= 1;
rc.Bottom -= 1;
Marshal.StructureToPtr(rc, m.LParam, true);
m.Result = IntPtr.Zero;
}
else
{
WinAPI_NCCALCSIZE_PARAMS csp;
csp = (WinAPI_NCCALCSIZE_PARAMS)m.GetLParam(typeof(WinAPI_NCCALCSIZE_PARAMS));
csp.rgrc0.Top += 1;
csp.rgrc0.Bottom -= 1;
csp.rgrc0.Left += 1;
csp.rgrc0.Right -= 1;
Marshal.StructureToPtr(csp, m.LParam, true);
//Return zero to preserve client rectangle
m.Result = IntPtr.Zero;
}
break;
case (int)WinAPI_WM.WM_NCPAINT:
{
m.WParam = NCPaint(m.WParam);
break;
}
}
base.WndProc(ref m);
}
public IntPtr NCPaint(IntPtr region)
{
IntPtr hDC = GetWindowDC(this.Handle);
if (hDC != IntPtr.Zero)
{
Graphics grTemp = Graphics.FromHdc(hDC);
int ScrollBarWidth = SystemInformation.VerticalScrollBarWidth;
int ScrollBarHeight = SystemInformation.HorizontalScrollBarHeight;
WINDOWINFO wi = new WINDOWINFO();
wi.cbSize = (uint)Marshal.SizeOf(wi);
//得到当前控件的窗口信息
GetWindowInfo(Handle, ref wi);
wi.rcClient.Right--;
wi.rcClient.Bottom--;
//获得当前控件的区域
Region UpdateRegion = new Region(new Rectangle(wi.rcWindow.Top, wi.rcWindow.Left, wi.rcWindow.Right - wi.rcWindow.Left, wi.rcWindow.Bottom - wi.rcWindow.Top));
//获得客户区以外的区域
UpdateRegion.Exclude(new Rectangle(wi.rcClient.Top, wi.rcClient.Left, wi.rcClient.Right - wi.rcClient.Left, wi.rcClient.Bottom - wi.rcClient.Top));
//if (IsHScrollVisible && IsVScrollVisible)
//{
// UpdateRegion.Exclude(Rectangle.FromLTRB
// (wi.rcClient.Right + 2, wi.rcClient.Bottom + 2,
// wi.rcWindow.Right, wi.rcWindow.Bottom));
//}
//得到当前区域的句柄
IntPtr hRgn = UpdateRegion.GetHrgn(grTemp);
//For Painting we need to zero offset the Rectangles.
Rectangle WindowRect = new Rectangle(wi.rcWindow.Top, wi.rcWindow.Left, wi.rcWindow.Right - wi.rcWindow.Left, wi.rcWindow.Bottom - wi.rcWindow.Top);
Point offset = Point.Empty - (Size)WindowRect.Location;
WindowRect.Offset(offset);
Rectangle ClientRect = WindowRect;
ClientRect.Inflate(-1, -1);
//Fill the BorderArea
Region PaintRegion = new Region(WindowRect);
PaintRegion.Exclude(ClientRect);
grTemp.FillRegion(SystemBrushes.Control, PaintRegion);
//Adjust ClientRect for Drawing Border.
ClientRect.Inflate(1, 1);
ClientRect.Width--;
ClientRect.Height--;
//Draw Outer Raised Border
//ControlPaint.DrawBorder3D(grTemp, WindowRect, Border3DStyle.Raised,
//Border3DSide.Bottom | Border3DSide.Left | Border3DSide.Right | Border3DSide.Top);
WindowRect.Width--;
WindowRect.Height--;
grTemp.DrawRectangle(_borderPen, WindowRect);
//Draw Inner Sunken Border
//ControlPaint.DrawBorder3D(grTemp, ClientRect, Border3DStyle.Sunken,
//Border3DSide.Bottom | Border3DSide.Left | Border3DSide.Right | Border3DSide.Top);
ReleaseDC(Handle, hDC);
grTemp.Dispose();
return hRgn;
}
return region;
}
#endregion
#region Override
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
Rectangle visibleRectangle = GetVisibleRect();
//draw border
//g.DrawRectangle(Pens.Olive,new Rectangle(0,0,this.Width-1,this.Height-1));
Brush categoryBrush = new SolidBrush(_categoryBackColor);
Brush categoryTextBrush = new SolidBrush(Color.Black);
Brush itemHoverBrush = new SolidBrush(SystemColors.Info);
Brush selectedBrush = new SolidBrush(Color.Orange);
Font categoryFont = new Font(Font, Font.Style | FontStyle.Bold);
Int32 top = _itemSpace;
if (vScrollBar1.Visible)
{
top -= vScrollBar1.Value;
}
Int32 left = 3;
int? imgWidth = null;
int? imgHeight = null;
foreach (ToolBoxCategory tbc in _categories)
{
if (tbc.IsOpen)
{
g.FillRectangle(categoryBrush, left, top, visibleRectangle.Width - 2 - 4, _categoryHeight);
g.DrawString(tbc.Name, categoryFont, categoryTextBrush, new Rectangle(4, top + 1, visibleRectangle.Width - 2 - 4, _categoryHeight));
top += _categoryHeight + _itemSpace;
foreach (ToolBoxItem tbi in tbc.Items)
{
if (tbi == _selectedItem)
{
g.DrawRectangle(Pens.Black, new Rectangle(left, top, visibleRectangle.Width - 2 * left, _itemHeight));
g.FillRectangle(selectedBrush, new Rectangle(left + 1, top + 1, visibleRectangle.Width - 2 * left - 2, _itemHeight - 1));
}
else if (tbi == _mouseHoverItem)
{
g.DrawRectangle(Pens.Black, new Rectangle(left, top, visibleRectangle.Width - 2 * left, _itemHeight));
g.FillRectangle(itemHoverBrush, new Rectangle(left + 1, top + 1, visibleRectangle.Width - 2 * left - 2, _itemHeight - 1));
}
if (null != tbi.Image)
{
if (null == imgWidth || null == imgHeight)
{
imgWidth = Math.Min(tbi.Image.Width, _itemHeight);
imgHeight = Math.Min(tbi.Image.Height, _itemHeight);
}
g.DrawImage(tbi.Image, new Rectangle(6, top + 1, imgWidth.Value, imgHeight.Value));
}
g.DrawString(tbi.Name, Font, categoryTextBrush, new Rectangle(6 + (null == imgWidth ? 0:imgWidth.Value), top + 1, visibleRectangle.Width - 2 - 4, _itemHeight));
top += _itemHeight + _itemSpace;
}
}
else
{
g.FillRectangle(categoryBrush, left, top, visibleRectangle.Width - 2 - 4, _categoryHeight);
g.DrawString(tbc.Name, categoryFont, categoryTextBrush, new Rectangle(4, top + 1, visibleRectangle.Width - 2 - 4, _categoryHeight));
top += _categoryHeight + _itemSpace;
}
}
}
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
ToolBoxItem item = GetToolboxItemByPoint(e.Location);
if (item != null)
{
if (item is ToolBoxCategory)
{
ToolBoxCategory tbc = item as ToolBoxCategory;
tbc.IsOpen = !tbc.IsOpen;
RefreshScrollBar();
Invalidate();
}
else
{
_selectedItem = item;
if (null != SelectedItemChanged)
SelectedItemChanged(this, _selectedItem);
if (2 == e.Clicks && null != ItemDoubleClicked)
ItemDoubleClicked(this, _selectedItem);
Invalidate();
}
}
Focus();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Right) return;
ToolBoxItem item = GetToolboxItemByPoint(e.Location);
if (item != null)
{
if (!(item is ToolBoxCategory))
{
//Console.WriteLine(DateTime.Now.Second.ToString() + ":" + e.Button.ToString());
if (e.Button == MouseButtons.Left)
{
if (null != ItemDragStart)
ItemDragStart(this, item);
return;
}
_mouseHoverItem = item;
Invalidate();
}
else
{
_mouseHoverItem = null;
Invalidate();
}
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
if (vScrollBar1.Visible)
{
int newVal = vScrollBar1.Value - (e.Delta / 120) * vScrollBar1.LargeChange;
if (newVal < 0)
vScrollBar1.Value = 0;
else if (newVal > (vScrollBar1.Maximum- vScrollBar1.LargeChange))
vScrollBar1.Value = vScrollBar1.Maximum;
else
vScrollBar1.Value = newVal;
Invalidate();
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
RefreshScrollBar();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
_mouseHoverItem = null;
Invalidate();
}
#endregion
#region Method
private void RefreshScrollBar()
{
Int32 totalHeight = GetTotalHeight();
Rectangle paintRect = GetVisibleRect();
if (totalHeight > paintRect.Height)
{
vScrollBar1.Visible = true;
vScrollBar1.Maximum = totalHeight - paintRect.Height;
vScrollBar1.LargeChange = vScrollBar1.Maximum / 5;
vScrollBar1.SmallChange = vScrollBar1.Maximum / 10;
vScrollBar1.Maximum += vScrollBar1.LargeChange;
}
else
{
vScrollBar1.Visible = false;
}
}
private void InitializeComponent()
{
this.vScrollBar1 = new System.Windows.Forms.VScrollBar();
this.SuspendLayout();
//
// vScrollBar1
//
this.vScrollBar1.Dock = System.Windows.Forms.DockStyle.Right;
this.vScrollBar1.LargeChange = 1;
this.vScrollBar1.Location = new System.Drawing.Point(-17, 0);
this.vScrollBar1.Name = "vScrollBar1";
this.vScrollBar1.Size = new System.Drawing.Size(17, 0);
this.vScrollBar1.TabIndex = 0;
this.vScrollBar1.Visible = false;
this.vScrollBar1.Scroll += new System.Windows.Forms.ScrollEventHandler(this.vScrollBar1_Scroll);
//
// ToolBox
//
this.Controls.Add(this.vScrollBar1);
this.ResumeLayout(false);
}
public ToolBox()
{
_borderPen = new Pen(_borderColor);
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.ResizeRedraw, true);
RefreshScrollBar();
this._categories.ItemChanged += new CollectionChangeEventHandler(OnCategoryCollectionChanged);
this.ItemChanged += new ItemChangedEventHandler(OnItemChanged);
}
private ToolBoxItem GetToolboxItemByPoint(Point pi)
{
ToolBoxItem result = null;
Int32 top = _itemSpace;
if (vScrollBar1.Visible)
{
top -= vScrollBar1.Value;
}
foreach (ToolBoxCategory tbc in _categories)
{
Rectangle rectCategory = new Rectangle(0, top, this.Width, _categoryHeight);
if (rectCategory.Contains(pi))
{
result = tbc;
break;
}
else
{
if (tbc.IsOpen)
{
top += _categoryHeight + _itemSpace;
Boolean find = false;
foreach (ToolBoxItem tbi in tbc.Items)
{
Rectangle rectItem = new Rectangle(0, top, this.Width, _itemHeight);
if (rectItem.Contains(pi))
{
tbi.Parent = tbc;
result = tbi;
find = true;
break;
}
else
{
top += _itemHeight + _itemSpace;
}
}
if (find)
{
break;
}
}
else
{
top += _categoryHeight + _itemSpace;
}
}
}
return result;
}
private Int32 GetTotalHeight()
{
Int32 result = _itemSpace;
foreach (ToolBoxCategory tbc in _categories)
{
if (tbc.IsOpen)
{
result += _categoryHeight + _itemSpace;
result += tbc.Items.Count * (_itemHeight + _itemSpace);
}
else
{
result += _categoryHeight + _itemSpace;
}
}
return result;
}
private Rectangle GetVisibleRect()
{
Rectangle rect = ClientRectangle;
if (vScrollBar1.Visible)
{
rect.Width -= vScrollBar1.Width;
}
return rect;
}
private void OnCategoryCollectionChanged(Object sender, CollectionChangeEventArgs e)
{
ToolBoxCategory tbc = e.Element as ToolBoxCategory;
if (e.Action == CollectionChangeAction.Add)
{
tbc.Items.ItemChanged += new CollectionChangeEventHandler(OnCategoryItemChanged);
}
else if (e.Action == CollectionChangeAction.Remove)
{
tbc.Items.ItemChanged -= new CollectionChangeEventHandler(OnCategoryItemChanged);
}
Invalidate();
}
private void OnCategoryItemChanged(Object sender, CollectionChangeEventArgs e)
{
Invalidate();
}
private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
Invalidate();
}
protected virtual void OnItemChanged(Object sender, EventArgs e)
{
}
public void ResetBorderColor()
{
_borderColor = Color.Black;
}
public bool ShouldSerializeBorderColor()
{
return (_borderColor == Color.Black) ? false : true;
}
#endregion
}
}