here on MSDN 讨论的 TransparencyKey 方法是最简单的方法。您将表单的 BackgroundImage 设置为图像掩码。图像蒙版具有填充某种颜色的透明区域——紫红色是一种流行的选择,因为实际上没有人使用这种可怕的颜色。然后将表单的TransparencyKey 属性设置为这种颜色,它基本上被屏蔽掉,将这些部分呈现为透明。
但我想在颜色选择器中,您希望紫红色作为一个选项可用,即使没有人选择它。因此,您必须以另一种方式创建自定义形状的表单——通过设置自定义区域。基本上,您创建一个Region object(基本上只是一个多边形)来描述表单的所需形状,然后将其分配给表单的Region property。
请注意,当您这样做时,您正在更改整个窗口的形状,而不仅仅是客户区,因此您的设计需要考虑到这一点。此外,区域不能进行抗锯齿处理,因此如果您使用没有直边的形状,结果往往会非常难看。
还有一个警告……我强烈建议不要这样做。完成它需要相当多的工作,即使你完成了,结果通常也是华而不实和用户敌对的。即使一切顺利,您最终也会得到看起来像this 的东西——没有人想要这样。用户非常习惯于无聊的旧矩形应用程序窗口。应用程序不应试图成为现实世界小部件的精确数字复制品。 似乎这样会使它们直观或易于使用,但实际上并非如此。良好设计的关键是为您的应用程序确定用户的心智模型,并找出一种将其与目标窗口环境设置的标准相结合的好方法。
我注意到这个标签仍然打开并且有一些空闲时间,所以我尝试快速制作一个示例。我让“表格”由两个随机大小的圆圈组成,只是为了强调自定义形状效果和透明度——不要在设计中读到任何东西或得到任何疯狂的想法!这是我想出的:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
public class MyCrazyForm : Form
{
private Size szFormSize = new Size(600, 600);
private Size szCaptionButton = SystemInformation.CaptionButtonSize;
private Rectangle rcMinimizeButton = new Rectangle(new Point(330, 130), szCaptionButton);
private Rectangle rcCloseButton = new Rectangle(new Point(rcMinimizeButton.X + szCaptionButton.Width + 3, rcMinimizeButton.Y), SystemInformation.CaptionButtonSize);
public MyCrazyForm()
{
// Not necessary in this sample: the designer was not used.
//InitializeComponent();
// Force the form's size, and do not let it be changed.
this.Size = szFormSize;
this.MinimumSize = szFormSize;
this.MaximumSize = szFormSize;
// Do not show a standard title bar (since we can't see it anyway)!
this.FormBorderStyle = FormBorderStyle.None;
// Set up the irregular shape of the form.
using (GraphicsPath path = new GraphicsPath())
{
path.AddEllipse(0, 0, 200, 200);
path.AddEllipse(120, 120, 475, 475);
this.Region = new Region(path);
}
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
// Force a repaint on activation.
this.Invalidate();
}
protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);
// Force a repaint on deactivation.
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Draw the custom title bar ornamentation.
if (this.Focused)
{
ControlPaint.DrawCaptionButton(e.Graphics, rcMinimizeButton, CaptionButton.Minimize, ButtonState.Normal);
ControlPaint.DrawCaptionButton(e.Graphics, rcCloseButton, CaptionButton.Close, ButtonState.Normal);
}
else
{
ControlPaint.DrawCaptionButton(e.Graphics, rcMinimizeButton, CaptionButton.Minimize, ButtonState.Inactive);
ControlPaint.DrawCaptionButton(e.Graphics, rcCloseButton, CaptionButton.Close, ButtonState.Inactive);
}
}
private Point GetPointFromLParam(IntPtr lParam)
{
// Handle 64-bit builds, which we detect based on the size of a pointer.
// Otherwise, this is functionally equivalent to the Win32 MAKEPOINTS macro.
uint dw = unchecked(IntPtr.Size == 8 ? (uint)lParam.ToInt64() : (uint)lParam.ToInt32());
return new Point(unchecked((short)dw), unchecked((short)(dw >> 16)));
}
protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0x112;
const int WM_NCHITTEST = 0x84;
const int WM_NCLBUTTONDOWN = 0xA1;
const int HTCLIENT = 1;
const int HTCAPTION = 2;
const int HTMINBUTTON = 8;
const int HTCLOSE = 20;
// Provide additional handling for some important messages.
switch (m.Msg)
{
case WM_NCHITTEST:
{
base.WndProc(ref m);
Point ptClient = PointToClient(GetPointFromLParam(m.LParam));
if (rcMinimizeButton.Contains(ptClient))
{
m.Result = new IntPtr(HTMINBUTTON);
}
else if (rcCloseButton.Contains(ptClient))
{
m.Result = new IntPtr(HTCLOSE);
}
else if (m.Result.ToInt32() == HTCLIENT)
{
// Make the rest of the form's entire client area draggable
// by having it report itself as part of the caption region.
m.Result = new IntPtr(HTCAPTION);
}
return;
}
case WM_NCLBUTTONDOWN:
{
base.WndProc(ref m);
if (m.WParam.ToInt32() == HTMINBUTTON)
{
this.WindowState = FormWindowState.Minimized;
m.Result = IntPtr.Zero;
}
else if (m.WParam.ToInt32() == HTCLOSE)
{
this.Close();
m.Result = IntPtr.Zero;
}
return;
}
case WM_SYSCOMMAND:
{
// Setting the form's MaximizeBox property to false does *not* disable maximization
// behavior when the caption area is double-clicked.
// Since this window is fixed-size and does not support a "maximized" mode, and the
// entire client area is treated as part of the caption to enable dragging, we also
// need to ensure that double-click-to-maximize is disabled.
// NOTE: See documentation for WM_SYSCOMMAND for explanation of the magic value 0xFFF0!
const int SC_MAXIMIZE = 0xF030;
if ((m.WParam.ToInt32() & 0xFFF0) == SC_MAXIMIZE)
{
m.Result = IntPtr.Zero;
}
else
{
base.WndProc(ref m);
}
return;
}
}
base.WndProc(ref m);
}
}
它在 Windows XP 和 7 上并排运行:
哇!它确实有效,但距离完成还有很长的路要走。还有很多小事需要做。例如:
- 标题按钮在单击时不会“按下”。有一个内置状态可以与
DrawCaptionButton 方法一起使用,但是您需要在单击其中一个按钮时强制重绘,或者直接在表单上进行重绘。
- 它不支持视觉样式。这是
ControlPaint 类的限制;它是在视觉样式发明之前编写的。实现对此的支持需要做更多的工作,但有a WinForms wrapper。您必须确保编写后备代码来处理视觉样式也被禁用的情况。
- 字幕按钮实际上并没有居中——我只是盯着它看。即使你的眼球比我的好,这仍然是一个糟糕的方法,因为标题按钮可以有不同的大小,这取决于系统设置和你运行的操作系统版本(Vista 改变了按钮的形状)。李>
- 当鼠标向上 移到标题栏按钮上时,其他窗口会调用这些操作。但是当您尝试使用
WM_NCLBUTTONUP(而不是WM_NCLBUTTONDOWN)时,您必须双击标题按钮才能使它们起作用。这是因为非客户区正在捕获鼠标。我确信有一个解决方案,但在我发现它之前我已经没有耐心了。
- 当窗口最小化(或恢复)时,您不会获得漂亮的动画效果,字幕按钮也不会在悬停时发光。您可以免费获得大量视觉细节,而这些默认样式在此处未使用。其中一些可以通过编写更多代码轻松添加,但是对于您编写的每一行代码,维护负担都会猛增——较新版本的 Windows 可能会破坏一些东西。更糟糕的是,有些事情实现起来远,所以它可能甚至不值得。而所有这些努力又是为了什么?
- 在激活/停用时重新绘制整个表单只是为了更新标题按钮可能是个坏主意。如果您在表单上绘制其他更复杂的内容,这可能会降低整个系统的速度。
- 一旦开始向表单添加控件,您可能会遇到问题。例如,即使使用 Label 控件,您也无法通过单击并按住该 Label 控件的顶部来拖动表单。标签控件不返回
HTTRANSPARENT 以响应 WM_NCHITTEST 消息,因此消息不会传递到父窗体。您可以继承 Label 来这样做并使用您的子类。
- 代码完全未经 Windows 8 测试,因为我没有副本。自定义非客户区往往会因新的操作系统更新而崩溃,这些更新会改变非客户区的呈现方式,因此您需要自己相应地调整代码。即使它有效,它也肯定不会具有正确的 Windows 8 外观。
-
等等,等等。
您还可以看到,就像我在上面警告的那样,圆形边框没有抗锯齿,因此看起来参差不齐。不幸的是,这是无法修复的。