Update PyQt menu - python

I am trying to dynamically update a menu with new items when I add new items via the form into Qsettings. For example, if you open my code and click the button and then click "new" it will open a QLineEdit with a button. When the button is clicked the list data gets stored via Qsettings.
I'd like to be able to update the menu somehow to show the items without restarting. I tried some things like calling repaint and update in a few areas with no luck.
Here is my code, I slimmed it down the best that I could for the example.
import functools
import sys
from PyQt5 import QtCore
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, \
QVBoxLayout, QLineEdit,QApplication, QWidgetAction, QTextBrowser, QAction, QMenu
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QHBoxLayout()
self.menu_action = QAction(QIcon("icon.png"),"New", self)
self.menu_btn = QPushButton()
self.menu = MyMenu("Menu", self.menu_btn)
self.add_menu = self.menu.addMenu(QIcon("icon.png"), "Menu")
self.add_menu.addAction(self.menu_action)
self.menu_btn.setMenu(self.menu)
self.textBox = QTextBrowser(self)
action = QWidgetAction(self.menu_btn)
action.setDefaultWidget(self.textBox)
self.menu_btn.menu().addAction(action)
settings = QtCore.QSettings('test_org', 'my_app')
self.new_items = settings.value('new_item', [])
print('%s' % self.new_items)
for item in self.new_items:
self.create_action = QAction(QIcon("icon.png"), item[0], self)
self.create_action.setData(item)
self.add_menu.addAction(self.create_action)
self.create_action.triggered.connect(functools.partial(self.menu_clicked, self.create_action))
self.layout.addWidget(self.menu_btn)
self.setLayout(self.layout)
self.menu_action.triggered.connect(self.open_window)
def open_window(self):
self.create_menu_item = Create_Menu_Item()
self.create_menu_item.show()
def menu_clicked(self, item):
itmData = item.data()
print(itmData)
class Create_Menu_Item(QWidget):
def __init__(self, parent=None):
super(Create_Menu_Item, self).__init__(parent)
self.resize(200, 195)
self.layout = QVBoxLayout()
self.form = QLineEdit()
self.btn = QPushButton()
self.layout.addWidget(self.form)
self.layout.addWidget(self.btn)
self.btn.clicked.connect(self.save_new_item)
self.setLayout(self.layout)
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
class MyMenu(QMenu):
def event(self,event):
if event.type() == QtCore.QEvent.Show:
self.move(self.parent().mapToGlobal(QtCore.QPoint(0,0))-QtCore.QPoint(0,self.height()))
return super(MyMenu,self).event(event)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
Anyone have any ideas? Thanks.

You can create a for loop to access the list.
Bare in mind it is a list of lists so a nested for loop is neccessary
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
# Add new menu items..
for x in self.new_item:
for y in x:
print(y)
The above will keep adding the WHOLE list of items every time you add a new item..
To just add the newest item, this is all you need (the last item added to the list)
w.add_menu.addAction(self.new_item[0][-1])
so
def save_new_item(self):
settings = QtCore.QSettings('test_org', 'my_app')
self.new_item = settings.value('new_item', [])
self.new_item.append([self.form.text()])
settings.setValue('new_item', self.new_item)
print(self.new_item)
print('saved!')
#ADD last item in the list
w.add_menu.addAction(self.new_item[0][-1])

Related

Dynamically create a PYQT5 UI form from a dictionary and then update UI with a new dictionary

