「控件控」一步一步玩控件:自定义TabControl——从山寨Apple Safari开始

Conmajia 2012-05-18 05:08:29
加精
嗯,各位好,又是我,生物钟颠倒的家伙。

今天我要山寨的是大名鼎鼎的Apple,传说中的「被山寨之王」。

没错,都被我山寨好几次了。

说起Apple,相信大家对他家的各种产品,不管他软还是硬,都有相当的好感。

最近Apple把自家的Web浏览器Safari升级到了第5版,并同步推出了Windows版,支持WinXP开始的全部Windows版本。

不得不说,这是一个很给力的浏览器,它看起来就像这样。



其实我并不是苹果控,我控红富士要多点。客观的评价Safari,这个软件界面华丽,速度快,但在Windows平台上,TopSites首页资源消耗巨大,操作习惯和常规Win浏览器有一定区别,部分网页不支持或不兼容(WebKit引擎)。

不多说了,这不是重点。重点在于它的「偏好设置(Preference)」界面,就是这个:



看到这个,你肯定会觉得怎么苹果的东西会变得这么一般呢?不过就是TabControl上面增加了几个图标嘛。

嗯,朋友,你说的似乎没错。但是,我曾经也算中肯的评价过苹果的东西,抛开外观,苹果的特点之一就是「闷骚」,还有「OSD」,也就是强迫症。

听我这么说显得很干瘪,那么就让我顺着导航标签,一路点击过去,看看会发生什么事。



没错,这窗口会自动伸缩,而且是动画的!这就是apple闷骚的地方!

为了不让他一家独骚,为了不辜负他被山寨之王的名头,我只好勉为其难的山寨一番了。

通过这此山寨,只要你用C#写过HelloWorld、HelloKitty之类的,你就能做出和这里写的一模一样的控件来。

所以说,控件嘛,没什么,就是一点点磨出来的。


山寨前的准备

山寨其实没啥好准备的,但还是需要几样重要的东西:

- 极大的耐心和毅力
- 原装货:Apple Safari 5
- 照相机:Snagit 10
- 生产线:Visual Studio 2005
- 手册:MSDN
- 苦力:野比

分析,分析

山寨的灵魂在于分析,首先把刚才拍的高清果照扯过来分解了。



我把他分解成这几个部分:

1. 根据标签不同修改窗体标题
2. 导航标签
3. 标签面板
4. 自动缩放

这篇是#1-1,就专心讨论关于标签的东西,也就是第2、3点。

组件设计

分析了其中的功能,那么就要想想怎么来实现。

从功能来看,这个窗口实际上是由多个子面板切换来实现的,最多他加了点自动缩放。所以从本质来说,还是一个标签切换的窗口。

我最早想到的就是大名鼎鼎却又丑得无以复加的TabControl。



按照标签切换这个思想,TabControl完全可以胜任这次的山寨需求。但是TabControl这么丑,必须要给它整整容才行。想不到我竟然有整容的才华。


下手吧,年轻人!

因为要改动的地方会很多,所以还是完全自己来绘制标签好了。为了完全自定义TabControl,同时方便循环利用,从TabControl派生一个我们自己的标签控件TabControlEx。

public class TabControlEx : System.Windows.Forms.TabControl 




这就是我们的TabControlEx,看起来和TabControl没什么两样(那是当然的)。

为了让他看起来不太一样,在构造函数里加上下面的代码。

    base.SetStyle(
ControlStyles.UserPaint | // 控件将自行绘制,而不是通过操作系统来绘制
ControlStyles.OptimizedDoubleBuffer | // 该控件首先在缓冲区中绘制,而不是直接绘制到屏幕上,这样可以减少闪烁
ControlStyles.AllPaintingInWmPaint | // 控件将忽略 WM_ERASEBKGND 窗口消息以减少闪烁
ControlStyles.ResizeRedraw | // 在调整控件大小时重绘控件
ControlStyles.SupportsTransparentBackColor, // 控件接受 alpha 组件小于 255 的 BackColor 以模拟透明
true); // 设置以上值为 true
base.UpdateStyles();


这段代码的意思就像注释里说的,注意ControlStyles这个枚举是可以按位组合的,所以上面要用「或(|)」来进行连接,这样系统就会完全忽视TabControl这个基类的界面显示,而使用我们自己的方式来呈现UI。

现在TabControlEx看起来是这样的。



啥米?!!OMG!东西哪去了??

嗯,当我第一次玩UserPaint的时候,也被吓了一跳。其实这就是上面我们设置的那句ControlStyles.UserPaint,于是系统就不帮我们画任何东西了。

所以从现在开始,一切都要靠自己了。下面所有的绘制都在OnPaint()方法中绘制。

为了先让我们找到方向,在OnPaint()方法中,我们先把Tab的位置找到,为此我们给每个Tab的边框都画出来。

    protected override void OnPaint(PaintEventArgs e)
{
for (int i = 0; i < this.TabCount; i++)
{
e.Graphics.DrawRectangle(Pens.Red, this.GetTabRect(i));
}
}



TabControl.GetTabRect(int)的功能是获得指定index的标签的矩形位置。画完后,我们的TabControlEx看起来不那么迷糊了。



可是,标签的大小还是不对,我们要的不是普通的那种长条,而是闷骚的苹果的瘦高型,要像这样。



嗯,好吧,我们回到构造函数,用下面的语句来设置大小。

    this.SizeMode = TabSizeMode.Fixed;  // 大小模式为固定
this.ItemSize = new Size(44, 55); // 设定每个标签的尺寸


上面设置44x55其实只是因为苹果原版刚好是这么大,先这么着,后面如果不合适了,回头再来改。现在标签是这样的了。



Apple标签的选中状态是带阴影的,看起来很酷,可是如果我用GDI+来画的话,什么渐变什么变换,烦都烦死了。怎么办呢?

请记住,我们正在山寨。所谓山寨的精神,就是不问方法、不择手段,只要最后「看起来一样」就行了。所以,我决定用上抠图大法,把apple的背景图抠出来。



把这个背景保存为TabBackground.bmp文件,然后添加到项目中,把它做成「嵌入的资源」,就像这样。



然后我们用一个变量来保存背景图。因为这张图随时会用到,所以还是做成全局变量(类级别),在构造函数里读取图片。

    Image backImage;
public TabControlEx()
{
// (略)
backImage = new Bitmap(this.GetType(), "TabButtonBackground.bmp"); // 从资源文件(嵌入到程序集)里读取图片
}


现在有了图标,加上去看看吧。在OnPaint()里这样写。

    if (this.SelectedIndex == i)
{
e.Graphics.DrawImage(backImage, this.GetTabRect(i));
}


只有被选中的标签才会出现这种背景。于是,标签变成这样了。



绘制文字

这会看着还挺单调的,所以我们来加点料。下面来画文字。说起文字,我想你应该注意到了,Safari的标签文字,都是带有阴影的(准确的说是高光)。



所以,在绘制文字时,先用高光色绘制第一遍,再用普通文字色(黑)绘制第二遍。

    protected override void OnPaint(PaintEventArgs e)
{
for (int i = 0; i < this.TabCount; i++)
{
// (略)

// Calculate text position
Rectangle bounds = this.GetTabRect(i);
PointF textPoint = new PointF();
SizeF textSize = TextRenderer.MeasureText(this.TabPages[i].Text, this.Font);

// 注意要加上每个标签的左偏移量X
textPoint.X
= bounds.X + (bounds.Width - textSize.Width) / 2;
textPoint.Y
= bounds.Bottom - textSize.Height - this.Padding.Y;

// Draw highlights
e.Graphics.DrawString(
this.TabPages[i].Text,
this.Font,
SystemBrushes.ControlLightLight, // 高光颜色
textPoint.X,
textPoint.Y);

// 绘制正常文字
textPoint.Y--;
e.Graphics.DrawString(
this.TabPages[i].Text,
this.Font,
SystemBrushes.ControlText, // 正常颜色
textPoint.X,
textPoint.Y);

}
}




缤纷色彩的源泉:图标

文字也有了,那么接下来就轮到图标了。TabControl是用ImageList控件来存储自己使用的图标的,那么添加一个ImageList,然后加入图标。注意这里都要32x32的图标,所以应该设置ImageList.ImageSize为32x32。

    // 绘制图标
if (this.ImageList != null)
{
int index = this.TabPages[i].ImageIndex;
string key = this.TabPages[i].ImageKey;
Image icon = new Bitmap(1, 1);

if (index > -1)
{
icon = this.ImageList.Images[index];
}
if (!string.IsNullOrEmpty(key))
{
icon = this.ImageList.Images[key];
}
e.Graphics.DrawImage(
icon,
bounds.X + (bounds.Width - icon.Width) / 2,
bounds.Top + this.Padding.Y);
}




嗯,现在我们的标签看起来像那么回事了,接下来就该难看的红线条退休了。再完善一下,我们的标签就OK了。






同步滚动演示。(上面是山寨,下面是正品,正品的文字开了抗锯齿,山寨没开,这是次要问题)




