PyQt5 can't add a label to scroll area from thread - python

I use a thread to receive messages from the socket server. I receive the message and try and add a label and I get the next error: "QObject::setParent: Cannot set parent, new parent is in a different thread"
Can someone please explain to me why it doesn't work and what should I do in order to make this work?
def threaded_receiveMessage(chat, network):
while True:
try:
chatterMessage = network.recieveData()
if chatterMessage:
chat.addLabel(chatterMessage, selfThread = chat)
except:
print("Disconnected.")
break
class RandomChattingMW(object):
def RandomChattingSetup(self, MainWindow, username):
# Connecting to network.
self.username = username
self.network = Network(username)
start_new_thread(threaded_receiveMessage, (self, self.network))
def addLabel(self, text):
print("Adding a label")
label = QtWidgets.QLabel(text)
label.setStyleSheet("font: 11pt;")
label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
print(selfThread)
self.lineEdit.setText("")
self.layout.addWidget(label)
This is just part of the code I don't think there's a need for everything. and just to make sure I am clear, I get a string from network.recieveData() which is a function that runs socket.recv in another file, and it does call the addLabel function, it crashes at this line: "self.lineEdit.setText("")"
Full code:
from PyQt5 import QtCore, QtGui, QtWidgets
import math
from NetWorkFolder.network import Network
from NetWorkFolder.NetworkManager import NetworkManager
from _thread import *
class RandomChattingMW(object):
def RandomChattingSetup(self, MainWindow, username):
# Connecting to network.
self.username = username
self.network = Network(username)
MainWindow.setObjectName("MainWindow")
MainWindow.resize(540, 590)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.buttons()
self.lineEdits()
self.randomWidgetSetup()
self.labels()
self.lineLength = 73
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
#NetWork Manager
self.manager = NetworkManager(self.network)
self.manager.messageChanged.connect(self.addLabel)
def buttons(self):
# Send message button
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(0, 560, 61, 31))
self.pushButton.setLayoutDirection(QtCore.Qt.LeftToRight)
self.pushButton.setAutoFillBackground(False)
self.pushButton.setIcon(QtGui.QIcon('chatParts/sendPic.png'))
self.pushButton.setIconSize(QtCore.QSize(50,31))
self.pushButton.setText("")
self.pushButton.setObjectName("pushButton")
self.pushButton.clicked.connect(self.sendMessage)
# Change Chat Button
self.newChatButton = QtWidgets.QPushButton(self.centralwidget)
self.newChatButton.setGeometry(QtCore.QRect(420, -1, 121,32))
self.newChatButton.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
self.newChatButton.setText("New Chat")
self.newChatButton.setStyleSheet("font-size:11pt")
self.newChatButton.setObjectName("newChatButton")
self.newChatButton.clicked.connect(self.newChatConnection)
def lineEdits(self):
# Write message to chat.
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(60, 560, 480, 30))
self.lineEdit.setObjectName("lineEdit")
def labels(self):
# My Username
self.usernameLabel = QtWidgets.QLabel(self.centralwidget)
self.usernameLabel.setEnabled(True)
self.usernameLabel.setText("Username: " + self.username)
self.usernameLabel.setStyleSheet("font-size:11pt")
self.usernameLabel.move(0,0)
self.usernameLabel.setAlignment(QtCore.Qt.AlignLeft|
QtCore.Qt.AlignVCenter)
self.usernameLabel.setObjectName("usernameLabel")
self.usernameLabel.resize(self.usernameLabel.sizeHint().width(), 30)
# The person i am chatting with.
self.chatUsernameLabel = QtWidgets.QLabel(self.centralwidget)
self.chatUsernameLabel.setEnabled(True)
self.chatUsernameLabel.setText("Chatter: " + "Name")
self.chatUsernameLabel.setStyleSheet("font-size:11pt")
self.chatUsernameLabel.move(self.usernameLabel.sizeHint().
width()+15,0)
self.chatUsernameLabel.setAlignment(
QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.chatUsernameLabel.setObjectName("chatUsernameLabel")
self.chatUsernameLabel.resize(
self.chatUsernameLabel.sizeHint().width(), 30)
def randomWidgetSetup(self):
# Chat. ScrollArea
self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
self.scrollArea.setGeometry(QtCore.QRect(0, 30, 540, 530))
self.scrollArea.setFrameShadow(QtWidgets.QFrame.Plain)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
# Body that holds the widgets.
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
# Box that holds the widgets.
self.layout = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents)
self.scrollAreaWidgetContents.setLayout(self.layout)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.layout.addStretch(-1)
self.layout.setSpacing(10)
# Adds a label with the message sent to you/you sent to the scroll area.
#QtCore.pyqtSlot(str)
def addLabel(self, text):
print("Adding a label")
label = QtWidgets.QLabel(text)
label.setStyleSheet("font: 11pt;")
label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
print(selfThread)
self.lineEdit.clear()
self.layout.addWidget(label)
# Makes the message.
def sendMessage(self):
pass
# Send the message to the network.
def sendMessageNetwork(self, message):
try:
self.network.send(message)
except:
self.addLabel("All The User's have Left the chat!\nClick on new chat to find a new Group!")
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "RandomChatting"))
I put the NetworkManager class in another file so it will be a bit cleaner but its the same as what you wrote.

To understand the problem you must have the following clear concepts:
When a widget is added to the window then the widget is a child of the window.
Widgets are not thread-safe, so they should not be accessed from another thread.
Parent widgets access child widgets so the child must belong to the same thread as the parent.
With the above it is concluded that widgets should not be created in another thread but that is what you are doing causing the error, the solution in these cases is to send the information ("chatterMessage") to the GUI thread through a thread element -safe as the signals where widgets should be created.
Considering the above, a possible solution is the following implementation:
class NetworkManager(QtCore.QObject):
messageChanged = QtCore.pyqtSignal(str)
def __init__(self, network, parent=None):
super().__init__(parent)
self._network = network
#property
def network(self):
return self._network
def start(self):
threading.Thread(target=self._execute, daemon=True).start()
def _execute(self):
while True:
try:
message = self.network.recieveData()
if message:
self.messageChanged.emit(message)
except:
print("Disconnected.")
break
class RandomChattingMW(object):
def setupUi(self, MainWindow):
# ...
class RandomChatting(QtWidgets.QMainWindow, RandomChattingMW):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
#QtCore.pyqtSlot(str)
def addLabel(self, text):
print("Adding a label")
label = QtWidgets.QLabel(text)
label.setStyleSheet("font: 11pt;")
label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
self.lineEdit.clear()
self.layout.addWidget(label)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = RandomChatting()
w.show()
network = Network(username)
manager = NetworkManager(network)
manager.messageChanged.connect(w.addLabel)
manager.start()
sys.exit(app.exec_())
Note: I have considered that RandomChattingMW has been created with Qt Designer so you must restore that code.

Related

How to get value of current selection in QTreeView i am using StandardItemModel()

I am using QTreeView to display data with parent child relationship. child tree-view can in turn have another set of child as well.
Here i am using strip() method inside "load_os_compartment_bucket()" to expand parent and show its child contents for which i am using update_model() method. I should fetch actual selected value of the of that parent index so that i can use that in update_model() method
I haven't shown the complete code because its huge so wanted to focus only on QTreeView and combobox based on the selection i make on qtreeview i have to load combobox with list of drop downs.
identity.list_compartments(self.compt_name_id["PL_IN"]).data
in the above code for sample "PL_IN" is have hardcoded but i want to get the actual value which gets selected in that treeview.
I tried using self.treeView.selectedIndexes() but that doesn't seem to work.
def load_os_compartment_bucket(self):
identity = oci.identity.IdentityClient(self.config)
compt_id = identity.list_compartments(self.config["tenancy"]).data
object_storage = oci.object_storage.ObjectStorageClient(self.config)
self.namespace = object_storage.get_namespace().data
# self.MyTreeViewModel.clear()
for compartments in compt_id:
if "." not in compartments.name:
self.compt_name_id[compartments.name] = compartments.id
parent_item = QtGui.QStandardItem(compartments.name.strip())
parent_item.setData(True, StandardItemModel.ExpandableRole)
self.MyTreeViewModel.appendRow(parent_item)
print(self.compt_name_id)
def update_model(self, index):
parent_node = QtGui.QStandardItem(self.MyTreeViewModel.itemFromIndex(index))
parent_item_index = index.row()
print(parent_node.data())
print(parent_item_index)
parent = self.MyTreeViewModel.itemFromIndex(index)
newmodel = self.MyTreeViewModel.data(index, QtCore.Qt.UserRole+1)
print(self.treeView.selectedIndexes())
print(newmodel)
print(parent.rowCount())
identity = oci.identity.IdentityClient(self.config)
child_compt_id = identity.list_compartments(self.compt_name_id["PL_IN"]).data
for child_comp in child_compt_id:
if "." not in child_comp.name:
children = QtGui.QStandardItem("{}".format(child_comp.name))
parent.appendRow(children)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
timeout = QtCore.pyqtSignal(str)
def __init__(self, *args, **kwargs):
QtWidgets.QMainWindow.__init__(self, *args, **kwargs)
self.setupUi(self)
self.updateTime()
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.updateTime)
self.timer.start(1000)
# Object Storage related API Calls
object_storage = oci.object_storage.ObjectStorageClient(self.config)
namespace = object_storage.get_namespace().data
self.MyTreeViewModel = StandardItemModel()
self.treeView.setModel(self.MyTreeViewModel)
self.most_used_cat_header = ['Compartment Name']
self.MyTreeViewModel.setHorizontalHeaderLabels(self.most_used_cat_header)
self.treeView.setSortingEnabled(True)
self.treeView.expanded.connect(self.update_model)
class StandardItemModel(QtGui.QStandardItemModel):
ExpandableRole = QtCore.Qt.UserRole + 500
def hasChildren(self, index):
if self.data(index, StandardItemModel.ExpandableRole):
return True
return super(StandardItemModel, self).hasChildren(index)
My Ui code is below which has treeview and combobox
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui_testmain.ui'
#
# Created by: PyQt5 UI code generator 5.13.0
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.treeView = QtWidgets.QTreeView(self.centralwidget)
self.treeView.setGeometry(QtCore.QRect(220, 40, 291, 151))
self.treeView.setObjectName("treeView")
self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.comboBox.setGeometry(QtCore.QRect(270, 230, 191, 41))
self.comboBox.setObjectName("comboBox")
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", "MainWindow"))
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_())
i want to get the correct parent data so that i can use that and fetch child details for the dict self.compt_name_id[].
I found a way around for the question i posted here. Attaching my code. Also i had problem of appending same child data again and again used rowCount() to fix it. Not sure if thats they right way to do but for now this works for me.
def update_model(self, index):
parent_node = QtGui.QStandardItem(self.MyTreeViewModel.itemFromIndex(index))
parent_item_index = index.row()
parent = self.MyTreeViewModel.itemFromIndex(index)
if parent.rowCount() > 0:
return
current_idx_data = ""
for i in self.treeView.selectedIndexes():
current_idx_data = i.data()
identity = oci.identity.IdentityClient(self.config)
identity.list_compartments(self.compt_name_id[str(current_idx_data)]).data
for child_comp in child_compt_id:
if "." not in child_comp.name:
children = QtGui.QStandardItem("{}".format(child_comp.name))
parent.appendRow(children)

Cannot set parent error with QThread when using QMessageBox

I am trying to run a progress bar on a thread and a function on another thread. The following is my approach and it is working fine, until I add a QMessageBox.
I created two new classes for QThread, one handles the progress bar, another my function. They are being called when the button is pressed using the onButtonClicked function
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox, QLineEdit, QProgressBar, QLabel, QFileDialog, QCheckBox, QMenuBar, QStatusBar
import time
TIME_LIMIT = 100
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.msg = QMessageBox()
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
self.label.setObjectName("label")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
self.lineEdit.setObjectName("lineEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.Actionlistenr()
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "TextLabel"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
def Actionlistenr(self):
self.pushButton.clicked.connect(self.onButtonClick)
def test(self):
if self.lineEdit.text() == "":
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText("Please select a document first!")
self.msg.setWindowTitle("Error")
return self.msg.exec()
# If this was just a regular print statement,
# then it would work, or any other statement that
# does not involve a QMessageBox
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
self.calc2 = External2(self)
self.calc2.start()
def onCountChanged(self, value):
self.progressBar.setValue(value)
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
class External2(QThread, object):
"""
Runs a counter thread.
"""
def __init__(self, outer_instance):
super().__init__()
self.outer_instance = outer_instance
def run(self):
self.outer_instance.test()
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_())
I am getting QObject::setParent: Cannot set parent, new parent is in a different thread Error when doing this, only when I add a QMessageBox in my test function. I am assuming that this is happening because QMessagebox is running on the main thread, not my External2() class, how can I fix this?
The first thing you have to do is verify if the requirements are met (in this case the QLineEdit is not empty) for the heavy duty to start. In these cases I prefer to use the worker thread approach. To launch the heavy task, the method must be invoked asynchronously, for example using QTimer.singleShot(), and to pass the additional arguments use functools.partial(), you must also use #pyqtSlot to be sure that the tasks are executed in the thread Right.
On the other hand you should not modify the class generated by Qt Designer(1) but create another class that inherits from the widget and use the first one to fill it.
from PyQt5 import QtCore, QtGui, QtWidgets
from functools import partial
import time
TIME_LIMIT = 100
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
self.label.setObjectName("label")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
self.lineEdit.setObjectName("lineEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
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", "MainWindow"))
self.label.setText(_translate("MainWindow", "TextLabel"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.msg = QtWidgets.QMessageBox()
self.Actionlistenr()
thread = QtCore.QThread(self)
thread.start()
self.m_worker = Worker()
self.m_worker.countChanged.connect(self.progressBar.setValue)
self.m_worker.moveToThread(thread)
def Actionlistenr(self):
self.pushButton.clicked.connect(self.onButtonClick)
def request_information(self):
filename = self.lineEdit.text()
if filename:
wrapper = partial(self.m_worker.task, filename)
QtCore.QTimer.singleShot(0, wrapper)
else:
self.msg.setIcon(QtWidgets.QMessageBox.Critical)
self.msg.setText("Please select a document first!")
self.msg.setWindowTitle("Error")
self.msg.exec_()
#QtCore.pyqtSlot()
def onButtonClick(self):
self.request_information()
class Worker(QtCore.QObject):
countChanged = QtCore.pyqtSignal(int)
#QtCore.pyqtSlot(str)
def task(self, filename):
# execute heavy task here
print("start")
print(filename)
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.countChanged.emit(count)
print("finished")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
(1) http://pyqt.sourceforge.net/Docs/PyQt5/designer.html
Okay this per-sae does a bit more than you asked but it also does a bit less also -- however it does include all the bits and pieces and how they interconnect so you should be able to extrapolate from this and get it to do whatever it is you are wanting to get done. Note your program had a few issues so I could not duplicate it verbatim -- one of those issues pertaining to your specific problem is that you cannot run anything that inherits from QWidgets from within a thread -- I solved this by creating a multi-process to handle the 2nd Window situation. Still with how I have outlined the Threading you can see that you do not need to have that QMessageBox within the Thread but you could cause something from within that Thread to launch the QMessageBox back in the QMainWindow -- as I have pointed out -- if you need the Thread to launch that QMessageBox -- This works although you might need to add a bit to show the functionality going on within the Threads -- I know this because I have already tested that aspect of this.
from sys import exit as sysExit
from time import sleep as tmSleep
from PyQt5.QtCore import Qt, QObject, QThread, QRunnable, pyqtSignal, pyqtSlot
# from PyQt5.QtGui import ??
#Widget Container Objects
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDockWidget
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QMenuBar, QStatusBar, QLabel
#Widget Action Objects
from PyQt5.QtWidgets import QMessageBox, QFileDialog, QPushButton, QLineEdit
from PyQt5.QtWidgets import QProgressBar, QCheckBox, QAction, QStyleFactory
# Part of Threading
# Note be very careful with Signals/Slots as they are prone to Memory Leaks
class ThreadSignals(QObject):
ObjctSignal = pyqtSignal(object)
IntgrSignal = pyqtSignal(int)
# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class Processor(QWidget):
def __init__(self, Id):
QWidget.__init__(self)
self.ThreadActive = True
self.RunProcess = False
self.Id = Id
self.Name = '---- Threaded Process ' + str(self.Id)
self.Msg = self.Name
def Connect(self, sigHandle, sigFlag):
self.QueData = queQue()
cnt = 0
self.Flag = sigFlag
sigHandle.emit(self)
tmSleep(0.005) # 5 Milliseconds
# This simulates a continuously running process
# The waits are necessary to allow the OS to do stuff because
# python IS NOT multiprocessing due to the GIL -- look it up
self.lstData = []
while self.ThreadActive:
while self.RunProcess:
cnt += 1
if cnt % 10 == 0:
self.lstData.append(cnt)
if cnt % 100 == 0:
self.Msg = self.Name + ' : Loop ' + str(cnt)
self.QueData.put(self.Msg)
self.QueData.put(self.lstData.copy())
sigFlag.emit(cnt)
self.lstData = []
tmSleep(0.005) # 5 Milliseconds
tmSleep(0.005) # 5 Milliseconds
def GetData(self):
RetData = []
if not self.QueData.empty():
RetData = list(self.QueData.get())
return RetData
def StartProcess(self):
self.RunProcess = True
self.Msg = self.Name + ' Started'
self.Flag.emit(-1)
def StopProcess(self):
self.RunProcess = False
self.Msg = self.Name + ' Stopped'
self.Flag.emit(-1)
def DisConnect(self):
self.RunProcess = False
self.ThreadActive = False
self.Msg = self.Name + ' Disconnected'
# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class WorkerProcess(QRunnable):
def __init__(self, StartrFunc, Id):
super(WorkerProcess, self).__init__()
self.StartrFunc = StartrFunc
# def StarterFunc(self):
# self.ProcessObject = Processor(#)
# self.ProcessObject.Connect(sigHandle, sigFlag)
self.setAutoDelete(False)
self.Id = Id
self.name = '----- WorkerProcess ' + str(Id)
# Create Signal (aka Sender) Here
self.signals = ThreadSignals()
self.sigHndl = self.signals.ObjctSignal
self.sigFlag = self.signals.IntgrSignal
#pyqtSlot()
def run(self):
print('Inside ',self.name)
self.StartrFunc(self.sigHndl, self.sigFlag)
# def StarterFunc(self):
# self.ProcessObject = Processor(#)
# self.ProcessObject.Connect(sigHandle, sigFlag)
print('******************************')
print('--- Process Completed')
# Note while this process has completed this thread is still active
def DisConnect(self):
# This disconnects all of its Signals
self.signals.disconnect()
# This is your Menu and Tool Bar class it does not handle the Tool Bar
# at this time but it could be expanded to do so fairly easily just
# keep in mind everything on a Tool Bar comes from the Menu Bar
class MenuToolBar(QDockWidget):
def __init__(self, parent):
QDockWidget.__init__(self)
self.Parent = parent
self.MainMenu = parent.menuBar()
# This is used to have a handle to the Menu Items
# should you implement a Tool Bar
self.MenuActRef = {'HelloAct':0,
'ResetAct':0}
# ******* Create the World Menu *******
self.WorldMenu = self.MainMenu.addMenu('World')
# ******* Create World Menu Items *******
self.HelloAct = QAction('&Hello', self)
# In case you have or want to include an Icon
# self.HelloAct = QAction(QIcon('Images/hello.ico'), '&Hello', self)
self.HelloAct.setShortcut("Ctrl+H")
self.HelloAct.setStatusTip('Say Hello to the World')
self.HelloAct.triggered.connect(self.SayHello)
self.MenuActRef['HelloAct'] = self.HelloAct
self.ResetAct = QAction('&Reset', self)
# self.ResetAct = QAction(QIcon('Images/reset.ico'), '&Hello', self)
self.ResetAct.setShortcut("Ctrl+H")
self.ResetAct.setStatusTip('Reset the Dialog')
self.ResetAct.triggered.connect(self.ResetWorld)
self.MenuActRef['ResetAct'] = self.ResetAct
# ******* Setup the World Menu *******
self.WorldMenu.addAction(self.HelloAct)
self.WorldMenu.addSeparator()
self.WorldMenu.addAction(self.ResetAct)
self.InitToolBar()
def InitToolBar(self):
# If you create a Tool Bar initialize it here
pass
# These are the Menu/Tool Bar Actions
def SayHello(self):
self.Parent.MenuSubmit()
def ResetWorld(self):
self.Parent.MenuReset()
# Its easiest and cleaner if you Class the Center Pane
# of your MainWindow object
class CenterPanel(QWidget):
def __init__(self, parent):
QWidget.__init__(self)
self.Parent = parent
self.Started = False
#-----
self.lblTextBox = QLabel()
self.lblTextBox.setText('Text Box Label')
#-----
self.lneTextBox = QLineEdit()
#-----
self.btnPush = QPushButton()
self.btnPush.setText('Start')
self.btnPush.clicked.connect(self.Starter)
#-----
self.btnTest = QPushButton()
self.btnTest.setText('Test')
self.btnTest.clicked.connect(self.TestIt)
#-----
HBox = QHBoxLayout()
HBox.addWidget(self.btnPush)
HBox.addWidget(self.btnTest)
HBox.addStretch(1)
#-----
self.pbrThusFar = QProgressBar()
self.pbrThusFar.setProperty('value', 24)
#-----
VBox = QVBoxLayout()
VBox.addWidget(self.lblTextBox)
VBox.addWidget(self.lneTextBox)
VBox.addWidget(QLabel(' ')) # just a spacer
VBox.addLayout(HBox)
VBox.addWidget(QLabel(' ')) # just a spacer
VBox.addWidget(self.pbrThusFar)
VBox.addStretch(1)
#-----
self.setLayout(VBox)
def Starter(self):
if self.Started:
self.btnPush.setText('Start')
self.Started = False
self.Parent.OnStart()
else:
self.btnPush.setText('Reset')
self.Started = True
self.pbrThusFar.setProperty('value', 24)
self.Parent.OnReset()
def TestIt(self):
# Note this cannot be handled within a Thread but a Thread can be
# designed to make a call back to the MainWindow to do so. This
# can be managed by having the MainWindow pass a handle to itself
# to the Thread in question or by using a Signal/Slot call from
# within the Thread back to the MainWindow either works
Continue = True
if self.lneTextBox.text() == '':
DocMsg = QMessageBox()
DocMsg.setIcon(QMessageBox.Critical)
DocMsg.setWindowTitle("Error")
DocMsg.setText("There is no Document. Do you want to Quit?")
DocMsg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
DocMsg.setDefaultButton(QMessageBox.No)
DocMsg.setWindowFlags(Qt.WindowStaysOnTopHint)
MsgReply = DocMsg.exec_()
if MsgReply == QMessageBox.Yes:
sysExit()
def HandleSubmit(self):
self.lneTextBox.setText('Center Panel Menu Submit')
# This is sort of your Main Handler for your interactive stuff as such
# it is best to restrict it to doing just that and let other classes
# handle the other stuff -- it also helps maintain overall perspective
# of what each piece is designed for
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Main Window')
# Sometimes its best to place the window where you want but just setting its size works too
# Still I do this in two lines to make it clear what each position is
WinLeft = 150; WinTop = 150; WinWidth = 400; WinHight = 200
# self.setGeometry(WinLeft, WinTop, WinWidth, WinHight)
self.resize(WinWidth, WinHight)
self.CenterPane = CenterPanel(self)
self.setCentralWidget(self.CenterPane)
# The Menu and Tool Bar for your MainWindow should be classed as well
self.MenuBar = MenuToolBar(self)
self.SetStatusBar(self)
# Not exactly sure what all this does yet but it does remove
# oddities from the window so I always include it - for now
self.setStyle(QStyleFactory.create('Cleanlooks'))
# Part of Threading
self.Thread1Connected = False
self.Thread2Connected = False
# Create Handles for the Threads
# I used this methodology as it was best for my program but
# there are other ways to do this it depends on your needs
self.Thread1Hndl = QObject()
self.Thread2Hndl = QObject()
# This is used to start the Thread 1
self.MyThread1 = WorkerProcess(self.Threader1, 1)
# Create Slots (aka Receivers) Here
self.MyThread1.signals.ObjctSignal.connect(self.Thread1_Hndl)
self.MyThread1.signals.IntgrSignal.connect(self.Thread1_Flag)
# This is used to start the Thread 2
self.MyThread2 = WorkerProcess(self.Threader2, 2)
# Create Slots (aka Receivers) Here
self.MyThread2.signals.ObjctSignal.connect(self.Thread2_Hndl)
self.MyThread2.signals.IntgrSignal.connect(self.Thread2_Flag)
def MenuSubmit(self):
self.CenterPane.HandleSubmit()
def MenuReset(self):
self.CenterPane.lineEdit.setText('Main Window Menu Reset')
def SetStatusBar(self, parent):
StatusMsg = ''
parent.StatBar = parent.statusBar()
if len(StatusMsg) < 1:
# This verbiage will disappear when you view menu items
StatusMsg = 'Ready'
parent.StatBar.showMessage(StatusMsg)
def OnStart(self):
if self.Thread1Connected:
self.Thread1Hndl.StartProcess()
if self.Thread2Connected:
self.Thread2Hndl.StartProcess()
def OnReset(self):
pass
# Part of Threading
def Thread1_Hndl(self, sigHandle):
self.Thread1Hndl = sigHandle
print('******************************')
print('--- Thread 1 Handle Sent Back Validation')
print(self.Thread1Hndl.Msg)
self.Thread1Connected = True
def Thread1_Flag(self, sigFlag):
print('******************************')
print('--- Thread 1 Loop Id Sent Back Validation')
print('----- Current Loop : ', sigFlag)
print(self.Thread1Hndl.Msg)
self.DoStuffT1()
if sigFlag > 1000:
self.Thread1Connected = False
self.Thread1Hndl.DisConnect()
print(self.Thread1Hndl.Msg)
def Thread2_Hndl(self, Handle):
self.Thread2Hndl = Handle
print('******************************')
print('--- Thread 2 Handle Sent Back Validation')
print(self.Thread2Hndl.Msg)
self.Thread2Connected = True
def Thread2_Flag(self, sigFlag):
print('******************************')
print('--- Thread 2 Loop Id Sent Back Validation')
print('----- Current Loop : ', sigFlag)
print(self.Thread2Hndl.Msg)
self.DoStuffT2()
if sigFlag > 1000:
self.Thread2Connected = False
self.Thread2Hndl.DisConnect()
print(self.Thread2Hndl.Msg)
def DoStuffT1(self):
# Just a place holder function for demonstration purposes
# Perhaps handle this here for one of the Threads
# self.CenterPane.pbrThusFar.setValue(value)
pass
def DoStuffT2(self):
# Just a place holder function for demonstration purposes
pass
# Part of Threading
# These Functions are being passed into completely Separate Threads
# do not try to print from within as stdout is not available
# Also keep in mind you cannot use anything within a Thread that
# inherits from QWidgets and a few from QGui as well
# Create the entire object within the Thread allowing for complete
# autonomy of its entire functionality from the Main GUI
def Threader1(self, sigHandle, sigFlag):
self.Thrdr1Obj = Processor(1) # Create Threader 1 Object from Class
self.Thrdr1Obj.Connect(sigHandle, sigFlag)
def Threader2(self, sigHandle, sigFlag):
self.Thrdr2Obj = Processor(2) # Create Threader 2 Object from Class
self.Thrdr2Obj.Connect(sigHandle, sigFlag)
if __name__ == "__main__":
# It is best to keep this function to its bare minimum as its
# main purpose is to handle pre-processing stuff
#
# Next you did not appear to be using sys.argv but if you od need
# to use command line arguments I strongly suggest you look into
# argparse its a python library and very helpful for this as such
# also much cleaner than dealing with them via regular means
MainThred = QApplication([])
MainGUI = MainWindow()
MainGUI.show()
sysExit(MainThred.exec_())
Finally if you have any questions on this do ask but I did try to include explanatories within the code. Also I did a bit of extra cross object calling so you could see how it might be done -- this is not a production version but more a proof of concept that demonstrates numerous concepts within it

Getting a second window pass a variable to the main ui and close

I'm trying to get a variable selected from the list to be sent back to the main window and then close the second window when the ok button is pressed. Currently the second window will not close. Any help will be greatly appreciated.
Code:
class select_variable_window(QDialog):
def __init__(self, list, parent=None):
super().__init__(parent)
self.label = QtWidgets.QLabel(self)
self.label.setGeometry(QtCore.QRect(20, 10, 251, 16))
self.label.setObjectName("label")
self.label=QLabel("Select Variable for Scatterplot", self)
self.dialogbutton = QtWidgets.QDialogButtonBox(self)
self.dialogbutton.setGeometry(QtCore.QRect(120, 260, 161, 32))
self.dialogbutton.setOrientation(QtCore.Qt.Horizontal)
self.dialogbutton.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.dialogbutton.setObjectName("dialogbutton")
self.variablelist = QtWidgets.QListWidget(self)
self.variablelist.setGeometry(QtCore.QRect(10, 30, 391, 221))
self.variablelist.setObjectName("variablelist")
self.variablelist.setSelectionMode(QAbstractItemView.SingleSelection)
self.variablelist.addItems(list)
self.dialogbutton.accepted.connect(self.accept)
self.dialogbutton.rejected.connect(self.reject)
QtCore.QMetaObject.connectSlotsByName(self)
def accept(self):
self.scattervariable=[item.text() for item in self.variablelist.selectedItems()]
print(self.scattervariable)
self.close()# wont close
return
def reject(self):
self.close()#wont close
return
First of all do not use list as the name of a variable since it is a reserved word, and doing so is considered a bad practice that in the future can bring problems.
On the other hand accept() is a method that closes the window and gives the return code QDialog.Accepted, but you are overwriting it and preventing it from doing its job, so after doing your logic you have to call the method of the parent to do his typical closing and establish a return code.
On the other hand, it is recommended that the names of the classes begin with capital letters, read the PEP8 for more information. On the other hand I recommend learning to use layouts to establish the position of element.
from PyQt5 import QtCore, QtWidgets
class Select_variable_window(QtWidgets.QDialog):
def __init__(self, items, parent=None):
super().__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
label = QtWidgets.QLabel("Select Variable for Scatterplot:")
self.variablelist = QtWidgets.QListWidget()
self.variablelist.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.variablelist.addItems(items)
dialogbutton = QtWidgets.QDialogButtonBox()
dialogbutton.setOrientation(QtCore.Qt.Horizontal)
dialogbutton.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
lay.addWidget(label)
lay.addWidget(self.variablelist)
lay.addWidget(dialogbutton)
dialogbutton.accepted.connect(self.accept)
dialogbutton.rejected.connect(self.reject)
def accept(self):
self.scattervariable = [item.text() for item in self.variablelist.selectedItems()]
super().accept() # <-- call parent method
class FirstWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
button = QtWidgets.QPushButton("Open Dialog")
button.clicked.connect(self.on_clicked)
lay.addWidget(button)
#QtCore.pyqtSlot()
def on_clicked(self):
w = Select_variable_window(["1", "2", "3", "4"])
if w.exec_() == QtWidgets.QDialog.Accepted:
print(w.scattervariable)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = FirstWidget()
w.show()
sys.exit(app.exec_())

show arrows in QStackedWidget [duplicate]

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)

PyQt: How to switch widgets in QStackedWidget

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)

Categories

Resources