Window Manager parent-children relationship weird behavior in python PyQt5 - python

What I want to achieve in python PyQt5 is when I open the script I open a parent window automatically with a button on the middle of the screen. When I click this button a new window suppose to open, and when I close the window I can close separately the parent window and the child window. I want to be able to close the parent window first. I've done that and it works but there is a weird behavior when I open more than 10 windows sometimes when I close a window it recreates another window by itself, and sometimes one of the windows closes all of the other opened windows. How to fix this issue, the code has no errors, but it does not work as intended.
I've tried to convert the first child window to a parent window, and if all parent windows are closed the remaining child windows to become parent. This is somewhat successful, but not what I want. Here is the code:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QAction, QFileDialog
from PyQt5.QtGui import QKeySequence
from PyQt5.QtCore import Qt
class WindowManager(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.button = QPushButton("Create New Window", self)
self.button.clicked.connect(self.new_window)
self.setCentralWidget(self.button)
self.children = []
if self.parent() is not None and len(self.parent().children) > 0 and self.parent().children[0] == self:
self.is_first_child = True
else:
self.is_first_child = False
def new_window(self):
window_manager = WindowManager(self)
window_manager.resize(self.size())
window_manager.setWindowModality(Qt.NonModal)
window_manager.setWindowFlag(Qt.Window)
window_manager.create_new_window_on_close = True
self.children.append(window_manager)
window_manager.show()
def closeEvent(self, event):
if self.parent() is None:
if self.children:
if all(child.parent() is not None for child in self.children):
self.children[0].setParent(None)
self.children[0].setWindowModality(Qt.NonModal)
self.children[0].setWindowFlag(Qt.Window)
self.children[0].show()
self.children[0].children = self.children[1:]
self.close()
else:
if self in self.parent().children:
self.parent().children.remove(self)
self.close()
if __name__ == "__main__":
app = QApplication(sys.argv)
window_manager = WindowManager()
window_manager.show()
sys.exit(app.exec_())
Code 2 :
import sys
from random import randint
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QMenu,
QMenuBar,
QPushButton,
QVBoxLayout,
QWidget,
)
class AnotherWindow(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.label = QLabel("Another Window % d" % randint(0, 100))
layout.addWidget(self.label)
self.setLayout(layout)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.windows1 = []
menubar = self.menuBar()
self.file_menu = menubar.addMenu("File")
self.button1 = QPushButton("New")
self.button1.clicked.connect(self.create_window1)
self.file_menu.addAction(self.button1.text(), lambda: self.create_window1())
def create_window1(self):
window = QMainWindow(None)
window.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint)
menubar = window.menuBar()
menubar.addMenu(self.file_menu)
self.windows1.append(window)
window.show()
def closeEvent(self, event):
event.accept()
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()

I rewrote your script to fix the bugs, but I removed some unused parts:
is_first_child flag
create_new_window_on_close flag
Unused QKeySequence, QAction and QFileDialog imports
The main problem I found about was the WindowManager.children reference which messed up in the code. From what I could understand, only the parent should contain the current reference to the children list with updated data. Whenever the parent would be closed, this updated reference should be passed into one of the children.
The main flaw was in this line:
def new_window(self):
# ... code ...
self.children.append(window_manager)
# ... code ...
You're always inserting a new child into the current's instance reference of child. That is, if you click on the child QPushButton, you're never updating the parent's children list. You're always updating the current instance one.
In short words: it got confusing. So I reworked that into using a single shared list between all instances of WindowManager. Whenever a parent window is created, it creates it's own list. Whenever a child is created, it acquires the parent's list reference.
Here's my version of your script:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5.QtCore import Qt
from random import randint
# Store taken indexes
taken = []
# If you're on Windows, this might not work unless you use another terminal
# application that allows ansi colors. (default windows console does not
# support it)
red = '\033[91m%s\033[0m'
green = '\033[92m%s\033[0m'
# With this we can open 1000 different windows on our testing environment
# ensuring each window contains a different index value
def uniqueIndex():
idx = randint(0, 1000)
while (idx in taken):
idx = randint(0, 1000)
taken.append(idx)
return idx
class WindowManager(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
# Get an unique identifier (only for testing)
self.index = uniqueIndex()
# If this instance is the parent, create a new shared reference.
if (parent is None):
self.children = []
# Otherwise, acquire the reference from the parent window, and add
# itself inside it.
else:
self.children = parent.children
# print on terminal that we're adding a window
print('%s %d' % (green % 'Adding: ', self.index))
# Update the window title, and add it to the shared children list
self.update_parent(parent)
self.children.append(self)
self.button = QPushButton("Create New Window", self)
self.button.clicked.connect(self.new_window)
self.setCentralWidget(self.button)
self.setMinimumWidth(300)
def new_window(self):
window_manager = WindowManager(self)
window_manager.resize(self.size())
window_manager.setWindowModality(Qt.NonModal)
window_manager.setWindowFlag(Qt.Window)
window_manager.show()
# Used just for testing.
def update_parent(self, parent=None):
if (parent is None):
self.setWindowTitle('Parent %d' % self.index)
else:
self.setWindowTitle('Parent %d: Index %d' % (parent.index, self.index))
self.setParent(parent)
def closeEvent(self, event):
# Remove the self from the shared children list
self.children.remove(self)
# print on the terminal that we're removing a window
print('%s: %d' % (red % 'Removing:', self.index))
# If the array is not empty:
if (self.children):
# Reparent all children based on the current parent (which can be None)
for child in self.children:
if (child.parent() == self):
child.update_parent(self.parent())
# set parent clears all window flags.
child.setWindowModality(Qt.NonModal)
child.setWindowFlag(Qt.Window)
# ensure the child is visible
child.show()
# Ensure we're closing the current window.
self.close()
if __name__ == "__main__":
app = QApplication(sys.argv)
window_manager = WindowManager()
window_manager.show()
sys.exit(app.exec_())

Related

PyQT signal connection with extra information using Lambda is not working correctly

I'm running a simple code to creat 4 checkboxes in a widget
I have a simple function to change the checkbox text when clicked based on wheater it's checked or not
I'm trying to connect the "clicked" signal to slot "on_checkBox_ss" using Lambda method to pass an extar parameter to identify the clicked checkbox
but it's not working well, it pass False/True instead of the checkbox index
here is the code
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QWidget, QCheckBox, QApplication, QVBoxLayout
class mainwidget():
def __init__(self) -> None:
super().__init__()
self.widget = QWidget()
vlayout = QVBoxLayout(self.widget)
self.CHK_BoxGRP = [checkBoxClass(self.widget,f"ChkBox_{x}") for x in range(0,4)]
[vlayout.addWidget(self.CHK_BoxGRP[i].chkbox) for i in range(0,4)]
# Build the connection
[self.CHK_BoxGRP[y].chkbox.clicked.connect(lambda y:self.on_checkBox_ss(y)) for
y in range (0,4)]
#function to process the clicked checkbox
def on_checkBox_ss(self,boxnum):
if self.CHK_BoxGRP[boxnum].chkbox.isChecked():
self.CHK_BoxGRP[boxnum].chkbox.setText("Clicked")
else:
self.CHK_BoxGRP[boxnum].chkbox.setText("Not Clicked")
""" Below is check box class """
class checkBoxClass:
def __init__(self,PARENT,CHKLABEL) -> None:
#super().__init__()
self.parent = PARENT
self.chkLabel = CHKLABEL
#self.operat_on = OPERAT_ON
self.chkbox = QtWidgets.QCheckBox(self.parent)
self.chkbox.setStyleSheet("border-color: rgb(85, 85, 255);")
self.chkbox.setObjectName(self.chkLabel)
self.chkbox.setChecked(True)
self.chkbox.setText(f"GROUP {self.chkLabel[-1]}")
""" start of main code"""
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainwindow = mainwidget()
mainwindow.widget.show()
sys.exit(app.exec())
QPushButton.clicked.connect() method automatically passes the state of the button (bool) as default first argument, so if there is only one argument -which is 'y' in comprehension in this case- is overwritten with the state of the button.
You can see that if you just print(boxnum) in 'on_checkBox_ss', it prints out True or False which is the state of clicked button, and by coincidence list[True] will return #1 index and list[False] will return #0 (as it should be because boolean expressions are just 0 or 1 at the end of the day)
I said that this was a coincidence because your code actually works (just not the way as intended) and doesn't give an error which make it seem like the problem has something to do with signals and loops in the framework.
So the solution is to overwrite the state argument with anything (which will be 'x' -or anything for that matter) and pass 'y' explicitly.
[self.CHK_BoxGRP[y].chkbox.clicked.connect(lambda x="", y=y: self.on_checkBox_ss(y)) for y in range(4)]
So it will be:
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QWidget, QCheckBox, QApplication, QVBoxLayout
class mainwidget():
def __init__(self) -> None:
super().__init__()
self.widget = QWidget()
vlayout = QVBoxLayout(self.widget)
self.CHK_BoxGRP = [checkBoxClass(self.widget,f"ChkBox_{x}") for x in range(0,4)]
[vlayout.addWidget(self.CHK_BoxGRP[i].chkbox) for i in range(0,4)]
# Build the connection
#[self.CHK_BoxGRP[y].chkbox.clicked.connect(lambda y:self.on_checkBox_ss(y)) for
#y in range (0,4)]
[self.CHK_BoxGRP[y].chkbox.clicked.connect(lambda x="", y=y:self.on_checkBox_ss(y)) for y in range(4)]
#function to process the clicked checkbox
def on_checkBox_ss(self,boxnum):
print(boxnum) # now prints out actual y value instead of True or False
if self.CHK_BoxGRP[boxnum].chkbox.isChecked():
self.CHK_BoxGRP[boxnum].chkbox.setText("Clicked")
else:
self.CHK_BoxGRP[boxnum].chkbox.setText("Not Clicked")
""" Below is check box class """
class checkBoxClass:
def __init__(self,PARENT,CHKLABEL) -> None:
#super().__init__()
self.parent = PARENT
self.chkLabel = CHKLABEL
#self.operat_on = OPERAT_ON
self.chkbox = QtWidgets.QCheckBox(self.parent)
self.chkbox.setStyleSheet("border-color: rgb(85, 85, 255);")
self.chkbox.setObjectName(self.chkLabel)
self.chkbox.setChecked(True)
self.chkbox.setText(f"GROUP {self.chkLabel[-1]}")
""" start of main code"""
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainwindow = mainwidget()
mainwindow.widget.show()
sys.exit(app.exec())

Pyqt5 - how to go back to hided Main Window from Secondary Window?

If I click Back from the second window, the program will just exit. How do I go back to mainwindow in this case? I assume I will need some more code in that clickMethodBack function.
import os
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QWidget, QLabel, QPushButton
import time
from PyQt5.QtCore import QSize
class GUI_Window():
def __init__(self):
self.main_window()
return
def main_window(self):
app = PyQt5.QtWidgets.QApplication(sys.argv)
self.MainWindow = MainWindow_()
self.MainWindow.show()
app.exec_()
return
class MainWindow_(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.TestAButton = QPushButton("TestA", self)
self.TestAButton.clicked.connect(self.TestA_clickMethod)
self.TestAButton.move(20, 0)
self.CloseButton = QPushButton("Close", self)
self.CloseButton.clicked.connect(self.Close_clickMethod)
self.CloseButton.move(20, 40)
self.TestingA = TestA_MainWindow()
def TestA_clickMethod(self):
self.TestAButton.setEnabled(False)
time.sleep(0.2)
self.TestingA.show()
self.hide()
try:
if self.TestingA.back == True:
self.show()
except:
None
def Close_clickMethod(self):
self.Test_Choice = 'Exit'
self.close()
class TestA_MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setMinimumSize(QSize(980,700))
self.setWindowTitle("TestA")
self.Back_Button = False
self.closeButton = QPushButton("Close", self)
self.closeButton.clicked.connect(self.clickMethodClose)
self.returnButton = QPushButton("Back", self)
self.returnButton.clicked.connect(self.clickMethodBack)
self.returnButton.move(0,30)
def clickMethodClose(self):
self.Back_Button = False
self.close()
def clickMethodBack(self):
self.returnButton.setEnabled(False)
time.sleep(0.5)
self.back = True
self.close()
# Run if Script
if __name__ == "__main__":
main = GUI_Window() # Initialize GUI
Your code has two very important issues.
you're using a blocking function, time.sleep; Qt, as almost any UI toolkit, is event driven, which means that it has to be able to constantly receive and handle events (coming from the system or after user interaction): when something blocks the event queue, it completely freezes the whole program until that block releases control;
you're checking for the variable too soon: even assuming the sleep would work, you cannot know if the window is closed after that sleep timer has ended;
The solution is to use signals and slots. Since you need to know when the second window has been closed using the "back" button, create a custom signal for the second window that will be emitted whenever the function that is called by the button is closed.
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(central)
self.testButton = QtWidgets.QPushButton('Test A')
self.closeButton = QtWidgets.QPushButton('Close')
layout.addWidget(self.testButton)
layout.addWidget(self.closeButton)
self.setCentralWidget(central)
self.testButton.clicked.connect(self.launchWindow)
self.closeButton.clicked.connect(self.close)
def launchWindow(self):
self.test = TestA_MainWindow()
self.test.backSignal.connect(self.show)
self.hide()
self.test.show()
class TestA_MainWindow(QtWidgets.QWidget):
backSignal = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.closeButton = QtWidgets.QPushButton('Close')
self.backButton = QtWidgets.QPushButton('Back')
layout.addWidget(self.closeButton)
layout.addWidget(self.backButton)
self.closeButton.clicked.connect(self.close)
self.backButton.clicked.connect(self.goBack)
def goBack(self):
self.close()
self.backSignal.emit()
def GUI_Window():
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
GUI_Window()
Notes:
I removed the GUI_Window class and made a function, as using a class for that is not really useful;
you should always prefer layout managers instead of setting manual geometries;
widgets should not be added to a QMainWindow as direct children, and a central widget should always be used (see the creation and use of central in the example); read more about it in the documentation;
only classes and constants should be capitalized, while variables, attributes and functions should always have names starting with a lowercase letter;

Move to next tab (and focus on the corresponding widget) when pressing the Tab key

In my app I have a QTabWidget which holds a variable number of seemingly "identical" tabs with a variable number of widgets.
I want, once the TAB (or shift-TAB) button is pressed, for the focus of the app to move to the next (or previous) tab, and focus on the corresponding widget of that tab (the one corresponding to the widget which had the focus until the key press).
What is the best way to go around this in a simple way? I tried using a QShortcut to catch the key-press but I can't seem to figure out a way to get the corresponding widget in the next or previous tab and focus on that.
Here's a minimal example of the code, which simply moves to the next tab but not to the corresponding widget:
import sys
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt5.QtWidgets import *
class tabdemo(QTabWidget):
def __init__(self, num_tabs=2):
super().__init__()
shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Tab), self)
shortcut.activated.connect(self.on_tab)
shortcut2 = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Backtab), self)
shortcut2.activated.connect(self.on_shift_tab)
self.tabs = []
for i in range(num_tabs):
newtab = QWidget()
self.tabs.append(newtab)
self.addTab(newtab, f'Tab {i}')
self.add_widgets_to(newtab)
def add_widgets_to(self, tab):
layout = QVBoxLayout()
tab.setLayout(layout)
layout.addWidget(QSpinBox())
layout.addWidget(QCheckBox())
gender = QHBoxLayout()
gender.addWidget(QRadioButton("Male"))
gender.addWidget(QRadioButton("Female"))
layout.addLayout(gender)
#QtCore.pyqtSlot()
def on_tab(self):
current_tab = self.currentIndex()
self.setCurrentIndex((current_tab + 1) % self.count())
# TODO find current widget in focus, and find the corresponding one in the next tab, and focus on that one... note that widgets could be complex (i.e., not direct children...)
#QtCore.pyqtSlot()
def on_shift_tab(self):
print("do_something")
current_tab = self.currentIndex()
self.setCurrentIndex((current_tab - 1) % self.count())
def main():
app = QApplication(sys.argv)
ex = tabdemo()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Since the OP indicates that each page will have identical components then an index can be associated so that the index of the tab can be obtained before changing it and then set the widget's focus then set the focus to the other corresponding widget.
import sys
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import (
QApplication,
QCheckBox,
QHBoxLayout,
QRadioButton,
QShortcut,
QSpinBox,
QTabWidget,
QVBoxLayout,
QWidget,
)
class Page(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
spinbox = QSpinBox()
checkbox = QCheckBox()
male_radio = QRadioButton("Male")
female_radio = QRadioButton("Female")
layout = QVBoxLayout(self)
layout.addWidget(spinbox)
layout.addWidget(checkbox)
gender = QHBoxLayout()
gender.addWidget(male_radio)
gender.addWidget(female_radio)
layout.addLayout(gender)
for i, widget in enumerate((spinbox, checkbox, male_radio, female_radio)):
widget.setProperty("tab_index", i)
class Tabdemo(QTabWidget):
def __init__(self, num_tabs=2):
super().__init__()
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self)
shortcut.activated.connect(self.next_tab)
shortcut2 = QShortcut(QKeySequence(Qt.Key_Backtab), self)
shortcut2.activated.connect(self.previous_tab)
for i in range(num_tabs):
page = Page()
self.addTab(page, f"Tab {i}")
#pyqtSlot()
def next_tab(self):
self.change_tab((self.currentIndex() + 1) % self.count())
#pyqtSlot()
def previous_tab(self):
self.change_tab((self.currentIndex() - 1) % self.count())
def change_tab(self, index):
focus_widget = QApplication.focusWidget()
tab_index = focus_widget.property("tab_index") if focus_widget else None
self.setCurrentIndex(index)
if tab_index is not None and self.currentWidget() is not None:
for widget in self.currentWidget().findChildren(QWidget):
i = widget.property("tab_index")
if i == tab_index:
widget.setFocus(True)
def main():
app = QApplication(sys.argv)
ex = Tabdemo()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Building on eyllanesc's answer, I improved the functionality to:
Account for the scrollbar location (if exists)
Use a bi-directional dictionary (implemented here) instead of a linear lookup
Dynamically add all relevant widgets using the update_map() method instead of having to add each widget manually.
Posting in case anyone finds this useful.
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QKeySequence
from PyQt5.QtWidgets import QWidget, QTabWidget, QShortcut, QApplication, QScrollArea
class BidirectionalDict(dict):
def __init__(self, *args, **kwargs):
super(BidirectionalDict, self).__init__(*args, **kwargs)
self.inverse = {}
for key, value in self.items():
self.inverse.setdefault(value, []).append(key)
def __setitem__(self, key, value):
if key in self:
self.inverse[self[key]].remove(key)
super(BidirectionalDict, self).__setitem__(key, value)
self.inverse.setdefault(value, []).append(key)
def __delitem__(self, key):
self.inverse.setdefault(self[key], []).remove(key)
if self[key] in self.inverse and not self.inverse[self[key]]:
del self.inverse[self[key]]
super(BidirectionalDict, self).__delitem__(key)
def get_first_inv(self, key):
return self.inverse.get(key, [None])[0]
class Page(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.widgets_map = BidirectionalDict()
# ... add your widgets ...
self.update_map()
def update_map(self):
widgets = self.findChildren(QWidget)
for i, widget in enumerate(widgets):
self.widgets_map[i] = widget
class MyQTabWidget(QTabWidget):
def __init__(self):
super().__init__()
shortcut = QShortcut(QKeySequence(Qt.Key_Tab), self)
shortcut.activated.connect(self.next_tab)
shortcut2 = QShortcut(QKeySequence(Qt.Key_Backtab), self)
shortcut2.activated.connect(self.previous_tab)
#pyqtSlot()
def next_tab(self):
self.change_tab((self.currentIndex() + 1) % self.count())
#pyqtSlot()
def previous_tab(self):
self.change_tab((self.currentIndex() - 1) % self.count())
def change_tab(self, new_tab_index):
old_tab: Page = self.currentWidget()
focus_widget = QApplication.focusWidget()
widget_index = old_tab.widgets_map.get_first_inv(focus_widget) if focus_widget else None
self.setCurrentIndex(new_tab_index)
new_tab: Page = self.currentWidget()
if new_tab is not None and widget_index is not None:
corresponding_widget: QWidget = new_tab.widgets_map[widget_index]
corresponding_widget.setFocus(True)
# Move scrollbar to the corresponding position
if hasattr(old_tab, 'scrollbar'):
# Tabs are identical so new_tab must have scrollbar as well
old_y = old_tab.scrollbar.verticalScrollBar().value()
scrollbar: QScrollArea = new_tab.scrollbar
scrollbar.verticalScrollBar().setValue(old_y)

Initiating a custom QWidget within another QWidget class' function

I'm new to python and pyqt. I am am trying to create a matrix of QToolButtons where upon pressing a button, a QDialog pops up for user input (more than one field).
I have a class for a button matrix object and a class for a dialog but can't seem to get a function within the button matrix class initiate an instance of the dialog class / widget.
Can anyone please advise me on what I'm doing wrong?
I have provided the code below:
from PyQt4 import QtGui
from PyQt4.QtGui import QApplication, QWidget, QFormLayout, QInputDialog, QPushButton, QToolButton, QLabel, QVBoxLayout, QHBoxLayout, QLineEdit
class Pixel(object):
def __init__(self, pixel_number, r_value, g_value, b_value):
self.pixel = pixel_number
self.red_value = r_value
self.green_value = g_value
self.blue_value = b_value
class inputdialogdemo(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
#super(inputdialogdemo, self).__init__(parent)
layout = QFormLayout()
self.btn1 = QPushButton("Enter red value")
self.le1 = QLineEdit()
self.btn1.clicked.connect(self.getRed)
layout.addRow(self.btn1,self.le1)
self.btn2= QPushButton("Enter green value")
self.le2 = QLineEdit()
self.btn2.clicked.connect(self.getGreen)
layout.addRow(self.btn1,self.le2)
self.btn3 = QPushButton("Enter blue value")
self.le3 = QLineEdit()
self.btn3.clicked.connect(self.getBlue)
layout.addRow(self.btn3,self.le3)
self.setLayout(layout)
self.setWindowTitle("RGB input dialog ")
def getRed(self):
num, ok = QInputDialog.getText(self, 'Red Input Dialog', 'Enter your name:')
if ok:
self.le1.setText(str(num))
def getGreen(self):
num,ok = QInputDialog.getInt(self,"Green input dualog","enter a number")
if ok:
self.le2.setText(str(num))
def getBlue(self):
num,ok = QInputDialog.getInt(self,"Blue input dualog","enter a number")
if ok:
self.le3.setText(str(num))
class ClusterArray(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
#self.button_layout = QHBoxLayout()
self.button_layout = QtGui.QGridLayout()
self.widget_layout = QtGui.QGridLayout()
for cluster_number in range(1, 15):
for pixel_number in range(1, 5):
button = QToolButton()
button.setText(str(cluster_number) + str(pixel_number))
button.setObjectName(f"Cluster{cluster_number},Pixel{pixel_number}")
button.released.connect(self.button_released)
self.button_layout.addWidget(button, cluster_number, pixel_number)
self.status_label = QLabel('No button clicked')
self.widget_layout.addItem(self.button_layout)
self.widget_layout.addWidget(self.status_label)
self.setLayout(self.widget_layout)
ex = inputdialogdemo()
def button_released(self):
sending_button = self.sender()
self.status_label.setText('%s Clicked!' % str(sending_button.objectName()))
ex = inputdialogdemo()
ex.show()
#i, okPressed = QInputDialog.getInt(self, "Get integer","Percentage:", 28, 0, 100, 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = ClusterArray()
widget.show()
# ex = inputdialogdemo()
# ex.show()
sys.exit(app.exec_()
At the moment I've just tried to get my class' function to create an object from some demo code in a class called 'inputdialogdemo'
Also, I would like to keep the inputted values associated with the corresponding button on the matrix somehow. I'm thinking the values should be stored in another object that each button represents. My object would be 'Pixel' that holds red, green and blue values. I have yet to code this functionality. Does this method sound feasible?
The inputdialogdemo instance is correctly created both in the __init__ and in button_released, the problem is that as soon as those function return, the ex instance gets garbage collected: since there is no persistent reference (ex is just a local variable), python automatically deletes it to avoid unnecessary memory consumption.
Since you're needing a dialog, the best solution is to inherit from QDialog instead of QWidget; this has two important benefits: it keeps the dialog modal (it stays on top of other windows and avoid interaction with them) and provides the exec_() method, which does not return until the dialog is closed; then you can add a QDialogButtonBox for standard Ok/Cancel buttons, and connect its accepted and rejected to the accept() and reject() slots of the dialog.
from PyQt4.QtGui import (QApplication, QWidget, QFormLayout, QInputDialog, QPushButton, QToolButton,
QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QDialog, QDialogButtonBox)
class inputdialogdemo(QDialog):
def __init__(self, parent = None):
# ...
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
layout.addRow(buttonBox)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
class ClusterArray(QWidget):
# ...
def button_released(self):
sending_button = self.sender()
self.status_label.setText('%s Clicked!' % str(sending_button.objectName()))
ex = inputdialogdemo()
if ex.exec_():
red = ex.le1.text()
green = ex.le2.text()
blue = ex.le2.text()
Some suggestions:
if you need a numerical value, don't use QLineEdit, but QSpinBox;
to add a layout to another, use setLayout(), not addItem();
to connect to a button click, use the clicked signal, not released;
while special characters are not strictly forbidden for object names, it's usually better to avoid them; also, use the object names for actual object names, not for keeping track of some properties
you can keep track of custom properties of any QObject using dynamic properties:
button.setProperty('cluster', cluster_number)
button.setProperty('pixel', pixel_number)
unless you have mandatory system requirements, you should really consider to switch to PyQt5, as PyQt4 is considered obsolete and deprecated/unsupported since 2015;
always prefer capitalized names for classes, as lower case names are normally used for variables and attributes only; read more on the
Style Guide for Python Code;

How to return variables from PyQt5 UI to Main function - Python

I have design a customized formlayout ui using pyqt5 and want to import variables back to the main function for further execution of the main function.
I have tried many ways to get the return values from the main function when the "OK" button has clicked but unable to get the variables from the main function.
Can you please guide me, how can i get the variables from the pyqt5 formlayout ui to main function -
Here is the Code of PyQt5 FormLayout UI function -
from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog,
QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit,
QVBoxLayout,QCheckBox)
import sys
app = QApplication([])
class Dialog(QDialog):
def __init__(self,dinput):
super(Dialog, self).__init__()
self.createFormGroupBox(dinput)
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(buttonBox)
self.setLayout(mainLayout)
self.setWindowTitle("Form Layout")
def accept(self):
print(self.linedit1.text())
print(self.combox1.currentText())
print(self.spinbox1.value())
self.closeEvent()
def reject(self):
print('Cancelled')
self.closeEvent()
def getoutput(self):
return self.linedit1.text()
def createFormGroupBox(self,dinput):
self.formGroupBox = QGroupBox("Form layout")
layout = QFormLayout()
self.linedit1 = QLineEdit()
self.linedit1.setText('TestName')
layout.addRow(QLabel(dinput[0]), self.linedit1)
self.combox1 = QComboBox()
self.combox1.setToolTip('Hello')
self.combox1.addItems(['India','France','UK','USA','Germany'])
layout.addRow(QLabel(dinput[1]), self.combox1)
self.spinbox1 = QSpinBox()
layout.addRow(QLabel(dinput[2]), self.spinbox1)
self.formGroupBox.setLayout(layout)
Main Function is -
import os
import sys
import pyformlayout as pyfl
# Staring Functions for Execution
dinput = ['LastName','Country','Age']
# Call the UI and get the inputs
dialog = pyfl.Dialog(dinput)
if(dialog.exec_()):
TName = dialog.getoutput
print('------------------')
print(TName)
# Main Function Continous by getting the inputs
# from UI
I am unable to get the desired values to the output function. Even i have used the getoutput function to return the values and get the output to "TName". But i am not able to get the value into the TName variable and nothing is displaying.
The Result i am getting is - (which is basically printing the accept button function but not the TName variable which is returned to Main function.
TestName
India
25
How can i get the return values from PyQt5 Formlayout UI function to Main function..?
In the first place, FormLayout is a layout, that is, a class that is responsible for positioning the widgets within a window, so it is irrelevant for these cases. On the other hand, closeEvent() should never be invoked, that is a function that serves to handle the closed window event.
Going to the point the accept method is called when Ok is pressed, so it is the right place to get the values so it must be stored in a variable, and then returned in the get_output() method:
pyformlayout.py
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
class Dialog(QtWidgets.QDialog):
def __init__(self, dinput):
super(Dialog, self).__init__()
self.createFormGroupBox(dinput)
buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
mainLayout = QtWidgets.QVBoxLayout(self)
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(buttonBox)
self.setWindowTitle("Form Layout")
def createFormGroupBox(self, dinput):
layout = QtWidgets.QFormLayout()
self.linedit1 = QtWidgets.QLineEdit('TestName')
self.combox1 = QtWidgets.QComboBox()
self.combox1.setToolTip('Hello')
self.combox1.addItems(['India','France','UK','USA','Germany'])
self.spinbox1 = QtWidgets.QSpinBox()
for text, w in zip(dinput, (self.linedit1, self.combox1, self.spinbox1)):
layout.addRow(text, w)
self.formGroupBox = QtWidgets.QGroupBox("Form layout")
self.formGroupBox.setLayout(layout)
def accept(self):
self._output = self.linedit1.text(), self.combox1.currentText(), self.spinbox1.value()
super(Dialog, self).accept()
def get_output(self):
return self._output
And in the file main.py I get the value if only the ok button has been pressed:
main.py
import pyformlayout as pyfl
# Staring Functions for Execution
dinput = ['LastName','Country','Age']
# Call the UI and get the inputs
dialog = pyfl.Dialog(dinput)
if dialog.exec_() == pyfl.Dialog.Accepted:
name, item, value = dialog.get_output()
print(name, item, value)

Categories

Resources