Removing dynamically created Qmenu items - python

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.

Related

How do I connect three or more windows in PyQt

I have a complex program in which I need to connect multiple windows. Unfortunately, I seem to be not fully understanding the concept/steps necessary to do this so bonus points if anyone can explain the steps/process well. In my current program, I have a list of items. Once I select them by moving them over to the right list widget, I need them to go to the third window. The third window should be activated by clicking the dots on the second window. The program runs and shows the second window appropriately but the signal/slot connection of the dots button does not work. However, the rest of the code is working because if I switch the toolkit to show the third window, that part is performing as expected. My code is below, and again, no errors are being returned, but clicking the dots button on the second window does nothing.
Also, a question - do I instantiate the third window within the second class, or only within the main window? Again, struggling to fully understand the process and I will need to do this multiple more times.
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QListWidget, QLineEdit, QTextEdit, QGridLayout, QHBoxLayout, QVBoxLayout, QSizePolicy, QFileDialog, QTabWidget, QCheckBox
import PyQt5.QtGui as qtg
import glob
import os
from PyQt5.QtCore import Qt, QSettings
import inspect
from PyQt5 import QtCore
import pandas as pd
import pathlib
import pyreadstat
import json
class ThirdWindow(QWidget):
def __init__(self):
super().__init__()
self.layout = QGridLayout()
self.setLayout(self.layout)
self.allVariables = QListWidget()
self.variablesSelected = QListWidget()
#self.allVariables.insertItem(0, 'Hello')
self.layout.addWidget(self.allVariables, 1,0)
self.layout.addWidget(self.variablesSelected, 1, 1)
def setItems(self, items):
self.allVariables.clear()
for item in items:
self.allVariables.addItem(item)
class SecondWindow(QWidget):
def __init__(self):
super().__init__()
##not sure if I am supposed to instantiate this here or only in the main window class
self.thirdWindow = ThirdWindow()
self.layout = QGridLayout(self)
self.by = QLabel("By")
self.byVariables = QLineEdit()
self.byButton = QPushButton("...")
self.layout.addWidget(self.by, 1, 0)
self.layout.addWidget(self.byVariables, 2, 0)
self.layout.addWidget(self.byButton, 2, 1)
def seconddWindowConnections(self):
self.byButton.clicked.connect(self.show_third_window)
#self.buttons['Toolkit'].clicked.connect(self.show_new_window)
def show_third_window(self):
self.thirdWindow.show()
class MainWindow(QWidget):
def __init__(self):
super().__init__()
# Add a title
self.setWindowTitle("GUI Querying Program")
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.initUI()
self.setButtonConnections()
self.sw = SecondWindow()
self.tw = ThirdWindow()
def initUI(self):
subLayouts = {}
subLayouts['LeftColumn'] = QGridLayout()
self.layout.addLayout(subLayouts['LeftColumn'],1)
# Buttons
self.buttons = {}
self.buttons['addVariable'] = QPushButton('>')
self.buttons['removeVariable'] = QPushButton('<')
self.buttons['Toolkit'] = QPushButton('Toolkit')
self.variables = QListWidget()
self.selectedVariables = QListWidget()
subLayouts['LeftColumn'].addWidget(self.variables, 7,0,4,1)
subLayouts['LeftColumn'].addWidget(self.selectedVariables, 7,1,4,1)
subLayouts['LeftColumn'].addWidget(self.buttons['addVariable'], 10,0,1,1)
subLayouts['LeftColumn'].addWidget(self.buttons['removeVariable'], 10,1,1,1)
subLayouts['LeftColumn'].addWidget(self.buttons['Toolkit'], 11,1,1,1)
names = ['apple', 'banana', 'Cherry']
self.variables.insertItems(0, names)
def setButtonConnections(self):
self.buttons['addVariable'].clicked.connect(self.add_variable)
self.buttons['Toolkit'].clicked.connect(self.show_new_window)
self.buttons['Toolkit'].clicked.connect(self.add_selected_variables)
def add_variable(self):
for item in self.variables.selectedItems():
self.selectedVariables.addItem(item.clone())
def show_new_window(self):
self.sw.show()
def add_selected_variables(self):
items = []
for i in range(self.selectedVariables.count()):
items.append(self.selectedVariables.item(i).clone())
self.tw.setItems(items)
if __name__ == "__main__":
import sys
app = QApplication([])
mw = MainWindow()
mw.show()
app.exec()
The main issue with your code is that secondWindowConnections is never called so the button actually does nothing. I corrected that and fixed a few other issues I found in my example below. I left out the bits where I made no changes and all the changes I did make I made inline notes explaining them:
class SecondWindow(QWidget):
def __init__(self):
super().__init__()
self.thirdWindow = None # dont initialize until neccessary
self.thirdWindowItems = []
self.layout = QGridLayout(self)
self.by = QLabel("By")
self.byVariables = QLineEdit()
self.byButton = QPushButton("...")
self.layout.addWidget(self.by, 1, 0)
self.layout.addWidget(self.byVariables, 2, 0)
self.layout.addWidget(self.byButton, 2, 1)
self.secondWindowConnections() # Run this to setup the
# signal for the third window.
def secondWindowConnections(self): # this had a typo
self.byButton.clicked.connect(self.show_third_window)
def show_third_window(self):
if self.thirdWindow is None: # if window has been created yet
self.thirdWindow = ThirdWindow() # create window
if not self.thirdWindow.isVisible(): # if window is showing
self.thirdWindow.show() # show window
self.thirdWindow.setItems(self.thirdWindowItems) # send items to window
def send_items(self, items): # this is to collect the variable that
self.thirdWindowItems = items # move to the third window
class MainWindow(QWidget):
def __init__(self):
super().__init__()
# Add a title
self.setWindowTitle("GUI Querying Program")
self.layout = QHBoxLayout()
self.setLayout(self.layout)
self.initUI()
self.setButtonConnections()
self.sw = None # dont initialize until neccessary.
def initUI(self):
subLayouts = {}
subLayouts['LeftColumn'] = QGridLayout()
self.layout.addLayout(subLayouts['LeftColumn'],1)
self.buttons = {}
self.buttons['addVariable'] = QPushButton('>')
self.buttons['removeVariable'] = QPushButton('<')
self.buttons['Toolkit'] = QPushButton('Toolkit')
self.variables = QListWidget()
self.selectedVariables = QListWidget()
subLayouts['LeftColumn'].addWidget(self.variables, 7,0,4,1)
subLayouts['LeftColumn'].addWidget(self.selectedVariables, 7,1,4,1)
subLayouts['LeftColumn'].addWidget(self.buttons['addVariable'], 10,0,1,1)
subLayouts['LeftColumn'].addWidget(self.buttons['removeVariable'], 10,1,1,1)
subLayouts['LeftColumn'].addWidget(self.buttons['Toolkit'], 11,1,1,1)
names = ['apple', 'banana', 'Cherry']
self.variables.insertItems(0, names)
def setButtonConnections(self):
self.buttons['addVariable'].clicked.connect(self.add_variable)
self.buttons['Toolkit'].clicked.connect(self.show_new_window)
# self.buttons['Toolkit'].clicked.connect(self.add_selected_variables)
# only use one connnect slot
def add_variable(self):
for item in self.variables.selectedItems():
self.selectedVariables.addItem(item.clone())
def show_new_window(self):
if self.sw is None: # check if window has been constructed
self.sw = SecondWindow() # construct window
if not self.sw.isVisible(): # If winow is not showing
self.sw.show() # show window
self.sw.send_items(self.add_selected_variables()) # send selected
# variables to second window
def add_selected_variables(self):
items = []
for i in range(self.selectedVariables.count()):
items.append(self.selectedVariables.item(i).clone())
# self.tw.setItems(items) ... self.tw doesnt exist so return them
return items

QGroupBox buttons not showing

So basically I'm trying to make an updating QGroupBox with some QPushButtons inside of it, here's the "updating" method and it is always called right after the list is changed:
def repaintList(self):
btn_ls = []
for i in range(len(self.list)):
btn_ls.append(buttons.QPushButton("t"))
layout = QVBoxLayout()
for i in range(len(btn_ls)):
layout.addWidget(btn_ls[i])
it's pretty simple, I have a method that updates the list for me and I've tested the functionality with print(len(self.list)) and print(btn_ls) enough to know that the list updating works, and that the btn_ls is made properly, but I'm not sure why it's not updating on the actual screen.
I've made an example of a simplified version of what I'm trying to accomplish:
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TestWindow(QWidget):
def __init__(self):
super(TestWindow,self).__init__()
self.list = []
self.cont = TestContainer(self.list, "Test Box", self)
self.adder = AdderButton("Add one", self, self.list, self.cont)
layout = QVBoxLayout()
layout.addWidget(self.cont)
self.setLayout(layout)
self.setGeometry(200,200,200,200)
class TestContainer(QGroupBox):
def __init__(self,ls,ttl,pare):
super(TestContainer,self).__init__(ttl,pare)
self.list = ls
self.repaintButtons()
def repaintButtons(self):
btn_ls = []
for i in range(len(self.list)):
btn_ls.append(QPushButton(str(i),self))
print(self.list)
layout = QVBoxLayout()
for i in range(len(btn_ls)):
layout.addWidget(btn_ls[i])
self.setLayout(layout)
class AdderButton(QPushButton):
def __init__(self,txt,pare,ls,displ):
super(AdderButton,self).__init__(txt,pare)
self.disp = displ
self.list = ls
self.clicked.connect(self.addOne)
def addOne(self):
self.list.append(1)
self.disp.repaintButtons()
def main():
app = QApplication(sys.argv)
tw = TestWindow()
tw.show()
app.exec()
if __name__ == "__main__":
main()
The desired result is that every time I press the button a new QPushButton would appear on the screen...
After doing more research, I came across the update() function which basically repaints the QGroupBox. It works in the sense that it adds one button at a time and updates each time a button is added.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TestWindow(QWidget):
def __init__(self):
super(TestWindow,self).__init__()
self.list = []
self.cont = TestContainer(self.list, "Test Box", self)
self.adder = AdderButton("Add one", self, self.list, self.cont)
layout = QVBoxLayout()
layout.addWidget(self.cont)
self.setLayout(layout)
self.setGeometry(200,200,200,200)
class TestContainer(QGroupBox):
def __init__(self,ls,ttl,pare):
super(TestContainer,self).__init__(ttl,pare)
self.list = ls
self.layout = QVBoxLayout()
self.setLayout(self.layout)
def addButton(self):
self.layout.addWidget(QPushButton("thing"))
class AdderButton(QPushButton):
def __init__(self,txt,pare,ls,displ):
super(AdderButton,self).__init__(txt,pare)
self.disp = displ
self.list = ls
self.clicked.connect(self.addOne)
def addOne(self):
self.list.append(1)
self.disp.addButton()
self.disp.update()
def main():
app = QApplication(sys.argv)
tw = TestWindow()
tw.show()
app.exec()
if __name__ == "__main__":
main()
I has to use self.layout() instead of layout to see widgets.
def repaintButtons(self):
btn_ls = []
for i in range(len(self.list)):
btn_ls.append(QPushButton(str(i),self))
print(self.list)
layout = QVBoxLayout()
self.setLayout(layout) # it has to be before `self.layout()`
for i in range(len(btn_ls)):
self.layout().addWidget(btn_ls[i])
BTW: self.setLayout() has to be before self.layout()
But there is other problem.
Using again
layout = QVBoxLayout()
self.setLayout(layout)
doesn't remove previous layout and it doesn't remove previous buttons and finally it adds again the same buttons to layout.
You would need add only new buttons to layout instead of adding all buttons again.
Or you would have to remove widgets from layout before adding them again.
EDIT:
Code which adds new button without removing previous buttons
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TestWindow(QWidget):
def __init__(self):
super(TestWindow,self).__init__()
self.list = []
self.container = TestContainer("Test Box", self, self.list)
self.adder = AdderButton("Add one", self, self.container)
layout = QVBoxLayout()
layout.addWidget(self.container)
self.setLayout(layout)
self.setGeometry(200,200,200,200)
class TestContainer(QGroupBox):
def __init__(self, title, parent, lst):
super(TestContainer,self).__init__(title, parent)
self.list = lst
layout = QVBoxLayout()
self.setLayout(layout)
# create buttons at start using `self.list`
for item in self.list:
self.layout().addWidget(QPushButton(str(item), self))
def addButton(self, item):
# add new item to `self.list` and create new button
self.list.append(item)
self.layout().addWidget(QPushButton(str(item), self))
class AdderButton(QPushButton):
def __init__(self, text, parent, target):
super(AdderButton,self).__init__(text, parent)
self.target = target
self.clicked.connect(self.addOne)
def addOne(self):
self.target.addButton(1)
def main():
app = QApplication(sys.argv)
tw = TestWindow()
tw.show()
app.exec()
if __name__ == "__main__":
main()
There are examples how to remove items but they don't work for me
Clear all widgets in a layout in pyqt
PyQt How to remove a layout from a layout

How to populate several QComboBox from a QFileSystemModel?

How does one use a QFileSystemModel to populate several QComboBox with subdirectories?
I have built a project management tool that allows me to create and manage my projects. I am currently using a combination of os.listdir and json to populate and validate my QComboboxes. But I am trying to learn a more modelview approach with QFileSystemModel.
So this is what I have:
class FileSystemModel(QW.QFileSystemModel):
def __init__(self, root, parent=None):
QW.QFileSystemModel.__init__(self, parent)
self.root = root
self.rootIndex = self.setRootPath(root)
class Window(QW.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__()
self.init()
def init(self):
layout = QW.QVBoxLayout()
self.cbox = QW.QComboBox()
self.cbox2 = QW.QComboBox()
self.model = FileSystemModel("C:\\projects\\")
self.cbox.setModel(self.model)
self.cbox2.setModel(self.model)
self.cbox.setRootModelIndex(self.model.rootIndex)
self.cbox.currentIndexChanged.connect(self._indexChanged)
layout.addWidget(self.cbox)
layout.addWidget(self.cbox2)
self.setLayout(layout)
def _indexChanged(self):
row = self.sender().currentIndex()
index = self.sender().rootModelIndex().child(row, 0)
self.cbox2.setRootModelIndex(index)
def main():
app = QW.QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
I was attempting to repopulate the cbox2 using the index from cbox, but with my code it doesn't seem to work - it just stays empty.
Okay here is modified version of what you had:
from sys import exit as sysExit
from PyQt5.QtCore import QDir, pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QFileSystemModel, QHBoxLayout, QComboBox
class SysDirModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
class SysFileModel(QFileSystemModel):
def __init__(self, DirPath):
QFileSystemModel.__init__(self)
self.setFilter(QDir.NoDotAndDotDot | QDir.Files)
self.setReadOnly(True)
# Property
self.setRootPath(DirPath)
# Property
self.RootIndex = self.index(DirPath)
def ResetPath(self, DirPath):
self.setRootPath(DirPath)
self.RootIndex = self.index(DirPath)
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.setGeometry(150, 150, 450, 100)
# If you use forward slash this works in Windows as well and it is cleaner
self.SysDirs = SysDirModel('C:/projects/')
self.SysFils = SysFileModel('C:/projects/')
# Setup first ComboBox
self.cbxDirs = QComboBox()
self.cbxDirs.setMinimumWidth(200)
self.cbxDirs.setModel(self.SysDirs)
self.cbxDirs.setRootModelIndex(self.SysDirs.RootIndex)
# This sends a Signal to a predefined Slot
self.cbxDirs.currentIndexChanged.connect(self.IndexChanged)
self.cbxFiles = QComboBox()
self.cbxFiles.setMinimumWidth(200)
self.cbxFiles.setModel(self.SysFils)
self.cbxFiles.setRootModelIndex(self.SysFils.RootIndex)
HBox = QHBoxLayout()
HBox.addWidget(self.cbxDirs)
HBox.addStretch(1)
HBox.addWidget(self.cbxFiles)
self.setLayout(HBox)
# This is the receiver of a Signal (aka Slot) so it ought to be used as such
#pyqtSlot(int)
def IndexChanged(self, RowIdx):
# Get your Current DirPath based on the Selected Value
index = self.cbxDirs.rootModelIndex().child(RowIdx, 0)
DirPath = self.cbxDirs.model().filePath(index)
# Reset what ComboBox 2's Model and what it is looking at
self.cbxFiles.clear()
self.SysFils.ResetPath(DirPath)
self.cbxFiles.setModel(self.SysFils)
if __name__ == '__main__':
MainThred = QApplication([])
MainGui = MainWindow()
MainGui.show()
sysExit(MainThred.exec_())

Return top items from QTreeview selection Pyside

I have a QTreeview and based on the users selection I would like to get a unique array containing all the names of the parents of the selected items.
So if any children are selected it would return the parent and if a parent is selected it would still return the parent.
Want returned:
>> [Kevin, Michelle, Nikki, Tim]
from PySide import QtGui, QtCore
from PySide import QtSvg, QtXml
import sys
class Person:
def __init__(self, name="", children=None):
self.name = name
self.children = children if children else []
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(300, 400)
self.init_ui()
def init_ui(self):
# Setup Tabs Widget
# self.treeview = QtGui.QTreeView()
self.treeview = QtGui.QTreeView()
self.treeview.setHeaderHidden(True)
self.treeview.setUniformRowHeights(True)
self.treeview.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.treeview.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.model = QtGui.QStandardItemModel()
self.treeview.setModel(self.model)
self.action = QtGui.QAction('Print', self)
self.action.setShortcut('F5')
self.action.triggered.connect(self.get_checked)
fileMenu = QtGui.QMenu("&File", self)
fileMenu.addAction(self.action)
self.menuBar().addMenu(fileMenu)
# Setup central widget
self.setCentralWidget(self.treeview)
# populate data
self.populate_people()
self.treeview.expandAll()
def populate_people(self):
parents = [
Person("Kevin", [Person("Tom"), Person("Sarah"), Person("Chester")]),
Person("Michelle", [Person("James"), Person("Corey"),Person("Leslie")]),
Person("Doug", [Person("Fred"), Person("Harold"),Person("Stephen")]),
Person("Nikki", [Person("Brody"), Person("Tyson"),Person("Bella")]),
Person("Tim", [Person("Marie"), Person("Val"),Person("Ted")])
]
for p in parents:
self.create_nodes(p, self.model)
def create_nodes(self, node, parent):
tnode = QtGui.QStandardItem()
tnode.setCheckable(True)
tnode.setData(QtCore.Qt.Unchecked, role=QtCore.Qt.CheckStateRole)
tnode.setData(node.name , role=QtCore.Qt.DisplayRole)
tnode.setData(node, role=QtCore.Qt.UserRole) # store object on item
parent.appendRow(tnode)
for x in node.children:
self.create_nodes(x, tnode)
def get_checked(self):
print "collecting parents..."
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here is a simple implementation:
def selectedParents(self):
parents = set()
for index in self.treeview.selectedIndexes():
while index.parent().isValid():
index = index.parent()
parents.add(index.sibling(index.row(), 0))
return [index.data() for index in sorted(parents)]
Note that selectedIndexes() returns the indexes in the order they were selected, and, if there are multiple columns, will include the index of every column in the selected row.
So the above method makes sure only the text from the first column is included, and also makes sure that the items are returned in the correct order.

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