Interactive interaction of selections with checkboxes - python

I have a popup window in which it reads in a list of options and populates it into groupbox and checkboxes.
I have a selection in which it contains the following data:
my_selection = {'drinks': ['coffee'], 'snacks': ['m&m']}
and I am trying to get the options - coffee and -m&m checked in the popup window.
However my selection can varies, meaning, itemA may have on of the drinksitems, and if I select itemB (itemA is no longer in selection), it could have an item from drinks and 2 items in snacks and I had wanted to make sure the correct option is checked, in the event, if there are same item naming but in different categories or vice verse.
I tried inserting the selection case into the class as follows:
class FormWindow(QtGui.QWidget):
def __init__(self, main_items, my_selection, parent=None, callback=None):
...
if my_selection:
for k, v in my.items():
for i in v:
if sub_chk.text() == i:
sub_chk.setChecked(True)
It only checks the latest item it found, in this case, only the items in Snacks is taken into account.
import sys
from PyQt4 import QtGui, QtCore
from collections import defaultdict
class FormWindow(QtGui.QWidget):
def __init__(self, main_items, parent=None, callback=None):
super(FormWindow, self).__init__(parent=parent)
self.callback = callback
layout = QtGui.QVBoxLayout()
self.checkbox_options = []
self.menu_tag_dict = defaultdict(set)
for main_name, sub_name in main_items.items():
# Set the main item
groupbox = QtGui.QGroupBox(self)
groupbox.setTitle(main_name.title())
groupbox.setLayout(QtGui.QVBoxLayout())
layout.addWidget(groupbox)
if sub_name:
# sub_txt = [(action.text()) for action in sub_name]
sub_txt = [action for action in sub_name]
# Creates QCheckbox for each option
for s in sub_txt:
sub_chk = QtGui.QCheckBox(s)
self.checkbox_options.append(sub_chk)
groupbox.layout().addWidget(sub_chk)
layout.addStretch()
self.setLayout(layout)
self.setWindowTitle('Form Checking')
self.show()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_items = {'drinks': ['coffee', 'tea', 'water'], 'snacks': ['biscuits', 'm&m']}
my_win = FormWindow(main_items)
sys.exit(app.exec_())
# Example1 of what my selection will return
my_selection = {'drinks': ['coffee'], 'snacks': ['m&m']}
# Example2 of what my selection will return
my_selection = {'drinks': ['water', 'coffee'], 'snacks': ['biscuits']}

Assuming that you want to obtain the checked items, then what you should do is to have a reflection of the data by removing or adding the selection according to the status of the checked, in the following example, if you press the button, the checked items will be printed:
import sys
from PyQt4 import QtGui, QtCore
from functools import partial
class FormWindow(QtGui.QWidget):
checkbox_options_changed = QtCore.pyqtSignal(dict)
def __init__(self, main_items, parent=None, callback=None):
super(FormWindow, self).__init__(parent=parent)
self.callback = callback
layout = QtGui.QVBoxLayout(self)
self.checkbox_options = {}
for main_name, sub_name in main_items.items():
groupbox = QtGui.QGroupBox()
groupbox.setTitle(main_name.title())
lay = QtGui.QVBoxLayout(groupbox)
layout.addWidget(groupbox)
self.checkbox_options[main_name] = set()
for s in sub_name:
sub_chk = QtGui.QCheckBox(s)
info = (main_name, s)
wrapper = partial(self.on_stateChanged, info)
sub_chk.stateChanged.connect(wrapper)
lay.addWidget(sub_chk)
layout.addStretch()
self.setWindowTitle("Form Checking")
#QtCore.pyqtSlot(tuple, QtCore.Qt.CheckState)
def on_stateChanged(self, info, state):
name, item = info
option = self.checkbox_options[name]
if state == QtCore.Qt.Checked:
option.add(item)
else:
if item in option:
option.remove(item)
self.checkbox_options_changed.emit(self.checkbox_options)
def get_checked_items(self):
return self.checkbox_options
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_items = {
"drinks": ["coffee", "tea", "water"],
"snacks": ["biscuits", "m&m"],
}
my_win = FormWindow(main_items)
w = QtGui.QWidget()
lay = QtGui.QVBoxLayout(w)
button = QtGui.QPushButton("Print me")
lay.addWidget(button)
lay.addWidget(my_win)
def on_clicked():
print(my_win.get_checked_items())
button.clicked.connect(on_clicked)
my_win.checkbox_options_changed.connect(print)
w.show()
sys.exit(app.exec_())
{'drinks': {'coffee', 'tea'}, 'snacks': set()}
{'drinks': {'coffee', 'tea'}, 'snacks': {'biscuits'}}

Related

Passing input value to a lambda function to trigger a signal [duplicate]

Still learning how pyqt works. I want to dynamically generate a customContextMenu and connect with a function. So far I got the following but the connect part not working ?
import sys
from PyQt4 import QtGui, QtCore
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
# create button
self.button = QtGui.QPushButton("test button", self)
self.button.resize(100, 30)
# set button context menu policy
self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.connect(self.button, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menu)
self.popMenu = QtGui.QMenu(self)
def on_context_menu(self, point):
self.popMenu.clear()
#some test list for test
testItems = ['itemA', 'itemB', 'itemC']
for item in testItems:
action = self.btn_selectPyFilterPopMenu.addAction("Selected %s" % item)
self.connect(action,QtCore.SIGNAL("triggered()"),self,QtCore.SLOT("printItem('%s')" % item))
self.popMenu.exec_(self.button.mapToGlobal(point))
#pyqtSlot(str)
def printItem(self, item):
print item
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
Your code is almost right. You just need to connect the signals to a lambda with a default argument, like this:
for item in testItems:
action = self.popMenu.addAction('Selected %s' % item)
action.triggered.connect(
lambda chk, item=item: self.printItem(item))
The default argument ensures that each lambda gets a copy of the current loop variable. Also note that an initial chk argument is also required. This is because the triggered signal sends its current checked-state (true or false) by default, which would clobber the item argument of the lambda.
Finally, I would urge to use the new-style syntax when connecting signals - the old style can be very error-prone, and is far less pythonic.
I tryed and correct the example given in the first post. Here is a working version.
Right click on the button, select an item and it will be printed inn your terminal :
import sys
from PyQt4 import QtGui, QtCore
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
# create button
self.button = QtGui.QPushButton("test button",self)
self.button.resize(100, 30)
# set button context menu policy
self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.connect(self.button, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menu)
self.popMenu = QtGui.QMenu(self)
def on_context_menu(self, point):
self.popMenu.clear()
#some test list for test
testItems = ['itemA', 'itemB', 'itemC']
for item in testItems:
action = self.popMenu.addAction('Selected %s' % item)
action.triggered[()].connect(
lambda item=item: self.printItem(item))
self.popMenu.exec_(self.button.mapToGlobal(point))
#QtCore.pyqtSlot(str)
def printItem(self, item):
print item
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
If I understand you right:
import sys
from PyQt4 import QtGui, QtCore
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
# create button
self.button = QtGui.QPushButton("test button", self)
self.button.resize(100, 30)
# set button context menu policy
self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect (self.on_context_menu)
def on_context_menu(self, point):
popMenu = QtGui.QMenu(self)
#some test list for test
testItems = ['itemA', 'itemB', 'itemC']
#
for item in testItems:
action = QtGui.Action(item)
action.triggered.connect(lambda x: print item)
popMenu.exec_(self.button.mapToGlobal(point))

How to simulate a QTest.mousePress event on a QListWidgetItem?

