用C#制作 个性化窗体 winform 界面

引言:
谁都希望自己的应用程序能让人留下一个深刻的印象,让自己的程序窗体有一件与众不同的"外衣"是一个好办法。试想:在一大堆的普通窗口中突然跳出一个很酷的界面,一定能让人眼睛一亮进而产生兴趣的。
在 VB,VC中如何定制可伸缩个性化窗口早就不是什么秘密了,已经有了大量相关的文章进行介绍,无非都是如何调用系统API之类的方法,但是在.Net中调 用API却相对比较麻烦,所以使用.Net制作个性化窗体的文章也有一些,一般都是使用透明背景加图片的方式,所以不能移动或者不能任意放大缩小窗体。那 有没有不需要调用系统API的方法来实现可伸缩的个性化窗体的办法呢?当然有,.Net Framework提供了一套非常强大的系统类库,我们下面就要做一个使用"纯".Net打造的可伸缩个性化窗体。
我们需要将窗体所有的"皮肤" 全部换成我们自己定义的,包括标题栏,边框和系统按纽等,所以我们首先需要定做一套自己的皮肤图形文件。因为窗体是可伸缩的,所以我们不能简单的取一整幅 图片来作为窗体皮肤,而是根据需要先将图片切割为不同的部分,一般来说,有以下图示几大部分(红线为切割线):


根据方位,将图片各部分命名 为:Bottom_Left,Bottom_Middle,Bottom_Right,Middle_Left,Middle_Right,Top_Left,Top_Middle,Top_Right,SysButton_Min,SysButton_Max,SysButton_Close,SysButton_Restore 等。注意,有些图片是可以伸缩的地方,比如Middle_Left,Bottom_Middle等处的图片可以只是一小块,以后需要进行重复贴图。而有些 固定大小的图片,比如Bottom_Left,Top_Left等以后只用贴一次,实际应用的时候要注意区分。
采用以上原则,你便可以制作皮肤图片,图示如下:


然后可以将这些图片放到ImageList控件或资源文件中供程序调用。(关于如何制作资源文件请参考:Visual C#资源文件编程--创建资源文件)
接下来,我们使用Visual Studio .Net新建一个Windows应用程序的项目,在窗体的属性设置中,将窗体的FormBorderStyle属性设置为None(无边框样式),如下图所示:


定义一个资源管理器:新建 一个资源文件,命名为SkinWindow.Skin.resx,把所有的图片加入其中。

在Form1类中,声名位图变量,获取图片

View Code
        Bitmap Bottom_Left = SkinWindow_Skin.Bottom_Left;
Bitmap Bottom_Middle = SkinWindow_Skin.Bottom_Middle;
Bitmap Bottom_Right = SkinWindow_Skin.Bottom_Right;
Bitmap Middle_Left = SkinWindow_Skin.Middle_Left;
Bitmap Middle_Right = SkinWindow_Skin.Middle_Right;
Bitmap SysButton_Close = SkinWindow_Skin.SysButton_Close;
Bitmap SysButton_Max = SkinWindow_Skin.SysButton_Max;
Bitmap SysButton_Min = SkinWindow_Skin.SysButton_Min;
Bitmap Top_LeftActive = SkinWindow_Skin.Top_LeftActive;
Bitmap Top_MiddleActive = SkinWindow_Skin.Top_MiddleActive;
Bitmap Top_RightActive = SkinWindow_Skin.Top_RightActive;

重载Form的OnPaint事件:

View Code
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
//手工画窗体的各个部分
DrawMiddle_Left(e.Graphics);//画左边框
DrawBottom_Middle(e.Graphics);//画下边框
DrawMiddle_Right(e.Graphics);//画右边框
DrawBottom_Left(e.Graphics);//画左下角
DrawBottom_Right(e.Graphics);//画右下角
DrawTop_Left(e.Graphics);//画标题栏左边
DrawTop_Right(e.Graphics);//画标题栏右边
DrawTop_Middle(e.Graphics);//画标题栏中间
DrawSys_Button(e.Graphics);//画系统按纽
base.OnPaint(e);
}
//中间
private void DrawMiddle_Left(Graphics g)
{
Brush brush = new TextureBrush(Middle_Left, new Rectangle(0, 0,Middle_Left.Width, Middle_Left.Height));
g.FillRectangle(brush, 0, 0, Middle_Left.Width, Height - Bottom_Middle.Height);
}
//下边
private void DrawBottom_Middle(Graphics g)
{
Brush brush = new TextureBrush(Bottom_Middle, new Rectangle(0, 0, 1, Bottom_Middle.Height));
g.FillRectangle(brush, Bottom_Left.Width, Height - Bottom_Middle.Height, Width-Bottom_Left.Width-Bottom_Right.Width, Bottom_Middle.Height);
}
private void DrawBottom_Left(Graphics g)
{
Brush brush = new TextureBrush(Bottom_Left, new Rectangle(0, 0, Bottom_Left.Width, Bottom_Left.Height));
g.FillRectangle(brush, 0, Height - Bottom_Middle.Height, Bottom_Left.Width, Bottom_Left.Height);
}
private void DrawBottom_Right(Graphics g)
{
Brush brush = new TextureBrush(Bottom_Right, new Rectangle(0, 0, Bottom_Right.Width, Bottom_Right.Height));
g.FillRectangle(brush, Width - Bottom_Right.Width, Height - Bottom_Middle.Height, Bottom_Right.Width, Bottom_Right.Height);
}
//右边
private void DrawMiddle_Right(Graphics g)
{
Brush brush = new TextureBrush(Middle_Right, new Rectangle(0, 0, Middle_Right.Width, Middle_Right.Height));
g.FillRectangle(brush, Width-Middle_Left.Width, 0, Middle_Left.Width, Height - Bottom_Middle.Height);
}

//写top
private void DrawTop_Left(Graphics g)
{
Brush brush = new TextureBrush(Top_LeftActive, new Rectangle(0, 0, Top_LeftActive.Width, Top_LeftActive.Height));
g.FillRectangle(brush, 0, 0, Top_LeftActive.Width, Top_LeftActive.Height);
}
private void DrawTop_Right(Graphics g)
{
Brush brush = new TextureBrush(Top_RightActive, new Rectangle(0, 0, Top_RightActive.Width, Top_RightActive.Height));
g.FillRectangle(brush,Width-Top_RightActive.Width, 0, Top_RightActive.Width, Top_RightActive.Height);
}
private void DrawTop_Middle(Graphics g)
{
int mWidth = Width - Top_LeftActive.Width - Top_RightActive.Width;
Brush brush = new TextureBrush(Top_MiddleActive, new Rectangle(0, 0,1, Top_MiddleActive.Height));
g.FillRectangle(brush, Top_LeftActive.Width, 0, mWidth, Top_MiddleActive.Height);
}
private void DrawSys_Button(Graphics g)
{
int mWidth = Width - SysButton_Close.Width;
Brush brush = new TextureBrush(SysButton_Close, new Rectangle(0, 0, SysButton_Close.Width,SysButton_Close.Height));
g.FillRectangle(brush, mWidth, 0, SysButton_Close.Width,SysButton_Close.Height );
mWidth = Width - SysButton_Close.Width-SysButton_Max.Width;
Brush brush2 = new TextureBrush(SysButton_Max, new Rectangle(0, 0, SysButton_Max.Width,SysButton_Max.Height));
g.FillRectangle(brush2, mWidth, 0, SysButton_Max.Width,SysButton_Max.Height);
mWidth = Width - SysButton_Close.Width-SysButton_Max.Width-SysButton_Min.Width;
Brush brush3 = new TextureBrush(SysButton_Max, new Rectangle(0, 0, SysButton_Min.Width,SysButton_Min.Height));
g.FillRectangle(brush3, mWidth, 0, SysButton_Min.Width,SysButton_Min.Height);
}

衣服穿上了,现在我们的程序就有了不同的外观:


看上去已经很酷了,不过只是花架子,因为边框,标题栏,系统按纽都是我们自己画上去的假的边框,标题栏和系统按纽,所以这个窗体既不能移动也不能自 由的放大缩小,点关闭都没用。以前我们写程序从来都不需要关心这个的,这些都是窗体的基本功能呀?没有从来都没有想到这个竟然还会是个问题吧?
怎么办呢?答案就是我们自己来做,不过会比较麻烦,因为取消掉了边框,所以Windows不会帮你发出系统事件,你捕捉不到系统发生了什么事情的话,就没有办法写下响应代码,所以我们要自己检测鼠标的坐标,并根据鼠标的动作,自己来发出事件消息,然后进行响应。
首先我们先定义出一些响应事件的代码,我定义了一个抽象的基类MouseAction,用来表示所有的鼠标事件,它有一个抽象方法Action:

