QScrollArea does not attach to label - python

I am trying to display an image label with scrollbars within a Box layout.
However, the scroll area appears at the wrong place with the wrong size.
Could you please tell me what I am doing wrong?
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QLabel, QScrollArea
from PyQt5.QtGui import QPixmap
class ApplicationWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
main_widget = QWidget(self)
btn = QPushButton("Bye", self)
btn.clicked.connect(self.close)
img = QPixmap("1.jpg")
label = QLabel(main_widget)
label.setPixmap(img)
scrollArea = QScrollArea(main_widget)
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(label)
l = QVBoxLayout(main_widget)
l.addWidget(label)
l.addWidget(btn)
self.setCentralWidget(main_widget)
def closeEvent(self, ce):
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
aw = ApplicationWindow()
aw.show()
app.exec_()
The result is:

The problem is that instead of adding the QLabel to QVBoxLayout you must add the QScrollArea. You must change:
l.addWidget(label)
to
l.addWidget(scrollArea)

Related

How to auto-resize a PySide6 app to the minimum size when decreasing a `pixmap`?

I use the following code to set a PySide6 app to the minimal possible size. This works fine when increasing the size of the widgets (set - to + in line 37), but not when decreasing it - in effect, the size of the windows does decrease, but it seems to be one step late.
I found a few workarounds, most notably in Qt Layout, resize to minimum after widget size changes, but none of what I tried seems to be working (and I have met other issues with app.processEvents(), which should be avoided anyway).
Edit: In the new code example below, I think the problem is the width of the QPushButton, which is calculated too late.
Interestingly, this width of the QPushButton is solved by the layout.setSizeConstraint(QLayout.SetFixedSize) workaround, but the window width is not.
app.processEvents() works for this example, but I see bad side effects on other signals when using it.
New code example:
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (QApplication, QLabel, QLayout, QMainWindow,
QPushButton, QVBoxLayout, QWidget)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.i = 0
self.button = QPushButton("push me!")
self.button.clicked.connect(self.clicked)
self.label = QLabel()
layout = QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.label)
# https://stackoverflow.com/a/21458822/880783
# layout.setSizeConstraint(QLayout.SetFixedSize) # (ineffective)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.clicked()
self.show()
def clicked(self):
npix = 500 - 50 * self.i
self.label.setPixmap(QPixmap(npix, npix))
# app.processEvents() # (effective, but discouraged)
self.adjustSize()
self.i += 1
app = QApplication()
win = Window()
app.exec()
Original code example:
import threading
import time
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QLayout,
QMainWindow,
QWidget,
)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.label = QLabel()
layout = QHBoxLayout()
layout.addWidget(self.label)
# https://stackoverflow.com/a/21458822/880783
# layout.setSizeConstraint(QLayout.SetFixedSize) # (ineffective)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.show()
def run(self):
for i in range(10):
npix = 500 - 50 * i
self.label.setPixmap(QPixmap(npix, npix))
# app.processEvents() # (ineffective)
self.adjustSize()
time.sleep(1)
app = QApplication()
threading.Thread(target=Window().run).start()
app.exec()
#musicamante has posted very helpful comments, which I now turn into an answer.
Basically, this code works great:
from PySide6.QtCore import QMetaObject, Qt, QTimer, Slot
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (QApplication, QLabel, QMainWindow, QPushButton,
QVBoxLayout, QWidget)
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.i = 0
self.button = QPushButton("push me!")
self.button.clicked.connect(self.clicked)
self.label = QLabel()
layout = QVBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.label)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.setWindowFlag(Qt.MSWindowsFixedSizeDialogHint)
self.clicked()
self.show()
# #Slot()
# def adjustSize(self):
# super().adjustSize()
def clicked(self):
npix = 500 - 50 * self.i
self.label.setPixmap(QPixmap(npix, npix))
self.i += 1
# # As in https://stackoverflow.com/a/23954088/880783 - does not work
# QMetaObject.invokeMethod(self, "adjustSize")
# This works!
QTimer.singleShot(0, self.adjustSize)
app = QApplication()
win = Window()
app.exec()
As one can see, I have also tried the approach put forward https://stackoverflow.com/a/23954088/880783 - without success, however.

Problem integrating QFontComboBox in Qmenu

I'm trying to integrate QFontComboBox within Qmenu.
I try to make two things happen when selecting a particular font from the menu:
The Qmenu will close.
print the selected font.
from PyQt5.QtCore import QObject
from PyQt5.QtGui import QIcon, QFont, QCursor
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QFontComboBox, QWidgetAction, QMenu, QPushButton
class Window(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Pyside2 FontCombo Box")
self.setGeometry(300,200,300,250)
self.setFontBox()
self.setIcon()
self.show()
def setIcon(self):
appIcon = QIcon("icon.png")
self.setWindowIcon(appIcon)
def setFontBox(self):
self.font_button = QPushButton(self)
self.font_button.setFixedWidth(300)
self.font_button.clicked.connect(lambda: self.setFontmenu())
vbox = QVBoxLayout()
vbox.addWidget(self.font_button)
self.setLayout(vbox)
def setFontmenu(self):
font_menu = QMenu()
font_submenu = QFontComboBox()
font_submenu.setCurrentFont(QFont("Arial"))
objectTest = QObject()
widget = QWidgetAction(objectTest)
widget.setDefaultWidget(font_submenu)
font_menu.addAction(widget)
font_menu.exec_(QCursor.pos())
menu = font_menu
menu.addSeparator()
font_submenu.showPopup()
font_submenu.setFocus()
font_submenu.currentFontChanged.connect(self._changed)
def _changed(self):
font = self.currentFont().family()
print(font)
return
myapp = QApplication(sys.argv)
window = Window()
myapp.exec_()
sys.exit()

Disable higlighting selected items in QListWidget pyqt5

How do I disable higlighting selected items in QListWidget pyqt5?
Tried the following which didn't work:
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QApplication, QListWidget, QListWidgetItem, QGridLayout, QLabel, QWidget
app = QApplication([])
widget = QWidget()
listWidget = QListWidget()
item = QListWidgetItem('Pogba', listWidget)
layout.addWidget(listWidget)
#Attempt
palette = QPalette()
palette.setColor(QPalette.Highlight, listWidget.palette().color(QPalette.Base))
palette.setColor(QPalette.HighlightedText, listWidget.palette().color(QPalette.Text))
listWidget.setPalette(palette)
widget.setLayout(layout)
widget.show()
app.exec()
And the following only makes the item grey instead of blue:
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QApplication, QListWidget, QListWidgetItem, QGridLayout, QLabel, QWidget
app = QApplication([])
widget = QWidget()
listWidget = QListWidget()
item = QListWidgetItem('Pogba', listWidget)
layout.addWidget(listWidget)
#Other attempt
listWidget.setFocusPolicy(Qt.NoFocus)
widget.setLayout(layout)
widget.show()
app.exec()
Is there another way to achieve this?

Why my QToolButton which I try to make collapsible doesn't collapse properly?

guys.
Asking for your help to troubleshoot my test script.
I am practicing to make collapsible button with widgets inside.
Script was mainly taken from another question in stackoverflow about collapsible buttons.
So I am trying to put under QTabWidget my class CollpsibleBox(QWidget). Problem is that my CollapsibleBox is acting very weird - buttons are jumping , sometimes it doesn't open/close properly.
I was wondering if it's some mistake in placing correctly my widget under QTabWidget or is there some problem with animation?
import random
from PySide2.QtGui import QPixmap, QBrush, QColor, QIcon, QPainterPath, QPolygonF, QPen, QTransform
from PySide2.QtCore import QSize, Qt, Signal, QPointF, QRect, QPoint, QParallelAnimationGroup, QPropertyAnimation, QAbstractAnimation
from PySide2.QtWidgets import QMainWindow, QDialog, QVBoxLayout, QHBoxLayout, QGraphicsView, QGraphicsScene, QFrame, \
QSizePolicy, QGraphicsPixmapItem, QApplication, QRubberBand, QMenu, QMenuBar, QTabWidget, QWidget, QPushButton, \
QSlider, QGraphicsPolygonItem, QToolButton, QScrollArea, QLabel
extraDict = {'buttonSetA': ['test'], 'buttonSetB': ['test']}
tabList = ['Main', 'Extra']
_ui = dict()
class MainWindow(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent=parent)
self.create()
def create(self, **kwargs):
_ui['mainLayout'] = QVBoxLayout()
_ui['tabWidget'] = QTabWidget()
_ui['mainLayout'].addWidget(_ui['tabWidget'])
for tab in tabList:
_ui['tab' + tab] = QWidget()
_ui['tabWidget'].addTab(_ui['tab' + tab], tab)
_ui['tabExtra'].layout = QVBoxLayout()
_ui['tabExtra'].setLayout(_ui['tabExtra'].layout)
_ui['content'] = QWidget()
_ui['tabExtra'].layout.addWidget(_ui['content'])
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
box = CollapsibleBox(name)
vlay.addWidget(box)
lay = QVBoxLayout()
for j in range(8):
label = QLabel("{}".format(j))
color = QColor(*[random.randint(0, 255) for _ in range(3)])
label.setStyleSheet(
"background-color: {}; color : white;".format(color.name())
)
label.setAlignment(Qt.AlignCenter)
lay.addWidget(label)
box.setContentLayout(lay)
self.setLayout(_ui['mainLayout'])
class CollapsibleBox(QWidget):
def __init__(self, name):
super(CollapsibleBox, self).__init__()
self.toggle_button = QToolButton(text=name, checkable=True, checked=False)
self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggle_button.setArrowType(Qt.RightArrow)
self.toggle_button.pressed.connect(self.on_pressed)
self.toggle_animation = QParallelAnimationGroup(self)
self.content_area = QScrollArea(maximumHeight=0, minimumHeight=0)
self.content_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.content_area.setFrameShape(QFrame.NoFrame)
lay = QVBoxLayout(self)
lay.setSpacing(0)
lay.setContentsMargins(0, 0, 0, 0)
lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area)
self.toggle_animation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
self.toggle_animation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))
self.toggle_animation.addAnimation(QPropertyAnimation(self.content_area, b"maximumHeight"))
def on_pressed(self):
checked = self.toggle_button.isChecked()
self.toggle_button.setArrowType(Qt.DownArrow if not checked else Qt.RightArrow)
self.toggle_animation.setDirection(QAbstractAnimation.Forward
if not checked
else QAbstractAnimation.Backward
)
self.toggle_animation.start()
def setContentLayout(self, layout):
lay = self.content_area.layout()
del lay
self.content_area.setLayout(layout)
collapsed_height = (self.sizeHint().height() - self.content_area.maximumHeight())
content_height = layout.sizeHint().height()
for i in range(self.toggle_animation.animationCount()):
animation = self.toggle_animation.animationAt(i)
animation.setDuration(500)
animation.setStartValue(collapsed_height)
animation.setEndValue(collapsed_height + content_height)
content_animation = self.toggle_animation.animationAt(self.toggle_animation.animationCount() - 1)
content_animation.setDuration(500)
content_animation.setStartValue(0)
content_animation.setEndValue(content_height)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.setGeometry(500, 100, 500, 500)
window.show()
sys.exit(app.exec_())
The problem is that you are only adding two widgets to the full layout, and the layout will try to place them as better as possible (tipically at the center of the area that is available for each widget, based on its size hints).
You could either set the alignment of the widget for the layout (placing the buttons on top of their available space):
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
box = CollapsibleBox(name)
vlay.addWidget(box, alignment=Qt.AlignTop)
Or add a stretch to the bottom of the layout:
vlay = QVBoxLayout(_ui['content'])
for name in extraDict.keys():
# ...
vlay.addStretch(1)
which will position all buttons on top of the layout.
As a side note, I'd suggest you to avoid the dictionary logic for the ui, as it might become very confusing and prone to errors. If you really need to do that for some (I hope, very good) reason that's ok, but please avoid it when asking questions: it makes really hard to read your code, and people might end up just ignoring your question at all.