Forgive me if the question has already been asked, but I couldn't find the answer anywhere.
I am trying to test a small gui that contains a QListWidget and a QTreeWidget.
More specifically, I want to test the drag and drop behavior from one of the QListWidgetItem of the QListWidget to the QTreeWidget.
The ui works as intented but the problem comes when testing the drag and drop behavior as I am attempting to use QTest.mousePress() on the item, as this method only takes a QWidget as an input, and not a QListWidgetItem.
import sys
from PySide2 import QtWidgets, QtGui, QtCore
def startup():
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
class MainWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
main_layout = QtWidgets.QHBoxLayout(self)
self.source = SourceList()
main_layout.addWidget(self.source)
self.destination = DestinationTree()
main_layout.addWidget(self.destination)
self.add_items(self.source)
def add_items(self, list_widget):
items = ['apple', 'banana']
for item in items:
list_widget_item = QtWidgets.QListWidgetItem(item)
list_widget.addItem(list_widget_item)
def item_count(self):
number_of_items = 0
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
number_of_items += 1
iterator += 1
return number_of_items
class SourceList(QtWidgets.QListWidget):
def __init__(self, parent=None):
super(SourceList, self).__init__(parent)
self.setViewMode(QtWidgets.QListWidget.IconMode)
self.setDragEnabled(True)
def startDrag(self, supportedActions):
items = self.selectedItems()
name = items[0].text()
drag = QtGui.QDrag(self)
ba = bytearray(name, 'utf-8')
drag_mime_data = QtCore.QMimeData()
drag_mime_data.setData('MoveQComponentItem', QtCore.QByteArray(ba))
drag.setMimeData(drag_mime_data)
drag.exec_(QtCore.Qt.MoveAction)
class DestinationTree(QtWidgets.QTreeWidget):
def __init__(self, parent=None):
super(DestinationTree, self).__init__(parent)
self.setAcceptDrops(True)
def dragEnterEvent(self, drag_event):
mime_data = drag_event.mimeData()
if mime_data.hasFormat('MoveQComponentItem'):
drag_event.acceptProposedAction()
def dragMoveEvent(self, drag_event):
return
def dragLeaveEvent(self, drag_event):
return
def dropEvent(self, drop_event):
print('Entering Drop event')
byte_array = drop_event.mimeData().data('MoveQComponentItem')
name = byte_array.data().decode('utf8')
print(name)
item = QtWidgets.QTreeWidgetItem()
item.setText(0, name)
self.addTopLevelItem(item)
def item_count(self):
number_of_items = 0
iterator = QtWidgets.QTreeWidgetItemIterator(self)
while iterator.value():
number_of_items += 1
iterator += 1
return number_of_items
if __name__ == '__main__':
startup()
I'd like to test this with something similar to this :
def test_shouldAddOneWidgetToTheTree_whenDragingFromListItemToTree(self):
my_q_list_widget_item = main_window.source.item(0)
tree_widget = main_window.destination
QtTest.QTest.mousePress(my_q_list_widget_item, QtCore.Qt.LeftButton)
QtTest.QTest.mouseMove(tree_widget)
QtTest.QTest.mouseRelease(tree_widget, QtCore.Qt.LeftButton)
count = tree_widget.item_count()
assert count == 1
Ideally I'd need a solution that works both on python 2 and 3, also if that helps, I'm using pytest.
Any idea would be greatly appreciated :)

How to change the parent of QTreeWidget item

How do I change the parent of a of QTreeWidget item?
The process is:
Get the row number of the item using indexOfChild().
Remove the item from the parent using takeChild() by passing the index.
Add the item to the new parent.
In the following example, items that are children of the first branch are moved to the second branch.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
button = QtWidgets.QPushButton("Press me")
tree_widget = QtWidgets.QTreeWidget()
for i in range(2):
it = QtWidgets.QTreeWidgetItem(tree_widget.invisibleRootItem(),["{}".format(i)])
for j in range(4):
child_item = QtWidgets.QTreeWidgetItem(it, ["{}-{}".format(i, j)])
tree_widget.expandAll()
lay = QtWidgets.QVBoxLayout(w)
lay.addWidget(button)
lay.addWidget(tree_widget)
def change_parent(item, new_parent):
old_parent = item.parent()
ix = old_parent.indexOfChild(item)
item_without_parent = old_parent.takeChild(ix)
new_parent.addChild(item_without_parent)
#QtCore.pyqtSlot()
def on_clicked():
it = tree_widget.topLevelItem(0)
if it.childCount() > 0:
child_item = it.child(0)
new_parent = tree_widget.topLevelItem(1)
change_parent(child_item, new_parent)
button.clicked.connect(on_clicked)
w.show()
sys.exit(app.exec_())

Removing dynamically created Qmenu items

