实现对系统菜单的自绘是非常麻烦的事情,花了不少时间,虽然实现了,但是还是不是很满意,因为还会闪烁,用了很多方法没能解决,就暂时这样先了。先来看看最后的效果:
先说说最开始的想法,因为我原来以为这是不会很难实现的一个东西,做了才知道,这个东西还真是特别,用正常的办法处理是不行的。
第一次尝试:通过得到系统菜单的句柄,然后通过截取消息就可以对它进行绘制了,不过这个系统菜单很特别,并不是通过它本身的句柄来处理绘制消息的,失败。
第二次尝试:窗体弹出系统菜单的时候后产生一个WM_ININTMENUPOPUP消息,拦截这个消息,给每个菜单项加上自绘属性,然后通过WM_MEASUREITEM和WM_DRAWITEM消息计算菜单的大小和绘制菜单。这样可以绘制菜单了,但是不能绘制它的背景被边框,还有就是当鼠标在菜单上移动后,菜单没有重新绘制,然后就变的什么都没有了……,又失败了。
第三次尝试:俗话说,失败是成功他妈,继续再接再厉。通过Google、Baidu,翻了一页又一页,终于知道系统菜单窗口创建的类名为"#32768",那在它创建的时候得到他的句柄,然后截取他的消息进行处理总行了吧,这次总算没有让我失望,呵呵。然后结合第二次的尝试,就得到了最终的方法:
第一步:HOOK,通过WM_CREATE消息得到菜单绘制窗口的句柄,通过它实现菜单背景、边框的重绘和鼠标移动后重新刷新窗口。在收到WM_DESTROY消息后,释放获得的菜单句柄。通过WM_INITMENUPOPUP消息给每一个菜单项添加上自绘属性(MF_OWNERDRAW),并得到菜单的父窗体的句柄。在WM_UNITMENUPOPUP消息后释放菜单的父窗体的句柄。
第二步:实现一个OwnerFormNativeWindow类,这个类的功能是通过菜单的父窗口的句柄,截取WM_MEASUREITEM和WM_DRAWITEM消息,计算每个菜单项的小小和绘制每一个菜单项。
第三步:实现一个HookMenuNativeWindow类,这个类的功能是通过系统菜单的窗口句柄,
截取相应的绘制消息,绘制菜单的背景和边框和鼠标移动后刷新菜单窗口。需要注意的是鼠标移动后刷新窗口的消息不是WM_MOUSEMOVE,而是一个没有命名的消息:0x1e5。
第四步:实现一个SystemMenuRender类,这个类主要是实现对系统菜单的绘制。
最后需要说明的是,各个Windows版本,获取菜单信息的参数设置不同和绘制需要处理的消息也不同,我没有环境进行测试,只在XP下运行了,在其他系统上可能会有先东西需要改动。(原文)最后来看看关键的代码:
public class OwnerDrawSystemMenuHook : IDisposable

