WPF 控件库——可拖动选项卡的TabControl
WPF 控件库系列博文地址:
一、先看看效果

二、原理
1、选项卡大小和位置
这次给大家介绍的控件是比较常用的TabControl,网上常见的TabControl样式有很多,其中一部分也支持拖动选项卡,但是带动画效果的很少见。这也是有原因的,因为想要做一个不失原有功能,还需要添加动画效果的控件可不是一行代码的事。要做成上图中的效果,我们不能一蹴而就,最忌讳的是一上来就想实现所有效果。
一开始,我们最好先用Blend看看原生的TabControl样式模板部分是如何实现的,这样我们也好有个参考。我们先从资产面板中拖一个TabControl放到窗体中,调整好合适的大小:

然后在它上面右键,编辑模板->编辑副本->确定,在自动生成的xaml代码中关键部分是这里:

可以看到,所有的选项卡(也就是TabItem)其实都是放在TabControl内部维护的一个TabPanel中,知道这些就够了,我们完全可以做一个定制的TabPanel来替换它: public class TabPanel : Panel 。既然这个TabPanel是一个容器,所以它必须负责计算TabItem的大小还要安排它的位置,我们可以重载父类Panel的 MeasureOverride 方法来处理这些逻辑: protected override Size MeasureOverride(Size constraint) 。在这个方法中我们通过 InternalChildren 这个只读属性来获取选项卡,选项卡的高度我们由 TabItemHeight 属性指定,由于TabPanel对用户是透明的,所以我们还要定制一个TabControl,里面加上 TabItemHeight 属性,让它和TabPanel的绑定。之后的 TabItemWidth 和 IsEnableTabFill 也同理。而选项卡的宽度则要分情况讨论了,如果 IsEnableTabFill = true 我们则要平分宽度,例如容器宽度为100,选项卡有10个,那么每个选项卡的宽度就是10。在这里要注意的是,选项卡的宽度最好不要有小数点,虽然有诸如 UseLayoutRounding 这种特性的帮助可以一定程度去除模糊,但在一个个连续排列的选项卡上反而会适得其反,你会发现两两之间的分割线宽度是不一致的,最好的办法就是“不公平的平分”,贴上一段代码来解释:
public static int[] DivideInt2Arr(int num, int count)
{
var arr = new int[count];
var div = num / count;
var rest = num % count;
for (int i = ; i < count; i++)
{
arr[i] = div;
}
for (int i = ; i < rest; i++)
{
arr[i] += ;
}
return arr;
}
假设现在的容器宽度是108,选项卡还是10个,通过 MeasureOverride 方法处理后,前八个的宽度则是11,后两个是10。如果 IsEnableTabFill = false 则不要平分了,直接放入容器即可。
现在选项卡大小搞定了,位置呢?太简单了,一个for循环不断叠加每个选项卡的宽度就可以了: size.Width += tabItem.ItemWidth; 。最后通过调用 Element.Arrange 即可排布选项卡的位置:
var rect = new Rect
{
X = size.Width - tabItem.BorderThickness.Left,
Width = itemWidth,
Height = TabItemHeight
};
tabItem.Arrange(rect);
因为选项卡左右都有边距,减去一个左边距,两者间的间隔就是一个边距了。
选项卡大小和位置的逻辑处理大致是上述的过程,由于篇幅有限,加之我不喜欢一贴一大段代码,所以只挑重点来讨论,完整的代码还要考虑各种情况,这里就不再赘述了。
2、动画处理
这一部分我们的关注点就是鼠标了,对选项卡而言,鼠标按下、鼠标移动、鼠标抬起,这些我们都要关注,所以分别给它们订阅一下事件。与之对应的,我们还要给选项卡添加几个标私有字段,用以记录状态,比如 _isDragging 、 _isDragged 、 _dragPoint 、 _isWaiting ,前两个我就不说了,都是字面意思,第三个则用来暂存鼠标移动时的位置,每次进入选项卡的 OnMouseMove 事件,都要将 _isDragged 和其旧值作差,以求得当前选项卡应该移动的距离。 _isWaiting 用途比较特殊,在用户拖动选项卡时,我们最好等待一个粘滞距离,比如20个单位宽度,也就是说,在水平方向鼠标移动了超过20个像素无关单位后,选项卡才开始被拖动。
在一开始的gif中可以看到,被拖动的选项卡改变位置时,其余的选项卡也会动态改变位置,那么位置改变的时机是如何确定的呢?很简单,只要将被拖动的选项卡到容器(TabPanel)左边界的这个距离除以 ItemWidth ,结果四舍五入就是这个选项卡当前应该所处的位置,紧接着下一步就是要把这个位置上的选项卡和当前被拖动的换个位置。此刻我们终于可以用动画来实现了,由于这个系列的文章多次讲过动画的代码了,所以就不再赘述。
上面一段讲的是换位置,那么添加选项卡、删除选项卡呢?其实有个捷径可以走,就是使用 FluidMoveBehavior ,把他往样式里一塞,好了,效果出来了!