I have a Qmenu that I am creating by loading a list with Qsettings and I am trying to be able to remove items from the menu by loading the list in a QListQWidget and deleting the selected items. Currently I am able to delete the menu items in the list widget and it removes them from qsettings as well but I can't figure out how to remove the items from the menu without restarting. I have tried various things such as removeAction etc but haven't been able to figure it out.
Here is my code:
import functools
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, \
QApplication, QAction, QMenu, QListWidgetItem, \
QListWidget, QGridLayout
class MainWindow(QWidget):
settings = QtCore.QSettings('test_org', 'my_app')
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.layout = QHBoxLayout()
self.menu_btn = QPushButton()
self.menu = QMenu()
self.add_menu = self.menu.addMenu("Menu")
self.menu_btn.setMenu(self.menu)
self.open_list_btn = QPushButton('open list')
self.load_items = self.settings.value('menu_items', [])
for item in self.load_items:
self.action = QAction(item[0], self)
self.action.setData(item)
self.add_menu.addAction(self.action)
self.action.triggered.connect(functools.partial(self.menu_clicked, self.action))
self.layout.addWidget(self.menu_btn)
self.layout.addWidget(self.open_list_btn)
self.setLayout(self.layout)
self.open_list_btn.clicked.connect(self.open_window)
def open_window(self):
self.create_menu_item = List()
self.create_menu_item.show()
def menu_clicked(self, item):
itmData = item.data()
print(itmData)
class List(QWidget):
settings = QtCore.QSettings('test_org', 'my_app')
def __init__(self, parent=None):
super(List, self).__init__(parent)
self.menu_items = self.settings.value('menu_items', [])
self.layout = QGridLayout()
self.list = QListWidget()
self.remove_btn = QPushButton('Remove')
self.layout.addWidget(self.list, 1, 1, 1, 1)
self.layout.addWidget(self.remove_btn, 2, 1, 1, 1)
self.setLayout(self.layout)
self.remove_btn.clicked.connect(self.remove_items)
for item in self.menu_items:
self.item = QListWidgetItem()
self.item.setText(str(item[0]))
self.list.addItem(self.item)
def remove_items(self):
self.menu_items = self.settings.value('menu_items', [])
del self.menu_items[self.list.currentRow()]
self.settings.setValue('menu_items', self.menu_items)
listItems = self.list.selectedItems()
if not listItems: return
for item in listItems:
self.list.takeItem(self.list.row(item))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
Does anyone have any ideas?
EDIT:
This is the structure of the list in QSettings. I load the menu with this and I load the QlistWidget with this. I am trying to get the menu to remove the items as well when I remove them for the QListWidget.
mylist = ['item_name',['itemdata1', 'itemdata2', 'itemdata3'],
'item_name2',['itemdata1', 'itemdata2', 'itemdata3'],
'item_name3',['itemdata1', 'itemdata2', 'itemdata3']]
I think the data structure that you are using is incorrect because when I execute your code it generates twice as many QActions, the structure I propose is a dictionary where the keys are the name of the QAction and the value of the list of data:
{
'item0': ['itemdata00', 'itemdata01', 'itemdata02'],
'item1': ['itemdata10', 'itemdata11', 'itemdata12'],
...
}
To build the initial configuration use the following script:
create_settings.py
from PyQt5 import QtCore
if __name__ == '__main__':
settings = QtCore.QSettings('test_org', 'my_app')
d = {}
for i in range(5):
key = "item{}".format(i)
value = ["itemdata{}{}".format(i, j) for j in range(3)]
d[key] = value
settings.setValue('menu_items', d)
print(d)
settings.sync()
On the other hand I think that the widget that you want to handle the destruction of QActions should take over the corresponding QMenu as I show below:
import sys
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
menu_btn = QtWidgets.QPushButton()
open_list_btn = QtWidgets.QPushButton('open list')
layout.addWidget(menu_btn)
layout.addWidget(open_list_btn)
menu = QtWidgets.QMenu()
menu_btn.setMenu(menu)
self.menu_manager = MenuManager("menu_items", "Menu")
menu.addMenu(self.menu_manager.menu)
self.menu_manager.menu.triggered.connect(self.menu_clicked)
open_list_btn.clicked.connect(self.menu_manager.show)
def menu_clicked(self, action):
itmData = action.data()
print(itmData)
class MenuManager(QtWidgets.QWidget):
def __init__(self, key, menuname, parent=None):
super(MenuManager, self).__init__(parent)
self.settings = QtCore.QSettings('test_org', 'my_app')
self.key = key
self.layout = QtWidgets.QVBoxLayout(self)
self.listWidget = QtWidgets.QListWidget()
self.remove_btn = QtWidgets.QPushButton('Remove')
self.layout.addWidget(self.listWidget)
self.layout.addWidget(self.remove_btn)
self.remove_btn.clicked.connect(self.remove_items)
self.menu = QtWidgets.QMenu(menuname)
load_items = self.settings.value(self.key, [])
for name, itemdata in load_items.items():
action = QtWidgets.QAction(name, self.menu)
action.setData(itemdata)
self.menu.addAction(action)
item = QtWidgets.QListWidgetItem(name)
item.setData(QtCore.Qt.UserRole, action)
self.listWidget.addItem(item)
def remove_items(self):
for item in self.listWidget.selectedItems():
it = self.listWidget.takeItem(self.listWidget.row(item))
action = it.data(QtCore.Qt.UserRole)
self.menu.removeAction(action)
self.sync_data()
def sync_data(self):
save_items = {}
for i in range(self.listWidget.count()):
it = self.listWidget.item(i)
action = it.data(QtCore.Qt.UserRole)
save_items[it.text()] = action.data()
self.settings.setValue(self.key, save_items)
self.settings.sync()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I got it figured out. I am not sure of a better way but I did it using object names.
In the MainWindow I set objectNames to self.action using the first item of each list in the list of lists inside the for loop like this:
self.action.setObjectName(item[0])
Then I created this function in the MainWindow class:
def remove_menu_item(self, value):
self.add_menu.removeAction(self.findChild(QAction, value))
Then I added this:
w.remove_menu_item(item.text())
To the remove function in the List class to get the same first item in the list of lists which is now the objectName for the QActions.

