I'm trying to write a script which will display a busy indication
while performing the task. And when the task is over, the progress bar
will fill to the end showing that 100% task has been completed.
I just want the progress bar to show a task is going on.But when I start
the task, busy indication stops.It seems to me that the indication and
the task can not continue together. Please help me. Here's mycode:
from PyQt4 import QtCore, QtGui
from time import sleep
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(344, 159)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.pb = QtGui.QProgressBar(self.centralwidget)
self.pb.setGeometry(QtCore.QRect(20, 20, 301, 31))
self.pb.setProperty("value", 0)
self.pb.setObjectName(_fromUtf8("pb"))
self.btn = QtGui.QPushButton(self.centralwidget)
self.btn.setGeometry(QtCore.QRect(20, 70, 98, 27))
self.btn.setObjectName(_fromUtf8("btn"))
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 344, 25))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QObject.connect(self.btn, QtCore.SIGNAL(_fromUtf8("clicked()")), self.action)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def action(self):
self.pb.setRange(0, 0)
sleep(3) # Here I want to run a command.For example: os.system('copy something')
self.pb.setRange(0, 100)
self.pb.setValue(100)
QtGui.qApp.processEvents()
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.btn.setText(_translate("MainWindow", "Start", None))
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
First, it's a bad idea to directly edit the code created with QtDesigner. You may have seen the line # WARNING! All changes made in this file will be lost! at the top of the document. For such a simple widget, you're better off with manual coding.
Secondly, take a closer look at what the action slot actually does.
def action(self):
self.pb.setRange(0, 0) # Un
sleep(3) # <-- Your slot blocks HERE
self.pb.setRange(0, 100)
self.pb.setValue(100)
QtGui.qApp.processEvents()
There is no reason for your progressBar to update its value while your slot is blocked in sleep. When action is called, the slot thread sleeps for 3 sec then sets the progress bar to a full 100.
You can't expect the progressBar to magically update itself while your task is running. If you have no idea how long it will take and you can't subdivise it in steps, you should consider using a pulsed ProgressBar instead (see example 1 below). If you can easily get the progression of your task (say copying n files), you should update the value of your progressBar accordingly.
Either way, you should use QThread to get a non-blocking behaviour, and signals to communicate between your thread(s) and your main application.
The main application starts the QThread implementing the long-running task.
The QThread notifies the task progression (if available) or completion to the main application
Example 1 - Pulse ProgressBar:
If minimum and maximum are both set to 0, the progress bar will show a busy indicator instead of a percentage of steps.
class MyCustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyCustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
# Create a progress bar and a button and add them to the main layout
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,1)
layout.addWidget(self.progressBar)
button = QtGui.QPushButton("Start", self)
layout.addWidget(button)
button.clicked.connect(self.onStart)
self.myLongTask = TaskThread()
self.myLongTask.taskFinished.connect(self.onFinished)
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def onFinished(self):
# Stop the pulsation
self.progressBar.setRange(0,1)
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
time.sleep(3)
self.taskFinished.emit()
Example 2 - Classic ProgressBar:
class MyCustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyCustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,100)
button = QtGui.QPushButton("Start", self)
layout.addWidget(self.progressBar)
layout.addWidget(button)
button.clicked.connect(self.onStart)
self.myLongTask = TaskThread()
self.myLongTask.notifyProgress.connect(self.onProgress)
def onStart(self):
self.myLongTask.start()
def onProgress(self, i):
self.progressBar.setValue(i)
class TaskThread(QtCore.QThread):
notifyProgress = QtCore.pyqtSignal(int)
def run(self):
for i in range(101):
self.notifyProgress.emit(i)
time.sleep(0.1)
A bit late, but I've written detailed documentation on this very issue since a lot of people seem to face this problem.
Introduction to Progress Bars
Finally I got what I wanted, though little bit editing needed . I just added this line
to onFinished():
self.progressBar.setValue(1)
to confirm 100% task completion.
Here's the code:
from PyQt4 import QtCore, QtGui
from time import sleep
import sys, os
class MyCustomWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MyCustomWidget, self).__init__(parent)
layout = QtGui.QVBoxLayout(self)
# Create a progress bar and a button and add them to the main layout
self.progressBar = QtGui.QProgressBar(self)
self.progressBar.setRange(0,1)
layout.addWidget(self.progressBar)
button = QtGui.QPushButton("Start", self)
layout.addWidget(button)
button.clicked.connect(self.onStart)
self.myLongTask = TaskThread()
self.myLongTask.taskFinished.connect(self.onFinished)
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def onFinished(self):
# Stop the pulsation
self.progressBar.setRange(0,1)
self.progressBar.setValue(1)
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
os.system('sudo apt-get install leafpad')
self.taskFinished.emit()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyCustomWidget()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
apply this:
progressbar.setMinimum(0)
progressbar.setMaximum(0)
progressbar.setValue(0)
This setting will have a busy appearance, You do not need to add it to any function, it can be in the class constructor if you want
Related
I’ve got some issues with threading in my program. I have a main program and also a GUI-Class, created with PyQt. I’ve created a small example of my problems.
When I start the python script and press start, the progress bar is working fine, but I get the following warnings:
QBackingStore::endPaint() called with active painter on backingstore paint device
QObject::setParent: Cannot set parent, new parent is in a different thread
These alarms occur due to different threads. But to be honest, I have no idea how to handle the objects, like this progress bar of the gui-class.
I’ve tried many ways but haven’t found a good solution for this.
By pressing start, it starts, by pressing stop the progress bar stops. But here is the next issue, mostly my kernel dies when I press stop. I guess it’s connected to the same problem with the threading?!
The reason for the threading is so that the script can still be interacted with while it is running.
Attached are my two python files: my program and also the gui.
MAINPROGRAM:
#MAINPROGRAM
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import threading
import time
class exampleprogram(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(exampleprogram, self).__init__(parent)
self.setupUi(self)
self.pushButton_Start.clicked.connect(self.start)
self.pushButton_Stop.clicked.connect(self.stop)
self.running = False
def start(self):
if(self.running == False):
print("Start")
self.thread = threading.Thread(target=self.run, args=())
self.thread.start()
def stop(self):
print("Stop")
self.running = False
def run(self):
self.running = True
x = 0
thread = threading.currentThread()
while getattr(thread, "do_run", True):
self.thread.do_run = self.running
if(x == 100):
thread.do_run = False
self.progressBar.setValue(x)
time.sleep(0.1)
x = x+1
self.stop()
def main():
app = QApplication(sys.argv)
form = exampleprogram()
form.show()
app.exec_()
if __name__ == '__main__':
main()
GUI:
#GUI
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(573, 92)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 195, 30))
self.layoutWidget.setObjectName("layoutWidget")
self.formLayout_3 = QtWidgets.QFormLayout(self.layoutWidget)
self.formLayout_3.setContentsMargins(0, 0, 0, 0)
self.formLayout_3.setObjectName("formLayout_3")
self.pushButton_Start = QtWidgets.QPushButton(self.layoutWidget)
self.pushButton_Start.setObjectName("pushButton_Start")
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.pushButton_Start)
self.pushButton_Stop = QtWidgets.QPushButton(self.layoutWidget)
self.pushButton_Stop.setObjectName("pushButton_Stop")
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.pushButton_Stop)
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(230, 20, 311, 23))
self.progressBar.setProperty("value", 0)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Test Gui"))
self.pushButton_Start.setText(_translate("MainWindow", "Start"))
self.pushButton_Stop.setText(_translate("MainWindow", "Stop"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Does anyone have an idea, how to fix it?
EDIT: After stopping the run-function it should be possible to run it again by pressing the start button. Therefore it doesn't matter if it was stopped by the counter x or by pressing the stop button. It's just important not too start multiple times the function.
Many thanks in advance!
Andrew
Whilst it's possible to use Python threads with PyQt, it's generally better to use QThread with a separate worker object, since it more easily allows thread-safe communication between the worker thread and main thread via signals and slots. One of the main problems with your example is precisely that you are attempting to perform GUI-related operations outside of the main thread, which is not supported by Qt (hence the warning messages).
If you want to start, stop and pause the worker from the GUI, it will require some careful handling to ensure the worker and thread shut down cleanly if the user tries to close the window while they are still running. In the example below, I have used a simple abort mechanism in the closeEvent; if you want more sophisticated handling, you could, for example, ignore() the close-event while the worker is still running. Pressing Start will start/contine the worker, and pressing Stop will pause it. Once the worker has finished, pressing Start will completely re-start it (i.e. it will go back to zero):
class Worker(QtCore.QObject):
progress = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self._paused = True
self._count = -1
def start(self):
self._paused = False
if self._count < 0:
print('Start')
self._run()
else:
print('Continue')
def stop(self, *, abort=False):
self._paused = True
if abort:
print('Abort')
self._count = -1
else:
print('Pause')
def _run(self):
self._count = 0
self._paused = False
while 0 <= self._count <= 100:
if not self._paused:
QtCore.QThread.msleep(100)
self._count += 1
self.progress.emit(self._count)
self.stop(abort=True)
self.finished.emit()
print('Finished')
class exampleprogram(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(exampleprogram, self).__init__(parent)
self.setupUi(self)
self.thread = QtCore.QThread(self)
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.start)
self.worker.finished.connect(self.thread.quit)
self.worker.progress.connect(self.progressBar.setValue)
self.pushButton_Start.clicked.connect(self.start)
self.pushButton_Stop.clicked.connect(self.stop)
def start(self):
if self.thread.isRunning():
self.worker.start()
else:
self.thread.start()
def stop(self):
if self.thread.isRunning():
self.worker.stop()
def closeEvent(self, event):
self.worker.stop(abort=True)
self.thread.quit()
self.thread.wait()
I have a GUI made in Designer (pyqt5). A function in my main class needs to work on a separate thread. I also catch the stdout on a QtextEdit LIVE during operations. Everything so far works.
Right now I'm trying to implement a ProgressBar onto my main GUI form. The bar needs to show live progression just like it does on the textEdit.
The example code below works on Linux without any warnings. But on Windows I get the error:
QObject::setParent: Cannot set parent, new parent is in a different thread
I know that this is due to me having a ui element modification within my threaded function. I did my research but all the answers point to using QThreads (just when I started to understand basic threading!). I would prefer a way to update my GUI without having to change the current threading system below.
Here is the example code:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject):
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_run.clicked.connect(self.start_task)
self.finished.connect(self.end_task)
def start_task(self):
self.thread = threading.Thread(target=self.run_test)
self.thread.start()
self.ui.pushButton_run.setEnabled(False)
def end_task(self):
self.ui.pushButton_run.setEnabled(True)
def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def run_test(self):
for i in range(100):
per = i + 1
self.ui.progressBar.setValue(per)
print("%%%s" % per)
time.sleep(0.15) # simulating expensive task
print("Task Completed!")
time.sleep(1.5)
self.ui.progressBar.reset()
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
the ui:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'form.ui'
#
# Created: Mon Apr 30 13:43:19 2018
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(Form)
self.centralwidget.setObjectName("centralwidget")
self.pushButton_run = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_run.setGeometry(QtCore.QRect(40, 20, 311, 191))
self.pushButton_run.setObjectName("pushButton_run")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(40, 230, 721, 241))
self.textEdit.setObjectName("textEdit")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(40, 490, 721, 23))
self.progressBar.setObjectName("progressBar")
Form.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(Form)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 25))
self.menubar.setObjectName("menubar")
Form.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(Form)
self.statusbar.setObjectName("statusbar")
Form.setStatusBar(self.statusbar)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "MainWindow"))
self.pushButton_run.setText(_translate("Form", "RUN"))
Somehow I need to -instantly- inform the gui thread (from my running thread) that the progress bar value is changing (a process that could take up minutes to complete).
Define a custom signal that sends updates to the progress-bar:
class Form(QMainWindow):
finished = pyqtSignal()
updateProgress = pyqtSignal(int)
def __init__(self, parent=None):
super(Form, self).__init__(parent)
...
self.updateProgress.connect(self.ui.progressBar.setValue)
def run_test(self):
for i in range(100):
per = i + 1
self.updateProgress.emit(per)
...
I have two buttons (wgtbtnA & wgtbtnB) placed on two different pages (page1 and page2, respectively) inside a parent object (objectName: stackedWidget). My dilemma is this: when I run the code, the arrows don't display in PyQt. Why? How do I alternate from page1 to page2 and vice-versa?
Here is an image of runtime, which conveys what I am asking for:
Qt Designer:
I'd like to keep those small back arrows.
Below is my code:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'stackedWidget.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
import sys
import os
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(512, 304)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.stackedWidget = QtGui.QStackedWidget(self.centralwidget)
self.stackedWidget.setGeometry(QtCore.QRect(150, 60, 161, 121))
self.stackedWidget.setObjectName(_fromUtf8("stackedWidget"))
self.page = QtGui.QWidget()
self.page.setObjectName(_fromUtf8("page"))
self.wgtMainWindow = QtGui.QPushButton(self.page)
self.wgtMainWindow.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtMainWindow.setObjectName(_fromUtf8("wgtMainWindow"))
self.stackedWidget.addWidget(self.page)
self.page_2 = QtGui.QWidget()
self.page_2.setObjectName(_fromUtf8("page_2"))
self.wgtbtnB = QtGui.QPushButton(self.page_2)
self.wgtbtnB.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtbtnB.setObjectName(_fromUtf8("wgtbtnB"))
self.stackedWidget.addWidget(self.page_2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 512, 21))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.stackedWidget.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.wgtMainWindow.setText(_translate("MainWindow", "Widget A", None))
self.wgtbtnB.setText(_translate("MainWindow", "Widget B", None))
class ControlMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ControlMainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mySW = ControlMainWindow()
mySW.show()
sys.exit(app.exec_())
You could use the buttons to change the page: {your QPushButton}.clicked.connect(lambda: {your QStackedWidget}.setCurrentIndex({another page}))
By Example:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'stackedWidget.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
import sys
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(512, 304)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.stackedWidget = QtGui.QStackedWidget(self.centralwidget)
self.stackedWidget.setGeometry(QtCore.QRect(150, 60, 161, 121))
self.stackedWidget.setObjectName(_fromUtf8("stackedWidget"))
self.page = QtGui.QWidget()
self.page.setObjectName(_fromUtf8("page"))
self.wgtMainWindow = QtGui.QPushButton(self.page)
self.wgtMainWindow.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtMainWindow.setObjectName(_fromUtf8("wgtMainWindow"))
self.stackedWidget.addWidget(self.page)
self.page_2 = QtGui.QWidget()
self.page_2.setObjectName(_fromUtf8("page_2"))
self.wgtbtnB = QtGui.QPushButton(self.page_2)
self.wgtbtnB.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtbtnB.setObjectName(_fromUtf8("wgtbtnB"))
self.stackedWidget.addWidget(self.page_2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 512, 21))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.stackedWidget.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.wgtMainWindow.setText(_translate("MainWindow", "Widget A", None))
self.wgtbtnB.setText(_translate("MainWindow", "Widget B", None))
class ControlMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ControlMainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.wgtbtnB.clicked.connect(lambda : self.ui.stackedWidget.setCurrentIndex(0))
self.ui.wgtMainWindow.clicked.connect(lambda : self.ui.stackedWidget.setCurrentIndex(1))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mySW = ControlMainWindow()
mySW.show()
sys.exit(app.exec_())
Start app:
after clicked button:
after clicked another button:
the two arrows you see in the designer ( in your message below) don't carry over to your application, its a function in the designer so you can switch between them easily
To implement your arrows from one screen to the next you just need to propagate those arrows making sure that they appear in each view -- now this can easily be done via code cannot say how difficult it might be in the designer. Here is a example that does something like what you might be wanting.
from sys import exit as sysExit
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Win1Disply(QFrame):
def __init__(self, parent):
QFrame.__init__(self)
self.setFrameShape(QFrame.StyledPanel)
self.setLineWidth(0.2)
# -------
self.Cntnr = QVBoxLayout()
self.Cntnr.addWidget(QTextEdit('This is Window 1 with whatever contents you want'))
self.Win1Btn = QPushButton('>>')
self.Win1Btn.clicked.connect(parent.RightArrow)
self.Cntnr.addWidget(self.Win1Btn)
self.Cntnr.addStretch(1)
# -------
self.setLayout(self.Cntnr)
class Win2Disply(QFrame):
def __init__(self, parent):
QFrame.__init__(self)
self.setFrameShape(QFrame.StyledPanel)
self.setLineWidth(0.2)
# -------
self.Cntnr = QVBoxLayout()
self.Cntnr.addWidget(QTextEdit('This is Window 2 with whatever contents you want'))
self.Win1Btn = QPushButton('>>')
self.Win1Btn.clicked.connect(parent.RightArrow)
self.Cntnr.addWidget(self.Win1Btn)
self.Cntnr.addStretch(1)
# -------
self.setLayout(self.Cntnr)
class OptionButtons(QToolButton):
# Class OptionButtons ("Text", Connector) inherits from QToolButton
def __init__(self, Text, Connector):
QToolButton.__init__(self)
self.setText(Text)
self.setStyleSheet("font: bold;color: blue;height: 55px;width: 55px;")
self.setIconSize(QSize(32,32))
self.clicked.connect(Connector)
############################## Settings Class ##############################
class OptionSettings(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent)
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
self.btnWin1 = OptionButtons('Win One', self.ShowWindow1)
self.btnWin2 = OptionButtons('Win Two', self.ShowWindow2)
# Vertical Box for Buttons *************************************
self.UpLeft = QVBoxLayout()
self.UpLeft.addWidget(self.btnWin1)
self.UpLeft.addWidget(self.btnWin2)
self.UpLeft.addStretch(1)
# Display Area on Right
# Widget Flip Display ******************************************
self.UpRite = QHBoxLayout()
self.Contents = QStackedWidget()
self.Contents.addWidget(QTextEdit('Nothing Selected'))
self.Contents.addWidget(Win1Disply(self))
self.Contents.addWidget(Win2Disply(self))
self.Contents.addWidget(QTextEdit('Settings Saved'))
self.Contents.setCurrentIndex(0)
self.UpRite.addWidget(self.Contents)
# Button and Display Area on Top
self.Upper = QHBoxLayout()
self.Upper.addLayout(self.UpLeft)
self.Upper.addLayout(self.UpRite)
# Save and Cancel Area on Bottom
self.btnSave = QPushButton("Save")
self.btnSave.clicked.connect(self.SaveSettings)
self.btnCncl = QPushButton("Cancel")
self.btnCncl.clicked.connect(self.close)
self.Lower = QHBoxLayout()
self.Lower.addStretch(1)
self.Lower.addWidget(self.btnSave)
self.Lower.addWidget(self.btnCncl)
# Entire Options Window Layout
self.OuterBox = QVBoxLayout()
self.OuterBox.addLayout(self.Upper)
self.OuterBox.addLayout(self.Lower)
self.setLayout(self.OuterBox)
self.setWindowTitle('Settings')
#Geometry(Left, Top, Width, Hight)
self.setGeometry(250, 250, 550, 450)
self.setModal(True)
self.exec()
def ShowWindow1(self):
self.Contents.setCurrentIndex(1)
def ShowWindow2(self):
self.Contents.setCurrentIndex(2)
def SaveSettings(self):
self.Contents.setCurrentIndex(3)
def RightArrow(self):
if self.Contents.currentIndex() == 1:
self.Contents.setCurrentIndex(2)
else:
self.Contents.setCurrentIndex(1)
class CenterPanel(QWidget):
def __init__(self, MainWin):
QWidget.__init__(self)
CntrPane = QTextEdit('Center Panel is Placed Here')
hbox = QHBoxLayout(self)
hbox.addWidget(CntrPane)
self.setLayout(hbox)
class MenuToolBar(QDockWidget):
def __init__(self, MainWin):
QDockWidget.__init__(self)
self.MainWin = MainWin
self.MainMenu = MainWin.menuBar()
self.WndowMenu = self.MainMenu.addMenu('Windows')
self.OptnAct = QAction('Options', self)
self.OptnAct.setStatusTip('Open the Options Window')
self.OptnAct.triggered.connect(MainWin.ShowOptions)
self.WndowMenu.addAction(self.OptnAct)
self.InitToolBar(MainWin)
def InitToolBar(self, MainWin):
self.mainToolBar = MainWin.addToolBar("Quick Access")
self.mainToolBar.addAction(self.OptnAct)
class UI_MainWindow(QMainWindow):
def __init__(self):
super(UI_MainWindow, self).__init__()
self.setWindowTitle('Main Window')
# Left, Top, Width, Height
self.setGeometry(200, 200, 550, 550)
self.CenterPane = CenterPanel(self)
self.setCentralWidget(self.CenterPane)
self.MenuToolBar = MenuToolBar(self)
def ShowOptions(self):
self.Options = OptionSettings(self)
if __name__ == '__main__':
MainApp = QApplication([])
MainGui = UI_MainWindow()
MainGui.show()
sysExit(MainApp.exec_())
If you really need arrows in your stacked widget, another solution is to implement your own "Promoted Widget": it allows you to create your design with custom widgets, that might extend the basic widgets provided by Qt. You won't be able to interact with your own implementation in Designer, but you'll get the result once you run your program.
This is the procedure: create your own subclass of the widget you want to extend, define your custom methods or override existing ones (remember that some private methods require a specific return value type, check the documentation).
It's usually better to save the subclass(es) you created in a separate files.
Then, in Designer add the widget you need (in this case, StackedWidget), right click on it and select "Promote to..."; in the dialog that will be shown, type the subclass name you created in the "Promoted class name" field (in the example below, it will be "StackedWidgetWithArrowButtons") and the file that contains it in the "header file" field: it will be treated as a python import, so do not add the trailing ".py" and remember that if you saved it in a subdirectory you'll need the full "module" path, for example "mysubclasses.customstackwidget", if the file is "customstackwidget" in the "mysubclasses" directory.
Save the ui, compile it and run the program.
class StackedWidgetWithArrowButtons(QtWidgets.QStackedWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QStackedWidget.__init__(self, *args, **kwargs)
self.backwardButton = QtWidgets.QToolButton(self)
self.backwardButton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_ArrowLeft))
self.backwardButton.setMaximumSize(24, 24)
self.backwardButton.setFocusPolicy(QtCore.Qt.NoFocus)
self.forwardButton = QtWidgets.QToolButton(self)
self.forwardButton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_ArrowRight))
self.forwardButton.setMaximumSize(24, 24)
self.forwardButton.setFocusPolicy(QtCore.Qt.NoFocus)
self.currentChanged.connect(self.checkSwitchButtons)
def checkSwitchButtons(self):
self.forwardButton.setEnabled(self.currentIndex() < self.count() - 1)
self.backwardButton.setEnabled(self.currentIndex() > 0)
def addWidget(self, widget):
# this is a private method of QStackedWidget that is called when
# the ui is being built by the program, we just implement it
# to ensure that the buttons are correctly enabled;
# the index *has* to be returned
index = QtWidgets.QStackedWidget.addWidget(self, widget)
self.checkSwitchButtons()
return index
def removeWidget(self, widget):
# not necessary, but in case you want to remove widgets in the
# future, it will check buttons again
index = QtWidgets.QStackedWidget.removeWidget(self, widget)
self.checkSwitchButtons()
return index
def mousePressEvent(self, event):
# due to the way QStackedWidget is implemented, children widgets
# that are not in its layout might not receive mouse events,
# but we just need to track clicks so this is enough
if event.button() == QtCore.Qt.LeftButton:
if event.pos() in self.backwardButton.geometry():
self.setCurrentIndex(self.currentIndex() - 1)
elif event.pos() in self.forwardButton.geometry():
self.setCurrentIndex(self.currentIndex() + 1)
def resizeEvent(self, event):
# the base class resizeEvent *has* to be called, otherwise
# you could encounter problems with children widgets
QtWidgets.QStackedWidget.resizeEvent(self, event)
# now ensure that the buttons are always placed on the top
# right corner; this positioning is completely manual and you
# have to take button sizes in consideration to avoid
# overlapping buttons; obviously you can place them wherever
# you want.
self.forwardButton.move(self.rect().right() - self.forwardButton.width(), 0)
self.backwardButton.move(self.forwardButton.x() - self.backwardButton.width(), 0)
If you don't want buttons (or you don't like the way they appear) you could implement your own paintEvent. In this case I created small triangles using QPolygons.
class StackedWidgetWithTriangles(QtWidgets.QStackedWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QStackedWidget.__init__(self, *args, **kwargs)
self.backwardRect = QtCore.QRect(0, 0, 16, 16)
self.forwardRect = QtCore.QRect(0, 0, 16, 16)
self.forwardArrow = QtGui.QPolygon([QtCore.QPoint(-6, -6), QtCore.QPoint(6, 0), QtCore.QPoint(-6, 6)])
self.backwardArrow = QtGui.QPolygon([QtCore.QPoint(6, -6), QtCore.QPoint(-6, 0), QtCore.QPoint(6, 6)])
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
if event.pos() in self.backwardRect:
self.setCurrentIndex(self.currentIndex() - 1)
elif event.pos() in self.forwardRect:
self.setCurrentIndex(self.currentIndex() + 1)
def resizeEvent(self, event):
QtWidgets.QStackedWidget.resizeEvent(self, event)
self.forwardRect.moveLeft(self.rect().right() - self.forwardRect.width())
self.backwardRect.moveLeft(self.forwardRect.x() - self.forwardRect.width())
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# set colors according to the possibility of going back or forward,
# showing a "disabled" arrow whenever it's not possible
if self.currentIndex() > 0:
qp.setPen(QtCore.Qt.darkGray)
qp.setBrush(QtCore.Qt.black)
else:
qp.setPen(QtCore.Qt.lightGray)
qp.setBrush(QtCore.Qt.transparent)
qp.drawPolygon(self.backwardArrow.translated(self.backwardRect.center()))
if self.currentIndex() < self.count() - 1:
qp.setPen(QtCore.Qt.darkGray)
qp.setBrush(QtCore.Qt.black)
else:
qp.setPen(QtCore.Qt.lightGray)
qp.setBrush(QtCore.Qt.transparent)
qp.drawPolygon(self.forwardArrow.translated(self.forwardRect.center()))
you have no logic in your ControlMainWindow class to switch between your widgets. (also i don't see any arrow widgets for switching) you'll need to add a listener to your QTbuttons in your main class like below to execute your logic:
yourQTbutton.itemClicked.connect(self.functioWithUIchangingLogic)
I have a QMainWindow created in QT Designer with a GraphicsView object that has been promoted to a pyQTGraph PlotWidget. I want to use a QTimer event to get live serial data (Y) and plot it with (X) being 1 second increments set by the QTimer event. However, the problem I'm having is that when using QT's QMainWindow as the main form I get the error "QObject::startTimer: QTimer can only be used with threads started with QThread", but if I use QT's QWidget as the main form everything works fine with no errors.
How can I use QTimer with QT's QMainWindow
QT4 and QT Designer
Python 3
PyQTGraph
Here is the Form Code:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'MainPlotWindow.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(735, 374)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.plot = PlotWidget(self.centralwidget)
self.plot.setGeometry(QtCore.QRect(30, 40, 681, 261))
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.plot.sizePolicy().hasHeightForWidth())
self.plot.setSizePolicy(sizePolicy)
self.plot.setFrameShape(QtGui.QFrame.WinPanel)
self.plot.setFrameShadow(QtGui.QFrame.Sunken)
self.plot.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.plot.setObjectName(_fromUtf8("plot"))
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 735, 21))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
from pyqtgraph import PlotWidget
Here is the simple test program that reproduces the error:
from PyQt4.QtCore import QThread, SIGNAL, QSettings
import sys
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from MainPlotWindow import *
'''=====================================================================
M A I N G U I
====================================================================='''
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def PlotUpdate():
print("Hello")
timer = QtCore.QTimer()
timer.timeout.connect(PlotUpdate)
timer.start(1000) # 1 Second Refesh Rate
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
Any help would be very much appreciated!
I recommend that the QTimer this inside the widget, as I show in the following example:
class MyForm(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.PlotUpdate)
timer.start(100) # 1 Second Refesh Rate
self.curve = self.ui.plot.plot()
self.counter = 0
def PlotUpdate(self):
x = self.counter + np.arange(0, 10)
y = 10*np.sin(np.pi*x/10)
self.curve.setData(x, y)
self.counter += 1
Output:
I have two buttons (wgtbtnA & wgtbtnB) placed on two different pages (page1 and page2, respectively) inside a parent object (objectName: stackedWidget). My dilemma is this: when I run the code, the arrows don't display in PyQt. Why? How do I alternate from page1 to page2 and vice-versa?
Here is an image of runtime, which conveys what I am asking for:
Qt Designer:
I'd like to keep those small back arrows.
Below is my code:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'stackedWidget.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
import sys
import os
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(512, 304)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.stackedWidget = QtGui.QStackedWidget(self.centralwidget)
self.stackedWidget.setGeometry(QtCore.QRect(150, 60, 161, 121))
self.stackedWidget.setObjectName(_fromUtf8("stackedWidget"))
self.page = QtGui.QWidget()
self.page.setObjectName(_fromUtf8("page"))
self.wgtMainWindow = QtGui.QPushButton(self.page)
self.wgtMainWindow.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtMainWindow.setObjectName(_fromUtf8("wgtMainWindow"))
self.stackedWidget.addWidget(self.page)
self.page_2 = QtGui.QWidget()
self.page_2.setObjectName(_fromUtf8("page_2"))
self.wgtbtnB = QtGui.QPushButton(self.page_2)
self.wgtbtnB.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtbtnB.setObjectName(_fromUtf8("wgtbtnB"))
self.stackedWidget.addWidget(self.page_2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 512, 21))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.stackedWidget.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.wgtMainWindow.setText(_translate("MainWindow", "Widget A", None))
self.wgtbtnB.setText(_translate("MainWindow", "Widget B", None))
class ControlMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ControlMainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mySW = ControlMainWindow()
mySW.show()
sys.exit(app.exec_())
You could use the buttons to change the page: {your QPushButton}.clicked.connect(lambda: {your QStackedWidget}.setCurrentIndex({another page}))
By Example:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'stackedWidget.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
import sys
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(512, 304)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.stackedWidget = QtGui.QStackedWidget(self.centralwidget)
self.stackedWidget.setGeometry(QtCore.QRect(150, 60, 161, 121))
self.stackedWidget.setObjectName(_fromUtf8("stackedWidget"))
self.page = QtGui.QWidget()
self.page.setObjectName(_fromUtf8("page"))
self.wgtMainWindow = QtGui.QPushButton(self.page)
self.wgtMainWindow.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtMainWindow.setObjectName(_fromUtf8("wgtMainWindow"))
self.stackedWidget.addWidget(self.page)
self.page_2 = QtGui.QWidget()
self.page_2.setObjectName(_fromUtf8("page_2"))
self.wgtbtnB = QtGui.QPushButton(self.page_2)
self.wgtbtnB.setGeometry(QtCore.QRect(50, 50, 75, 23))
self.wgtbtnB.setObjectName(_fromUtf8("wgtbtnB"))
self.stackedWidget.addWidget(self.page_2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 512, 21))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.stackedWidget.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.wgtMainWindow.setText(_translate("MainWindow", "Widget A", None))
self.wgtbtnB.setText(_translate("MainWindow", "Widget B", None))
class ControlMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ControlMainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.wgtbtnB.clicked.connect(lambda : self.ui.stackedWidget.setCurrentIndex(0))
self.ui.wgtMainWindow.clicked.connect(lambda : self.ui.stackedWidget.setCurrentIndex(1))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
mySW = ControlMainWindow()
mySW.show()
sys.exit(app.exec_())
Start app:
after clicked button:
after clicked another button:
the two arrows you see in the designer ( in your message below) don't carry over to your application, its a function in the designer so you can switch between them easily
To implement your arrows from one screen to the next you just need to propagate those arrows making sure that they appear in each view -- now this can easily be done via code cannot say how difficult it might be in the designer. Here is a example that does something like what you might be wanting.
from sys import exit as sysExit
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Win1Disply(QFrame):
def __init__(self, parent):
QFrame.__init__(self)
self.setFrameShape(QFrame.StyledPanel)
self.setLineWidth(0.2)
# -------
self.Cntnr = QVBoxLayout()
self.Cntnr.addWidget(QTextEdit('This is Window 1 with whatever contents you want'))
self.Win1Btn = QPushButton('>>')
self.Win1Btn.clicked.connect(parent.RightArrow)
self.Cntnr.addWidget(self.Win1Btn)
self.Cntnr.addStretch(1)
# -------
self.setLayout(self.Cntnr)
class Win2Disply(QFrame):
def __init__(self, parent):
QFrame.__init__(self)
self.setFrameShape(QFrame.StyledPanel)
self.setLineWidth(0.2)
# -------
self.Cntnr = QVBoxLayout()
self.Cntnr.addWidget(QTextEdit('This is Window 2 with whatever contents you want'))
self.Win1Btn = QPushButton('>>')
self.Win1Btn.clicked.connect(parent.RightArrow)
self.Cntnr.addWidget(self.Win1Btn)
self.Cntnr.addStretch(1)
# -------
self.setLayout(self.Cntnr)
class OptionButtons(QToolButton):
# Class OptionButtons ("Text", Connector) inherits from QToolButton
def __init__(self, Text, Connector):
QToolButton.__init__(self)
self.setText(Text)
self.setStyleSheet("font: bold;color: blue;height: 55px;width: 55px;")
self.setIconSize(QSize(32,32))
self.clicked.connect(Connector)
############################## Settings Class ##############################
class OptionSettings(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent)
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
self.btnWin1 = OptionButtons('Win One', self.ShowWindow1)
self.btnWin2 = OptionButtons('Win Two', self.ShowWindow2)
# Vertical Box for Buttons *************************************
self.UpLeft = QVBoxLayout()
self.UpLeft.addWidget(self.btnWin1)
self.UpLeft.addWidget(self.btnWin2)
self.UpLeft.addStretch(1)
# Display Area on Right
# Widget Flip Display ******************************************
self.UpRite = QHBoxLayout()
self.Contents = QStackedWidget()
self.Contents.addWidget(QTextEdit('Nothing Selected'))
self.Contents.addWidget(Win1Disply(self))
self.Contents.addWidget(Win2Disply(self))
self.Contents.addWidget(QTextEdit('Settings Saved'))
self.Contents.setCurrentIndex(0)
self.UpRite.addWidget(self.Contents)
# Button and Display Area on Top
self.Upper = QHBoxLayout()
self.Upper.addLayout(self.UpLeft)
self.Upper.addLayout(self.UpRite)
# Save and Cancel Area on Bottom
self.btnSave = QPushButton("Save")
self.btnSave.clicked.connect(self.SaveSettings)
self.btnCncl = QPushButton("Cancel")
self.btnCncl.clicked.connect(self.close)
self.Lower = QHBoxLayout()
self.Lower.addStretch(1)
self.Lower.addWidget(self.btnSave)
self.Lower.addWidget(self.btnCncl)
# Entire Options Window Layout
self.OuterBox = QVBoxLayout()
self.OuterBox.addLayout(self.Upper)
self.OuterBox.addLayout(self.Lower)
self.setLayout(self.OuterBox)
self.setWindowTitle('Settings')
#Geometry(Left, Top, Width, Hight)
self.setGeometry(250, 250, 550, 450)
self.setModal(True)
self.exec()
def ShowWindow1(self):
self.Contents.setCurrentIndex(1)
def ShowWindow2(self):
self.Contents.setCurrentIndex(2)
def SaveSettings(self):
self.Contents.setCurrentIndex(3)
def RightArrow(self):
if self.Contents.currentIndex() == 1:
self.Contents.setCurrentIndex(2)
else:
self.Contents.setCurrentIndex(1)
class CenterPanel(QWidget):
def __init__(self, MainWin):
QWidget.__init__(self)
CntrPane = QTextEdit('Center Panel is Placed Here')
hbox = QHBoxLayout(self)
hbox.addWidget(CntrPane)
self.setLayout(hbox)
class MenuToolBar(QDockWidget):
def __init__(self, MainWin):
QDockWidget.__init__(self)
self.MainWin = MainWin
self.MainMenu = MainWin.menuBar()
self.WndowMenu = self.MainMenu.addMenu('Windows')
self.OptnAct = QAction('Options', self)
self.OptnAct.setStatusTip('Open the Options Window')
self.OptnAct.triggered.connect(MainWin.ShowOptions)
self.WndowMenu.addAction(self.OptnAct)
self.InitToolBar(MainWin)
def InitToolBar(self, MainWin):
self.mainToolBar = MainWin.addToolBar("Quick Access")
self.mainToolBar.addAction(self.OptnAct)
class UI_MainWindow(QMainWindow):
def __init__(self):
super(UI_MainWindow, self).__init__()
self.setWindowTitle('Main Window')
# Left, Top, Width, Height
self.setGeometry(200, 200, 550, 550)
self.CenterPane = CenterPanel(self)
self.setCentralWidget(self.CenterPane)
self.MenuToolBar = MenuToolBar(self)
def ShowOptions(self):
self.Options = OptionSettings(self)
if __name__ == '__main__':
MainApp = QApplication([])
MainGui = UI_MainWindow()
MainGui.show()
sysExit(MainApp.exec_())
If you really need arrows in your stacked widget, another solution is to implement your own "Promoted Widget": it allows you to create your design with custom widgets, that might extend the basic widgets provided by Qt. You won't be able to interact with your own implementation in Designer, but you'll get the result once you run your program.
This is the procedure: create your own subclass of the widget you want to extend, define your custom methods or override existing ones (remember that some private methods require a specific return value type, check the documentation).
It's usually better to save the subclass(es) you created in a separate files.
Then, in Designer add the widget you need (in this case, StackedWidget), right click on it and select "Promote to..."; in the dialog that will be shown, type the subclass name you created in the "Promoted class name" field (in the example below, it will be "StackedWidgetWithArrowButtons") and the file that contains it in the "header file" field: it will be treated as a python import, so do not add the trailing ".py" and remember that if you saved it in a subdirectory you'll need the full "module" path, for example "mysubclasses.customstackwidget", if the file is "customstackwidget" in the "mysubclasses" directory.
Save the ui, compile it and run the program.
class StackedWidgetWithArrowButtons(QtWidgets.QStackedWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QStackedWidget.__init__(self, *args, **kwargs)
self.backwardButton = QtWidgets.QToolButton(self)
self.backwardButton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_ArrowLeft))
self.backwardButton.setMaximumSize(24, 24)
self.backwardButton.setFocusPolicy(QtCore.Qt.NoFocus)
self.forwardButton = QtWidgets.QToolButton(self)
self.forwardButton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_ArrowRight))
self.forwardButton.setMaximumSize(24, 24)
self.forwardButton.setFocusPolicy(QtCore.Qt.NoFocus)
self.currentChanged.connect(self.checkSwitchButtons)
def checkSwitchButtons(self):
self.forwardButton.setEnabled(self.currentIndex() < self.count() - 1)
self.backwardButton.setEnabled(self.currentIndex() > 0)
def addWidget(self, widget):
# this is a private method of QStackedWidget that is called when
# the ui is being built by the program, we just implement it
# to ensure that the buttons are correctly enabled;
# the index *has* to be returned
index = QtWidgets.QStackedWidget.addWidget(self, widget)
self.checkSwitchButtons()
return index
def removeWidget(self, widget):
# not necessary, but in case you want to remove widgets in the
# future, it will check buttons again
index = QtWidgets.QStackedWidget.removeWidget(self, widget)
self.checkSwitchButtons()
return index
def mousePressEvent(self, event):
# due to the way QStackedWidget is implemented, children widgets
# that are not in its layout might not receive mouse events,
# but we just need to track clicks so this is enough
if event.button() == QtCore.Qt.LeftButton:
if event.pos() in self.backwardButton.geometry():
self.setCurrentIndex(self.currentIndex() - 1)
elif event.pos() in self.forwardButton.geometry():
self.setCurrentIndex(self.currentIndex() + 1)
def resizeEvent(self, event):
# the base class resizeEvent *has* to be called, otherwise
# you could encounter problems with children widgets
QtWidgets.QStackedWidget.resizeEvent(self, event)
# now ensure that the buttons are always placed on the top
# right corner; this positioning is completely manual and you
# have to take button sizes in consideration to avoid
# overlapping buttons; obviously you can place them wherever
# you want.
self.forwardButton.move(self.rect().right() - self.forwardButton.width(), 0)
self.backwardButton.move(self.forwardButton.x() - self.backwardButton.width(), 0)
If you don't want buttons (or you don't like the way they appear) you could implement your own paintEvent. In this case I created small triangles using QPolygons.
class StackedWidgetWithTriangles(QtWidgets.QStackedWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QStackedWidget.__init__(self, *args, **kwargs)
self.backwardRect = QtCore.QRect(0, 0, 16, 16)
self.forwardRect = QtCore.QRect(0, 0, 16, 16)
self.forwardArrow = QtGui.QPolygon([QtCore.QPoint(-6, -6), QtCore.QPoint(6, 0), QtCore.QPoint(-6, 6)])
self.backwardArrow = QtGui.QPolygon([QtCore.QPoint(6, -6), QtCore.QPoint(-6, 0), QtCore.QPoint(6, 6)])
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
if event.pos() in self.backwardRect:
self.setCurrentIndex(self.currentIndex() - 1)
elif event.pos() in self.forwardRect:
self.setCurrentIndex(self.currentIndex() + 1)
def resizeEvent(self, event):
QtWidgets.QStackedWidget.resizeEvent(self, event)
self.forwardRect.moveLeft(self.rect().right() - self.forwardRect.width())
self.backwardRect.moveLeft(self.forwardRect.x() - self.forwardRect.width())
def paintEvent(self, event):
qp = QtGui.QPainter(self)
qp.setRenderHints(qp.Antialiasing)
# set colors according to the possibility of going back or forward,
# showing a "disabled" arrow whenever it's not possible
if self.currentIndex() > 0:
qp.setPen(QtCore.Qt.darkGray)
qp.setBrush(QtCore.Qt.black)
else:
qp.setPen(QtCore.Qt.lightGray)
qp.setBrush(QtCore.Qt.transparent)
qp.drawPolygon(self.backwardArrow.translated(self.backwardRect.center()))
if self.currentIndex() < self.count() - 1:
qp.setPen(QtCore.Qt.darkGray)
qp.setBrush(QtCore.Qt.black)
else:
qp.setPen(QtCore.Qt.lightGray)
qp.setBrush(QtCore.Qt.transparent)
qp.drawPolygon(self.forwardArrow.translated(self.forwardRect.center()))
you have no logic in your ControlMainWindow class to switch between your widgets. (also i don't see any arrow widgets for switching) you'll need to add a listener to your QTbuttons in your main class like below to execute your logic:
yourQTbutton.itemClicked.connect(self.functioWithUIchangingLogic)