您的位置:首页 > 其它

一步一步玩控件:TabControl——从制作山寨Safari窗体开始

2012-05-18 06:20 344 查看
绘制标签图标

// 绘制图标
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];
}
// ImageKey优先级较高
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功能了。

(下集预报:实现自动收缩的窗体。敬请期待)

© 野比 2012 版权所有

现在继续昨天的山寨。昨天我们分析得到了4条需要山寨的部分,如下。

根据标签不同修改窗体标题

导航标签

标签面板

自动缩放

通过昨天的努力,我们已经搞定了第2、3条,所以,今天的任务,就只剩下两条

根据标签不同修改窗体标题

导航标签

标签面板

自动缩放

修改窗体标题

我们参考下图,



我们制作的TabControlEx是作为它所在窗体的子控件存在的,为了获得包含TabControlEx的窗体(的引用),可以调用TabControlEx的FindForm()方法(从Control继承)。FindForm()可以获取容纳该控件的顶层窗体,在我们的例子里,就是我们的山寨Safari窗体。

为了在TabControlEx刚刚加入父控件的时候(也就是窗体初始化的时候)就能够顺利「劫持」到窗体的引用,并修改它的标题(否则显示Tab0的时候会发现窗体的标题还未改变),我们重写一下TabControlEx的ParentChanged事件,在获取到父窗体之后顺便把第一个标签Text赋给窗体做标题。

// 对父窗体的引用
Form owner;
protected override void OnParentChanged(EventArgs e)
{
// 如果没有劫持到,则搜索
if (owner == null)
owner = this.FindForm();

owner.Text = this.TabPages[0].Text;
}


这样,我们就可以在启动时就修改父窗体标题了(以及后面修改窗体大小)。我们最终的目的是每次切换标签时都改变父窗体标题,现在我们拿到了窗体的引用,只需要重写TabControlEx的Selected事件。

protected override void OnSelected(TabControlEventArgs e)
{
parent.Text = e.TabPage.Text;
}


下面是完成之后的效果



自动调整窗体大小

完成了杂项工作,现在要进入今天的重点:自动调整大小。在开始之前,先来回顾一下这个闷骚的功能。



下面来好好分析一下到底发生了什么事。

注意,大家发现右下角那个问号没有?根据观察,那个问号始终是保持在窗体右下角的,这就好办了,直接Anchor到Right和Bottom就行。因此下面的分析中直接无视它了。

从本质上来看,因为切换的标签内容高度不同,所以窗体高度也发生了改变。但不管怎么变,窗体的底部到最下面一个控件的距离Δ没有变化,参考分析图。





所以,动画就是在|H1-H2|这段距离内发生的,Δ据截图估算,约为20px(把它写成常量)。

const int FORM_DELTA = 20;


因为窗体有可能变大或变小,另外,值得注意的是,Safari是在窗体动画完成,调整大小到位以后,才显示新标签的控件,这样做可以显得很有动感,而且留下了足够的时间加载控件。所以,动画应该在标签的Selecting事件里解决,而显示控件留到Selected事件。

下面来分析大小调整的算法。

© 野比 2012 版权所有

山寨算法:从不追求精确还原

通过慢镜头分析,可以看到在相同时间差内窗体大小的运动距离是不同的,窗体大小不是匀速改变的。前半段是加速收缩或膨胀,最后是减速到位。



尽管现在还写不出来,为了不让算法影响设计进度,先把算法写在单独的方法里(更好的应该是写成委托,直接传递方法,但你认为一个山寨货有必要吗)。

private double getHeight(double time)
{
// (Not implemented yet)
}


既然这样,那么算法的问题我们稍后再来讨论,

算法尝试(本节待修改)

目前流行的加减速函数有很多,最简单的从1次函数(匀速)、2次函数(匀加速)到3、4甚至5次函数都有人在用。这类指数型的加速函数使用简单方便,用得很多。下面是在Mahematica里绘制的几种函数曲线,从上倒下分别为:g=10的自由落体函数,y=x^2,y=x^3,y=x^4和y=x直线(注意:为了让大家看清函数细节,x和y轴不是1:1的)。



看起来要实现又加速又减速还真是麻烦,看来只有去掉减速了。反正山寨嘛,只要「看起来像」就行了。没办法,我们是搞山寨的,手艺当然不行了,所以到底用那种,还真的不知道。山寨大法告诉我们,不知道的东西,「试,就对了」。那么就选3个版本的getHeight()来试试。

动画(本节待修改)

现在研究怎样让窗体动起来。由于动画过程较长,将近1秒,那么我们实现的时候应当尽量以不影响主线程为前提。除了动不动就多线程这种有点大炮打蚊子太2的方法外,我们还可以用系统自带的定时器(System.Windows.Forms.Timer)。在每个Timer.Tick事件里挪一步,合起来就成了动画。

// Δ常量
int FORM_DELTA = 20;
// 动画用Timer
Timer timer;
// 经历时间计数器
int elapsed = 0;

// 构造函数
public TabControlEx()
{
// (略)

// 初始化Timer
timer = new Timer();
timer.Interval = 100;
timer.Enabled = false;
timer.Tick += new EventHandler(timer_Tick);
}

// Timer tickle
void timer_Tick(object sender, EventArgs e)
{
if (parent == null)
return;

elapsed++;
parent.Height = getHeight(elapsed, FORM_DELTA);
}


现在我们可以填写刚才分析的Selecting和Selected事件了。

(未完待续)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: