2013-04-29 16 views
8

Chciałbym zaimplementować mój własny półprzezroczysty pasek przewijania, który rysuje się na górze QListWidget zamiast zajmować w nim stałą przestrzeń. Nie chcę używać QML jako mojego QListWidget, a jego dynamiczna zawartość jest już w pełni rozwinięta w ciągu 6 miesięcy.Dostosowywanie paska przewijania przez QListWidget

Jak mogę to osiągnąć. Arkusze stylów są bezużyteczne do tego celu, ponieważ nie określają położenia paska przewijania. Chciałbym, żeby był na szczycie QListWidget, a nie na boku, zajmując jego przestrzeń.

mówię o czymś w sąsiedztwie to:

enter image description here

Wszelkie wskazówki, jak to zrobić, że będzie mile widziane.

+0

Myślę, że jedynym sposobem jest stworzenie własnego sterowania opartego na QListView i zaimplementowanie niestandardowego rysunku paska przewijania (teraz tylko rysunek, ale również logika). Spróbuję napisać przykładowy kod jutro. –

+0

Musi to być QListWidget, btw - to jest to, czego używam, gdy umieszczam "widżety" na żywo w wierszach zgodnie z wymaganiami mojej aplikacji. Nie można tego osiągnąć dzięki systemowi delegowania QListView. – JasonGenX

+0

Delegaci służą do rysowania treści. Przewiń nie jest treścią. Celem jest ukrywanie przewijania i implementacja niestandardowego przewijania nad istniejącym QListWidget. –

Odpowiedz

14

To, co próbujesz zrobić, jest doskonałym przykładem jednej rzeczy, która uporczywie mnie denerwuje o qt - jeśli jest jakiś efekt graficzny, o którym nie pomyśleli graficy Qt, samodzielne tworzenie to ból, ciągła walka przeciwko Qt, i zazwyczaj kończy się zrezygnowaniem.

Podejrzewam, że robisz to z małymi ekranami na głowie (telefony komórkowe? Tablety?), Więc myślę, że nie ma innego sposobu rozwiązania tego problemu.

To, co tu próbuję, jest hacky, ale poza tym prawdopodobnie musiałbyś przepisać cały pasek przewijania, aby dodać kilka brakujących szczegółów. Moja propozycja to:

#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 

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

Takie scroller będzie przezroczysty (zarówno wizualnie jak i wydarzenie mądry) w każdy z nich jest zakaz ważnych części. Nadal tworzy pewne artefakty na układających się pod nim widgetach - domyślam się, że setMask() nigdy nie był używany w ten sposób. Aby go złagodzić, można podłączyć sygnał valueChanged() do gniazda update() w oknie podglądu widgetu listy. To działało dobrze na moim przykładzie z zabawkami, ale jeśli umieścisz niestandardowe widżety na liście, może to stać się nie do zniesienia. Może również spowodować problemy z wydajnością w przypadku bardziej złożonych aplikacji - zwłaszcza jeśli piszesz na platformy mobilne.

Alternatywnie można po prostu "rozwidlić" całą klasę QScrollBar i po prostu zmodyfikować jej paintEvent tak, aby używał mniej podkontroli niż SC_All - z dodatkowym setAttribute(Qt::WA_OpaquePaintEvent, false); w konstruktorze powinien zapewnić przezroczystość wizualną. Następnie powinieneś również przekazywać zdarzenia myszy (jeśli nie trafiasz na nic ważnego) do swojego widoku widgetu listy (ponownie, problem z widżetami niestandardowymi w widoku).

Pozostaje tylko napisanie własnej klasy układu (lub ręczne jej pozycjonowanie), która spowoduje umieszczenie zarówno listview, jak i paska przewijania na właściwych pozycjach - dźwięk QStackedLayout jest przyjemny, ale pozwala na widoczność tylko jednej warstwy czas - wyraźnie nie to, czego szukamy.

Ostatni krok polega na wyłączeniu domyślnych pasków przewijania w widoku i łączeniu sygnałów/gniazd domyślnego (niewidocznego) paska przewijania z gniazdami/sygnałami paska przewijania w celu uzyskania efektu rzeczywistego przewijania.

Wkrótce będzie to wymagało wprowadzenia kodu LOT. Czy jesteś pewien, że taki prosty efekt jest tego wart?

** EDIT: **

utworzyć klasę układ dla układania widgety na szczycie siebie - to pytanie dał mi motywację do zrobienia go w końcu;)

#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 

A stacklayout. plik 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; 
} 

Zakładając, masz już jakieś fajne przezroczysty pasek przewijania, aby go wstawić byś zrobił:

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); 
+0

BTW Jeśli zdecydujesz się mimo to zakodować, mogę zaoferować więcej kodu/pomocy. –

+0

Wybacz mi, ale ten plik to klasa TransparentScrollBar? Przypuszczam, że jest to prosty widget. –

+0

Przez przezroczysty pasek przewijania miałem na myśli coś takiego, jak w odpowiedzi Dmitrija Sazonova. –

2

Oto przykładowy kod dla twojego questoina.

Nie wykonano: Mouse przeciąganie od zmieniacza

Sporządzono: Pomoc jakiejkolwiek najechaniu myszą/opuszczenia Imprezy Wsparcie przewijanie pasek przewijania jest przezroczysty dla zdarzeń myszy

Jest to dobry punkt startu dla każdego dostosowanie w zależności od zadania. Zastosowanie:

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

} 

Preview:

Sample

Jest możliwe, aby skonfigurować y widget dla przycisku przewijania: Oto jeden zwyczaj: 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)); 
    } 

} 

Proszę komentować, jeśli nie może obsłużyć interakcję myszy.

+0

Czy 'Qt :: WA_TransparentForMouseEvents' nie wyłączy wszystkich funkcji myszy na takim pasku przewijania? Jedynym sposobem, jaki widzę, aby działało, byłoby filtrowanie zdarzeń myszy (lub przechwytywanie w inny sposób, np. Poprzez podklasy z widżetem drzewa) z rzutni i podawanie go do widgetu paska przewijania. Będzie to coraz trudniejsze, jeśli masz niestandardowe widżety w widoku. Zastosowanie maski, jak zaproponowałem, może być opcją. –

+0

Czy masz na myśli, że pasek przewijania służy tylko do celów informacyjnych, a nie do aktywnego przewijania widgetu za jego pomocą? –

+0

Do interakcji można ustawić dowolny widget w MegaScrollBar :: MegaScrollBar(). Należy również zaimplementować przekierowanie zdarzeń myszy z widżetu m_scrollBtn. –

Powiązane problemy