How to remove Item from QListWidget

I'm stuck using myItem.hide() method every time I need to remove Item from QListWidget list. Hiding an item instead of deleting/removing makes things unnecessary complex. I would appreciate if you show me how to delete Item from ListWidget permanently.
from PyQt4 import QtGui, QtCore
class MyApp(object):
def __init__(self):
super(MyApp, self).__init__()
self.mainWidget = QtGui.QWidget()
self.mainLayout = QtGui.QVBoxLayout()
self.mainWidget.setLayout(self.mainLayout)
self.hLayout = QtGui.QHBoxLayout()
self.mainLayout.insertLayout(0, self.hLayout)
self.listA=QtGui.QListWidget()
for i in range(3):
self.listA.addItem('Item '+str(i))
self.hLayout.addWidget(self.listA)
self.buttonGroupbox = QtGui.QGroupBox()
self.buttonlayout = QtGui.QVBoxLayout()
self.buttonGroupbox.setLayout(self.buttonlayout)
okButton = QtGui.QPushButton('Remove Selected')
okButton.clicked.connect(self.removeSel)
self.buttonlayout.addWidget(okButton)
self.mainLayout.addWidget(self.buttonGroupbox)
self.mainWidget.show()
sys.exit(app.exec_())
def removeSel(self):
listItems=self.listA.selectedItems()
if not listItems: return
for item in listItems:
print type(item), dir(item)
I don't know why but removeItemWidget don't work as expected. You have to use take item instead:
def removeSel(self):
listItems=self.listA.selectedItems()
if not listItems: return
for item in listItems:
self.listA.takeItem(self.listA.row(item))
Posting here an example showing how to implement same approach but now applied to QTreeWidget which a bit more involved than QListWidget.
from PyQt4 import QtGui, QtCore
class MyApp(object):
def __init__(self):
super(MyApp, self).__init__()
self.mainWidget = QtGui.QWidget()
self.mainLayout = QtGui.QVBoxLayout()
self.mainWidget.setLayout(self.mainLayout)
self.hLayout = QtGui.QHBoxLayout()
self.mainLayout.insertLayout(0, self.hLayout)
self.listA=QtGui.QTreeWidget()
self.listA.setColumnCount(3)
self.listA.setHeaderLabels(['Checkbox','Name','Data'])
for i in range(3):
item=QtGui.QTreeWidgetItem()
item.setCheckState(0,QtCore.Qt.Checked)
item.setText(1, 'Item '+str(i))
item.setData(2, QtCore.Qt.UserRole, id(item) )
item.setText(2, str(id(item) ) )
self.listA.addTopLevelItem(item)
self.hLayout.addWidget(self.listA)
self.buttonGroupbox = QtGui.QGroupBox()
self.buttonlayout = QtGui.QVBoxLayout()
self.buttonGroupbox.setLayout(self.buttonlayout)
okButton = QtGui.QPushButton('Remove Selected')
okButton.clicked.connect(self.removeSel)
self.buttonlayout.addWidget(okButton)
getDataButton = QtGui.QPushButton('Get Items Data')
getDataButton.clicked.connect(self.getItemsData)
self.buttonlayout.addWidget(getDataButton)
self.mainLayout.addWidget(self.buttonGroupbox)
self.mainWidget.show()
sys.exit(app.exec_())
def removeSel(self):
listItems=self.listA.selectedItems()
if not listItems: return
for item in listItems:
itemIndex=self.listA.indexOfTopLevelItem(item)
self.listA.takeTopLevelItem(itemIndex)
print '\n\t Number of items remaining', self.listA.topLevelItemCount()
def getItemsData(self):
for i in range(self.listA.topLevelItemCount()):
item=self.listA.topLevelItem(i)
itmData=item.data(2, QtCore.Qt.UserRole)
itemId=itmData.toPyObject()
print '\n\t Item Id Stored as Item Data:', itemId, 'Item Checkbox State:', item.checkState(0)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
MyApp()
A ListWidget is a list of ListWidgetItems. A ListWidgetItems can be assigned a custom widget to override the default, so removeItemWidget() only removes the custom widget. Hence the need for takeItem, which pops the item from the list and returns it (similar to how a python list works)

Categories

Resources