自定义QListWidget上的滚动条

时间:2013-04-29 18:52:58

标签: qt

我想实现我自己的半透明滚动条,它在QListWidget之上绘制,而不是占用视口中的永久空间。我不希望使用QML作为我的QListWidget,它的动态内容已经完全开发了6个月了。

我怎样才能做到这一点。样式表对于此目的是无用的,因为它们不会确定滚动条的位置。我希望它在QListWidget之上,而不是在它的一边,占用它的空间。

我正在谈论附近的事情:

enter image description here

任何关于如何做到这一点的提示将不胜感激。

2 个答案:

答案 0 :(得分:15)

你想要做的是一个完美的例子,一直让我厌倦了关于qt的事情 - 如果Qt的设计师没有想到的某些图形效果,那么你自己创造它是一种痛苦,不断的对抗Qt,无论如何通常都会放弃。

我怀疑你用小屏幕做手机(手机?平板电脑?),所以我猜没有其他方法可以解决这个问题。

我在这里尝试的是hacky,但是否则你可能不得不自己重写整个滚动条只是为了添加那些缺少的细节。我的主张是:

#ifndef MYSCROLLBAR_H
#define MYSCROLLBAR_H

#include <QScrollBar>

class MyScrollBar : public QScrollBar
{
    Q_OBJECT
public:
    explicit MyScrollBar(QWidget *parent = 0);

protected:
    void showEvent ( QShowEvent * event );
signals:

public slots:
   void updateMask();
};

#endif // MYSCROLLBAR_H

在myscrollbar.cpp中

#include "myscrollbar.h"

#include <QPaintEvent>
#include <QRegion>

#include <QStyleOptionSlider>

MyScrollBar::MyScrollBar(QWidget *parent) :
    QScrollBar(parent)
{
    connect(this, SIGNAL(valueChanged(int)), this, SLOT(updateMask()));
}

void MyScrollBar::updateMask(){
    QStyleOptionSlider opt;
    initStyleOption(&opt);

    QRegion r(style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this));
    r+= style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this);
    r+= style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this);
    setMask(r);
}

void MyScrollBar::showEvent ( QShowEvent * event ){
    QScrollBar::showEvent(event);
    updateMask();
}

这种卷轴在任何非关键部分都是透明的(在视觉和事件方面)。它仍然会在其下方的小部件上创建一些工件 - 我想setMask()从未被认为是这样使用的。要缓解它,您可以将valueChanged()信号连接到列表小部件视口的update()插槽。这在我的玩具示例中运行良好,但如果您在列表中嵌入自定义小部件,则可能无法忍受应对。如果应用程序更复杂,它也可能会导致性能问题 - 尤其是在为移动平台编写时。

或者你可以“分叉”整个QScrollBar类,只需修改它的paintEvent,使用比SC_All少的subControls - 在构造函数中使用额外的setAttribute(Qt::WA_OpaquePaintEvent, false);它应该提供视觉透明度。然后你还应该将鼠标事件(如果没有击中任何重要事件)转发到列表窗口小部件的视口(再次,在视图中查看自定义窗口小部件的麻烦)。

现在剩下的就是编写自己的布局类(或者只是手动定位它),它会将listview和滚动条彼此放在正确的位置 - QStackedLayout听起来不错,但它只允许在任何给定时间看到一个图层 - 显然不是我们想要的。

最后一步是在视图上关闭默认滚动条,并将默认(不可见)滚动条的信号/插槽连接到滚动条的插槽/信号,以实现实际滚动效果。

很快,这将需要 LOT 编码才能完成。你确定这样一个简单的效果值得吗?

**编辑:**

我创建了一个用于堆叠小部件的布局类 - 这个问题让我有动力去做最后的事情;)

#ifndef STACKLAYOUT_H
#define STACKLAYOUT_H

#include <QLayout>

class StackLayout : public QLayout
{
    Q_OBJECT
public:
    StackLayout();
    explicit StackLayout(QWidget *parent);
    ~StackLayout();

    void addItem ( QLayoutItem * item );
    int count () const;
    Qt::Orientations expandingDirections () const;
    bool hasHeightForWidth () const;
    int heightForWidth ( int w ) const;
    QLayoutItem * itemAt ( int index ) const;
    bool isEmpty () const;
    QSize maximumSize () const;
    int minimumHeightForWidth ( int w ) const;
    QSize minimumSize () const;
    void setGeometry ( const QRect & r );
    QSize sizeHint () const;
    QLayoutItem * takeAt ( int index );

private:
     QList<QLayoutItem *> itemList;

};

#endif // STACKLAYOUT_H

和stacklayout.cpp文件:

StackLayout::StackLayout()
    :QLayout()
{}

StackLayout::StackLayout(QWidget *parent) :
    QLayout(parent)
{
}

StackLayout::~StackLayout(){
    QLayoutItem *item;
    foreach (item, itemList){
        delete item;
    }
}

void StackLayout::addItem ( QLayoutItem * item ){
    itemList.append(item);
}

int StackLayout::count () const{
    return itemList.count();
}

Qt::Orientations StackLayout::expandingDirections () const{
    Qt::Orientations result = 0;
    QLayoutItem *item;
    foreach (item, itemList){
        result = result | item->expandingDirections();
    }
    return result;
}

bool StackLayout::hasHeightForWidth () const{
    QLayoutItem *item;
    foreach (item, itemList){
        if (item->hasHeightForWidth())
            return true;
    }
    return false;
}

int StackLayout::heightForWidth ( int w ) const{
    int result = 0;
    QLayoutItem *item;
    foreach (item, itemList){
        if (item->hasHeightForWidth())
            result = qMax(result, item->heightForWidth(w));
    }
    return result;
}

QLayoutItem * StackLayout::itemAt ( int index ) const{
    if (index<itemList.count())
        return itemList[index];
    return 0;
}

bool StackLayout::isEmpty () const{
    QLayoutItem *item;
    foreach (item, itemList){
        if (!item->isEmpty())
            return false;
    }
    return true;
}

QSize StackLayout::maximumSize () const{
    QSize result=QLayout::maximumSize();
    QLayoutItem *item;
    foreach (item, itemList){
        result = result.boundedTo(item->maximumSize());
    }
    return result;
}

int StackLayout::minimumHeightForWidth ( int w ) const{
    int result = 0;
    QLayoutItem *item;
    foreach (item, itemList){
        if (item->hasHeightForWidth())
            result = qMax(result, item->minimumHeightForWidth(w));
    }
    return result;
}

QSize StackLayout::minimumSize () const{
    QSize result=QLayout::minimumSize();
    QLayoutItem *item;
    foreach (item, itemList){
        result = result.expandedTo(item->minimumSize());
    }
    return result;
}

void StackLayout::setGeometry ( const QRect & r ){
    QLayoutItem *item;
    foreach (item, itemList){
        item->setGeometry(r);
    }
}

QSize StackLayout::sizeHint () const{
    QSize result=QSize(0,0);
    QLayoutItem *item;
    foreach (item, itemList){
        result = result.expandedTo(item->sizeHint());
    }
    return result;
}

QLayoutItem * StackLayout::takeAt ( int index ){
    if (index < itemList.count())
        return itemList.takeAt(index);
    return 0;
}

假设您已经有一些漂亮的透明滚动条,要插入它,您可以这样做:

QWidget* w = new QWidget();

StackLayout* sl = new StackLayout(w);
QListView* lv = new QListView(w);
sl->addWidget(lv);
QHBoxLayout* hbl = new QHBoxLayout();
sl->addItem(hbl);

TransparentScrollBar* tsc = new TransparentScrollBar(w);

hbl->addWidget(tsc,0);
hbl->insertStretch(0,1);

答案 1 :(得分:3)

以下是您的questoin的示例代码。

未完成: 鼠标拖动滚动条

完成: 支持任何鼠标悬停/离开事件 支持滚动 滚动条对于鼠标事件是透明的

根据您的任务,这是任何自定义的良好起点。用法:

GUI::MegaScrollBar *bar = new GUI::MegaScrollBar( ui->listWidget );
bar->resize( 40, 30 ); // First arg - width of scroller

MegaScrollBar.h

#ifndef MEGASCROLLBAR_H
#define MEGASCROLLBAR_H

#include <QWidget>
#include <QPointer>


class QAbstractItemView;
class QResizeEvent;

namespace GUI
{

    class MegaScrollBar
        : public QWidget
    {
        Q_OBJECT

    public:
        MegaScrollBar( QAbstractItemView *parentView );
        ~MegaScrollBar();

    private slots:
        void updatePos();

    private:
        bool eventFilter( QObject *obj, QEvent *event );
        void onResize( QResizeEvent *e );
        void paintEvent( QPaintEvent * event );
        void resizeEvent( QResizeEvent * event );

        QPointer< QAbstractItemView > m_view;
        QPointer< QWidget > m_scrollBtn;
    };

}


