我正在寻找在C#中创建自定义形状的表单。
我有一个在某些地方透明的背景图片(png)。
无论如何,要使表单形状成为此图像的形状,而不是“常规”矩形?
我只是问这个问题,因为我希望为我的PC设计一个自定义外观(有点像雨量计/火箭坞相结合,但采用“压缩”方式)。
我听说过使用“透明键”,但这会从背景中删除颜色(我将在以后使用颜色选择器,因此,如果用户选择了该特定颜色,它将不会显示)。
一如既往,任何帮助将不胜感激。
最佳答案
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上并行运行:
ew!它确实可以工作,但是距离完成还有很长的路要走。还有很多小事情需要完成。例如:
单击时字幕按钮不会“按下”。有一个内置状态,可以与
DrawCaptionButton
方法一起使用,但是您需要在单击其中一个按钮时强制重绘,或者在此后直接在窗体上进行重绘。它不支持视觉样式。这是
ControlPaint
类的限制;它是在视觉样式发明之前编写的。实现对此的支持还需要做很多工作,但是有a WinForms wrapper。您将必须确保编写后备代码来处理禁用视觉样式的情况。字幕按钮实际上并未居中-我只是盯着它看。即使您的眼球比我的眼球好,这仍然是一个糟糕的方法,因为字幕按钮的大小可能不同,具体取决于系统设置和您所运行的操作系统的版本(Vista更改了按钮的形状)。
当鼠标悬停在标题栏按钮上方时,其他窗口将调用操作。但是,当您尝试使用
WM_NCLBUTTONUP
(而不是WM_NCLBUTTONDOWN
)时,必须双击字幕按钮才能使它们起作用。这是因为非客户区正在捕获鼠标。我敢肯定有解决方案,但是在发现它是什么之前,我就忍耐了一下。当窗口最小化(或还原)时,您不会获得漂亮的动画效果,字幕按钮也没有悬停发光。您可以免费获得大量视觉效果,这些默认风格在这里是行不通的。通过编写更多代码,可以轻松添加其中的一些内容,但是对于您编写的每一行,维护负担激增—新版本的Windows可能会破坏事情。更糟糕的是,有些事情远非易事,所以甚至不值得。而所有这些努力又是为了什么呢?
仅在激活/停用时重新绘制整个表单以更新字幕按钮可能是一个坏主意。如果要在表单上绘制其他更复杂的内容,则可能会减慢整个系统的运行速度。
一旦开始向表单添加控件,您可能会遇到问题。例如,即使有了Label控件,也无法通过单击并按住该Label控件的顶部来拖动表单。标签控件不会响应
HTTRANSPARENT
消息而返回WM_NCHITTEST
,因此该消息不会传递给父表单。您可以为Label
子类这样做,而改用您的子类。该代码未经Windows 8完全测试,因为我没有副本。自定义非客户区域往往会因新的OS更新而爆炸,这些更新会更改非客户区域的呈现方式,因此您需要自行调整相应的代码。即使可以正常使用,也肯定不会具有正确的Windows 8外观。
等,等等。
您还可以看到,就像我在上面警告的那样,圆形边框没有抗锯齿,因此看起来参差不齐。不幸的是,这是无法解决的。
关于c# - 创建自定义形状的Windows窗体而不是“默认”矩形?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25307970/