到此,标签导航部分已经完成,剩下的,就是窗体的自动缩放和同步修改Text功能了。

留着到#1-2再玩吧,今天就先做这么多。

(未完待续)
...全文
9669 112 打赏 收藏 转发到动态 举报
写回复
用AI写文章
112 条回复
切换为时间正序
请发表友善的回复…
发表回复
幻影gool 2014-06-04
  • 打赏
  • 举报
回复
最近老有人叫我用HtmlLayout写界面,看来那个也挺好的
oioixiabings 2014-06-04
  • 打赏
  • 举报
回复
太厉害了,学习了许多,希望大侠们多出点这样实际的内容让我们拜读!多谢了
墨雪子竹 2014-06-03
  • 打赏
  • 举报
回复
楼主实在是牛掰,不佩服不行啊
liuruitao647 2014-06-03
  • 打赏
  • 举报
回复
求博客地址。。。简介有点意思。。
c电磁 2014-06-01
  • 打赏
  • 举报
回复
目测楼主是做UI设计的。
_小黑_ 2014-05-31
  • 打赏
  • 举报
回复
学习了
zmidl 2014-05-31
  • 打赏
  • 举报
回复
引用 17 楼 conmajia 的回复:
[Quote=引用 12 楼 的回复:] 360那个界面是用DirectUI做的 [/Quote] 山寨的精神就是能用免费的就用免费的,能不用专业的,就不用专业的
只要能实现一样的效果,如果性能上不亚于原版 就OK了 野比 期待你 更多的 分享
鸿宇高科 2014-05-30
  • 打赏
  • 举报
回复
楼主我问一下public class TabControlEx : System.Windows.Forms.TabControl 这段代码是写在哪里的,是新建的用户控件中么?
tokilar 2013-12-22
  • 打赏
  • 举报
回复
很好很强大!由衷地赞一个~
Conmajia 2013-09-02
  • 打赏
  • 举报
回复
109#楼以后还在留邮箱求源码的都吃屎去吧
oSuiFeng1234567896 2013-01-29
  • 打赏
  • 举报
回复
如何实现鼠标移动到标签上改变背景
xjzhangbowei 2012-09-22
  • 打赏
  • 举报
回复
好文章,好LZ
yy0523 2012-09-22
  • 打赏
  • 举报
回复
看不懂!求源代码!!!!!412352965@qq.com目前正在学习中···
cy_lulu 2012-09-21
  • 打赏
  • 举报
回复
很有用。感觉好有意思。
旮旯旮 2012-09-10
  • 打赏
  • 举报
回复
谢谢楼主分享!!!!
旮旯旮 2012-09-05
  • 打赏
  • 举报
回复
好贴要留名!!!!
dyk 2012-07-31
  • 打赏
  • 举报
回复
mark 自己动手做一做 收获更多
Conmajia 2012-07-31
  • 打赏
  • 举报
回复
[Quote=引用 131 楼 的回复:]

其实360的TAB并不是楼主说的那种原理,360的纯图片,加个背景遮罩而已,与楼主描述的原理是完全不同的.另外,通常文字在制作"单像素投影"时,是向"右下方"偏移一像素,而不只是向下,否则,白色文字会出现上边缘视觉脱节现象,当然,做成文字描边效果更佳.至于窗体随控件内容自动伸缩,这个也不需要太麻烦,只要以"扫描仪"原理,从底部向下一行行扫,检查在扫描点有无控件,如有,则设置窗体的高度为检测到的扫……
[/Quote]感谢回复。
没仔细分析360的,换成Win7就没装360直接Defender了。。
「扫描仪」的招我想过,目测效率太低了,不如遍历控件Bottom属性来的快。
dylike 2012-07-31
  • 打赏
  • 举报
回复
其实360的TAB并不是楼主说的那种原理,360的纯图片,加个背景遮罩而已,与楼主描述的原理是完全不同的.另外,通常文字在制作"单像素投影"时,是向"右下方"偏移一像素,而不只是向下,否则,白色文字会出现上边缘视觉脱节现象,当然,做成文字描边效果更佳.至于窗体随控件内容自动伸缩,这个也不需要太麻烦,只要以"扫描仪"原理,从底部向下一行行扫,检查在扫描点有无控件,如有,则设置窗体的高度为检测到的扫描点的高度,枚举所有控件或者在代码中预设高度就"不是很智能"了.不过楼主对GDI+如此感兴趣,已为相当不错,继续加油.
晚安苏州 2012-07-30
  • 打赏
  • 举报
回复
楼主闲的慌……蛋疼,支持一个
加载更多回复(92)

110,499

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