...{

Fileds#region Fileds
private IntPtr _hMenu;
private OwnerFormNativeWindow _formNativeWindow;
private HookMenuNativeWindow _menuNativeWindow;
private SystemMenuRenderer _renderer;
private static WindowsHook _windowsHook;
private static int _osVersion;
private static WindowsType _windowsType;
private const string MenuRealFormClassName = "#32768";
#endregion

Constructors#region Constructors
static OwnerDrawSystemMenuHook()

...{
_osVersion = Environment.OSVersion.Version.Major;
_windowsType = SystemInformationHelper.GetWindowsVersionType();
}
public OwnerDrawSystemMenuHook()

...{
_windowsHook = new WindowsHook(
NativeMethods.WindowsHookCodes.WH_CALLWNDPROC);
_windowsHook.HookMessage += new NativeMethods.HookProc(CallWndProcHook);
}
#endregion

Properties#region Properties
public SystemMenuRenderer Renderer

...{
get

...{
if (_renderer == null)

...{
_renderer = new SystemMenuProfessionalRenderer();
}
return _renderer;
}
set

...{
_renderer = value;
}
}
internal bool IsUseOwnerDraw

...{

get ...{
return _windowsType != WindowsType.Windows95 &&
_windowsType != WindowsType.WindowsNT4; }
}
internal bool IsMenuHandler

...{

get ...{ return _hMenu != NativeMethods.FALSE; }
}
#endregion

HookProc Methods#region HookProc Methods
private IntPtr CallWndProcHook(
int code, IntPtr wparam, IntPtr lparam)

...{
if (code == (int)NativeMethods.HookCodes.HC_ACTION)

...{
NativeMethods.CWPSTRUCT cwp =
(NativeMethods.CWPSTRUCT)Marshal.PtrToStructure(
lparam,
typeof(NativeMethods.CWPSTRUCT));
switch (cwp.message)

...{
case (int)NativeMethods.WindowsMessage.WM_CREATE:
WmCreate(ref cwp);
break;
case (int)NativeMethods.WindowsMessage.WM_INITMENUPOPUP:
WmInitMenupopup(ref cwp);
break;
case (int)NativeMethods.WindowsMessage.WM_UNINITMENUPOPUP:
WmUnitMenupopup(ref cwp);
break;
case (int)NativeMethods.WindowsMessage.WM_DESTROY:
WmDestroy(ref cwp);
break;
}
}
return NativeMethods.FALSE;
}
#endregion

Windows Message Methods#region Windows Message Methods
private void WmDestroy(ref NativeMethods.CWPSTRUCT cwp)

...{
if (_menuNativeWindow != null)

...{
_menuNativeWindow.Dispose();
_menuNativeWindow = null;
}
}
private void WmCreate(ref NativeMethods.CWPSTRUCT cwp)

...{
StringBuilder builder = new StringBuilder(0x40);
NativeMethods.GetClassName(
cwp.hWnd, builder, 0x40);
string text1 = builder.ToString();
if (string.Compare(text1, MenuRealFormClassName, false) == 0)

...{
_menuNativeWindow = new HookMenuNativeWindow(this, cwp.hWnd);
}
}
private void WmInitMenupopup(ref NativeMethods.CWPSTRUCT cwp)

...{
if (NativeMethods.HIWORD(cwp.lParam) != 1)

...{
return;
}
_hMenu = cwp.wParam;
IntPtr hMenu = cwp.wParam;
NativeMethods.MENUITEMINFO itemInfo =
new NativeMethods.MENUITEMINFO();
int itemID;
bool success;
int fMask = (int)NativeMethods.MIIM.MIIM_STATE;
fMask |= _osVersion > 4 ?
(int)NativeMethods.MIIM.MIIM_FTYPE :
(int)NativeMethods.MIIM.MIIM_TYPE;
itemInfo.cbSize = Marshal.SizeOf(itemInfo);
itemInfo.fMask = fMask;
int itemCount = NativeMethods.GetMenuItemCount(hMenu);
for (int item = 0; item < itemCount; item++)

...{
success = NativeMethods.GetMenuItemInfo(
hMenu,
item,
true,
ref itemInfo);
itemID = NativeMethods.GetMenuItemID(hMenu, item);
int uFlags = (int)NativeMethods.MF.MF_BYPOSITION |
(int)NativeMethods.MF.MF_OWNERDRAW;
if ((itemInfo.fType & (int)NativeMethods.MF.MF_SEPARATOR) == 0)

...{
uFlags |= itemInfo.fState;
}
else

...{
uFlags |= (int)NativeMethods.MF.MF_SEPARATOR;
}
NativeMethods.ModifyMenu(
hMenu,
item,
uFlags,
itemID,
item.ToString());
}
_formNativeWindow = new OwnerFormNativeWindow(this, cwp.hWnd);
}
private void WmUnitMenupopup(ref NativeMethods.CWPSTRUCT cwp)

...{
if (_formNativeWindow != null)

...{
_formNativeWindow.Dispose();
_formNativeWindow = null;
}
_hMenu = NativeMethods.FALSE;
}
private IntPtr WmMeasureItem(ref Message m)

...{
NativeMethods.MEASUREITEMSTRUCT lParam =
(NativeMethods.MEASUREITEMSTRUCT)Marshal.PtrToStructure(
m.LParam,
typeof(NativeMethods.MEASUREITEMSTRUCT));
if (lParam.CtlType != (int)NativeMethods.ODT.ODT_MENU)

...{
return NativeMethods.FALSE;
}
NativeMethods.MENUITEMINFO itemInfo =
new NativeMethods.MENUITEMINFO();
StringBuilder menuText;
int menuItemHeight;
int minMenuItemWidth = 0;
int fMask = (int)NativeMethods.MIIM.MIIM_STATE;
fMask |= _osVersion > 4 ?
(int)NativeMethods.MIIM.MIIM_FTYPE :
(int)NativeMethods.MIIM.MIIM_TYPE;
itemInfo.cbSize = Marshal.SizeOf(itemInfo);
itemInfo.fMask = fMask;
NativeMethods.GetMenuItemInfo(
_hMenu,
lParam.itemID,
false,
ref itemInfo);
int state = itemInfo.fState;
int type = itemInfo.fType;
if ((type & (int)NativeMethods.MF.MF_SEPARATOR) != 0)

...{
menuText = new StringBuilder("-");
menuItemHeight = 3;
}
else

...{
menuText = new StringBuilder(256);
NativeMethods.GetMenuString(
_hMenu,
lParam.itemID,
menuText,
256,
(int)NativeMethods.MF.MF_BYCOMMAND);
menuItemHeight = SystemInformation.MenuHeight;
}
IntPtr dc = NativeMethods.GetDC(IntPtr.Zero);
Graphics graphics = Graphics.FromHdcInternal(dc);
MeasureItemExEventArgs e = new MeasureItemExEventArgs(
graphics,
menuText.ToString(),
SystemFonts.MenuFont,
menuItemHeight);
try

...{
Renderer.CalcSystemMenuItem(e);
minMenuItemWidth = TextRenderer.MeasureText(
graphics,
"关闭(&C) Alt + F4",
SystemFonts.MenuFont).Width +
SystemInformation.MenuCheckSize.Width;
}
finally

...{
graphics.Dispose();
}
NativeMethods.ReleaseDC(IntPtr.Zero, dc);
lParam.itemHeight = e.ItemHeight;
if (e.ItemWidth < minMenuItemWidth)

...{
lParam.itemWidth = minMenuItemWidth;
}
else

...{
lParam.itemWidth = e.ItemWidth;
}
Marshal.StructureToPtr(lParam, m.LParam, false);
return NativeMethods.TRUE;
}
private IntPtr WmDrawItem(ref Message m)

...{
NativeMethods.DRAWITEMSTRUCT lParam =
(NativeMethods.DRAWITEMSTRUCT)Marshal.PtrToStructure(
m.LParam,
typeof(NativeMethods.DRAWITEMSTRUCT));
if (lParam.hwndItem != _hMenu)

...{
return NativeMethods.FALSE;
}
NativeMethods.MENUITEMINFO itemInfo =
new NativeMethods.MENUITEMINFO();
StringBuilder menuText;
itemInfo.cbSize = Marshal.SizeOf(itemInfo);
itemInfo.fMask =
(int)NativeMethods.MIIM.MIIM_STATE |
(int)NativeMethods.MIIM.MIIM_FTYPE;
NativeMethods.GetMenuItemInfo(
_hMenu,
lParam.itemID,
false,
ref itemInfo);
int state = itemInfo.fState;
int type = itemInfo.fType;
bool _isSeparator = false;
if ((type & (int)NativeMethods.MF.MF_SEPARATOR) != 0)

...{
_isSeparator = true;
menuText = new StringBuilder(1);
}
else

...{
menuText = new StringBuilder(256);
NativeMethods.GetMenuString(
_hMenu,
lParam.itemID,
menuText,
256,
(int)NativeMethods.MF.MF_BYCOMMAND);
}
using (Graphics graphics = Graphics.FromHdc(lParam.hDC))

...{
Renderer.DrawSystemMenuItem(new DrawItemExEventArgs(
graphics,
menuText.ToString(),
SystemInformation.MenuFont,
lParam.rcItem.Rect,
lParam.itemID,
(DrawItemState)lParam.itemState,
_isSeparator));
}
return NativeMethods.TRUE;
}
private void WmNcPaint(ref Message m)

...{
IntPtr hDc = NativeMethods.GetWindowDC(m.HWnd);
try

...{
using (Graphics g = Graphics.FromHdc(hDc))

...{
DrawNc(g, m.HWnd);
}
}
finally

...{
NativeMethods.ReleaseDC(m.HWnd, hDc);
}
}
private void WmPrint(ref Message m)

...{
try

...{
using (Graphics g = Graphics.FromHdcInternal(m.WParam))

...{
DrawNc(g, m.HWnd);
}
}
finally

...{
NativeMethods.ReleaseDC(m.HWnd, m.WParam);
}
}
private void WmWindowPosChanging(ref Message m)

...{
NativeMethods.WINDOWPOS windowPos =
(NativeMethods.WINDOWPOS)m.GetLParam(
typeof(NativeMethods.WINDOWPOS));
//if ((windowPos.flags & (int)NativeMethods.SWP.SWP_NOMOVE) == 0)
//{
// windowPos.cx += 24;
//}
if ((windowPos.flags & (int)NativeMethods.SWP.SWP_NOSIZE) == 0)

...{
windowPos.cx += Renderer.MenuBarWidth;
Marshal.StructureToPtr(windowPos, m.LParam, false);
}
}
private void WmNcCalcSize(ref Message m)

...{
if (m.WParam == NativeMethods.FALSE)

...{
NativeMethods.RECT rect = (NativeMethods.RECT)m.GetLParam(
typeof(NativeMethods.RECT));
rect.Left += 24;
Marshal.StructureToPtr(
rect,
m.LParam,
false);
}
else

...{
NativeMethods.NCCALCSIZE_PARAMS lParam =
(NativeMethods.NCCALCSIZE_PARAMS)m.GetLParam(
typeof(NativeMethods.NCCALCSIZE_PARAMS));
lParam.rectProposed.Left += 24;
Marshal.StructureToPtr(
lParam,
m.LParam,
false);
}
}
#endregion

Helper Methods#region Helper Methods
private void DrawNc(Graphics g, IntPtr hWnd)

...{
Rectangle rect = Rectangle.Round(g.VisibleClipBounds);
Rectangle clientRect = GetClientRect(hWnd, 24);
g.ExcludeClip(clientRect);
Renderer.DrawSystemMenuNC(
new SystemMenuNCRenderEventArgs(g, rect));
}
private Rectangle GetClientRect(IntPtr hWnd, int leftMargin)

...{
NativeMethods.RECT lpRect = new NativeMethods.RECT();
int style = NativeMethods.GetWindowLong(
hWnd, (int)NativeMethods.GWL.GWL_STYLE);
int styleEx = NativeMethods.GetWindowLong(
hWnd, (int)NativeMethods.GWL.GWL_EXSTYLE);
NativeMethods.AdjustWindowRectEx(
ref lpRect,
style,
false,
styleEx);
int left = -lpRect.Left;
int right = -lpRect.Top;
NativeMethods.GetClientRect(
hWnd,
ref lpRect);
lpRect.Left += (leftMargin + left);
lpRect.Right += (leftMargin + left);
lpRect.Top += right;
lpRect.Bottom += right;
return lpRect.Rect;
}
private int GetSelctedMenuItemIndex(IntPtr hMenu)

...{
int count = NativeMethods.GetMenuItemCount(hMenu);
for (int i = 0; i < count; i++)

...{
int state = NativeMethods.GetMenuState(
hMenu,
i,
(int)NativeMethods.MF.MF_BYPOSITION);
if ((state & (int)NativeMethods.MF.MF_HILITE) != 0)

...{
return i;
}
}
return -1;
}
#endregion

IDisposable 成员#region IDisposable 成员
public void Dispose()

...{
if (_windowsHook != null)

...{
_windowsHook.Dispose();
}
if (_formNativeWindow != null)

...{
_formNativeWindow.Dispose();
}
if (_menuNativeWindow != null)

...{
_menuNativeWindow.Dispose();
}
_hMenu = IntPtr.Zero;
_renderer = null;
}
#endregion

OwnerFormNativeWindow Class#region OwnerFormNativeWindow Class
private class OwnerFormNativeWindow : NativeWindow, IDisposable

...{
private OwnerDrawSystemMenuHook _owner;
public OwnerFormNativeWindow(
OwnerDrawSystemMenuHook owner, IntPtr hForm)
: base()

...{
_owner = owner;
base.AssignHandle(hForm);
}
protected override void WndProc(ref Message m)

...{
switch (m.Msg)

...{
case (int)NativeMethods.WindowsMessage.WM_MEASUREITEM:
if (_owner.WmMeasureItem(ref m) == NativeMethods.TRUE)

...{
m.Result = NativeMethods.TRUE;
}
else

...{
base.WndProc(ref m);
}
break;
case (int)NativeMethods.WindowsMessage.WM_DRAWITEM:
if (_owner.WmDrawItem(ref m) == NativeMethods.TRUE)

...{
m.Result = NativeMethods.TRUE;
}
else

...{
base.WndProc(ref m);
}
break;
default:
base.WndProc(ref m);
break;
}
}

IDisposable 成员#region IDisposable 成员
public void Dispose()

...{
base.ReleaseHandle();
_owner = null;
}
#endregion
}
#endregion

HookMenuNativeWindow Class#region HookMenuNativeWindow Class
private class HookMenuNativeWindow : NativeWindow, IDisposable

...{
private OwnerDrawSystemMenuHook _owner;
private int _lastSelectedIndex = -1;
public HookMenuNativeWindow(
OwnerDrawSystemMenuHook owner, IntPtr hMenu)
: base()

...{
_owner = owner;
base.AssignHandle(hMenu);
}
protected override void WndProc(ref Message m)

...{
if (!_owner.IsMenuHandler)

...{
base.WndProc(ref m);
return;
}
switch (m.Msg)

...{
case (int)NativeMethods.WindowsMessage.WM_NCPAINT:
_owner.WmNcPaint(ref m);
m.Result = NativeMethods.FALSE;
break;
case (int)NativeMethods.WindowsMessage.WM_PRINT:
base.WndProc(ref m);
if (_owner.IsUseOwnerDraw)

...{
_owner.WmPrint(ref m);
}
break;
case (int)NativeMethods.WindowsMessage.WM_NCCALCSIZE:
base.WndProc(ref m);
_owner.WmNcCalcSize(ref m);
break;
case (int)NativeMethods.WindowsMessage.WM_WINDOWPOSCHANGING:
base.WndProc(ref m);
_owner.WmWindowPosChanging(ref m);
break;
case (int)NativeMethods.WindowsMessage.WM_KEYDOWN:
if (!_owner.IsUseOwnerDraw)

...{
base.WndProc(ref m);
return;
}
int keyCode = m.WParam.ToInt32();
switch ((Keys)keyCode)

...{
case Keys.Left:
case Keys.Right:
case Keys.Up:
case Keys.Down:
int prevSel =
_owner.GetSelctedMenuItemIndex(_owner._hMenu);
bool bLeftKey = keyCode != (int)Keys.Left;
if (!bLeftKey)

...{
SetRedraw(m.HWnd, false);
}
base.WndProc(ref m);
if (!bLeftKey)

...{
SetRedraw(m.HWnd, true);
}
_lastSelectedIndex =
_owner.GetSelctedMenuItemIndex(_owner._hMenu);
if (_lastSelectedIndex != prevSel)

...{
Invalidate(m.HWnd);
}
break;
default:
base.WndProc(ref m);
break;
}
break;
case 0x1e5:
if (!_owner.IsUseOwnerDraw)

...{
base.WndProc(ref m);
return;
}
int curIndex = m.WParam.ToInt32();
if (_lastSelectedIndex != curIndex)

...{
_lastSelectedIndex = curIndex;
SetRedraw(m.HWnd, false);
base.WndProc(ref m);
SetRedraw(m.HWnd, true);
if (SystemInformation.IsMenuAnimationEnabled)

...{
_owner.WmNcPaint(ref m);
}
Invalidate(m.HWnd);
}
else

...{
base.WndProc(ref m);
}
break;
case (int)NativeMethods.WindowsMessage.WM_ERASEBKGND:
m.Result = NativeMethods.TRUE;
break;
default:
base.WndProc(ref m);
break;
}
}
private void SetRedraw(IntPtr hWnd, bool bRedraw)

...{
IntPtr redraw = bRedraw ?
NativeMethods.TRUE : NativeMethods.FALSE;
NativeMethods.SendMessage(
hWnd,
(int)NativeMethods.WindowsMessage.WM_SETREDRAW,
redraw,
NativeMethods.FALSE);
}
private void Invalidate(IntPtr hWnd)

...{
NativeMethods.RedrawWindow(
hWnd,
IntPtr.Zero,
IntPtr.Zero,
(uint)(
NativeMethods.RDW.RDW_INVALIDATE |
NativeMethods.RDW.RDW_NOERASE));
}

IDisposable 成员#region IDisposable 成员
public void Dispose()

...{
base.ReleaseHandle();
_owner = null;
}
#endregion
}
#endregion
}
声明:
本文版权归作者和CS 程序员之窗所有,欢迎转载,转载必须保留以下版权信息,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
作者:Starts_2000
出处:CS 程序员之窗 http://www.csharpwin.com。
你可以免费使用或修改提供的源代码,但请保留源代码中的版权信息,详情请查看:
CS程序员之窗开源协议 http://www.csharpwin.com/csol.html。