9

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

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

我说的是这附近的一些事情:

在此处输入图像描述

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

4

2 回答 2

17

你正在尝试做的是一个完美的例子,它一直让我对 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);在构造函数中附加它应该提供视觉透明度。然后,您还应该将鼠标事件(如果没有击中任何重要的东西)转发到列表小部件的视口(同样,视图中的自定义小部件有问题)。

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

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

很快,这将需要大量的编码才能完成。你确定这么简单的效果值得吗?

** 编辑: **

我创建了一个布局类,用于将小部件相互堆叠 - 这个问题最终给了我这样做的动力;)

#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);
于 2013-05-03T02:38:40.243 回答
3

这是您的问题的示例代码。

未完成:鼠标拖动滚动条

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

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

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();
    }

}

预习:

样本

可以为滚动按钮设置任何小部件:这是自定义小部件: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

滚动按钮.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 ) );
    }

}

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

于 2013-05-03T15:39:08.297 回答