但是这里有一个坑要注意, FluidMoveBehavior 虽然可以化简一部分动画逻辑,但是它有点越权了,它把你位置移动的逻辑也给做了,你会发现,如果不加处理,在你自己的动画结束后它还会再来一遍它的动画。可以将 FluidMoveBehavior 的 Duration 属性暂时归零来解决这个问题: FluidMoveDuration = new Duration(TimeSpan.FromSeconds()); 。
这篇文章只是大致介绍一下实现的过程和思路,感兴趣的可以下载源码,多多交流,共同提高。
三、源码
本文所讨论的控件源码已经在github开源:https://github.com/NaBian/HandyControl
WPF 控件库——可拖动选项卡的TabControl的更多相关文章
- 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程
反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) 背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...
- WPF 控件库——仿制Windows10的进度条
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- WPF 控件库——仿制Chrome的ColorPicker
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- WPF 控件库——轮播控件
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- WPF 控件库——带有惯性的ScrollViewer
WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...
- 《Dotnet9》系列-开源C# WPF控件库3《HandyControl》强力推荐
大家好,我是Dotnet9小编,一个从事dotnet开发8年+的程序员.我最近开始写dotnet分享文章,希望能让更多人看到dotnet的发展,了解更多dotnet技术,帮助dotnet程序员应用do ...
- 我的WPF控件库——KAN.WPF.XCtrl(141105)
自己开发的WPF控件库,只是初版,有扩展的Button,TextBox,Window.详细参见前几篇博文. WPF自定义控件(一)——Button:http://www.cnblogs.com/Qin ...
- 国内开源C# WPF控件库Panuon.UI.Silver推荐
国内优秀的WPF开源控件库,Panuon.UI的优化版本.一个漂亮的.使用样式与附加属性的WPF UI控件库,值得向大家推荐使用与学习. 今天站长(Dotnet9,站长网址:https://dotne ...
- 国内开源C# WPF控件库Panuon.UI.Silver强力推荐
国内优秀的WPF开源控件库,Panuon.UI的优化版本.一个漂亮的.使用样式与附加属性的WPF UI控件库,值得向大家推荐使用与学习. 今天站长(Dotnet9,站长网址:https://dotne ...
随机推荐
- 【转】Jenkins+Ant+Jmeter搭建持续集成的接口测试平台
一.什么是接口测试? 接口测试是测试系统组件间接口的一种测试.接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点.测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间的相互逻 ...
- Spring启动后获取所有拥有特定注解的Bean,注解的属性值
最近项目中遇到一个业务场景,就是在Spring容器启动后获取所有的Bean中实现了一个特定接口的对象,第一个想到的是ApplicationContextAware,在setApplicationCon ...
- linux lcd设备驱动剖析三
上一节文章中详细地剖析了probe函数,但是从始至终都没有看到打开读写文件接口的操作函数,只看到了下面这个操作结构体 [cpp] view plain? static struct fb_ops s3 ...
- PLSQL Developer备份恢复oracle数据
使用PL sql提供的功能可以快速的备份恢复oracle数据. 1 导出存储过程,触发器,序列等所有用户对象.(备份) 在PL/SQL Developer的菜单Tools(工具) => Exp ...
- 用Dos黑窗口运行Cmd命令
public class BlackWindow { private static BlackWindow _instance; public static BlackWindow Instance ...
- 关于Pycharm中如何注释
主要有三种方法: ①在程序行前面加“#” ②将需要注释的代码用'''----''' 包起来,此方法类似C的注释方法 ③将需要注释的代码选住然后利用“Ctrl+/”进行注释 # test=input(' ...
- ubuntu 14.04 安装压缩包版mysql
既有环境: 1)下载MySQL Community Server 5.6.17 压缩版(mysql-5.6.17-linux-glibc2.5-x86_64.tar.gz) 2)安装目录: /opt/ ...
- SQL2005 异常处理机制(Begin try Begin Catch)
将可能会出错的sql 写在begin try...end try 之间,若出错,刚程序就跳到紧接着的begin try...end try 的beign catch...end catch中,执行be ...
- 【bzoj3942】[Usaco2015 Feb]Censoring
[题目大意] 有一个S串和一个T串,长度均小于1,000,000,设当前串为U串,然后从前往后枚举S串一个字符一个字符往U串里添加,若U串后缀为T,则去掉这个后缀继续流程. [样例输入] whatth ...
- libevent源码分析
这两天没事,看了一下Memcached和libevent的源码,做个小总结. 1.入门 1.1.概述Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的网络 ...