Summary:
I am trying to make a pyqt5 UI that reads in a dictionary from a json file and dynamically creates an editable form. I would then like to be able to change the json file and have my form update.
What I've tried:
I think the simplest solution would be just to somehow destroy the ui and re-initialize the ui with a new dictionary, I have tried to understand this post, but I am not to sure how to modify this answer to reboot and then instantiate a new UI class with a new dictionary?
I have also read a few posts like this post which I think I can use to first delete all my widgets, then delete my layout and then re add new widgets and layouts from a new dictionary, but I wonder if this is just over complicating the problem?
Some example code:
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.addLayout(self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
if __name__ == "__main__":
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary1)
MainWindow.show()
sys.exit(test_app.exec_())
I suppose I am at the stage of learning where I probably don't know exactly the right questions to ask or how to describe terminology correctly, so I hope this makes sense.
As long as you're using a QFormLayout, you can consider using removeRow(), and do that before adding the new widgets (even the first time). Note that the layout has to be created outside that function, so that you can always reuse it as needed.
import sys
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QFormLayout,
QMainWindow, QLineEdit, QPushButton)
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary.copy()
self.setWindowTitle("Main Window")
central = QWidget(self)
self.setCentralWidget(central)
self.main_layout = QVBoxLayout(central)
self.form_layout = QFormLayout()
self.main_layout.addLayout(self.form_layout)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
while self.form_layout.rowCount():
self.form_layout.removeRow(0)
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.form_layout.addRow(key, self.dynamic_dictionary[key])
QApplication.processEvents()
self.adjustSize()
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
self.createDynamicForm()
As suggested in the comments, I tried out the Qtable widget. In this script the table builder refreshes, removes all table rows and then builds the table from a dictionary.
It builds the first table as a show event, although I don't think that's actually needed in the end. I thought it might be a way to have the table rebuild upon closing and reopening the window as suggested in the comments too. In the end the refresh button is enough on it's own I think, without closing the window.
The research I did was from a training video by Chris Zurbrigg on the the Table widget in pyside2.
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QMainWindow, QLineEdit, QPushButton,
QTableWidget, QTableWidgetItem, QHeaderView)
class TableEditUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('table edit')
self.setMinimumWidth(500)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.create_widgets()
self.create_layouts()
self.toggle = True
self.set_dictionary()
self.refresh_table()
self.create_connections()
def create_widgets(self):
self.table = QTableWidget()
self.table.setColumnCount(2)
self.table.setHorizontalHeaderLabels(["Attribute", "Data"])
header_view = self.table.horizontalHeader()
header_view.setSectionResizeMode(1, QHeaderView.Stretch)
self.toggle_dictionary_btn = QPushButton('toggle dictionary')
def refresh_table(self):
self.table.setRowCount(0)
self.dynamic_dictionary = {}
for count, (key, value) in enumerate(self.dictionary.items()):
print(count, key, value)
self.dynamic_dictionary[key] = QLineEdit(value)
self.table.insertRow(count)
self.insert_item(count, 0, str(key))
self.table.setCellWidget(count, 1, self.dynamic_dictionary[key])
def insert_item(self, row, column, text):
item = QTableWidgetItem(text)
self.table.setItem(row, column, item)
def showEvent(self, e):
super(TableEditUI, self).showEvent(e)
self.refresh_table
def create_layouts(self):
self.main_layout = QVBoxLayout(self.main_widget)
self.main_layout.addWidget(self.table)
self.main_layout.addWidget(self.toggle_dictionary_btn)
def create_connections(self):
self.toggle_dictionary_btn.clicked.connect(self.set_dictionary)
def set_dictionary(self):
self.toggle = not self.toggle
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
if self.toggle:
self.dictionary = dictionary1
else:
self.dictionary = dictionary2
self.refresh_table()
if __name__ == "__main__":
app = QApplication(sys.argv)
TableEditUIWindow = TableEditUI()
TableEditUIWindow.show()
sys.exit(app.exec())
Solution 1
I modify your codes in a way that delete the self.dynamiclayout and rebuild it. Just by using the link you have posted.
You just need to add a call of deleteItemsOfLayout(self.dynamiclayout) and then self.createDynamicForm() in your updateDictionary() function. Another small modification is changing self.main_layout.addLayout(self.dynamiclayout) in your createDynamicForm() function to self.main_layout.insertLayout(0, self.dynamiclayout) in order to keep the order of elements in the self.main_layout.
import sys
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
# copy from https://stackoverflow.com/a/45790404/9758790
def deleteItemsOfLayout(layout):
if layout is not None:
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.setParent(None)
else:
deleteItemsOfLayout(item.layout())
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.insertLayout(0, self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
self.dictionary = dictionary2
deleteItemsOfLayout(self.dynamiclayout)
self.createDynamicForm()
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
if __name__ == "__main__":
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary1)
MainWindow.show()
sys.exit(test_app.exec_())
Solution 2
You can also re-build the whole test_app. However,
In this way I think you must make the dictionary a global varibal instead of a property of the class JsonEditor. To be honest, I think this is not elegant.
In addition, the restart of the whole application may be unnecessary, perhaps you just need to close() the window and create a new one as suggested by #musicamante. I am not sure how to create it again. I wonder whether it's possible for a widget to close() itself and rebuild itself again, if not, you need to add the JsonEditor to parent widget and close/rebuild the JsonEditor there. I think it will be troublesome.
Note that the window will "flash"(disappear and appear again) in the solution 2.
import sys
from PyQt5.QtWidgets import QWidget, qApp
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QFormLayout
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore
class JsonEditor(QMainWindow):
def __init__(self, dictionary):
super().__init__()
self.dictionary = dictionary
self.setWindowTitle("Main Window")
self.setGeometry(200, 200, 800, 100)
self.main_widget = QWidget(self)
self.setCentralWidget(self.main_widget)
self.main_layout = QVBoxLayout(self.main_widget)
self.createDynamicForm()
self.createUpdateButton()
def createDynamicForm(self):
self.dynamiclayout = QFormLayout()
self.dynamic_dictionary = {}
for key, value in self.dictionary.items():
self.dynamic_dictionary[key] = QLineEdit(value)
self.dynamiclayout.addRow(key, self.dynamic_dictionary[key])
self.main_layout.addLayout(self.dynamiclayout)
def createUpdateButton(self):
self.update_button = QPushButton('update')
self.main_layout.addWidget(self.update_button)
self.update_button.clicked.connect(self.updateDictionary)
def updateDictionary(self):
dictionary2 = {}
dictionary2['foo2'] = 'foo_string2'
dictionary2['bar2'] = 'bar_string2'
dictionary2['foo_bar'] = 'foo_bar_string2'
global dictionary
dictionary = dictionary2
qApp.exit(EXIT_CODE_REBOOT)
dictionary1 = {}
dictionary1['foo'] = 'foo_string'
dictionary1['bar'] = 'bar_string'
dictionary = dictionary1
EXIT_CODE_REBOOT = -11231351
if __name__ == "__main__":
exitCode = 0
while True:
test_app = QApplication(sys.argv)
MainWindow = JsonEditor(dictionary)
MainWindow.show()
exitCode = test_app.exec_()
test_app = None
if exitCode != EXIT_CODE_REBOOT:
break
In fact, this is my first time to try to restart a Qt GUI. I referred to this answer, however, it didn't work directly as I stated in the comment below it. This answer works fine and is adopted in my answer. I also tried this answer, however, the application configuration stayed unchanged if using this implementation.

I want to change the sound that plays every time a button is pressed depending on a checked actiion in a QMenu

I have a QMainWindow, inside there is a QMenu, QLineEdit, and one QPushButton.
Every time I click the button, it plays a sound and then adds a text to the QLineEdit.
In my QMenu the user must be able to choose which sound plays by checking it.
I tried to achieve this by changing a variable self.s inside the MainWindow class every time a QAction is checked, meanwhile, the other QAction's are unchecked. So in my playsound() I just put the self.view.s as the argument.
But it seems that it's only reading the original self.view.s, which is the first sound. My signals to change self.view.s does not work. Also, the other QActions aren't unchecked as I wanted them to.
Below is my code:
import sys
from functools import partial
from playsound import playsound
from threading import Thread
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.buttons = {}
self.setWindowTitle("Try")
central_widget = QWidget()
self.setCentralWidget(central_widget)
self.lay = QVBoxLayout(central_widget)
self.lineedit()
button = {"HEY! ": (0, 0, 0, 0)}
page = QWidget()
layout = QGridLayout(page)
for btnText, pos in button.items():
self.buttons[btnText] = QPushButton(btnText)
layout.addWidget(self.buttons[btnText], *pos)
self.lay.addWidget(page)
self.music()
def music(self):
self.s = 'sound1.mp3'
self.x = 'sound1.mp3'
self.y = 'sound2.mp3'
self.z = 'disable.mp3'
def lineedit(self):
self.le = QLineEdit()
self.le.setFixedHeight(35)
self.lay.addWidget(self.le)
def set_lineedit(self, text):
self.le.setText(text)
self.le.setFocus()
def line(self):
return self.le.text()
class Menu:
def __init__(self, MainWindow):
super().__init__()
self.view = MainWindow
self.menuBar()
#self.actionSignals()
def menuBar(self):
self.menuBar = QMenuBar()
self.view.setMenuBar(self.menuBar)
self.menu = QMenu(self.menuBar)
self.menu.setTitle('Menu')
self.sounds = QMenu(self.menu)
self.sounds.setTitle('Select Sound')
self.sound1 = QAction(self.menuBar)
self.sound2 = QAction(self.menuBar)
self.disable = QAction(self.menuBar)
self.mute = QAction(self.menuBar)
self.mute.setText('Mute Background')
self.mute.setCheckable(True)
self.mute.setChecked(False)
self.sound1.setText('Sound 1')
self.sound1.setCheckable(True)
self.sound1.setChecked(True)
self.sound2.setText('Sound 2')
self.sound2.setCheckable(True)
self.sound2.setChecked(False)
self.disable.setText('Disable Sound')
self.disable.setCheckable(True)
self.disable.setChecked(False)
self.sounds.addAction(self.sound1)
self.sounds.addAction(self.sound2)
self.sounds.addAction(self.disable)
self.menuBar.addAction(self.menu.menuAction())
self.menu.addAction(self.mute)
self.menu.addAction(self.sounds.menuAction())
def menu_signals(self):
self.sound1.triggered.connect(self.sound_1)
self.sound2.triggered.connect(self.sound_2)
self.disable.triggered.connect(self.disabled)
def sound_1(self, checked):
if checked:
self.sound2.setChecked(False)
self.disable.setChecked(False)
self.view.s = self.view.x
else:
self.sound1.setChecked(True)
def sound_2(self, checked):
if checked:
self.sound1.setChecked(False)
self.disable.setChecked(False)
self.view.s = self.view.y
else:
self.sound2.setChecked(True)
def disabled(self, checked):
if checked:
self.sound2.setChecked(False)
self.sound1.setChecked(False)
self.view.s = self.view.z
else:
self.sound1.setChecked(True)
class Controller:
def __init__(self, MainWindow):
self.view = MainWindow
self.connectSignals()
def background(self):
while True:
playsound('background.mp3')
def playsound(self):
playsound(self.view.s, False)
def buildExpression(self, sub_exp):
expression = self.view.line() + sub_exp
self.view.set_lineedit(expression)
def connectSignals(self):
for btnText, btn in self.view.buttons.items():
self.view.buttons[btnText].clicked.connect(self.playsound)
self.view.buttons[btnText].clicked.connect(partial(self.buildExpression, btnText))
app = QApplication(sys.argv)
w = MainWindow()
x = Controller(w)
Thread(target = x.background, daemon = True).start()
m = Menu(w)
w.show()
app.exec()
I want to be able to change the value within playsound() depending on which QAction is checked in the Menu Bar. While one QAction is checked, the other QAction's should be unchecked.
This is where an action group comes into play. QActionGroup allows for mutually exclusive actions. It also provides convenient access to the selected action through the checkedAction method.
Create a QActionGroup object (e.g. self.soundGroup = QActionGroup(self))
Create your actions with the group as parent (e.g. self.sound1 = QAction(self.soundGroup))
For each of your actions, set their corresponding sound as their data, e.g. self.sound1.setData('sound1.mp3')
Ensure the action group is exclusive (I believe it's the default, but you may use self.soundGroup.setExclusive(True))
Use self.soundGroup.checkedAction() to get the checked action (selected sound) instead of self.view.s: playsound(self.soundGroup.checkedAction().data(), False)
You do not need any of your wiring between the actions and updates to self.view.s anymore. Just remove all of that.

The layout is incorrect after remove widget

I am implement my project using pyqt5. Currently, I have a window including many widget. Now, I want to remove some widgets. The window looks like:
Now, I want to remove the 'name1' widget including the QLabel and QPushButton.
However, after removing all 'name1' widgets, the 'name2' widgets including QLabel and QPushButton can not self-adapte with the window, like:
All my code is:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Window(QDialog):
def __init__(self):
super().__init__()
self.initGUI()
self.show()
def initGUI(self):
layout = QVBoxLayout()
self.setLayout(layout)
removeLayout = QHBoxLayout()
self.__removeText = QLineEdit()
self.__removeBtn = QPushButton('Remove')
self.__removeBtn.clicked.connect(self.remove)
removeLayout.addWidget(self.__removeText)
removeLayout.addWidget(self.__removeBtn)
ROIsLayout = QVBoxLayout()
for name in ['name1', 'name2']:
subLayout = QHBoxLayout()
subText = QLabel(name)
subText.setObjectName(name)
subBtn = QPushButton(name)
subBtn.setObjectName(name)
subLayout.addWidget(subText)
subLayout.addWidget(subBtn)
ROIsLayout.addLayout(subLayout)
layout.addLayout(removeLayout)
layout.addLayout(ROIsLayout)
self.__ROIsLayout = ROIsLayout
def remove(self, checked=False):
name = self.__removeText.text()
while True:
child = self.__ROIsLayout.takeAt(0)
if child == None:
break
while True:
subChild = child.takeAt(0)
if subChild == None:
break
obName = subChild.widget().objectName()
if name == obName:
widget = subChild.widget()
widget.setParent(None)
child.removeWidget(widget)
self.__ROIsLayout.removeWidget(widget)
del widget
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
update:
Actually, the issue may be the takeAt. The following code is workable:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Window(QDialog):
def __init__(self):
super().__init__()
self.initGUI()
self.show()
def initGUI(self):
layout = QVBoxLayout()
self.setLayout(layout)
removeLayout = QHBoxLayout()
self.__removeText = QLineEdit()
self.__removeBtn = QPushButton('Remove')
self.__removeBtn.clicked.connect(self.remove)
removeLayout.addWidget(self.__removeText)
removeLayout.addWidget(self.__removeBtn)
ROIsLayout = QVBoxLayout()
for name in ['name1', 'name2']:
subLayout = QHBoxLayout()
subLayout.setObjectName(name)
subText = QLabel(name, parent=self)
subText.setObjectName(name)
subBtn = QPushButton(name, parent=self)
subBtn.setObjectName(name)
subLayout.addWidget(subText)
subLayout.addWidget(subBtn)
ROIsLayout.addLayout(subLayout)
print(name, subLayout, subText, subBtn)
layout.addLayout(removeLayout)
layout.addLayout(ROIsLayout)
self.__ROIsLayout = ROIsLayout
self.record = [subLayout, subText, subBtn]
def remove(self, checked=False):
layout = self.record[0]
txt = self.record[1]
btn = self.record[2]
layout.removeWidget(txt)
txt.setParent(None)
txt.deleteLater()
layout.removeWidget(btn)
btn.setParent(None)
btn.deleteLater()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
But, I have printed the QLabel/QPushButton in the self.record, and I find it is the same with that from child.takeAt(0).widget().
The main issue in your code is that you're constantly using takeAt(). The result is that all items in the __ROIsLayout layout will be removed from it (but not deleted), which, in your case, are the sub layouts. This is clearly not a good approach: only the widgets with the corresponding object name will be actually deleted, while the others will still be "owned" by their previous parent, will still be visible at their previous position and their geometries won't be updated since they're not managed by the layout anymore.
There are multiple solutions to your question, all depending on your needs.
If you need to remove rows from a layout, I'd consider setting the object name on the layout instead, and look for it using self.findChild().
Also consider that, while Qt allows setting the same object name for more than one object, that's not suggested.
Finally, while using del is normally enough, it's usually better to call deleteLater() for all Qt objects, which ensures that Qt correctly removes all objects (and related parentship/connections).
Another possibility, for this specific case, is to use a QFormLayout.

How to get data from dynamically created textboxes when a button is clicked in Pyqt5?

I am trying to make a window application in pyqt5 in which user enters a number then click("press me") button .
After that a number of rows are created according to the number entered by the user and one push button ("GO")
Each column has three labels with three textboxes
I managed to make the rows already , but what i can't manage is fetching the data from the textboxes when the push button is clicked
Note1: For simplicity , i was just trying the code for one textbox only then i will add more textboxes
Note2: i heard about some function called Lambda , but i searched for it and i couldn't find good explanation to it
Note3: Similar questions that didn't work for me:
Acessing dynamically added widgets I didn't know how to use this answer as i have two kinds of widgets in the layout , label and qlinedit
getting values from dynamically created qlinedits This answer didn't suit my case as i want one only button to get the data in all created textboxes
Code :
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5 import *
from PyQt5.QtWidgets import QLineEdit,QLabel,QGridLayout
import sys
class Window(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.home()
def home(self):
self.grid=QGridLayout()
self.setLayout(self.grid)
self.label=QLabel(self)
self.label.setText("NO")
self.grid.addWidget(self.label,0,1)
self.pushButton_ok = QtWidgets.QPushButton("Press me", self)
self.pushButton_ok.clicked.connect(self.addtextbox)
self.grid.addWidget(self.pushButton_ok,0,10)
self.input1=QLineEdit(self)
self.grid.addWidget(self.input1,0,5)
def addtextbox(self):
no_of_process=(self.input1.text())
no=int(no_of_process)
n=0
while(n<no):
self.bursttime=QLabel(self)
self.bursttime.setText("b")
self.timeinput=QLineEdit(self)
self.grid.addWidget(self.bursttime,2*n+1,0)
self.grid.addWidget(self.timeinput,2*n+1,1)
n=n+1
self.go=QtWidgets.QPushButton("GO",self)
self.grid.addWidget(self.go,6,0)
self.go.clicked.connect(self.printvalues)
def printvalues():
n=0
#fetch data in some way
application = QtWidgets.QApplication(sys.argv)
window = Window()
window.setWindowTitle('Dynamically adding textboxes using a push button')
window.resize(250, 180)
window.show()
sys.exit(application.exec_())
Main Window of program
when user enters for example 2 to create 2 rows
Try it:
import sys
from PyQt5.QtWidgets import (QLineEdit, QLabel, QGridLayout, QWidget,
QPushButton, QApplication, QSpinBox)
class Window(QWidget):
def __init__(self):
super().__init__()
self.home()
def home(self):
self.grid = QGridLayout()
self.setLayout(self.grid)
self.label = QLabel(self)
self.label.setText("NO")
self.grid.addWidget(self.label, 0, 1)
# self.input1 = QLineEdit(self)
self.input1 = QSpinBox(self) # +++
self.input1.setMinimum(1)
self.input1.setMaximum(12)
self.input1.setValue(3)
self.grid.addWidget(self.input1, 0, 5)
self.pushButton_ok = QPushButton("Press me", self)
self.pushButton_ok.clicked.connect(self.addtextbox) #(self.addCheckbox)
self.grid.addWidget(self.pushButton_ok, 0, 10)
def addtextbox(self):
countLayout = self.layout().count()
if countLayout > 3:
for it in range(countLayout - 3):
w = self.layout().itemAt(3).widget()
self.layout().removeWidget(w)
w.hide()
self.lineEdits = [] # +++
for n in range(self.input1.value()):
self.bursttime = QLabel(self)
self.bursttime.setText("b_{}".format(n))
self.timeinput = QLineEdit(self)
self.timeinput.textChanged.connect(lambda text, i=n : self.editChanged(text, i)) # +++
self.grid.addWidget(self.bursttime, 2*n+1, 0)
self.grid.addWidget(self.timeinput, 2*n+1, 1)
self.lineEdits.append('') # +++
self.go = QPushButton("GO") #, self)
self.grid.addWidget(self.go, 2*n+2, 0)
self.go.clicked.connect(self.printvalues)
def printvalues(self):
# fetch data in some way
for i, v in enumerate(self.lineEdits): # +++
print("bursttime: b_{}, timeinput: {}".format(i, v)) # +++
def editChanged(self, text, i): # +++
self.lineEdits[i] = text # +++
def addCheckbox(self):
print("def addCheckbox(self):")
if __name__ == "__main__":
application = QApplication(sys.argv)
window = Window()
window.setWindowTitle('Dynamically adding textboxes using a push button')
window.resize(250, 180)
window.show()
sys.exit(application.exec_())
I was working on a PyQt5 app which had a dynamically loaded whole tab, with QTableView, QLineEdit and few QPushButtons, and had similar problem, I needed data from that one QLineEdit every tab. I've used QSignalMapper because I had to take data on textChanged() signal, but since you have a simple push button to just grab data, you can use QObject.findChildren() like I did in this example:
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5 import *
from PyQt5.QtWidgets import QLineEdit,QLabel,QGridLayout
import sys
class Window(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.home()
def home(self):
self.grid=QGridLayout()
self.setLayout(self.grid)
self.label=QLabel(self)
self.label.setText("NO")
self.grid.addWidget(self.label,0,1)
self.pushButton_ok = QtWidgets.QPushButton("Press me", self)
self.pushButton_ok.clicked.connect(self.addtextbox)
self.grid.addWidget(self.pushButton_ok,0,10)
self.input1=QLineEdit(self)
self.grid.addWidget(self.input1,0,5)
def addtextbox(self):
no_of_process=(self.input1.text())
no=int(no_of_process)
n=0
while(n<no):
self.bursttime=QLabel(self)
self.bursttime.setText("b")
self.timeinput=QLineEdit(self)
self.timeinput.setObjectName("timeinput_{0}".format(n))
self.grid.addWidget(self.bursttime,2*n+1,0)
self.grid.addWidget(self.timeinput,2*n+1,1)
n=n+1
self.go=QtWidgets.QPushButton("GO",self)
self.grid.addWidget(self.go,6,0)
self.go.clicked.connect(self.printvalues)
def printvalues(self):
for child in self.findChildren(QLineEdit, QtCore.QRegExp("timeinput_(\d)+")):
print(child.text())
application = QtWidgets.QApplication(sys.argv)
window = Window()
window.setWindowTitle('Dynamically adding textboxes using a push button')
window.resize(250, 180)
window.show()
sys.exit(application.exec_())
P.S. I fixed your pushButton_ok.clicked() signal, it was calling addCheckBox() which doesn't exist.

context menu in QFileDialog

I would like to implement a custom context menu in a QFileDialog. In the code below, I managed to create a context menu to the main window, but I would like the menu to be displayed when a file is selected : how to know the right widget in the QFileDialog I should apply setContextMenuPolicy ?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
self.myFileDialog = QFileDialog()
self.myFileDialog.setContextMenuPolicy(Qt.CustomContextMenu)
self.myFileDialog.customContextMenuRequested.connect(self.openMenu)
layout = QVBoxLayout()
layout.addWidget(self.myFileDialog)
self.setLayout(layout)
self.action_perso = QAction( "MyOwnMenu", self )
self.connect( self.action_perso, SIGNAL("triggered()"), self.test )
def openMenu(self, position):
menu = QMenu()
menu.addAction(self.action_perso)
menu.exec_(self.myFileDialog.mapToGlobal(position))
def test(self):
print("coucou")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I found a solution, which may not be the best. It relies on two elements:
Thank to a personnal function objectTree (shown here but not used) that lists recursively all child objects of the QFileDialog, I identified the right widget, i.e. the QTreeView (I understood that the QTreeView is the right widget by trying to hide successively all QListView and QTreeView widgets). Hence, I can select it by its objectName with self.findChild(QTreeView, "treeView")
Application of setContextMenuPolicy( Qt.ActionsContextMenu ) to this QTreeView. I have also tried to implement a setContextMenuPolicy(Qt.CustomContextMenu), and it worked partially: my menu did appear but under the origin menu that was not unactivaded !
Below is the code I propose :
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class CustomWidget(QFileDialog):
def __init__(self, parent=None):
super(CustomWidget,self).__init__(parent)
# fetch the QTreeView in the QFileDialog
self.myTree = self.findChild(QTreeView, "treeView")
# set the context menu policy to ActionsContextMenu
self.myTree.setContextMenuPolicy( Qt.ActionsContextMenu )
# Define a new action
self.action_perso = QAction( "MyOwnMenu", self.myTree )
self.myTree.addAction( self.action_perso )
# connect this action to a personnal function
self.connect( self.action_perso, SIGNAL("triggered()"), self.myFunction )
def myFunction(self):
print("coucou")
def objectTree(self, objet, plan, j):
""" list recursively all child objects of objet to fetch the right widget """
n = len( objet.children() )
for i, child in enumerate( objet.children() ):
#print("\t"*j, end="")
plan_sup = plan+"."+str(i)
#print( plan_sup, child )
if isinstance(child, QTreeView):
self.listViews.append(child)
self.objectTree(child, plan_sup, j+1)
class MainWidget(QWidget):
def __init__(self, parent=None):
super(MainWidget,self).__init__(parent)
#creation of main layout
mainLayout = QVBoxLayout()
# creation of a widget inside
self.monWidget = CustomWidget()
mainLayout.addWidget( self.monWidget )
self.setLayout( mainLayout )
self.show()
app = QApplication(sys.argv)
window = MainWidget()
sys.exit(app.exec_())

Categories