Qt自定义控件之SeekBar
在客户端软件开发中,SeekBar
(拖动条)是一种常见的控件,经常用于播放器,控制面板等窗口中。Qt默认提供了QSlider
来提供相应的功能,然而QSlider
和我们常见(如安卓中的SeekBar)的SeekBar
相比,有几处不同
- 无法点击跳转,
QSlider
默认行为是点击后向点击处移动特定长度 - 滑块中心无法移动到两端端点
- 存在信号冗余(调用
setValue
方法后会发出valueChanged
信号,往往会带来问题)
为了解决以上问题,我们需要继承QSlider
,实现自己的SeekBar
类。实现效果如下
使用QSS进行修饰美化
QSlider
是一个QSS比较复杂的控件,其Subcontrol较多,具体可参考QSS Subcontrol#QSlider。简单来说,QSlider
下包括一个滑槽(groove
),groove
中又包括已滑过区域(sub-page
),未滑过区域(add-page
)和滑块(handle
)三个部分。下文中sub-page
和add-page
将统称为进度条。SeekBar
的QSS如下
1 | QSlider { |
为了提高灵活性,我们将QSS中的具体数值暂时使用QString
的占位符代替,以便在加载中再根据SeekBar
的高度指定,也方便在运行期间SeekBar
的大小改变时进行调整。
1 | void SeekBar::LoadStyleSheet() { |
屏蔽setValue()时的信号
由于在大多数情况下,QSlider
只是作为一个进度指示的控件,我们往往会监听QSlider::valueChanged
信号做出相应的跳转动作,也会在进度改变时使用QSlider::setValue()
设置值,这就导致我们可能会在设置值后收到valueChanged
信号并在做出相应处理后又调用setValue
,从而导致死循环。如在一个播放器中,我们可能会监听QSlider::valueChanged
信号,在用户手动调整进度时调用QMediaPlayer::setPosition
调整播放进度,而且会在QMediaPlayer
发出positionChanged
信号后调用QSlider::setValue
。一旦这样,就会发生以下循环:
为了避免这个死循环,我们在调用QSlider::setValue
时需要屏蔽QSlider::valueChanged
信号。因此我们重写该方法。
1 | void SeekBar::setValue(int value) { |
实现点击跳转
该效果的实现方法在网上有很多,如QSlider mouse direct jump,然而如果直接使用回答中的方法,会发现存在一些问题。
- 由于我们在QSS中为进度条左右各设置了宽度为1/2滑块高度的
margin
,进度条的的左端点并非控件的最左端,进度条的宽度也并非控件的宽度,导致当我们点击进度条左右端点时,预期应该设置为最小/最大值,然而实际上却并非如此。因此,我们在通过位置计算比例确定值时,需要进行一些调整。 - 滑块拖动释放后也会触发,触发了两次
valueChanged
信号,在存在误差的情况下会导致细微的抖动
经过修正后的代码如下
1 | void SeekBar::mouseReleaseEvent(QMouseEvent *event) { |
总结
经过以上调整后,SeekBar
已基本满足日常使用,用户可根据具体设计再做调整。完整代码