做.NET WinForm的开发也有一段时间了,对.NET的界面设计也有了一定的了解。自认为自己学的这些东西都是网上看高手们的东西后总结出来的,第一次把这些东西写出来。
不好的地方请大家多多指教。
大家先看一下做出来的效果吧!
网上也看过很多做.NET窗体的例子,我只是把网上的这些东西综合了一下,主要有下面这些特点:
1、边框是半透明的,透明度可根据需要自己更改;
2、可以改变窗体的大小,改变后样式不变;
3、窗体的边框是不规则的;
4、重点解决了窗体会出现闪烁的问题,在窗体移动的时候也不会闪烁;
5、使用方便,只要将AlphaFormPanel拖动到一般的窗体上就可以实现换肤;
设计思路说明:
一、.NET下处理一个窗体部分透明我所知道的有两种方法:
1、用一张支持Alhpa通道的图片来处理半透明,这种方式处理出来的效果会很好,甚至可以用一张动态的图片来做背景。
相信有人看过那个游动的鱼的程序,鱼的边缘是半透明的,就是用这种方式做的。这种方式整个窗体都是通过UpdateLayeredWindow画
出来的,如果要在上面加控件的话,所有的控件都要自己来绘制,显然在具体的项目中用这种方式的话会大大增加开发的
难度。有兴趣的人可以看看这个程序:
关键的代码就是根据这种支持Alhpa通道的图片来绘制窗体
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern int UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize,
IntPtr hdcSrc, ref Point pptSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
public void SetBits(Bitmap bitmap)
{
if (!haveHandle) return;
if (!Bitmap.IsCanonicalPixelFormat(bitmap.PixelFormat) || !Bitmap.IsAlphaPixelFormat(bitmap.PixelFormat))
throw new ApplicationException("图片必须是32位带Alhpa通道的图片。");
IntPtr oldBits = IntPtr.Zero;
IntPtr screenDC = Win32.GetDC(IntPtr.Zero);
IntPtr hBitmap = IntPtr.Zero;
IntPtr memDc = Win32.CreateCompatibleDC(screenDC);
try
{
Win32.Point topLoc = new Win32.Point(Left, Top);
Win32.Size bitMapSize = new Win32.Size(bitmap.Width, bitmap.Height);
Win32.BLENDFUNCTION blendFunc = new Win32.BLENDFUNCTION();
Win32.Point srcLoc = new Win32.Point(0, 0);
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
oldBits = Win32.SelectObject(memDc, hBitmap);
blendFunc.BlendOp = Win32.AC_SRC_OVER;
blendFunc.SourceConstantAlpha = 255;
blendFunc.AlphaFormat = Win32.AC_SRC_ALPHA;
blendFunc.BlendFlags = 0;
Win32.UpdateLayeredWindow(Handle, screenDC, ref topLoc, ref bitMapSize, memDc, ref srcLoc, 0, ref blendFunc, Win32.ULW_ALPHA);
}
finally
{
if (hBitmap != IntPtr.Zero)
{
Win32.SelectObject(memDc, oldBits);
Win32.DeleteObject(hBitmap);
}
Win32.ReleaseDC(IntPtr.Zero, screenDC);
Win32.DeleteDC(memDc);
}
}
2、用两个窗体来实现边框半透明
后面的窗体用来做边框,前面的窗体放其他的控件,前面的窗体跟随后面的窗体移动和改变大小,这样就很容易的控制后面的窗体
半透明显示。这种方式虽然麻烦一点,但是不用像第一种方式那样自己绘制所有的控件,所以还是可以在项目中使用的。我的这个
例子用的就是这种方式实现的。
二、窗体边框的处理
我的窗体的边框是用图片来处理的,为了使窗体的边框在改变大小后的样式不改变,上下边框做成了3段式的,中间部分平铺,两端保持不变
这样窗体任意缩放后样式都不会改变。如果对GDI+熟悉的话,也可以不用图片来处理,直接绘制渐变填充也可以。我重写的承载图片的PictureBox,
让PictureBox将所有的消息都传给父窗体来处理,这样就可以由父窗体统一的处理窗体的缩放和移动了,具体的代码如下:
public partial class PictureBoxEX : PictureBox
{
public delegate void delSetFormSize(int intInfo);
public event delSetFormSize evtSetFormSize;
#region 属性
private bool _bTopLeft = false;
public bool BTopLeft
{
get { return _bTopLeft; }
set { _bTopLeft = value; }
}
private bool _bTop = false;
public bool BTop
{
get { return _bTop; }
set { _bTop = value; }
}
private bool _bTopRight = false;
public bool BTopRight
{
get { return _bTopRight; }
set { _bTopRight = value; }
}
private bool _bLeft = false;
public bool BLeft
{
get { return _bLeft; }
set { _bLeft = value; }
}
private bool _bBottomLeft = false;
public bool BBottomLeft
{
get { return _bBottomLeft; }
set { _bBottomLeft = value; }
}
private bool _bBottom = false;
public bool BBottom
{
get { return _bBottom; }
set { _bBottom = value; }
}
private bool _bRight = false;
public bool BRight
{
get { return _bRight; }
set { _bRight = value; }
}
private bool _bBottomRight = false;
public bool BBottomRight
{
get { return _bBottomRight; }
set { _bBottomRight = value; }
}
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
public PictureBoxEX()
{
;
}
#endregion
#region 重新鼠标消息
const int WM_NCHITTEST = 0x0084;
const int HTTRANSPARENT = -1;
const int HTLEFT = 10;
const int HTRIGHT = 11;
const int HTTOP = 12;
const int HTTOPLEFT = 13;
const int HTTOPRIGHT = 14;
const int HTBOTTOM = 15;
const int HTBOTTOMLEFT = 0x10;
const int HTBOTTOMRIGHT = 17;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WM_NCHITTEST:
Point vPoint = new Point((int)m.LParam & 0xFFFF,
(int)m.LParam >> 16 & 0xFFFF);
vPoint = PointToClient(vPoint);
int intInfo = -1;
if (_bTopLeft)
{
if (vPoint.X <= 10)
{
intInfo = HTTOPLEFT;
}
}
else if (_bLeft)
{
intInfo = HTLEFT;
}
else if (_bBottomLeft)
{
intInfo = HTBOTTOMLEFT;
}
else if (_bBottom)
{
intInfo = HTBOTTOM;
}
else if (_bBottomRight)
{
intInfo = HTBOTTOMRIGHT;
}
else if (_bRight)
{
intInfo = HTRIGHT;
}
else if (_bTopRight)
{
if (vPoint.X >= ClientSize.Width - 10)
{
intInfo = HTTOPRIGHT;
}
}
else if (_bTop)
{
if (vPoint.Y <= 5)
{
m.Result = (IntPtr)HTTOP;
intInfo = HTTOP;
}
}
if (evtSetFormSize != null && intInfo != -1)
{
evtSetFormSize(intInfo);
}
//将消息传给父窗体来处理
m.Result = (IntPtr)HTTRANSPARENT;
break;
}
}
#endregion
}
关键代码说明:
一、用到的消息
private const int WM_NCLBUTTONDBLCLK = 0x00A3;
private int _intInfo = -1; //消息回传值
private const int HTLEFT = 10;
private const int HTRIGHT = 11;
private const int HTTOP = 12;
private const int HTTOPLEFT = 13;
private const int HTTOPRIGHT = 14;
private const int HTBOTTOM = 15;
private const int HTBOTTOMLEFT = 0x10;
private const int HTBOTTOMRIGHT = 17;
private const int WM_NCHITTEST = 0x84;
private const int HTCLIENT = 0x01;
private const int HTCAPTION = 0x02;
1、改变窗体大小和移动窗体的位置
所有的和边框有关的消息都传到后面的窗体的处理,承载边框的PictureBoxEx 会将传给它的系统消息忽略掉
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCHITTEST)
{
this.DefWndProc(ref m);
//移动窗体位置
if (m.Result.ToInt32() == HTCLIENT && this.WindowState != FormWindowState.Maximized)
{
m.Result = new IntPtr(HTCAPTION);
}
else
{
base.WndProc(ref m);
}
//改变窗体大小
if (ChangeFormSize && _intInfo != -1 && this.WindowState != FormWindowState.Maximized)
{
m.Result = (IntPtr)_intInfo;
}
_intInfo = -1;
}
//双击鼠标左键的消息
else if (m.Msg == WM_NCLBUTTONDBLCLK)
{
if (ChangeFormSize)
{
//相当于单击一次最大化按钮
btnMax_MouseClick(null, EventArgs.Empty);
}
}
else
{
base.WndProc(ref m);
}
}
2、减少窗体闪烁
下面的是比较常见的减少窗体闪烁的方法
private void SetStyles()
{
SetStyle(
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.SupportsTransparentBackColor, true);
SetStyle(ControlStyles.Selectable, false);
UpdateStyles();
}
还可以设置窗体双缓存在减少闪烁
this.DoubleBuffered = true;
在窗体控件的创建过程中,如果控件过多的话,用下面这个函数来处理控件的创建也可以减少闪烁
public class AvoidControlFlicker
{
private int _paintFrozen;
public void FreezePainting(Control toFreezeControl, bool isToFreeze)
{
if (null == toFreezeControl)
throw new ArgumentNullException("toFreezeControl");
if (isToFreeze && toFreezeControl.IsHandleCreated && toFreezeControl.Visible)
{
if (0 == _paintFrozen++)
{
NativeMethods.SendMessage(toFreezeControl.Handle, NativeConsts.WM_SETREDRAW, 0, 0);
}
}
if (!isToFreeze)
{
if (0 == _paintFrozen) return;
if (0 == --_paintFrozen)
{
NativeMethods.SendMessage(toFreezeControl.Handle, NativeConsts.WM_SETREDRAW, 1, 0);
toFreezeControl.Invalidate(true);
}
}
}
}
3、为了给初学者提供一些帮助,其它一些处理函数也写出来
/// <summary>
/// 窗体位置改变
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void formStyle_LocationChanged(object sender, EventArgs e)
{
if (PForm != null)
{
//同时改变前面的子窗体的位置
CForm.Location = new Point(this.Location.X + pbLeft.Width, this.Location.Y + panelTop.Height);
CForm.Update();
}
}
/// <summary>
/// 关闭按钮单击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnExit_MouseClick(object sender, EventArgs e)
{
this.Close();
}
//关闭窗体时要同时关闭窗体所在的父窗体
protected override void OnClosing(CancelEventArgs e)
{
e.Cancel = true;
base.OnClosing(e);
Owner.Close();
}
/// <summary>
/// 最大化按钮单击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnMax_MouseClick(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Maximized)
{
this.WindowState = FormWindowState.Normal;
this.Opacity = FormOpacity;
}
else
{
this.WindowState = FormWindowState.Maximized;
this.Opacity = 1;
}
}
/// <summary>
/// 最小化按钮单击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnMin_MouseClick(object sender, EventArgs e)
{
this.WindowState = FormWindowState.Minimized;
}
/// <summary>
/// 窗体大小改变
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void formStyle_SizeChanged(object sender, EventArgs e)
{
if (PForm != null)
{
//同时改变子窗体的大小
CForm.Size = new Size(this.Size.Width - pbLeft.Width - pbRight.Width, this.Size.Height - panelTop.Height - panelBottom.Height);
}
}
最后,提供一下这个控件以及示例代码给大家下载: