I am new to python and I am trying to rewrite a GUI with PyQt5. The GUI I made was writen with Tkinter. I am using frames in order to bring up different pages. With Tkinter I succeeded to get pages to the front with tkraise() function. But with PyQt5 it seems as if the function gets ignored.
The file is a test file where I try different things befor adding it to my main file.
In the Dothing function I added a print("yes") function to see if it enters the function and it does, but it's not using the raise_() function somehow.
Could anybody explain me what I did wrong or maybe send me a link adress for more information so I search myself. I have already looked on the website of QT and other forums but I couldn't find a answer.
My file looks like this:
import sys
from PyQt5.QtWidgets import (QWidget, QGridLayout, QPushButton, QApplication, QFrame, QLabel, QColorDialog)
from PyQt5.QtGui import (QColor)
Lijst = ["Ferri", "Yvonne", "Ineke"] # , "Sidneger", "Deniel", "Tobie", "Nicol"
class Test(QWidget):
def __init__(self, *args, **kwargs):
super().__init__()
self.List = [(255,0,0), (0,255,0), (0,0,255)]
# self.List = Lijst
# container = QFrame(self)
self.setGeometry(300,300,300,300)
self.Frames = {}
for F in (self.List):
selected_color = QColor(F[0],F[1],F[2])
self.frame = QFrame(self)
self.frame.setStyleSheet("QWidget { background-color: %s}" % selected_color.name())
self.frame.setGeometry(100, 0, 300, 300)
Framas = Pages(self.frame, self, selected_color)
self.Frames[F] = Framas
def DoThing(self):
self.Frames[(255, 0, 0)].raise_()
print("yes")
pass
def DoThing2(self):
self.Frames[(0, 255, 0)].raise_()
pass
def DoThing3(self):
self.Frames[(0, 0, 255)].raise_()
pass
class Pages(QFrame):
def __init__(self, parent, controller, selected_color, *args, **kwargs):
super().__init__()
self.controller = controller
self.frame = parent
self.button = QPushButton("Change", self.frame) # adding frame as parent
self.button.move(10, 10)
self.button.clicked.connect(self.controller.DoThing)
self.button2 = QPushButton("Change2", self.frame)
self.button2.move(10, 50)
self.button2.clicked.connect(self.controller.DoThing2)
self.button3 = QPushButton("Change3", self.frame)
self.button3.move(10, 90)
self.button3.clicked.connect(self.controller.DoThing3)
if __name__ == '__main__':
app = QApplication(sys.argv)
Test_window = Test()
Test_window.show()
sys.exit(app.exec_())
Having the source of the original code is difficult to know exactly what you require. So I'll try to help you with what I can.
First you must use only one QFrame, you for each iteration are creating 2: the QFrame that you set the color and the other the Page.
Secondly you are using raise_() on the Page since you add the page to the dictionary, but the Page does not have the color, but the other QFrame.
import sys
from PyQt5.QtWidgets import (QWidget, QPushButton, QApplication, QFrame)
from PyQt5.QtGui import (QColor)
class Test(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setGeometry(300,300,300,300)
colors = [(255,0,0), (0,255,0), (0,0,255)]
self.Frames = {}
for F in colors:
selected_color = QColor(*F)
frame = Pages(parent=self, controller=self)
frame.setStyleSheet("QWidget { background-color: %s}" % selected_color.name())
frame.setGeometry(100, 0, 300, 300)
self.Frames[F] = frame
def DoThing(self):
self.Frames[(255, 0, 0)].raise_()
def DoThing2(self):
self.Frames[(0, 255, 0)].raise_()
def DoThing3(self):
self.Frames[(0, 0, 255)].raise_()
class Pages(QFrame):
def __init__(self, parent, controller):
super().__init__(parent)
self.controller = controller
self.button = QPushButton("Change", self) # adding frame as parent
self.button.move(10, 10)
self.button.clicked.connect(self.controller.DoThing)
self.button2 = QPushButton("Change2", self)
self.button2.move(10, 50)
self.button2.clicked.connect(self.controller.DoThing2)
self.button3 = QPushButton("Change3", self)
self.button3.move(10, 90)
self.button3.clicked.connect(self.controller.DoThing3)
if __name__ == '__main__':
app = QApplication(sys.argv)
Test_window = Test()
Test_window.show()
sys.exit(app.exec_())
Related
I want Qt.TextWrapAnywhere for my QLabel in a Layout.
I followed This instruction.My code is also same to give a minimal 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.resize(100, 200)
self.label = QLabel()
self.label.setWordWrap(True)
self.label.setText("11111111111111111111\n2222222211111111")
self.slabel = SuperQLabel()
self.slabel.setMinimumWidth(10)
self.slabel.setText("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
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_())
I changed little bit that code self.slabel.setMinimumWidth(10) otherwise resizing Label according to width wont work.
It is perfectly wrapping the text according to width.But the Problem is when height is considered self.label = QLabel() Normal QLabel auto adjust height according to content with layout.
For example if i add one \n with text that means Qlabel must show 2 lines.
But with this new Custom Label e.g.self.slabel = SuperQLabel() wrapping is good as long as there is space for height in layout.
I think i have to use setminimumHeight() but dont know how to get proper height after custom wrapping.
As long as the label is shown in a scroll area (which will not create issues with the top level layout), a better solution is to use a QTextEdit subclass, with the following configuration:
readOnly must be True;
scroll bars are disabled;
the vertical size policy must be Preferred (and not Expanding);
both minimumSizeHint() and sizeHint() should use the internal QTextDocument to return a proper height, with a minimum default width;
any change in size or contents must trigger updateGeometry() so that the parent layout will know that the hint has changed and geometries could be computed again;
the hint must include possible decorations of the scroll area (which is a QFrame);
This allows avoiding the paintEvent() override, and provide a better and easier implementation of the size mechanism due to the features provided by QTextDocument, while minimizing the possibility of recursion to an acceptable level.
class WrapLabel(QtWidgets.QTextEdit):
def __init__(self, text=''):
super().__init__(text)
self.setStyleSheet('''
WrapLabel {
border: 1px outset palette(dark);
border-radius: 8px;
background: palette(light);
}
''')
self.setReadOnly(True)
self.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
QtWidgets.QSizePolicy.Maximum)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.textChanged.connect(self.updateGeometry)
def minimumSizeHint(self):
doc = self.document().clone()
doc.setTextWidth(self.viewport().width())
height = doc.size().height()
height += self.frameWidth() * 2
return QtCore.QSize(50, height)
def sizeHint(self):
return self.minimumSizeHint()
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateGeometry()
class ChatTest(QtWidgets.QScrollArea):
def __init__(self):
super().__init__()
self.messages = []
container = QtWidgets.QWidget()
self.setWidget(container)
self.setWidgetResizable(True)
layout = QtWidgets.QVBoxLayout(container)
layout.addStretch()
self.resize(480, 360)
for i in range(1, 11):
QtCore.QTimer.singleShot(1000 * i, lambda:
self.addMessage('1' * randrange(100, 250)))
def addMessage(self, text):
self.widget().layout().addWidget(WrapLabel(text))
QtCore.QTimer.singleShot(0, self.scrollToBottom)
def scrollToBottom(self):
QtWidgets.QApplication.processEvents()
self.verticalScrollBar().setValue(
self.verticalScrollBar().maximum())
Update: HTML and QTextDocument
When using setHtml() and setDocument(), the source could have pre formatted text that doesn't allow wrapping. To avoid that, it's necessary to iterate through all QTextBlocks of the document, get their QTextBlockFormat, check the nonBreakableLines() property and eventually set it to False and set the format back with a QTextCursor.
class WrapLabel(QtWidgets.QTextEdit):
def __init__(self, text=None):
super().__init__()
if isinstance(text, str):
if Qt.mightBeRichText(text):
self.setHtml(text)
else:
self.setPlainText(text)
elif isinstance(text, QtGui.QTextDocument):
self.setDocument(text)
# ...
def setHtml(self, html):
doc = QtGui.QTextDocument()
doc.setHtml(html)
self.setDocument(doc)
def setDocument(self, doc):
doc = doc.clone()
tb = doc.begin() # start a QTextBlock iterator
while tb.isValid():
fmt = tb.blockFormat()
if fmt.nonBreakableLines():
fmt.setNonBreakableLines(False)
# create a QTextCursor for the current text block,
# then set the updated format to override the wrap
tc = QtGui.QTextCursor(tb)
tc.setBlockFormat(fmt)
tb = tb.next()
super().setDocument(doc)
Be aware, though, that this could not be enough whenever objects with predefined or minimum width are used: images and tables. The result will be that if the object is larger than the available space, it will be cropped on its right (or left for RightToLeft text layouts).
After Some Research,I successfully fixed it.
There is a trick
This is Full Responsive With/Without Emoji😅
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter,QFontMetrics,QFont
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QStyleOption, QVBoxLayout, QWidget, QStyle
import math
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())
fm=QFontMetrics(self.font())
#To get unicode in Text if using Emoji(Optional)
string_unicode = self.text().encode("unicode_escape").decode()
##To remove emoji/unicode from text while calculating
string_encode = self.text().encode("ascii", "ignore")
string_decode = string_encode.decode()
#If Unicode/Emoji is Used
if string_unicode.count("\\U0001") > 0:
height=fm.boundingRect(self.rect(),Qt.TextWordWrap,string_decode).height()+1
# +1 is varrying according to Different font .SO set different value and test.
else:
height=fm.boundingRect(self.rect(),Qt.TextWordWrap,string_decode).height()
row=math.ceil(fm.horizontalAdvance(self.text())/self.width())
self.setMinimumHeight(row*height)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.resize(100, 200)
self.label = QLabel()
self.label.setWordWrap(True)
self.label.setStyleSheet("background:red;")
self.label.setText("11111111111111111111\n2222222211111111")
self.emoji_font = QFont("Segoe UI Emoji",15,0,False)
self.emoji_font.setBold(True)
self.slabel = SuperQLabel()
self.slabel.setMinimumWidth(10)
self.slabel.setStyleSheet("background:green;")
self.slabel.setFont(self.emoji_font)
########### Plain Text ######################
# self.slabel.setText("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
#################### Or Using Emoji ############
self.slabel.setText("111111111111111111😉111ABCDDWAEQQ111111111111😅1111111111😉111111wqewqgdfgdfhyhtyhy1111111😅111111111111😉1111111111111111111")
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_())
My question is simple, I am trying to open a child window within the main window. I took help of all of the answers so this question is not a duplicate. My child window comes for some time and then disappears automatically.
import random
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import QtWidgets, uic
class SubWindow(QWidget):
def __init__(self, parent = None):
super(SubWindow, self).__init__(parent)
label = QLabel("Sub Window", self)
label.setGeometry(0, 0, 20, 10)
self.show()
class pro(QWidget):
def __init__(self, parent=None):
super(pro, self).__init__(parent)
self.acceptDrops()
self.x = 200
self.y = 200
self.width = 800
self.length = 800
self.setGeometry(self.x, self.y, self.width, self.length)
self.setWindowTitle("PA")
label = QLabel(self)
pixmap = QPixmap('p_bg.png')
label.setPixmap(pixmap)
label.setGeometry(0, 0, 1100, 1000)
self.initgui()
def register_system(self):
opener = SubWindow()
opener.show()
def initgui(self):
self.btn_log = QPushButton(self)
self.btn_log.setGeometry(440, 250, 150, 30)
self.btn_log.setText(" Login ")
self.btn_log.adjustSize()
self.btn_sign = QPushButton(self)
self.btn_sign.setGeometry(440, 300, 150, 30)
self.btn_sign.setText(" Register ")
self.btn_sign.adjustSize()
self.btn_sign.clicked.connect(self.register_system)
self.welcome = QLabel("Arial font", self)
self.welcome.setText("Welcome")
self.welcome.setGeometry(410, 100, 200, 30)
self.welcome.setStyleSheet("background-color:transparent;")
self.welcome.setFont(QFont("Arial", 30))
self.show()
def window():
apk = QApplication(sys.argv)
win = pro()
win.show()
sys.exit(apk.exec_())
window()
I took help to make a child window from here:https://www.codersarts.com/post/multiple-windows-in-pyqt5-codersarts , I used type 2 resource in my case.
The reason probably is that the variable opener has scope within the register_system method only; it is getting deleted after the method exits, you can modify the code either to create the variable as an attribute to the class object using self and then show the widget like this:
def register_system(self):
self.opener = SubWindow()
self.opener.show()
Or, you can just create a variable opener at global scope, then use the global variable to assign and show the widget:
# imports
opener = None
class SubWindow(QWidget):
def __init__(self, parent = None):
#Rest of the code
class pro(QWidget):
def __init__(self, parent=None):
# Rest of the code
def register_system(self):
global opener
opener = SubWindow()
opener.show()
# Rest of the code
On a side note, you should always use layout for the widgets in PyQt application, you can take a look at Layout Management
I am writing a small program and ideally I want to have a fixed size of groups which can unfold in which I have further spoilers which represent items which i can open and close in order to add some entities to my system.
I have been looking for similar questions here and got to the following to work semiproperly:
I have added the -Buttons to remove those childs from the groups and a + to add childs to a group.
This seems to work fine as long as I am not removing or adding widgets.
My code looks like this:
spoilers.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Spoiler(QWidget):
def __init__(self, parent=None, title='', animationDuration=300, addRemoveOption='None'):
super(Spoiler, self).__init__(parent=parent)
self.animationDuration = animationDuration
self.toggleAnimation = QParallelAnimationGroup()
self.contentArea = QScrollArea()
self.headerLine = QFrame()
self.toggleButton = QToolButton()
self.mainLayout = QGridLayout()
self.childWidgets = []
self.toggleButton.setStyleSheet("QToolButton { border: none; }")
self.toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggleButton.setArrowType(Qt.RightArrow)
self.toggleButton.setText(str(title))
self.toggleButton.setCheckable(True)
self.toggleButton.setChecked(False)
self.addRemoveOperation = addRemoveOption
if addRemoveOption is not 'None':
if addRemoveOption is 'Add':
self.addRemoveButton = QPushButton('+')
else:
self.addRemoveButton = QPushButton('-')
self.addRemoveButton.clicked.connect(self.onAddRemoveButton)
self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
# start out collapsed
self.contentArea.setMaximumHeight(0)
self.contentArea.setMinimumHeight(0)
# let the entire widget grow and shrink with its content
self.toggleAnimation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
self.toggleAnimation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))
self.toggleAnimation.addAnimation(QPropertyAnimation(self.contentArea, b"maximumHeight"))
# don't waste space
self.mainLayout.setVerticalSpacing(0)
self.mainLayout.setContentsMargins(0, 0, 0, 0)
self.mainLayout.addWidget(self.toggleButton, 0, 0, 1, 1, Qt.AlignLeft)
if addRemoveOption is not 'None':
self.mainLayout.addWidget(self.addRemoveButton, 0, 2, 1, 1, Qt.AlignRight)
self.mainLayout.addWidget(self.contentArea, 1, 0, 1, 3)
self.setLayout(self.mainLayout)
def start_animation(checked):
arrow_type = Qt.DownArrow if checked else Qt.RightArrow
direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward
self.toggleButton.setArrowType(arrow_type)
self.toggleAnimation.setDirection(direction)
self.toggleAnimation.start()
self.toggleButton.clicked.connect(start_animation)
self.contentLayout = QVBoxLayout()
self.setContentLayout(self.contentLayout)
def setContentLayout(self, contentLayout):
self.contentArea.destroy()
self.contentArea.setLayout(contentLayout)
collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
contentHeight = contentLayout.sizeHint().height()
for i in range(self.toggleAnimation.animationCount()-1):
spoilerAnimation = self.toggleAnimation.animationAt(i)
spoilerAnimation.setDuration(self.animationDuration)
spoilerAnimation.setStartValue(collapsedHeight)
spoilerAnimation.setEndValue(collapsedHeight + contentHeight)
contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
contentAnimation.setDuration(self.animationDuration)
contentAnimation.setStartValue(0)
contentAnimation.setEndValue(contentHeight)
def addChild(self, child):
self.childWidgets += [child]
self.contentLayout.addWidget(child)
self.setContentLayout(self.contentLayout)
def removeChild(self, child):
self.childWidgets.remove(child)
#self.contentLayout.removeWidget(child)
child.destroy()
#self.setContentLayout(self.contentLayout)
def onAddRemoveButton(self):
self.addChild(LeafSpoiler(root=self))
class LeafSpoiler(Spoiler):
def __init__(self, parent=None, root=None, title=''):
if(root == None):
addRemoveOption = 'None'
else:
addRemoveOption = 'Sub'
super(LeafSpoiler, self).__init__(parent=parent, title=title, addRemoveOption=addRemoveOption)
self.root = root
def onAddRemoveButton(self):
self.root.removeChild(self)
gui.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from spoilers import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setGeometry(300, 300, 300, 220)
# self.setWindowIcon(QIcon('web.png'))
self.centralWidget = QFrame()
self.centralLayout = QVBoxLayout()
self.centralWidget.setLayout(self.centralLayout)
self.spoiler1 = Spoiler(addRemoveOption='Add', title='Group 1')
self.spoiler2 = Spoiler(addRemoveOption='Add', title='Group 2')
for i in range(3):
leaf = LeafSpoiler(root=self.spoiler1)
self.spoiler1.addChild(leaf)
leaf = LeafSpoiler(root=self.spoiler2)
self.spoiler2.addChild(leaf)
self.centralLayout.addWidget(self.spoiler1)
self.centralLayout.addWidget(self.spoiler2)
self.setCentralWidget(self.centralWidget)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
I am not quiet sure why this doesnt work. I assume that Spoiler.setContentLayout() is not supposed to be called more than once.
I would be very happy if someone could help me out on this one!
Greetings,
Finn
I am not too sure whether I understood your question correctly. I assume you are talking about pyqt crashing when trying to remove a Spoilerleaf? At least this is what's happening on my machine.
Your "removeChild" method seems to be a culprit here. Without knowing too much about the source of the crash, replacing this with a call to deleteLater() enables child deletion on my machine:
class LeafSpoiler(Spoiler):
# [...] same init as your's
def onAddRemoveButton(self):
self.deleteLater()
I am using a QStackedWidget which has its own enterEvent and leaveEvent. When I move my mouse to the QStackedWidget the enterEvent sets the current index to 1 and on the leaveEvent it sets the current index to 0 so that a different widget is shown on mouse enter and mouse leave in the area of QStackedWidget. It does what I want only if I quickly move my mouse in and out, if I place my mouse too long in the area I get RecursionError: maximum recursion depth exceeded.
Is this because the widgets are changing so fast that the internal stack can't keep up? My question is "How can I make sure this error doesn't occur? I want to display one widget as long as the mouse is over the QStackedWidget and when it is not I want to display the original widget."
The following is the code that I modified (Original Source used buttons to set the index and it is PyQt4)
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import *
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.pixmap_opacity = 1.0
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.repaint()
class StackedWidget(QStackedWidget):
def __init__(self, parent = None):
QStackedWidget.__init__(self, parent)
def setCurrentIndex(self, index):
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
super().setCurrentIndex(index)
def enterEvent(self,event):
self.setCurrentIndex(1)
def leaveEvent(self,event):
self.setCurrentIndex(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
cal=QCalendarWidget()
stack.addWidget(cal)
editor = QTextEdit()
editor.setPlainText("Hello world! "*100)
stack.addWidget(editor)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
window.show()
sys.exit(app.exec_())
The recursion occurs because when you start the FaderWidget it changes focus and enterEvent is called again which creates a new FaderWidget.
The solution is to verify that the old index is different from the new index to just create the FadeWidget:
import sys
from PyQt5.QtCore import QTimeLine
from PyQt5.QtGui import QPainter, QPixmap
from PyQt5.QtWidgets import (
QApplication,
QCalendarWidget,
QGridLayout,
QStackedWidget,
QTextEdit,
QWidget,
)
class FaderWidget(QWidget):
def __init__(self, old_widget, new_widget):
QWidget.__init__(self, new_widget)
self.pixmap_opacity = 1.0
self.old_pixmap = QPixmap(new_widget.size())
old_widget.render(self.old_pixmap)
self.timeline = QTimeLine()
self.timeline.valueChanged.connect(self.animate)
self.timeline.finished.connect(self.close)
self.timeline.setDuration(333)
self.timeline.start()
self.resize(new_widget.size())
self.show()
def paintEvent(self, event):
painter = QPainter(self)
painter.setOpacity(self.pixmap_opacity)
painter.drawPixmap(0, 0, self.old_pixmap)
def animate(self, value):
self.pixmap_opacity = 1.0 - value
self.update()
class StackedWidget(QStackedWidget):
def setCurrentIndex(self, index):
if self.currentIndex() != index:
self.fader_widget = FaderWidget(self.currentWidget(), self.widget(index))
super().setCurrentIndex(index)
def enterEvent(self, event):
self.setCurrentIndex(1)
def leaveEvent(self, event):
self.setCurrentIndex(0)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = QWidget()
stack = StackedWidget()
cal = QCalendarWidget()
stack.addWidget(cal)
editor = QTextEdit()
editor.setPlainText("Hello world! " * 100)
stack.addWidget(editor)
layout = QGridLayout(window)
layout.addWidget(stack, 0, 0, 1, 2)
window.show()
sys.exit(app.exec_())
I have a list which is generated based on user-input.
I am trying to display this list in a QMessageBox. But, I have no way of knowing the length of this list. The list could be long.
Thus, I need to add a scrollbar to the QMessageBox.
Interestingly, I looked everywhere, but I haven’t found any solutions for this.
Below is, what I hope to be a “Minimal, Complete and Verifiable Example”, of course without the user input; I just created a list as an example.
I appreciate any advice.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class W(QWidget):
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
self.btn = QPushButton('Show Message', self)
self.btn.setGeometry(10, 10, 100, 100)
self.btn.clicked.connect(self.buttonClicked)
self.lst = list(range(2000))
self.show()
def buttonClicked(self):
result = QMessageBox(self)
result.setText('%s' % self.lst)
result.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = W()
sys.exit(app.exec_())
You can not add a scrollbar directly since the widget in charge of displaying the text is a QLabel. The solution is to add a QScrollArea. The size may be inadequate so a stylesheet has to be used to set minimum values.
class ScrollMessageBox(QMessageBox):
def __init__(self, l, *args, **kwargs):
QMessageBox.__init__(self, *args, **kwargs)
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
self.content = QWidget()
scroll.setWidget(self.content)
lay = QVBoxLayout(self.content)
for item in l:
lay.addWidget(QLabel(item, self))
self.layout().addWidget(scroll, 0, 0, 1, self.layout().columnCount())
self.setStyleSheet("QScrollArea{min-width:300 px; min-height: 400px}")
class W(QWidget):
def __init__(self):
super().__init__()
self.btn = QPushButton('Show Message', self)
self.btn.setGeometry(10, 10, 100, 100)
self.btn.clicked.connect(self.buttonClicked)
self.lst = [str(i) for i in range(2000)]
self.show()
def buttonClicked(self):
result = ScrollMessageBox(self.lst, None)
result.exec_()
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = W()
sys.exit(app.exec_())
Output:
Here is another way to override the widgets behavior.
You can get references to the children of the widget by using 'children()'.
Then you can manipulate them like any other widget.
Here we add a QScrollArea and QLabel to the original widget's QGridLayout. We get the text from the original widget's label and copy it to our new label, finally we clear the text from the original label so it is not shown (because it is beside our new label).
Our new label is scrollable. We must set the minimum size of the scrollArea or it will be hard to read.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class ScrollMessageBox(QMessageBox):
def __init__(self, *args, **kwargs):
QMessageBox.__init__(self, *args, **kwargs)
chldn = self.children()
scrll = QScrollArea(self)
scrll.setWidgetResizable(True)
grd = self.findChild(QGridLayout)
lbl = QLabel(chldn[1].text(), self)
lbl.setWordWrap(True)
scrll.setWidget(lbl)
scrll.setMinimumSize (400,200)
grd.addWidget(scrll,0,1)
chldn[1].setText('')
self.exec_()
class W(QWidget):
def __init__(self):
super(W,self).__init__()
self.btn = QPushButton('Show Message', self)
self.btn.setGeometry(10, 10, 100, 100)
self.btn.clicked.connect(self.buttonClicked)
self.message = ("""We have encountered an error.
The following information may be useful in troubleshooting:
1
2
3
4
5
6
7
8
9
10
Here is the bottom.
""")
self.show()
def buttonClicked(self):
result = ScrollMessageBox(QMessageBox.Critical,"Error!",self.message)
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = W()
sys.exit(app.exec_())