自定义 Winamp 风格的 TrackBar (滑块)
一个自定义绘制的 TrackBar,看起来像经典 Winamp 皮肤中的一样。
引言
我目前正在编写一个媒体播放器应用程序。基本上,我希望它看起来与经典的 Winamp UI 完全一样——只是功能更专业。而且无需费力地实现整个应用程序的皮肤化。我追求的不是 100% 的外观相似,而是大约 98%……
于是我开始制作一个模仿 Winamp 用户界面的窗体。进展相当顺利,而且速度也相对较快,即使我一路上遇到了许多有趣的“挑战”(我曾想过有一天有时间时,就这些写一个系列文章)。唯一真正的障碍是我在设计播放器时,需要一个 trackbar/slider 来显示播放进度和控制音量。
标准的 Windows Forms trackbar 当然不行,尽管我浏览了 CodeProject 和互联网上的其他地方,但找不到一个至少能做成 Winamp trackbar 样子的。我真的不想自己绘制 trackbar,除非绝对必要,所以如果我能找到一个稍微有点像的,我愿意做出妥协,在外观上有所不同。
我找到的最好的一个是由 John Underhill(前称 Steppenwolfe)开发的 MediaSlider 控件。它看起来很棒,而且外观对我来说足够接近,我可以接受。我以前用过它,很喜欢这个控件和它的外观。不幸的是,我也发现这个控件存在一些问题。最重要的是,当窗体大小调整时,它会闪烁很多。控件的绘制也存在一些问题,而且它不支持 Anchor
和 Dock
属性。
我已经在文章论坛中报告了其中大部分(但不是全部)问题,但看起来这篇文章不再更新了,而且 John 本人自 2010 年以来就没有回复过任何评论。所以我不得不选择自己修复这些 bug,寻找另一个控件,或者创建一个自己的控件。好吧,我之前已经尝试寻找其他控件但失败了。正如我之前提到的,最后一个选项并不是我真正想做的,但最终我决定那是最好的解决方案。我宁愿修复自己的代码而不是别人的代码中的 bug。
所以,这就是我所做的。这篇文章由此而生。
这个控件相当直接,如果你知道如何在 GDI+ 中绘制控件,代码中就没有什么有趣的地方了。所以我不会粘贴太多代码。实际上,我只是想与你分享这个控件,以便你可以在自己的项目中使用它,如果你愿意的话。
吹毛求疵的人会说,没有代码示例,它就不是一篇真正的文章,应该被发布为提示/技巧。我不同意。在我看来,提示/技巧更像是“做这个来完成那个”之类的简短说明。而且,如果你向别人推荐某个东西,你是在转述你发现的有用的第三方信息,想与他人分享。而不是你自己创作的代码。这只是我的观点。如果你愿意,可以不同意,但我不会把它改成提示...
相反,我想介绍一下我在控件中实现的一些功能,这样你就可以了解它的样子了。
控件功能概览
1. 基本控件功能
控件当然是双缓冲的,以尽量减少闪烁。它也派生自 Control
类,所以它同时支持 Anchor
和 Dock
属性。而且,如果你愿意,它还支持 Transparent
BackColor
!
2. 方向
Winamp 的两个 trackbar 都是水平的,所以理论上,我只需要让控件水平。但由于我还需要一个垂直 trackbar 来实现我的特殊功能,所以必须同时实现这两种方向。
水平方向没问题,但由于 .NET 计算 X 和 Y 坐标的方式,实现垂直方向有点麻烦。如果它的最小值在顶部,最大值在底部,基本上只需要切换 X 和 Y 以及宽度和高度即可,但大多数人更喜欢另一种方式,所以需要一些技巧才能让垂直 trackbar 正确工作。我希望我能做到。
3. 滑块按钮
滑块按钮的大小可以通过 SliderButtonSize
属性进行更改。有一个默认的 OnHover
功能,当鼠标悬停在按钮上时,按钮会亮起。如果你不想要这个效果,可以通过设置 UseHoverEffect = false
来关闭。
滑块按钮的可见性有 3 种设置:Always
(始终)、Never
(从不)和 OnHover
(悬停时)。该属性称为 ShowSlider
。
这样做的想法是,默认情况下,按钮始终可见。但如果你实现一个显示播放进度的进度条,那么可能根本不需要按钮。
使用 OnHover
时,当鼠标悬停在 trackbar 上时,按钮就会显示。Winamp 没有使用这个功能,但 Windows Media Player 中可以找到这种效果,所以我想在这里也实现它……
4. 轨道样式
轨道样式,我的意思是当你移动滑块按钮和/或 Value
改变时,轨道着色的方式。TrackStyle
属性有 4 种不同的值:None
(无)、FromLeftOrTop
(从左/上)、FromRightOrBottom
(从右/下)、FromZero
(从零)。
None
的意思当然是轨道上没有任何填充。
FromLeftOrTop
的意思是从 trackbar 的左侧(对于水平 trackbar;垂直 trackbar 从顶部)填充到控件的选定值。FromRightOrBottom
的意思是从 trackbar 的右侧(对于水平 trackbar;垂直 trackbar 从底部)填充到控件的选定值。FromZero
:表示从 0 填充到当前选定的值。
请注意:零不一定是 trackbar 的末端。例如,你可能有一个 trackbar,其 Minimum = -20
和 Maximum = 20
。这样 0 就会在中间,因此轨道将从中间填充。
5. 轨道颜色
控件的外观不像 MediaSlider
那样可定制。在此项目中我没有这方面的需求。唯一可定制的属性是进度轨道的颜色。有一个名为 EmptyTrackColor
的属性,用于确定未填充轨道时的颜色。当轨道被填充时,情况会更复杂一些,因为会应用一个非常简单的渐变,使用两种颜色。这些颜色通过 TrackLowerColor
和 TrackUpperColor
属性设置。
6. 搜索
Winamp GUI 是一个奇怪的东西。有很多控件外观完全相同,但工作方式却截然不同。例如,有两个 trackbars。如果你移动音量 trackbar 的滑块,值会立即更改。
如果你移动播放器进度 trackbar 的滑块,情况就不是这样了。在那里,一个半透明的“幽灵”滑块按钮显示了当你释放鼠标按钮时滑块将到达的位置。但滑块按钮本身不会移动(因此,值不会改变),直到你真正释放鼠标按钮。Winamp 似乎称之为“搜索”,因为你在进行操作时屏幕上会显示这个词。
我创建了一个名为 UseSeeking
的属性来实现这个确切的功能。请记住,当设置了这个属性后,ValueChanged
事件直到你释放按钮时才会触发。但是,会有一个 Seeking
事件被触发,以提供有关“幽灵”按钮值的信息。
“幽灵”滑块按钮的透明度可以通过 SeekSliderTransparency
属性进行设置。
7. 刻度
在 Winamp 中,trackbar 上没有像其他程序中常见的刻度线。取而代之的是,音量滑块上方有一个非常酷的音量刻度。我也想要这个,所以我不实现刻度,而是实现了*刻度字段*。
刻度是控件中最可定制的部分。你可以使用 ScaleFieldWidth
和 ScaleFieldMaxHeight
属性更改刻度字段的大小,使用 ScaleFieldSpacing
更改它们之间的间距,使用 ScaleFieldColor
属性更改颜色。字段的数量是根据上述尺寸属性计算的。
你还可以使用 ScaleFieldPosition
属性将刻度定位在 trackbar 的所需一侧。如果你根本不想要任何刻度,只需将 ScaleFieldPosition = Hidden
(隐藏)。
关于刻度的最后一个半重要的属性:ScaleFieldEqualizeHeights
。当 Minimum < 0
、Maximum > 0
且 Minimum
和 Maximum
的绝对值不同时,会使用此属性。下面你可以看到区别。
顺便说一句:源代码中有一个关于刻度的小惊喜:有一个单独的 ScalePanel 控件 - 一个只绘制刻度的面板。它的代码比滑块控件稍微粗糙一些,我最初认为我会将它与我开头提到的 MediaSlider 控件结合使用。
但当我开始制作 trackbar 控件时,我选择将其集成刻度。我只是保留了“旧”的刻度面板,纯粹是出于好玩……
8. 自动调整大小
为了方便你,我实现了一个 AutoSize
功能。当 AutoSize = true
时,控件的大小会留出刻度和带滑块的 trackbar 的空间,并且控件的大小会恰好包含整数个刻度字段。
9. 用户交互
如果你使用 trackbar 来显示某些进度,并且不希望用户手动更改它,只需设置 AllowUserValueChange = false
。你仍然可以在代码中设置值,但用户将无法在 GUI 中更改值。
如果你希望用户在控件获得焦点时使用箭头键更改值,可以设置 KeyChangeOption
属性。它有 3 种可能的值:NoKeyChange
(无键盘更改)、LeftAndRightArrowKeys
(左右箭头键)和 UpAndDownArrowKeys
(上下箭头键)。
NoKeyChange
意味着用户无法使用键盘更改值。
LeftAndRightArrowKeys
意味着用户可以使用左右箭头键增加/减少值。
UpAndDownArrowKeys
意味着用户可以使用上下箭头键增加/减少值。(译者注:此处原文此处描述与上一行相同,应为左右箭头键。)
当按下箭头键时,值会根据 SmallChange
属性值增加/减少。
请注意:这种行为不一定与 trackbar 的方向有关。例如,在 Winamp 中,即使 trackbar 是水平的,你也会使用向上/向下键来增加/减少音量。这有点不合逻辑,但你可以选择用这个控件来模仿相同的响应,如果你愿意的话。
无论 KeyChangeOption = LeftAndRightArrowKeys
还是 KeyChangeOption = UpAndDownArrowKeys
(但不是 KeyChangeOption = NoKeyChange
!),按下 **PageUp**、**PageDown**、**Home** 和 **End** 键都会产生以下效果:
PageUp
:根据LargeChange
属性值增加值PageDown
:根据LargeChange
属性值减少值Home
:将值设置为Maximum
属性值End
:将值设置为Minimum
属性值
控件还支持使用鼠标滚轮更改值。如果你不希望用户能够这样做,只需设置 AllowMouseWheelChange = false
。
10. 刻度线(1.2 版本新增)
好吧,我之前声称 Winamp 中没有带刻度的 trackbar 是不对的。均衡器实际上在其 trackbar 上有刻度线。
所以,尽管我很不情愿,我还是不得不实现了它。我还不得不对代码做一些更改,这会破坏向后兼容性,对此我非常抱歉。但别无选择。
在 1.2 版本中,有一个新的 ScaleType
属性。
这意味着我不得不从 ScalePosition
属性中移除 Hidden
选项(我也将其重命名为 ScaleFieldPosition
以保持一致性)。所以从这个版本开始,你应该设置 ScaleType = None
而不是 ScalePosition = Hidden
。
无论如何,当 ScaleType = Ticks
时,你可以使用相应的属性来自定义刻度线:TickAlignment
、TickColor
、TickEmphasizedColor
、TickEmphasizedHeight
、TickEmphasizeMinMaxAndZero
、TickHeight
、TickPosition
、TickSpacing
和 TickWidth
。
请注意,尽管上述所有示例都显示了 trackbar 两侧的刻度线,但这并非唯一的外观。当然,如果你强烈想这样做,也可以将它们定位在 trackbar 的一侧。
11. 内置工具提示(1.2 版本新增)
我一开始提到,我的选项之一是使用 MediaSlider
控件而不是开发自己的控件。所以我真的给自己设定了一个目标,让我的控件能够与它进行比较……
正如**会员 11773069** 在下面的评论中发现的那样,MediaSlider
控件有一个很好的功能叫做 Flyout
,它基本上允许你在工具提示/气球样式的气泡中显示控件信息。
这个功能的问题在于,Flyout
本身是控件绘制的一部分,而不是一个独立的窗口。这意味着控件必须足够大,才能同时容纳 flyout 和滑块。flyout 不能超出控件或窗体的边界。这意味着,如果你在窗体上有两个并排的滑块,你必须在它们之间留出空间来容纳其中一个或两个滑块的 flyout。至少在我看来是这样。可能有其他选项,但我没有真正使用过它。
无论如何,在我看来,弹出/flyout 应该是一个真正的窗口,这样它就可以覆盖其他控件。互联网上有很多关于如何实现这一点的例子,但要让它正常工作可能相当棘手。
我真的不想做这么复杂的解决方案,而且实际上工具箱中已经有一个控件能很好地完成这项工作——而且外观也很棒:ToolTip
控件。
我可以直接在我的窗体上使用 ToolTip
控件并与 WinampTrackBar
配合使用,但 ToolTip
控件作用于整个 trackbar 表面,而我希望能够只在滑块按钮上显示工具提示。
所以我将一个 ToolTip
组件集成到 trackbar 代码中,并创建了两个属性:ToolTipText
和 ToolTipTextSliderButton
。第一个属性在鼠标指针悬停在 trackbar 控件的任何部分时显示工具提示,第二个属性仅在鼠标指针悬停在滑块按钮上时显示工具提示。
为了方便起见,我选择公开 ToolTip
控件的所有属性,以便在属性网格中轻松自定义它们。
你可以从黑色背景色看出 ToolTip
可以毫无问题地超越控件边界。
历史
版本 1.3 (2015-08-07)
- BUG修复:修复了
TickAlignment
属性的缺失实现(我真笨!)。 - BUG修复:修复了两个渲染器中
GetTickLayoutRectangle
方法的像素错误。
版本 1.2 (2015-08-05)
- 新增:刻度
ScaleType
实现 - 新增 内置
ToolTip
,可以为整个控件或仅为滑块按钮设置工具提示 - 新增:
ScaleType
属性。可以是None
、ScaleFields
或Ticks
- 新增:
ScalePosition
属性重命名为ScaleFieldPosition
以保持一致性(注意:此更改会破坏与先前版本的兼容性) - 新增:
WinampTrackBarScalePosition
枚举重命名为WinampTrackBarScaleFieldPosition
以保持一致性(注意:此更改会破坏与先前版本的兼容性)。 - 新增:移除了
ScalePosition = Hidden
选项。现在请使用ScaleType = None
(注意:此更改会破坏与先前版本的兼容性)。 - 更改:更改了计算不同布局矩形的方式,以便在控件较大时也能使刻度字段或刻度靠近轨道。
- BUG修复:检查
Value
属性值是否超出界限。
版本 1.1 (2015-06-30)
- 新增:
SeekDone
事件 - 新增:
SliderButtonDoubleClick
事件 - 新增:
ValueChanging
事件,包括取消更改的可能性 - 新增:
ValueChanged
事件中的EventArgs
现在提供了关于值如何以及为何更改的信息(注意:此更改会破坏与先前版本的兼容性)。 - BUG修复:修复了
Trackbar
渲染器中的小型计算错误。 - 已添加:用于渲染器的
ValueToPixelValue
和PixelValueToValue
方法的单元测试,以避免出现更多像上述那样的 bug。
版本 1.0 (2015-06-02)
- 首次发布