技术没有先进与落后,只有合适与不合适。html
本篇的自定义控件是:滚动条(ScollBar)。框架
咱们能够在网上看到不少自定义的滚动条控件,它们大都是使用UserControl去作,即至少使用一个Panel或其它控件做滑块,使用UserControl自己或另外一个控件做为背景条,而有的复杂的还会加上顶端和底端的按钮。这样做的好处有不少,最主要的是支持承载更加复杂的视觉和动做效果,好比使用一系列图片来实现很是炫丽的动画效果等。ide
不过本次所实现的滚动条并不须要太多复杂的效果,扁平化样式便可,因此就不须要使用UserControl,因此直接参照上一篇的LTrackBar,实现一个相似样式的滚动条,这也是在上一篇以后紧接本篇的缘由之一。函数
就像实现上篇的LTrackBar时的圆角的坐标计算是个难点,而后着重结合示意图去讲解同样,在本篇,关于滚动条中比较反直觉的、不太好直观想像的,我仍会结合示意图去详细讲解。优化
相信看完的你,必定会有所收获。动画
本文地址:http://www.javashuo.com/article/p-hjkpknom-kw.htmlspa
滚动条(ScrollBar)控件是一个经常被忽略,可是应用却极其频繁的控件。因为不少控件都自带滚动条,因此每每使人忽略滚动条的存在。设计
可是这些控件自带的滚动条有个很是大的缺点,那就是几乎不能够单独对滚动条进行重绘,甚至连调整控件自带滚动条的宽度这种”看起来很简单“的操做,真正实现起来的工做量简直使人”怀疑人生“。orm
并且,系统的滚动条控件:VScrollBar和HScrollBar,其样式还是最基本的那样,其可重绘性很是小。xml
系统控件自带的滚动条,在通常的WinForm程序中,看着还挺不错的,可是一旦你的WinForm程序中使用了大比例的自定义控件后,系统的滚动条就有点”刺眼“了。因此,便须要本身的滚动条控件。
参考上一篇的 LTrackBar控件,我想实现的滚动条的外观样式以下:
(1)支持鼠标拖动。
(2)支持点击非”滑块“区,改变滑块位置。
(3)支持鼠标进入时改变滑块颜色。
(4)支持修改各部分颜色。
(1)支持改变滚动条方向:垂直或横向。
(2)支持改变颜色。
(3)支持圆角、直角显示。
滚动条控件LScrollBar与LTrackBar相似,一样是分为背景条(Bar)和滑块(Slider)。所使用的核心技术还是GDI+。
在这里,我会对滚动条进行详细的讲解,包括滚动条的效果、比例的计算等。
在本节的讲解中,我将以”直角“、”垂直“样式的滚动条进行讲解说明。
以下图所示,咱们将”滚动条“和”显示内容“拆分红两部分。
其中,半透明的黑色部分是”屏幕“,也就是咱们实际能够看到的范围。而“文档”的实际长度要远远超过屏幕可显示部分,因此就须要经过“滚动条”来上下“滚动”来看文档的其余部分。
那么,在直观的想像中,”滚动“应该以下面的动图演示那样:
可是实现状况却不是如此,由于如今中”屏幕“是不会动的,那么”滚动“的只能是”文档“,因此真实的”滚动“应该像下面这样:
也就是说,咱们在将滚动条的滑块”向下“拖动时,”文档“实际是”向上“拖动的。
在具体使用程序实现时,也就是改变”文档“的”Y“坐标值。
在知道了滚动条的内部原理后,就须要一些比值的计算了,好比滚动条“滑块”的长度、“滑块”的拖动距离与“文档”位置改变的距离的比值等。
首先,以下图所未,我加上了一些标识,以方便描述。
其中:
(1)
文档的长度=A。
屏幕的长度=可见文档部分的长度=B。
那么,不可见的文档长度=A-B=C。
(2)
滚动条的长度=D。
滑块的长度=E。
滑块可拖动的范围=D-E=F。
(3)
在实现的时候,咱们已经的量有:A(文档长度)、B(屏幕长度)、D(滚动条长度),而E(滑块长度)是不知道的,是计算出来的。
在平常使用过程当中咱们也会发现,若是要显示的内容越多,滑块的长度越短。
其中的比例关系以下:
B/A=E/D
可得:E=D*B/A。
此时咱们已经获得了滑块的长度,这是一个很是重要的基本量。
获得E(滑块长度)后,F(滑块可移动范围)也能够获得:
F=D-E。
(4)
由于“屏幕”已经显示了一部分的“文档”,因此真正须要经过滚动条去查看的“文档”部分是C(不可见文档长度):
C=A-B。
由此,咱们能够获得一个重要的比值关系:
n*F(滑块拖动范围)=m*C(不可见文档长度)
(注:加上n、m是由于F与C并非1:1的关系)
n/m=C/F
这个比值咱们使用X代替,即:
X=C/F=(A-B)/(D-E)。
(5)
为何要计算这个比值,由于咱们在使用滚动条时,并非去考虑鼠标是向下拖动仍是向上拖动,以及拖动了多少距离。而是只须要知道滚动条的“滑块”的位置,更准确的说是“滑块”顶端距滚动条顶端的距离,以下图所示,咱们所须要知道的只是下图中F(注意:此F不是上面中的F)的值而已。
经过F的值,咱们计算出B的值,而后再使“文档”向上移动B的距离,这就是滚动条的使用方法。
那么,怎么经过F来获得B就很是重要了,这就须要一个比值,经过这个比值将F转换为B,这个比值就是上面的X。
经过X,咱们能够获得如下信息:
a,拖动滚动条时,文档应该“上移”的距离B:
B=F*X。
b,文档位置改变后,滚动条所处的位置F:
F=B/X。
因为本篇实现的滚动条控件(LScrollBar)是参考前篇的LTrackBar来实现的,因此此处仅做提纲用,具体操做见前篇。
新建类:LScrollBar.cs
添加继承:Control(须要添加引用:System.Windows.Forms.dll)
修改可访问性为:public
因为本控件和前篇的LTrackBar有极大的类似性,因此一些属性也能够直接拿来使用。这也是“复用”的一种。
这是一种提示颜色,像一些软件、网页的滚动条在平时是一种颜色,在鼠标处于滚动条上方时又是一种颜色,本属性就是实现的这种效果。
此处和LTrackBar不同,对于滚动条(LScrollBar)而言,只有两种方向:水平、垂直。因此咱们须要新建一个方向枚举,为了不与LTrackBar方向枚举冲突,咱们将枚举命名为:OrientationScrollBar
在改变滚动条的方向时,下面的代码会自动交换滚动条的宽和高。
这里的尺寸是指滚动条(LScrollBar)的宽度(在垂直方向时)或高度(在水平方向时)。
为了支持此属性,还须要设置控件使之只能修改高度(在垂直方向时)或宽度(在水平方向时),就须要重写SetBoundsCore。
在重写了SetBoundsCore后,在设计器界面咱们便只能调整控件的宽或高。
这里使用“文档”这个说法是源自MFC,本处仍沿用其说法。其指的是所要显示的总长度。
这里用“长度”是方便说明,否则就要在垂直状态时说“高度”,水平状态时说“宽度”,太过繁琐。
在设置了“文档”长度时,咱们调用了两个方法:pInit()和pChangeSliderLocation()。
其中pInit()的做用是初始化各个比例、计算滑块长等。pChangeSliderLocation()的做用是改变滑块的位置。其代码以下:
指示窗口可显示的长度。
这里滑块的位置指的时滑块的顶端相对于滚动条顶端的距离。
这里我将其设置为公共只读,是由于这个属性更多的是用来查看,若是直接修改还须要计算比例什么的,而为了计算比例,就须要额外提供一些额外的值以支持其计算,很麻烦,因此就不让用户去直接修改。若是想修改滑块的位置,则使用下面的“显示位置”属性去设置。
这里的显示位置就是“文档”顶端与显示窗口的距离。
以垂直状态为例,就是其Y坐标,由于默认状况下,系统的坐标方向是向右为正、向下为正。而其原点就是显示窗口的左上角,因此“文档”的Y坐标值即是个负数,为了方便计算和处理,咱们将以正数的方式进行处理。
在用户设置了此属性时,咱们会自动进行相应的计算,并改变滑块的位置(即上面的“滑块位置”属性的值),这样对用户而言,只须要知道“文档”的位置就好了,这样当其设置了文档的位置后,滑块的位置会自动改变。
本属性是公共只读,以备某些状况下使用。
本属性是公共只读,以备某些状况下使用。
日常状态下,滚动条是支持鼠标按在滑块上拖动的,因此滑块的长度就不能过短。固然若是使用滚动条时仅是做为显示用,而不须要操做,则能够将滑块的最小长度设置为0。
本属性是为了在使用鼠标滚轮上下滚动时,和按上下左右键时,滑块每次移动的距离。
这个距离是“文档”角度下的距离,不是滑块真实移动的距离,这样设置的缘由是方便使用者按须要设置,好比其想实现一个ListBox,每次按键都是一行的高度,此时就能够将本属性设置为那一行的高度值。
对于本滚动条(LScrollBar)而言,只须要一个事件,那就是滑块发生了滚动时的事件。
固然,估计不少人在初次实现时,会想到有不少事件,像点击了滑块事件、点击了滑块上面空白部分事件、拖动事件等等。可是咱们要记得以前所说的滚动条的工做方式:只须要知道滑块的位置便可。前面事件的结果都是滑块的位置发生了改变,因此能够归到一个事件里面。
OnPaint是实现效果的根本,不过本次的实现内容比LtrackBar要简单不少,总的来讲就是先画一个背景条,再画一个滑块。
由于咱们要实现鼠标处于滚动条上方时滑块变色,因此咱们要将滑块的颜色设置为属性:“鼠标进入滚动条后滑块颜色”的值,并使控件发生重绘。
同上,咱们在鼠标离开滚动条后,将滑块的颜色设置为属性:“滑块颜色”的值。
在这里,咱们须要肯定下鼠标点击处的位置:点在了滑块上、点在了滑块上方、点在了滑块下方。
根据平常使用经验,在点击非滑块上时,是有相应的效果的,通常而方点击滑块上方空白处则表明“上一页”,就是减去属性“页面长度”的值。同理,点击滑块下方的空白处则表明“下一页”,就是加上属性“页面长度”的值。
这里实现的是当鼠标点在了滑块上时,按住鼠标拖动。
在上面的OnMouseDown中,咱们额外计算了两个值:fAbove、fBelow,其做用即是为了在此时计算滑块的位置。
具体以下图演示:
咱们想实现滚动鼠标滚轮键(中键)时滑块跟着上下滚动,便须要重写本方法。
其中须要用到事件的一个属性:Delta,其MSDN的说明以下:
在滚轮向下滚动时,Delta的值为负数;滚轮向上滚动时,Delta的值为正数。
根据上面MSDN的解释,咱们只须要知道滚轮是向上滚动仍是向下滚动就好了。根据滚轮是向上滚动仍是向下滚动,将显示位置的值加上或减去属性:“滚动间隔距离”的值。
本方法中,主要是为了实现按鼠标箭头键时执行至关的操做。
在滚动条是垂直状态时,按上箭头键,滑块向上滚动;按下箭头键,滑块向下滚动。
在滚动条是水平状态时,按左箭头键,滑块向左滚动;按右箭头键,滑块向右滚动。
在按键时,是将显示位置的值加上或减去属性“滚动间隔距离”的值。
为了不拖动滑块时、改变滚动条尺寸时滚动条闪烁,故在其构造函数中加上对双缓冲的支持。
为了达到双击控件就自动实现仅有的一个事件:L_Scrolled,因此在类的最上方加上默认事件支持。
在前面的属性中,有很多属性我除了加了Category和Description——这两个的含意我在LTrackBar那篇讲过,分别是分类和描述,还有一个“Browsable(false)”,以下图所示,其做用是不在设计界面的“属性”窗口中显示。
在一些不可设置的属性或者不想让用户直接经过属性窗口设置的属性,能够添加Browsable(false)以达到此目的。
固然,在代码界面,仍是能够看到该属性提示的。
以下图那样当鼠标放上去时弹出相应的中文提示,写代码时的智能提示中显示中文提示。
要想实现该效果,首先要在属性或方法上输入”///“,此时VS会自动补全,以后即可以添加想要的提示了。像上面的属性都是这样写的。
不过只这样写的话在同一个解决方案中是能够显示中文提示的,若是单独引用生成的dll就没有提示了,这时须要生成对应的xml帮助文档,这样在引用该dll时,VS会自动加载对应的xml文件,也就会有对应的提示了。
生成xml方法:选择控件类库属性,在”生成“标签页中,勾选“XML文件文件”,VS会自动填入生成路径,若是想生成到其余地方能够自行修改。
不过在单独引用dll时要保证dll和xml文件在同一目录下。
在本节中,咱们不止要演示滚动条LScrollBar的各类效果、特色,最主要的是演示一下怎么如何使用LScrollBar。
咱们会在一个panel中添加50个按钮,而后经过操做滚动条还使这些按钮上下移动。
首先,咱们新建一WinForm程序,在上面添加以下几个控件,其控件名以下:
接着,咱们双击“加载列表”按钮,在其方法中写入如下代码。代码功能是往panel1中添加50个按钮。
最后,咱们双击滚动条lScrollBar1,在其方法中写入如下代码。代码功能一是显示当前滑块的位置,以及当前全部按钮距初始位置的距离;二是改变全部按钮的位置,以实现滚动效果。
以后编译并运行程序,其运行效果以下:
LScrollBar实现到当前这种程度,对我目前而言,以及对大多数须要使用自定义滚动条的地方而言都是足够了的。可是,并非没有缺点或可优化的地方。
其中最主要的一点,就是没法直接代替系统控件自带的滚动条,好比替换ListBox自带的滚动条,替换TextBox自带的滚动条等等。
之因此没法替换,是由于对ListBox、TextBox等自带滚动条的控件而言,其控制滚动条的滚动和处理滚动条的滚动是经过Windows消息去处理的,而LScrollBar并无拦截和处理这些滚动条消息。
若是想使用LScrollBar替换ListBox、TextBox等控件自带的滚动条,能够参考下面的思路:
首先要在LScrollBar中增长对滚动条消息的处理,包括拦截和发送。
而后,能够直接将LScrollBar的宽度调成和ListBox、TextBox自带滚动条一样的宽度,而后覆盖上;
或者将ListBox、TextBox自带滚动条隐藏掉,在旁边放上LScrollBar。
经过上面的方法,应该就能够达到”自定义ListBox、TextBox控件的滚动条“的效果了。
注:鉴于篇幅及我的须要和使用场景,我并没去实现上面的替换ListBox、TextBox滚动条的效果,只是从逻辑层面上验证能够达到预期效果。
后续若是有须要,我会考虑写一篇文章去实现一下这种效果。
通篇下来,会发现技术层面上算不上太难,难点在于突破常规的思惟束缚。
能够看到,WinForm并不是不能实现炫丽、更加现代化的效果,不过须要一些想像力支撑、并多付出一些的努力罢了。一样的,像WPF、像Electron等WebUI,虽然自己就更加现代化,可是想到达到必定的炫丽效果、比较人性化界面,也是须要花费不小的精力的。并非说使用了新的语言、框架就能够很轻松的、或者自动的实现想要的效果。
技术并无先进和落后,只有合适与不合适,由于各有优势与缺点、各有擅长与不擅长。
因此,对本身掌握的知识多抱有一些信心,释放本身的想像力,在实践中提高本身。