在开发应用程序时,界面的美观性是吸引用户的重要因素之一。为了提升应用程序的界面美观,决定创建一个新的库,用于自定义表单皮肤。这个库的灵感来源于之前的RCM项目,从中提取了表单皮肤化的功能,并对其进行了改进。这个新的库解决了RCM项目中存在的一些问题,特别是针对XP系统的特定问题。此外,这个库能够支持所有类型的表单皮肤化,而不仅仅是RCM项目中设计的可调整大小的标准框架。
设计这个库的目的是尽可能地自动化,只需要编译库并将其引用添加到项目中,然后只需要三个命令就可以开始皮肤化表单:
就是这么简单。
用于皮肤化框架的位图与大多数常见的皮肤化引擎(如Win-blinds)的格式相同,有成千上万的皮肤可供选择。在演示中包含了用于演示的位图和PNG,以给一个格式的概念。
表单的框架有四个部分:标题栏、左侧、右侧和底部。每个部分有两种状态:活动和非活动。表单的按钮有三种状态:正常、悬停和按下。要确定使用的图像格式,只需查看随此项目包含的示例图像。
由于这是一个库,它是便携式的,不仅仅局限于C#开发者。没错,它可以在VB中使用!知道VB开发者非常喜欢用户控件,所以包含了一个VB演示项目。事实上,这应该对所有.NET语言实现都是可访问的,甚至可能从VS6开始(尽管还没有尝试过)。这些类的设计也是为了可以独立使用。它们中的大多数将需要cStoreDc和cGraphics类,这些类可以简单地嵌入到父类中。
表单可能是最棘手且最困难的控件之一来绘制。有许多消息可能会触发需要重新绘制表单的非客户区域的一部分或全部的意外需求。在野生环境中也缺乏良好的示例来正确地皮肤化表单,因此需要一些严肃的试错才能正确地做到这一点。现在不要误会,这不是在无边框表单上使用SetWindowRgn,这是在框架上绘制,就像窗口主题绘制一样。
为了正确地做到这一点,首先必须从源RECT中减去默认窗口大小,这取决于窗口的样式:
private RECT CalculateFrameSize(int x, int y, int cx, int cy)
{
RECT windowRect = new RECT(x, y, x + cx, y + cy);
// subtract original frame size
windowRect.Left -= _iFrameWidth;
windowRect.Right += _iFrameWidth;
windowRect.Top -= _iCaptionHeight;
windowRect.Bottom += _iFrameHeight;
// reset client area with new size
windowRect.Left += (_oLeftFrameBitmap.Width / 2);
windowRect.Right -= (_oRightFrameBitmap.Width / 2);
windowRect.Bottom -= (_oBottomFrameBitmap.Height / 2);
windowRect.Top += (_oCaptionBarBitmap.Height / 2);
return windowRect;
}
另一个问题是尝试模拟标题栏按钮的行为,例如,当鼠标按下,然后从按钮拖开并释放时,如何正确地改变按钮的可视状态,因为没有NC_MOUSELEAVE消息。通过启动一个计时器来实现这一点,该计时器在NC_MOUSEMOVE消息后通过窗口过程触发:
case WM_NCMOUSEMOVE:
_eLastButtonHit = HitTest();
if ((_eLastButtonHit == HIT_CONSTANTS.HTCLOSE) ||
(_eLastButtonHit == HIT_CONSTANTS.HTMAXBUTTON) ||
(_eLastButtonHit == HIT_CONSTANTS.HTMINBUTTON))
{
StartTimer();
InvalidateWindow();
}
base.WndProc(ref m);
break;
当使用与默认大小不同的按钮时,必须让操作系统知道,以便触发内部事件,如在悬停在标题栏按钮上时显示工具提示。这是通过存储按钮大小,进行相对命中测试,然后通过WM_NCHITTEST的结果传递正确的HIT_CONSTANTS标志来完成的:
case WM_NCHITTEST:
_eLastWindowHit = (HIT_CONSTANTS)DefWindowProc(m.HWnd, m.Msg, m.WParam, m.LParam);
_eLastButtonHit = HitTest();
if ((_eLastButtonHit == HIT_CONSTANTS.HTCLOSE) ||
(_eLastButtonHit == HIT_CONSTANTS.HTMAXBUTTON) ||
(_eLastButtonHit == HIT_CONSTANTS.HTMINBUTTON))
{
m.Result = (IntPtr)_eLastButtonHit;
}
else
{
m.Result = (IntPtr)_eLastWindowHit;
base.WndProc(ref m);
}
break;