1

My code has two files. One is the "main" file that uses a fixed size QFrame instead of the standard window frame. The second is also a subclassed QWidget for adding to the QScrollArea in the main widget.

I want the QFrame of the subwidget to be fixed and the label to actually use the wordwrap property instead of just making it longer to fit and screwing up the whole QScrollArea scrolling area. I have tried using QSizePolicy for the container_widget and QFrame of the subwidget. A minimal reproducible example is as follows.

main_window.py

import sys

from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg

from subwidget import SubWidget


class Window(qtw.QWidget):
    def __init__(self):
        super().__init__()
        
        # Configure Window
        self.setWindowFlags(qtc.Qt.FramelessWindowHint)
        self.setAttribute(qtc.Qt.WA_TranslucentBackground)
        self.setFixedSize(350, 450)
        
        # Init Methods
        self.setupUI()
        self.refresh_entries()
        
        self.show()
    
    def setupUI(self):
        # Main Layout/Frame
        self.main_layout = qtw.QHBoxLayout()
        self.main_frame = qtw.QFrame()
        self.main_frame_layout = qtw.QVBoxLayout()
        # Set Layouts
        self.setLayout(self.main_layout)
        self.main_layout.addWidget(self.main_frame)
        self.main_frame.setLayout(self.main_frame_layout)
        # Configure QFrame
        self.main_frame.setContentsMargins(10, 10, 10, 10)
        self.main_frame.setObjectName("main_frame")  
        self.main_frame.setStyleSheet("""
                                      border: None;
                                      border-radius: 10px;
                                      background: #1E1E1E;
                                      """)      
        # Secondary Layout/Widget
        self.main_scroll_area = qtw.QScrollArea()
        self.container_widget = qtw.QWidget()
        self.container_layout = qtw.QVBoxLayout()
        self.main_scroll_area.setObjectName("main_scroll_area")                
        self.main_scroll_area.setWidgetResizable(True)
        self.main_scroll_area.setVerticalScrollBarPolicy(qtc.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.main_scroll_area.setHorizontalScrollBarPolicy(qtc.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
        self.main_scroll_area.setWidget(self.container_widget)
        self.container_widget.setLayout(self.container_layout)
        self.container_widget.setObjectName("container_widget")
        # Widget -> Layout
        self.main_frame_layout.addWidget(self.main_scroll_area, 0, qtc.Qt.AlignmentFlag.AlignHCenter | qtc.Qt.AlignmentFlag.AlignCenter)
    
    def refresh_entries(self):
        self.clear_entries()
                
        for i in range(5):
            self.container_layout.addWidget(SubWidget(i ** i ** i, i))

    def clear_entries(self):
        for i in reversed(range(self.container_layout.count())):
            try:
                widget = self.container_layout.itemAt(i).widget()
                widget.setParent(None)
            except Exception:
                pass
        
        
if __name__ == "__main__":
    app = qtw.QApplication(sys.argv)
    win = Window()
    sys.exit(app.exec())

and subwidget.py

from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg


class SubWidget(qtw.QWidget):
    def __init__(self, name, index):
        super().__init__()
        
        # Variables
        self.name = str(name)
        self.index = index    
           
        # Init Methods
        self.setupUI()        
        
        self.show()
    
    def setupUI(self):
        # Main Layout/Frame
        self.main_layout = qtw.QHBoxLayout()
        self.main_frame = qtw.QFrame()
        self.main_frame_layout = qtw.QVBoxLayout()
        # Set Layouts
        self.setLayout(self.main_layout)
        self.main_layout.addWidget(self.main_frame)
        self.main_frame.setLayout(self.main_frame_layout)
        # Configure QFrame
        self.main_frame.setContentsMargins(5, 5, 5, 5)
        self.main_frame.setStyleSheet("""
                                      border: None;
                                      border-radius: 5px;
                                      background: #000000;
                                      """)               
        # Secondary Layout/Widget        
        self.name_lbl = qtw.QLabel()
        # Configure Seondary Widgets                
        self.name_lbl.setText(self.name)      
        self.name_lbl.setStyleSheet("""
                                    color: #FFFFFF;
                                    """)   
        self.name_lbl.setWordWrap(True)  # https://stackoverflow.com/questions/49838817/how-to-resize-qlabels-to-fit-contents-in-qscrollarea        
        # Widget -> Layout
        self.main_frame_layout.addWidget(self.name_lbl, 0, qtc.Qt.AlignmentFlag.AlignHCenter | qtc.Qt.AlignmentFlag.AlignCenter)

This is a visual of the resulting window/frame.

visual

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Yes, the background info is a bit too much and not really necessary. I suggest you to clean up a bit your question by focusing on the matter at hand only (the labels and scroll area) and provide a self-contained [mre]. Also please clarify if by "scrolled to the side" and "scrolling on the center" you actually meant the alignment, and finally consider that word wrapping has [known limits in layouts](https://doc.qt.io/qt-5/layout.html#layout-issues). – musicamante Aug 19 '21 at 12:06
  • @musicamante will do, thanks for the feedback. Going to edit the question and repost. – Robert Barbu Aug 19 '21 at 12:34
  • Post was edited to be clearer and to include a minimal reproducible example as proposed by @musicamante. – Robert Barbu Aug 19 '21 at 14:39
  • Note that QLabel wraps only at word breaks, so in your example it would never wrap anyway. – musicamante Aug 19 '21 at 14:49
  • Oh yeah, makes sense (I'm dumb :( ). I have some ideas, like making the label scrollable sideways with another QScrollArea or decreasing the font size. Really do not know, what is your suggestion? @musicamante – Robert Barbu Aug 19 '21 at 14:52
  • Unfortunately I cannot help you right now as I don't have a computer right now, but I suggest you to fix your MRE by placing spaces at regular intervals so that anybody else could try it and possibly provide a valid answer. In any case, some pointers: the label size hint is based on complex computations (that also depends on the parent restraints) and is not always reliable for size hint for the reasons linked above, but the main problem is that the scroll area uses the *container* widget's minimum size hint to resize it, based on those labels. I suggest you to use a custom QWidget subclass -> – musicamante Aug 19 '21 at 15:11
  • -> and then override both `sizeHint` and `minimumSizeHint`, call the default implementation, and print that value before returning it. In this way you'll see the various differences you'll have with different labels and when resizing the scroll area. A possible solution *could* be to return a size based on an arbitrary width and the default implementation height (eg: `return QSize(100, super().sizeHint().height())`), and also set an `Expanding` horizontal size policy for the container. But, as said, layout of word wrapped labels is pretty annoying, and you'll probably need to do lots of tests. – musicamante Aug 19 '21 at 15:16
  • @musicamante I'm afraid I do not understand what you mean by "regular spaces" as for subclassing QWidget for containing, what must I change, the sizeHint? How would I do that? EDIT: Your comment didn't load, I understand the subclassing now, thank you! – Robert Barbu Aug 19 '21 at 15:29
  • Just add some spaces every 5/10 letters in the "long" strings so that word wrap could actually make sense. – musicamante Aug 19 '21 at 15:56
  • @RobertBarbu If I understood what you are trying to achieve, you are trying to make the blackbox in your picture display all the numbers or make the blackbox able to scroll horizontally to see all the numbers? – Ted Aug 22 '21 at 15:54
  • If I am right, you are trying to wrap the long number inside the back area? – Ted Aug 22 '21 at 16:22
  • you could use a readonly `qtextedit`. however, I guess you are trying to make a qlabel that can wrap anywhere (not only words), so I show you my implementation of a wrap anywhere qlabel below, you can test it with your project (btw, your code definitely need some refactoring). – Ted Aug 23 '21 at 10:23
  • @Ted Sorry for the late response, I had no access to the internet and it's spotty now too. Anyhow, my goal is for long entry titles to wrap downward instead of extending all the subwidgets in the QScrollArea so the longest title is visible. I hope that clarifies what I mean. – Robert Barbu Aug 23 '21 at 10:55

1 Answers1

1

A Wrapable Anywhere QLabel

My way to achieve this to subclass QLabel and and implement the paintEvent, where you can set the text alignment to TextWrapAnywhere when you drawItemText.

Here is a sample code.

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QVBoxLayout, QWidget, QStyle

class SuperQLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super(SuperQLabel, self).__init__(*args, **kwargs)

        self.textalignment = Qt.AlignLeft | Qt.TextWrapAnywhere
        self.isTextLabel = True
        self.align = None

    def paintEvent(self, event):

        opt = QStyleOption()
        opt.initFrom(self)
        painter = QPainter(self)

        self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self)

        self.style().drawItemText(painter, self.rect(),
                                  self.textalignment, self.palette(), True, self.text())


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.setFixedSize(100, 200)

        self.label = QLabel()
        self.label.setWordWrap(True)
        self.label.setText("1111111111111111111111111111")

        self.slabel = SuperQLabel()
        self.slabel.setText("111111111111111111111111111")

        self.centralwidget = QWidget()
        self.setCentralWidget(self.centralwidget)

        self.mainlayout = QVBoxLayout()
        self.mainlayout.addWidget(self.label)
        self.mainlayout.addWidget(self.slabel)

        self.centralwidget.setLayout(self.mainlayout)


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

The result.

enter image description here

Note: this work is tested with pyqt5, pyside2, pyside6 and should work in qt5 as well (in c++).

Ted
  • 468
  • 2
  • 8