2017-01-12 1 views
2

Ich entwickle eine App zum Auswendiglernen von Text mit PyQt4. Ich möchte alle Wörter in Blasen zeigen, damit Sie sehen, wie lange das Wort ist. Aber wenn ich alle Blasen in meinem QScrollArea habe, sind sie untereinander ausgerichtet. Ich möchte sie nebeneinander ausgerichtet haben, aber mit Word-Wrap.PyQt: Wrap-Around-Layout von Widgets in einer QScrollArea

Ich habe die Blasen mit einer QLabel mit abgerundeten Grenzen arbeiten zu arbeiten. Aber jetzt, da ich die Wörter in QLabel's habe, betrachtet PyQt sie nicht als Wörter - sondern als Widgets. PyQt bringt also ein Widget unter das andere. Ich möchte, dass die Widgets Seite an Seite ausgerichtet werden, bis sie das Ende der Zeile erreichen, und dann sollten sie zur nächsten Zeile wechseln - was bedeutet, dass die QLabel's wie Wörter in einem Textdokument funktionieren sollte.

Hier ist mein Code so weit:

f = open(r'myFile.txt') 

class Bubble(QtGui.QLabel): 
    def __init__(self, text): 
     super(Bubble, self).__init__(text) 
     self.word = text 
     self.setContentsMargins(5, 5, 5, 5) 

    def paintEvent(self, e): 
     p = QtGui.QPainter(self) 
     p.setRenderHint(QtGui.QPainter.Antialiasing,True) 
     p.drawRoundedRect(0,0,self.width()-1,self.height()-1,5,5) 
     super(Bubble, self).paintEvent(e) 


class MainWindow(QtGui.QMainWindow): 
    def __init__(self, text, parent=None): 
     QtGui.QMainWindow.__init__(self, parent) 
     self.setupUi(self) 
     self.MainArea = QtGui.QScrollArea 
     self.widget = QtGui.QWidget() 
     vbox = QtGui.QVBoxLayout() 
     self.words = [] 
     for t in re.findall(r'\b\w+\b', text): 
      label = Bubble(t) 
      label.setFont(QtGui.QFont('SblHebrew', 18)) 
      label.setFixedWidth(label.sizeHint().width()) 
      self.words.append(label) 
      vbox.addWidget(label) 
     self.widget.setLayout(vbox) 
     self.MainArea.setWidget(self.widget) 

if __name__ == '__main__': 
    import sys 
    app = QtGui.QApplication(sys.argv) 
    myWindow = MainWindow(f.read(), None) 
    myWindow.show() 
    sys.exit(app.exec_()) 

Als ich das laufen erhalte ich:

picture of my example that I do not like

Aber ich würde die Worte gefallen (die Qlabel's die Wörter) neben sein miteinander, nicht untereinander, so (photoshopped):

enter image description here

Ich habe viel Forschung betrieben, aber keine Antworten helfen mir, die Widgets nebeneinander auszurichten.

+0

Warum aren nimmst du html? – ekhumoro

+0

erklären Sie Ihre Frage bitte – eyllanesc

+0

@ekhumoro, ich schreibe eine Computer-App. Ist HTML nicht für Webseiten? Außerdem lerne ich immer noch HTML. –

Antwort

1

Ich dachte, es wäre möglich, HTML in einem Widget QTextBrowser dafür zu verwenden, aber Qt rich-text engine unterstützt nicht die border-radius CSS-Eigenschaft, die für die Blase Etiketten benötigt würde.

So sieht es aus, als ob Sie einen PyQt-Port der Flow Layout example benötigen. Dies kann eine Sammlung von Widgets innerhalb eines Containers "word-wrap" machen und erlaubt es auch, die Ränder und den horizontalen/vertikalen Abstand anzupassen.

Hier ist eine Demo-Skript, das die FlowLayout Klasse implementiert und zeigt, wie es mit Ihrem Beispiel verwenden:

import sys 
from PyQt4 import QtCore, QtGui 

class FlowLayout(QtGui.QLayout): 
    def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1): 
     super(FlowLayout, self).__init__(parent) 
     self._hspacing = hspacing 
     self._vspacing = vspacing 
     self._items = [] 
     self.setContentsMargins(margin, margin, margin, margin) 

    def __del__(self): 
     del self._items[:] 

    def addItem(self, item): 
     self._items.append(item) 

    def horizontalSpacing(self): 
     if self._hspacing >= 0: 
      return self._hspacing 
     else: 
      return self.smartSpacing(
       QtGui.QStyle.PM_LayoutHorizontalSpacing) 

    def verticalSpacing(self): 
     if self._vspacing >= 0: 
      return self._vspacing 
     else: 
      return self.smartSpacing(
       QtGui.QStyle.PM_LayoutVerticalSpacing) 

    def count(self): 
     return len(self._items) 

    def itemAt(self, index): 
     if 0 <= index < len(self._items): 
      return self._items[index] 

    def takeAt(self, index): 
     if 0 <= index < len(self._items): 
      return self._items.pop(index) 

    def expandingDirections(self): 
     return QtCore.Qt.Orientations(0) 

    def hasHeightForWidth(self): 
     return True 

    def heightForWidth(self, width): 
     return self.doLayout(QtCore.QRect(0, 0, width, 0), True) 

    def setGeometry(self, rect): 
     super(FlowLayout, self).setGeometry(rect) 
     self.doLayout(rect, False) 

    def sizeHint(self): 
     return self.minimumSize() 

    def minimumSize(self): 
     size = QtCore.QSize() 
     for item in self._items: 
      size = size.expandedTo(item.minimumSize()) 
     left, top, right, bottom = self.getContentsMargins() 
     size += QtCore.QSize(left + right, top + bottom) 
     return size 

    def doLayout(self, rect, testonly): 
     left, top, right, bottom = self.getContentsMargins() 
     effective = rect.adjusted(+left, +top, -right, -bottom) 
     x = effective.x() 
     y = effective.y() 
     lineheight = 0 
     for item in self._items: 
      widget = item.widget() 
      hspace = self.horizontalSpacing() 
      if hspace == -1: 
       hspace = widget.style().layoutSpacing(
        QtGui.QSizePolicy.PushButton, 
        QtGui.QSizePolicy.PushButton, QtCore.Qt.Horizontal) 
      vspace = self.verticalSpacing() 
      if vspace == -1: 
       vspace = widget.style().layoutSpacing(
        QtGui.QSizePolicy.PushButton, 
        QtGui.QSizePolicy.PushButton, QtCore.Qt.Vertical) 
      nextX = x + item.sizeHint().width() + hspace 
      if nextX - hspace > effective.right() and lineheight > 0: 
       x = effective.x() 
       y = y + lineheight + vspace 
       nextX = x + item.sizeHint().width() + hspace 
       lineheight = 0 
      if not testonly: 
       item.setGeometry(
        QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint())) 
      x = nextX 
      lineheight = max(lineheight, item.sizeHint().height()) 
     return y + lineheight - rect.y() + bottom 

    def smartSpacing(self, pm): 
     parent = self.parent() 
     if parent is None: 
      return -1 
     elif parent.isWidgetType(): 
      return parent.style().pixelMetric(pm, None, parent) 
     else: 
      return parent.spacing() 