View Code
public abstract class MouseAction
{
public abstract void Action(int ScreenX, int ScreenY, System.Windows.Forms.Form form);
}

然后再来定义出它的各个派生类来表示出具体每个鼠标事件响应的代码。
下面是一个向右拉伸窗口事件的代码响应:

View Code
public class MouseDrag : MouseAction
{
private int x, y;
public MouseDrag(int hitX, int hitY)
{
x = hitX;
y = hitY;
}

public override void Action(int ScreenX, int ScreenY, System.Windows.Forms.Form form)
{
form.Location = new Point(ScreenX - x, ScreenY - y);
}
}

接下来我们开始编写发出事件的代码,先在Form1类中定义几个变量:

View Code
private int LEFT = 5, RIGHT = 5, BOTTOM = 5, TOP = 5, TITLE_WIDTH = 45;//边框和标题栏的大小
private int x = 0, y = 0;//保存鼠标的临时坐标
private MouseAction mouse;//鼠标的事件响应对象

附:e为System.Windows.Forms.MouseEventArgs

然后在Form的MouseDown事件中记录下鼠标的当前坐标:

View Code
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
x = e.X;
y = e.Y;
mouse = new MouseDrag(x,y);
}

然后再根据鼠标的坐标定义出事件响应对象:
//鼠标点击左上边框
if((e.X <= LEFT + 10 && e.Y <= TOP) || (e.Y <= TOP + 10 && e.X <= LEFT))
{
    mouse = new MouseSizeTopLeft(Location.X, Location.Y, Width, Height);
    return;
}

//当然有的事件也可以直接响应:
//鼠标点击系统关闭按纽
if(e.X > Width - 20 && e.Y > 6 && e.X < Width - 20 + SysButton_Min.Width && e.Y < 6 + SysButton_Min.Height)
{
    Close();
    return;
}

View Code
//大部分的事件响应实际上是在MouseMove事件中完成的:
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
this.Cursor = CheckCursorType(e.X, e.Y);//改变鼠标的指针形状
if (mouse != null)
{
mouse.Action(Control.MousePosition.X, Control.MousePosition.Y, this);//执行时间响应
//注意坐标是Control.MousePosition这个静态变量给出的,它的值为鼠标在桌面上的全局坐标
}
}

//给出每个不同部位的鼠标的指针形状:

View Code
private Cursor CheckCursorType(int X, int Y)
{
if (((X <= LEFT + 10 && Y <= TOP) || (Y <= TOP + 10 && X <= LEFT))
|| ((X >= Width - RIGHT - 10 && Y >= Height - BOTTOM)
|| (Y >= Height - BOTTOM - 10 && X >= Width - RIGHT)))
{
return Cursors.SizeNWSE;
}
else if (((Y <= TOP + 10 && X >= Width - RIGHT)
|| (Y <= TOP && X >= Width - RIGHT - 10))
|| ((X <= LEFT && Y >= Height - BOTTOM - 10)
|| (Y >= Height - BOTTOM && X <= LEFT + 10)))
{
return Cursors.SizeNESW;
}
else if (X >= Width - RIGHT || X <= LEFT)
{
return Cursors.SizeWE;
}
else if (Y >= Height - BOTTOM || Y <= TOP)
{
return Cursors.SizeNS;
}
else
{
return Cursors.Arrow;
}
}

//最后在MouseUp事件中将mouse变量释放掉:

View Code
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
mouse = null;
}

//为了更加逼真,还可以加上标题栏的双击最大化或者还原的事件:

View Code
private void Form1_DoubleClick(object sender, EventArgs e)
{
if (y > TOP && y < TITLE_WIDTH)
{
if (WindowState == FormWindowState.Normal)
{
WindowState = FormWindowState.Maximized;
//SysButton = SysButton_Restore;
Invalidate();
}
else if (WindowState == FormWindowState.Maximized)
{
WindowState = FormWindowState.Normal;
//SysButton = SysButton_Max;
Invalidate();
}
}
}

防止窗体被缩小成一个点,最好给窗口的MinimumSize赋上一个适当的值,例如200,200。
总结:
现在编译你的程序,运行试试,你的窗体已经拥有正常窗体所拥有的全部功能,并且还具有与众不同的外观,不明就里的人一下子还猜不出来你是怎么弄的,好了,乘别人还不知道,赶快拿出去炫耀一下吧 :)。

posted @ 2011-10-28 12:56  H.H.H  阅读(1887)  评论(0编辑  收藏  举报