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已基本满足日常使用,用户可根据具体设计再做调整。完整代码