#endif // MEGASCROLLBAR_H

MegaScrollBar.cpp

#include "MegaScrollBar.h"

#include <QAbstractItemView>
#include <QEvent>
#include <QResizeEvent>
#include <QScrollBar>
#include <QDebug>
#include <QPainter>

#include "ScrollButton.h"


namespace GUI
{

    MegaScrollBar::MegaScrollBar( QAbstractItemView *parentView )
        : QWidget( parentView, Qt::FramelessWindowHint )
        , m_view( parentView )
    {
        Q_ASSERT( parentView );

        setAttribute( Qt::WA_TranslucentBackground );
        setAttribute( Qt::WA_TransparentForMouseEvents );

        m_scrollBtn = new ScrollButton( parentView );
        m_scrollBtn->setFixedSize( 20, 40 );

        m_view->installEventFilter( this );

        QScrollBar *sb = m_view->verticalScrollBar();
        connect( sb, SIGNAL( valueChanged( int ) ), this, SLOT( updatePos() ) );
    }

    MegaScrollBar::~MegaScrollBar()
    {
        removeEventFilter( m_view );
    }

    bool MegaScrollBar::eventFilter( QObject *obj, QEvent *event )
    {
        switch ( event->type() )
        {
        case QEvent::Enter:
            m_scrollBtn->show();
            break;

        case QEvent::Leave:
            m_scrollBtn->hide();
            break;

        case QEvent::Resize:
            onResize( static_cast< QResizeEvent * >( event ) );
            break;
        }

        return QWidget::eventFilter( obj, event );
    }

    void MegaScrollBar::onResize( QResizeEvent *e )
    {
        const int x = e->size().width() - width();
        const int y = 0;
        const int w = width();
        const int h = e->size().height();

        move( x, y );
        resize( w, h );

        updatePos();
    }

    void MegaScrollBar::updatePos()
    {
        QScrollBar *sb = m_view->verticalScrollBar();
        const int min = sb->minimum();
        const int val = sb->value();
        const int max = sb->maximum();
        const int x = pos().x() + ( width() - m_scrollBtn->width() ) / 2;

        if ( max == 0 )
        {
            m_scrollBtn->move( x, pos().y() );
            return ;
        }

        const int maxY = height() - m_scrollBtn->height();
        const int y = ( maxY * val ) / max;

        m_scrollBtn->move( x, y );
    }

    void MegaScrollBar::paintEvent( QPaintEvent * event )
    {
        Q_UNUSED( event );
        QPainter p( this );
        QRect rc( 0, 0, rect().width() - 1, rect().height() - 1 );

        // Draw any scroll background
        p.fillRect( rc, QColor( 255, 255, 200, 100 ) );
    }

    void MegaScrollBar::resizeEvent( QResizeEvent * event )
    {
        Q_UNUSED( event );
        updatePos();
    }

}

预览:

Sample

可以为滚动按钮设置任何小部件:这是自定义小部件: ScrollButton.h

#ifndef SCROLLBUTTON_H
#define SCROLLBUTTON_H

#include <QWidget>


namespace GUI
{

    class ScrollButton
        : public QWidget
    {
        Q_OBJECT

    public:
        ScrollButton( QWidget *parent );
        ~ScrollButton();

    private:
        void paintEvent( QPaintEvent * event );
    };

}


#endif // SCROLLBUTTON_H

ScrollButton.cpp

#include "ScrollButton.h"

#include <QPainter>
#include <QGraphicsOpacityEffect>
#include <QColor>


namespace GUI
{

    ScrollButton::ScrollButton( QWidget *parent )
        : QWidget( parent )
    {
        QGraphicsOpacityEffect *op = new QGraphicsOpacityEffect( this );
        op->setOpacity( 0.5 );
        setGraphicsEffect( op );
    }

    ScrollButton::~ScrollButton()
    {
    }

    void ScrollButton::paintEvent( QPaintEvent * event )
    {
        Q_UNUSED( event );

        // Draw any scroll button
        QPainter p( this );
        QRect rc( 5, 5, rect().width() - 6, rect().height() - 6 );

        p.fillRect( rc, QColor( 0, 0, 0, 255 ) );
    }

}

如果您无法处理鼠标互动,请发表评论。