WPF 控件库系列博文地址:

WPF 控件库——仿制Chrome的ColorPicker

WPF 控件库——仿制Windows10的进度条

WPF 控件库——轮播控件

WPF 控件库——带有惯性的ScrollViewer

WPF 控件库——可拖动选项卡的TabControl

一、先看看效果

二、原理

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的更多相关文章

  1. 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程

    反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑)   背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...

  2. WPF 控件库——仿制Windows10的进度条

    WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...

  3. WPF 控件库——仿制Chrome的ColorPicker

    WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...

  4. WPF 控件库——轮播控件

    WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...

  5. WPF 控件库——带有惯性的ScrollViewer

    WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...

  6. 《Dotnet9》系列-开源C# WPF控件库3《HandyControl》强力推荐

    大家好,我是Dotnet9小编,一个从事dotnet开发8年+的程序员.我最近开始写dotnet分享文章,希望能让更多人看到dotnet的发展,了解更多dotnet技术,帮助dotnet程序员应用do ...

  7. 我的WPF控件库——KAN.WPF.XCtrl(141105)

    自己开发的WPF控件库,只是初版,有扩展的Button,TextBox,Window.详细参见前几篇博文. WPF自定义控件(一)——Button:http://www.cnblogs.com/Qin ...

  8. 国内开源C# WPF控件库Panuon.UI.Silver推荐

    国内优秀的WPF开源控件库,Panuon.UI的优化版本.一个漂亮的.使用样式与附加属性的WPF UI控件库,值得向大家推荐使用与学习. 今天站长(Dotnet9,站长网址:https://dotne ...

  9. 国内开源C# WPF控件库Panuon.UI.Silver强力推荐

    国内优秀的WPF开源控件库,Panuon.UI的优化版本.一个漂亮的.使用样式与附加属性的WPF UI控件库,值得向大家推荐使用与学习. 今天站长(Dotnet9,站长网址:https://dotne ...

随机推荐

  1. 【转】Jmeter安装成功后的目录介绍

    1.bin目录 Jmeter.bat  打开Jmeter主界面 Jmeter使用的日志文件名称被定义到Jmeter.properties中,默认在Jmeter.log可查看日志 2.dosc和prin ...

  2. 1140 Look-and-say Sequence

    题意:略 思路:第1个数是1(读作“1有1个”),因此第2个数就是11(读作“1有2个”),因此第3个数就是12(读作“1有1个,2有1个”),因此第4个数是1121(读作“1有2个,2有1个,1有1 ...

  3. winfrom绘制渐变 / 调用浏览器访问指定地址

    private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e) {//绘制渐变色背景 Graphics g ...

  4. Cassandra学习六 一些知识点

    http://www.flyml.net/2016/09/08/cassandra-tutorial-java-api-example/ Cassandra对查询的支持很弱,只支持主键列及索引列的查询 ...

  5. Angular2快速入门-3.多个组件(分离新闻列表页和详细页)

    上篇(Angular2快速入门-2.创建一个新闻列表)已经完成新闻列表的展示,并且点击新闻列表的时候,下面可以展示出新闻的详细信息,这节我们把新闻详细和新闻列表页面分离出来 新闻详细单独一个compo ...

  6. PHP框架 Laravel

    PHP框架 CI(CodeIgniter) http://www.codeigniter.com/ http://codeigniter.org.cn/ Laravel PHP Laravel htt ...

  7. 【基础巩固】文件流读写、大文件移动 FileStream StreamWriter File Path Directory/ ,m资料管理器(递归)

    C#获取文件名 扩展名 string fullPath = @"d:\test\default.avi"; string filename = Path.GetFileName(f ...

  8. shell编程之sed编辑器&gawk程序

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://twentyfour.blog.51cto.com/945260/560372 s ...

  9. 修改eclipse默认workspace

    三种方法 (只改其一可能无效,最好都试试) 1. 修改exlipse安装目录下\configuration\.settings\org.eclipse.ui.ide.prefs文件,修改RECENT_ ...

  10. mfs教程(二)

    mfs文件系统(二) 编译和安装 MooseFS部署的首选方法是从源代码安装 源代码包安装支持标准./configure && make && make install ...