How do I return text of specific font size and color from QTextDocument?

I have a QTextEdit which holds some formatted text. How do I return all text that matches a specific font size and/or font color?
I have tried the QTextDocument.objectForFormat() method, as an argument I provided a QTextFormat object with the property FontPointSize, but that returns None.
import sys
from PyQt5.QtWidgets import QMainWindow, QTextEdit, QApplication
from PyQt5.QtGui import QColor
from PyQt5 import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.textEdit = QTextEdit()
self.setCentralWidget(self.textEdit)
self.textEdit.setTextColor(QColor('#00aaff'))
self.textEdit.setFontPointSize(14.0)
self.textEdit.insertPlainText('Title\n')
self.textEdit.setTextColor(QColor('#e0e0e0'))
self.textEdit.setFontPointSize(11.0)
self.textEdit.insertPlainText('content\n')
self.textEdit.setTextColor(QColor('#00aaff'))
self.textEdit.setFontPointSize(14.0)
self.textEdit.insertPlainText('Title2\n')
self.textEdit.setTextColor(QColor('#e0e0e0'))
self.textEdit.setFontPointSize(11.0)
self.textEdit.insertPlainText('content_title2')
self.printFontSizes()
self.show()
def printFontSizes(self):
doc = self.textEdit.document()
for i in range(doc.blockCount()):
print(doc.findBlockByNumber(i).text(),': ',
doc.findBlockByNumber(i).charFormat().fontPointSize())
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
Running the above code prints this:
Title : 0.0
content : 14.0
Title2 : 11.0
content_title2 : 14.0
Is there something I am doing wrong here?
Since in my particular example the above code was displaying the wrong font size, I instead solved the problem by iterating over each line in the QTextEdit using QTextCursor and checking each one's font size. Here's the relevant code:
#!/bin/python3
import sys
from PyQt5.QtWidgets import QMainWindow, QTextEdit, QApplication
from PyQt5.QtGui import QColor, QTextCursor
from PyQt5 import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.textEdit = QTextEdit()
self.setCentralWidget(self.textEdit)
self.textEdit.setTextColor(QColor('#00aaff'))
self.textEdit.setFontPointSize(14.0)
self.textEdit.insertPlainText('Title\n')
self.textEdit.setTextColor(QColor('#e0e0e0'))
self.textEdit.setFontPointSize(11.0)
self.textEdit.insertPlainText('content\n')
self.textEdit.setTextColor(QColor('#00aaff'))
self.textEdit.setFontPointSize(14.0)
self.textEdit.insertPlainText('Title2\n')
self.textEdit.setTextColor(QColor('#e0e0e0'))
self.textEdit.setFontPointSize(11.0)
self.textEdit.insertPlainText('content_title2\n')
self.textEdit.insertPlainText('content2_title2\n')
self.printFontSizes()
self.show()
def printFontSizes(self):
doc = self.textEdit.document()
self.textEdit.moveCursor(QTextCursor.Start)
for i in range(doc.blockCount()):
if self.textEdit.fontPointSize() == 14.0:
print(doc.findBlockByNumber(i).text())
self.textEdit.moveCursor(QTextCursor.NextBlock)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())

Categories

Resources