class Bubble(QtGui.QLabel): 
    def __init__(self, text): 
     super(Bubble, self).__init__(text) 
     self.word = text 
     self.setContentsMargins(5, 5, 5, 5) 

    def paintEvent(self, event): 
     painter = QtGui.QPainter(self) 
     painter.setRenderHint(QtGui.QPainter.Antialiasing, True) 
     painter.drawRoundedRect(
      0, 0, self.width() - 1, self.height() - 1, 5, 5) 
     super(Bubble, self).paintEvent(event) 

class MainWindow(QtGui.QMainWindow): 
    def __init__(self, text, parent=None): 
     super(MainWindow, self).__init__(parent) 
     self.mainArea = QtGui.QScrollArea(self) 
     self.mainArea.setWidgetResizable(True) 
     widget = QtGui.QWidget(self.mainArea) 
     widget.setMinimumWidth(50) 
     layout = FlowLayout(widget) 
     self.words = [] 
     for word in text.split(): 
      label = Bubble(word) 
      label.setFont(QtGui.QFont('SblHebrew', 18)) 
      label.setFixedWidth(label.sizeHint().width()) 
      self.words.append(label) 
      layout.addWidget(label) 
     self.mainArea.setWidget(widget) 
     self.setCentralWidget(self.mainArea) 

if __name__ == '__main__': 

    app = QtGui.QApplication(sys.argv) 
    window = MainWindow('Harry Potter is a series of fantasy literature') 
    window.show() 
    sys.exit(app.exec_()) 
+0

Vielen Dank! Das hat genau das gemacht, was ich wollte! Obwohl ich sehr traurig bin zu sagen, ich verstehe nicht warum. Zwei Fragen: (1) Gibt es eine Möglichkeit, die Widgets von rechts nach links anzuzeigen? (2) Gibt es eine Möglichkeit, die Widgets in irgendeine Richtung auszurichten oder sie zu rechtfertigen? –

+0

@ CheynShmuel. Vielleicht - aber es würde eine Menge zusätzlicher Arbeit erfordern. Deshalb habe ich vorgeschlagen, HTML früher zu verwenden - es löst alle Schwierigkeiten beim Layout des Dokuments, und es erleichtert die Formatierung der Wörter auf verschiedene Arten. Leider unterstützen die 'QTextEdit' und' QTextBrowser' Klassen nicht den vollen Funktionsumfang von html/css, so dass man keine ausgefallenen Sachen wie abgerundete Ecken bekommen kann.Aber rechts-nach-links und Rechtfertigung * werden unterstützt. – ekhumoro

+0

Ich verstehe, es könnte schwer sein, aber zumindest ist es möglich. Könntest du mir wenigstens in die richtige Richtung zeigen? Ich verstehe, dass es schwer sein könnte, also könntest du wenigstens ein wenig helfen? –

