Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Qt for Python
  4. Layouting Issue with QScrollBar

Layouting Issue with QScrollBar

Scheduled Pinned Locked Moved Solved Qt for Python
2 Posts 1 Posters 330 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • A Offline
    A Offline
    AliSot2000
    wrote on last edited by
    #1

    Hi

    I'm trying to replicate an indicator like the one found in GoogleFotos. (Basically you have a scrollbar which has an indicator widget that points to the current location of the scroll bar and gives a readout of the date at the point.

    Now I was able to replicate that mostly within Qt. However, I was trying to force the size of the slider in the QScrollBar to remain constant regardless of the range it has. That also was a bit tricky, since the setStyleStheet function was behaving a bit erratic:
    Passing it:

    QScrollBar::handle:vertical {
        min-height: 50px;
        max-height: 50px
    }
    

    lead to no observable change.
    But passing it:

    QScrollBar:vertical {
        border: 1px dashed black;
        width: 15px;
    }  
    
            
    QScrollBar::handle:vertical {
        min-height: 50px;
        max-height: 50px
    }
    

    locked the size of the slider to 50px. It might be possible that I've missed something in the docs but I wasn't able to find any mention of that behavior in the Qt 6.6 docs.

    I'd like to add that for example the docs on the properties min-width and max-width aren't that precise. Before I discovered that I was in fact able to style the slider if I passed that extra block of styling, I assumed that QScrollBar::handle didn't support stylesheets with Python.

    However, testing with copy-pasting different sections from Customizing Scrollbar, I was able to discover the mentioned behavior.

    This revealed a new problem. Once I got the slider to a fixed size, it would not stop in front of the buttons anymore but now move over top of them. ScrollError.png

    Here's the Python Code for that Window:

    
    import sys
    from PyQt6.QtWidgets import (
        QApplication,
        QFormLayout,
        QGridLayout,
        QLabel,
        QScrollArea,
        QScroller,
        QWidget,
        QMainWindow,
        QScrollBar,
        QFrame,
        QHBoxLayout,
        QVBoxLayout,
        QListWidget,
    )
    from PyQt6 import QtGui
    from PyQt6.QtCore import Qt
    import random
    
    class RootWindow(QMainWindow):
        def __init__(self):
            super().__init__()
            self.dummy_widget = QWidget()
            self.dummy_widget.setMinimumWidth(100)
            self.dummy_widget.setMinimumHeight(500)
    
            self.placeholder = QFrame()
            self.placeholder.setFrameStyle(QFrame.Shape.Box)
    
            self.indicator = QLabel("Indicator", self)
            self.indicator.setVisible(False)
            self.indicator.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
    
            self.sc = QScrollBar(Qt.Orientation.Vertical)
            self.sc.setMaximum(1000)
            self.sc.valueChanged.connect(self.value_reader)
            self.sc.sliderReleased.connect(self.hide_indicator)
            self.sc.sliderPressed.connect(self.show_indicator)
            self.sc.setFixedWidth(15)
            self.sc.setPageStep(10)
            style_sheet = """
            QScrollBar:vertical {
                border: 1px dashed black;
                width: 15px;
            }  
    
            
            QScrollBar::handle:vertical {
                min-height: 50px;
                max-height: 50px
            }
    
            """
            self.sc.setStyleSheet(style_sheet)
    
            self.l = QHBoxLayout()
            self.dummy_widget.setLayout(self.l)
    
            self.l.addWidget(self.placeholder)
            self.l.addWidget(self.sc)
    
            self.setCentralWidget(self.dummy_widget)
    
        def show_indicator(self):
            self.indicator.setVisible(True)
            self.value_reader()
    
        def hide_indicator(self):
            self.indicator.setVisible(False)
    
        def value_reader(self):
            no_arrows = self.sc.height() - self.sc.width() * 2
            relative = self.sc.value() / (self.sc.maximum() - self.sc.minimum())
            # should be
            # movement_range = no_arrows - 50
            # is
            self.indicator.setText(str(self.sc.value()))
            movement_range = self.sc.height() - 50
            self.indicator.move(self.width() - 15 - self.indicator.width(),
                                int(relative * movement_range + 25 + 10 - self.indicator.height() / 2))
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        ex = RootWindow()
        ex.show()
        sys.exit(app.exec())
    

    I'm running Python3.8 and PyQt6.6

    I'm admittedly quite new to Qt and I haven't mastered all of it's concepts yet. It might very well be that there's some hidden trick to get that to work I've just not been able to track it down.

    Thank you for your help

    AliSot2000

    1 Reply Last reply
    0
    • A Offline
      A Offline
      AliSot2000
      wrote on last edited by AliSot2000
      #2

      Hi

      I managed to replicate the GooglePhotos ScrollBar with the following code snippet. Sadly I wasn't able to get the position of the handle of the scrollbar using any of the available methods offered by Qt. I ended up calculating the position myself in python and then moving the widget to the associated position.

      That is to say. None of the Methods like subControlRect() provided me with any information about the handle of the scrollbar. They always returned a QSomething() (Like QRect or QPoint) which was empty. That's why I ended up computing the position in python using the QPixelMetric, which was the only thing I could access in python.

      Here's the code snippet that ended up working for me:

      import math
      import sys
      
      from PyQt6.QtCore import Qt
      from PyQt6.QtGui import QPixmapCache
      from PyQt6.QtWidgets import (
          QApplication,
          QLabel,
          QWidget,
          QMainWindow,
          QScrollBar,
          QHBoxLayout,
          QTextEdit,
          QGridLayout,
          QPushButton,
          QSlider,
          QStyle
      )
      
      
      class RootWindow(QMainWindow):
          def __init__(self):
              super().__init__()
              self.sc = QScrollBar(Qt.Orientation.Vertical)
              self.dummy_widget = QWidget()
              self.dummy_widget.setMinimumWidth(100)
              self.dummy_widget.setMinimumHeight(500)
      
              self.placeholder = QWidget()
              self.placeholder_layout = QGridLayout()
              self.placeholder_layout.setSpacing(10)
              self.placeholder.setLayout(self.placeholder_layout)
              self.placeholder_layout.setContentsMargins(0, 0, 0, 0)
      
              self.style_sheet_input = QTextEdit()
              self.submit_btn = QPushButton("Submit")
              self.submit_btn.clicked.connect(self.set_style_sheet)
      
              self.scroll_max = QSlider(Qt.Orientation.Horizontal)
              self.scroll_max.valueChanged.connect(self.max_set)
              self.scroll_max.setMaximum(1000)
              self.scroll_max.setMinimum(50)
      
              self.page_size = QSlider(Qt.Orientation.Horizontal)
              self.page_size.valueChanged.connect(self.page_set)
              self.page_size.setMaximum(50)
              self.page_size.setMinimum(10)
      
              self.do_shit_btn = QPushButton("Do-Shit")
              self.do_shit_btn.clicked.connect(self.do_shit)
      
              self.placeholder_layout.addWidget(self.style_sheet_input, 0, 0, 1, 4)
              self.placeholder_layout.addWidget(self.submit_btn, 1, 2)
              self.placeholder_layout.addWidget(self.scroll_max, 1, 0)
              self.placeholder_layout.addWidget(self.page_size, 1, 1)
              self.placeholder_layout.addWidget(self.do_shit_btn, 1, 3)
      
              # self.sc = QScrollBar(Qt.Orientation.Horizontal)
              self.sc.setMaximum(20)
              self.sc.valueChanged.connect(self.value_reader)
              self.sc.sliderReleased.connect(self.hide_indicator)
              self.sc.sliderPressed.connect(self.show_indicator)
      
              self.l = QHBoxLayout()
              self.l.setContentsMargins(10, 10, 10, 10)
              self.l.setSpacing(10)
              # self.l = QVBoxLayout()
              self.dummy_widget.setLayout(self.l)
      
              self.l.addWidget(self.placeholder)
              self.l.addWidget(self.sc)
      
              self.setCentralWidget(self.dummy_widget)
      
              # Spacing
              self.upper_pad = QLabel("", self)
              self.upper_pad.setVisible(False)
              self.upper_pad.setStyleSheet("QLabel {background: rgba(255, 128, 0, 128);}")
      
              self.lower_pad = QLabel("", self)
              self.lower_pad.setVisible(False)
              self.lower_pad.setStyleSheet("QLabel {background: rgba(255, 128, 0, 128);}")
      
              # Arrows
              self.upper_arrow = QLabel("", self)
              self.upper_arrow.setVisible(False)
              self.upper_arrow.setStyleSheet("QLabel {background: rgba(128, 255, 0, 128);}")
      
              self.lower_arrow = QLabel("", self)
              self.lower_arrow.setVisible(False)
              self.lower_arrow.setStyleSheet("QLabel {background: rgba(128, 255, 0, 128);}")
      
              # Page
              self.upper_page = QLabel("", self)
              self.upper_page.setVisible(False)
              self.upper_page.setStyleSheet("QLabel {background: rgba(0, 255, 128, 128);}")
      
              self.lower_page = QLabel("", self)
              self.lower_page.setVisible(False)
              self.lower_page.setStyleSheet("QLabel {background: rgba(0, 255, 128, 128);}")
      
              # Scrollbar
              self.scrollbar = QLabel("", self)
              self.scrollbar.setVisible(False)
              self.scrollbar.setStyleSheet("QLabel {background: rgba(0, 128, 255, 128);}")
      
              self.indicator = QLabel("Indicator", self)
              self.indicator.setStyleSheet("QLabel {background: red;}")
              self.indicator.setVisible(True)
              self.indicator.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
              self.indicator.setFixedHeight(26)
              self.indicator.setFixedWidth(100)
      
          def do_shit(self, *args, **kwargs):
              """
              Function shows all overlays for debugging purposes (i.e. take a screenshot to make sure the calculations in python are correct
              """
              print(f"Args {args}")
              print(f"Kwargs {kwargs}")
              self.indicator.setVisible(True)
              self.upper_pad.setVisible(True)
              self.lower_pad.setVisible(True)
              self.upper_arrow.setVisible(True)
              self.lower_arrow.setVisible(True)
              self.upper_page.setVisible(True)
              self.lower_page.setVisible(True)
              self.scrollbar.setVisible(True)
      
          def max_set(self, v: int):
              self.sc.setMaximum(v)
              self.sc.setValue(0)
              print(f"Max: {v}")
      
          def page_set(self, v: int):
              self.sc.setPageStep(v)
              self.sc.setValue(0)
              print(f"Page: {v}")
      
          def set_style_sheet(self):
              style_sheet = self.style_sheet_input.toPlainText()
              print(style_sheet)
              self.sc.setStyleSheet(style_sheet)
      
          def show_indicator(self):
              self.indicator.setVisible(True)
              self.upper_pad.setVisible(True)
              self.lower_pad.setVisible(True)
              self.upper_arrow.setVisible(True)
              self.lower_arrow.setVisible(True)
              self.upper_page.setVisible(True)
              self.lower_page.setVisible(True)
              self.scrollbar.setVisible(True)
              self.value_reader()
      
          def hide_indicator(self):
              self.indicator.setVisible(False)
              self.upper_pad.setVisible(False)
              self.lower_pad.setVisible(False)
              self.upper_arrow.setVisible(False)
              self.lower_arrow.setVisible(False)
              self.upper_page.setVisible(False)
              self.lower_page.setVisible(False)
              self.scrollbar.setVisible(False)
      
          def value_reader(self):
              """
              This function is an implementation in python to get the QScrollBar().subControlRect() of the handle of the scrollbar. It then moves a QLabel to the 
              position of the scrollbar such that the middle of the scrollbar and the middle of QLabel are on the same y-coordinate. 
              Sadly, I couldn't find a way to make this work using the methods of Qt. Last I checked, subControlRect returns an empty QRect() for the Scrollbar handle. 
              """
      
              print(f"Scrollbar Size:", self.sc.size())
              min_handle_height = self.sc.style().pixelMetric(QStyle.PixelMetric.PM_ScrollBarSliderMin)
              no_arrows = self.sc.height() - self.sc.width() * 2
              relative = self.sc.value() / (self.sc.maximum() - self.sc.minimum())
      
              # Height should be page step / document length
              bar_height_rel = self.sc.pageStep() / (self.sc.maximum() - self.sc.minimum() + self.sc.pageStep())
              bar_height_px = math.floor(bar_height_rel * no_arrows)
              handle_height = max(min_handle_height, bar_height_px)
              print(handle_height)
      
              self.indicator.setText(str(self.sc.value()))
              movement_range = no_arrows - handle_height
              self.indicator.move(self.width() - self.sc.width() - 10 - self.indicator.width(),
                                  int(relative * movement_range
                                      + (handle_height / 2)
                                      + 10
                                      + self.sc.width()
                                      - self.indicator.height() / 2))
      
              # Spacing
              self.lower_pad.setFixedWidth(self.width())
              self.lower_pad.setFixedHeight(10)
              self.lower_pad.move(0, self.height() - 10)
      
              self.upper_pad.setFixedWidth(self.width())
              self.upper_pad.setFixedHeight(10)
              self.upper_pad.move(0, 0)
      
              # Arrows
              self.lower_arrow.setFixedHeight(self.sc.width())
              self.lower_arrow.setFixedWidth(self.width())
              self.lower_arrow.move(0, self.height() - 10 - self.sc.width())
      
              self.upper_arrow.setFixedHeight(self.sc.width())
              self.upper_arrow.setFixedWidth(self.width())
              self.upper_arrow.move(0, 10)
      
              # Page
              self.lower_page.setFixedWidth(self.width())
              self.lower_page.setFixedHeight(int(math.ceil(movement_range * (1 - relative))))
              self.lower_page.move(0, 10 + self.sc.width() + int(movement_range * relative + handle_height))
      
              self.upper_page.setFixedWidth(self.width())
              self.upper_page.setFixedHeight(int(movement_range * relative))
              self.upper_page.move(0, 10 + self.sc.width())
      
              # Handle
              self.scrollbar.setFixedWidth(self.width())
              self.scrollbar.setFixedHeight(handle_height)
              self.scrollbar.move(0, 10 + self.sc.width() + int(relative * movement_range))
      
              # Getting position of
              # stl = self.style()
              # Doesn't work: v is empty QRect()
              # v = stl.subControlRect(stl.ComplexControl.CC_ScrollBar, None, stl.SubControl.SC_ScrollBarSlider, self.sc)
              # stl.subElement doesn't work because it has no scrollbar stuff
      
      
              if self.sc.isSliderDown():
                  print(f"Slider is down")
              else:
                  print(f"Slider is up")
      
      
      
      if __name__ == '__main__':
          app = QApplication(sys.argv)
          x = app.style().pixelMetric(QApplication.style().PixelMetric.PM_ScrollBarExtent)
          print(x)
          print(QPixmapCache.cacheLimit())
          QPixmapCache.setCacheLimit(1024)
          print(QPixmapCache.cacheLimit())
      
          ex = RootWindow()
          ex.show()
          sys.exit(app.exec())
      
      
      1 Reply Last reply
      0
      • A AliSot2000 has marked this topic as solved on

      • Login

      • Login or register to search.
      • First post
        Last post
      0
      • Categories
      • Recent
      • Tags
      • Popular
      • Users
      • Groups
      • Search
      • Get Qt Extensions
      • Unsolved