1

Hier ist eine PyQt5 Version der Demo Skript Fluss Layout:

import sys 
from PyQt5 import QtCore, QtGui, QtWidgets 

class FlowLayout(QtWidgets.QLayout): 
    def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1): 
     super(FlowLayout, self).__init__(parent) 
     self._hspacing = hspacing 
     self._vspacing = vspacing 
     self._items = [] 
     self.setContentsMargins(margin, margin, margin, margin) 

    def __del__(self): 
     del self._items[:] 

    def addItem(self, item): 
     self._items.append(item) 

    def horizontalSpacing(self): 
     if self._hspacing >= 0: 
      return self._hspacing 
     else: 
      return self.smartSpacing(
       QtWidgets.QStyle.PM_LayoutHorizontalSpacing) 

    def verticalSpacing(self): 
     if self._vspacing >= 0: 
      return self._vspacing 
     else: 
      return self.smartSpacing(
       QtWidgets.QStyle.PM_LayoutVerticalSpacing) 

    def count(self): 
     return len(self._items) 

    def itemAt(self, index): 
     if 0 <= index < len(self._items): 
      return self._items[index] 

    def takeAt(self, index): 
     if 0 <= index < len(self._items): 
      return self._items.pop(index) 

    def expandingDirections(self): 
     return QtCore.Qt.Orientations(0) 

    def hasHeightForWidth(self): 
     return True 

    def heightForWidth(self, width): 
     return self.doLayout(QtCore.QRect(0, 0, width, 0), True) 

    def setGeometry(self, rect): 
     super(FlowLayout, self).setGeometry(rect) 
     self.doLayout(rect, False) 

    def sizeHint(self): 
     return self.minimumSize() 

    def minimumSize(self): 
     size = QtCore.QSize() 
     for item in self._items: 
      size = size.expandedTo(item.minimumSize()) 
     left, top, right, bottom = self.getContentsMargins() 
     size += QtCore.QSize(left + right, top + bottom) 
     return size 

    def doLayout(self, rect, testonly): 
     left, top, right, bottom = self.getContentsMargins() 
     effective = rect.adjusted(+left, +top, -right, -bottom) 
     x = effective.x() 
     y = effective.y() 
     lineheight = 0 
     for item in self._items: 
      widget = item.widget() 
      hspace = self.horizontalSpacing() 
      if hspace == -1: 
       hspace = widget.style().layoutSpacing(
        QtWidgets.QSizePolicy.PushButton, 
        QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Horizontal) 
      vspace = self.verticalSpacing() 
      if vspace == -1: 
       vspace = widget.style().layoutSpacing(
        QtWidgets.QSizePolicy.PushButton, 
        QtWidgets.QSizePolicy.PushButton, QtCore.Qt.Vertical) 
      nextX = x + item.sizeHint().width() + hspace 
      if nextX - hspace > effective.right() and lineheight > 0: 
       x = effective.x() 
       y = y + lineheight + vspace 
       nextX = x + item.sizeHint().width() + hspace 
       lineheight = 0 
      if not testonly: 
       item.setGeometry(
        QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint())) 
      x = nextX 
      lineheight = max(lineheight, item.sizeHint().height()) 
     return y + lineheight - rect.y() + bottom 

    def smartSpacing(self, pm): 
     parent = self.parent() 
     if parent is None: 
      return -1 
     elif parent.isWidgetType(): 
      return parent.style().pixelMetric(pm, None, parent) 
     else: 
      return parent.spacing() 

class Bubble(QtWidgets.QLabel): 
    def __init__(self, text): 
     super(Bubble, self).__init__(text) 
     self.word = text 
     self.setContentsMargins(5, 5, 5, 5) 

    def paintEvent(self, event): 
     painter = QtGui.QPainter(self) 
     painter.setRenderHint(QtGui.QPainter.Antialiasing, True) 
     painter.drawRoundedRect(
      0, 0, self.width() - 1, self.height() - 1, 5, 5) 
     super(Bubble, self).paintEvent(event) 

class MainWindow(QtWidgets.QMainWindow): 
    def __init__(self, text, parent=None): 
     super(MainWindow, self).__init__(parent) 
     self.mainArea = QtWidgets.QScrollArea(self) 
     self.mainArea.setWidgetResizable(True) 
     widget = QtWidgets.QWidget(self.mainArea) 
     widget.setMinimumWidth(50) 
     layout = FlowLayout(widget) 
     self.words = [] 
     for word in text.split(): 
      label = Bubble(word) 
      label.setFont(QtGui.QFont('SblHebrew', 18)) 
      label.setFixedWidth(label.sizeHint().width()) 
      self.words.append(label) 
      layout.addWidget(label) 
     self.mainArea.setWidget(widget) 
     self.setCentralWidget(self.mainArea) 

if __name__ == '__main__': 

    app = QtWidgets.QApplication(sys.argv) 
    window = MainWindow('Harry Potter is a series of fantasy literature') 
    window.show() 
    sys.exit(app.exec_()) 
Verwandte Themen