How to change the parent of QTreeWidget item - python

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_())

Related

how to get text in second QListWidget, the second QListWidget is in QStackedWidget connect to first QListWidget

I want to get text in second QListWidget, but it will give me wrong row number.
I know how to get text in a QListWidget, just use
QListWidget.currentRow()
to get row number and
QListWidget.currentItem().text()
to get text
But the text in second QListWidget, the second QListWidget is in QStackedWidget connect to first QListWidget, the row will always print(-1) and can't print(currentItem().text()
So How can I do to get text in second QListWidget?
Example code is here:
import sys
from PyQt5.QtWidgets import *
from numpy.random import randint
# data
d = {i: [j for j in randint(100, size=10)] for i in randint(100, size=10)}
d_key = list(d.keys())
class MainTest(QMainWindow):
def __init__(self):
super().__init__()
mhbox = QHBoxLayout()
# create first QlistWidget = self.leftlist
self.leftlist = QListWidget()
self.Stack = QStackedWidget()
self.stack = {}
for i in range(len(d_key)):
self.leftlist.insertItem(i, f"{d_key[i]}") # create item with key in d
self.stack[i] = QWidget()
self.Stack.addWidget(self.stack[i])
self.stack_ui(i)
mhbox.addWidget(self.leftlist)
mhbox.addWidget(self.Stack)
self.leftlist.currentRowChanged.connect(self.Stack.setCurrentIndex)
widget = QWidget()
widget.setLayout(mhbox)
self.setCentralWidget(widget)
self.show()
def stack_ui(self, i):
list_in_key = d[d_key[i]] # get list in d[key]
self.leftlist_in_leftlist = QListWidget()
for n in range(len(list_in_key)):
self.leftlist_in_leftlist.insertItem(n, f"{list_in_key[n]}")
self.leftlist_in_leftlist.currentRowChanged.connect(self.get_name)
self.hbox_in_leftlist = QHBoxLayout()
self.hbox_in_leftlist.addWidget(self.leftlist_in_leftlist)
self.stack[i].setLayout(self.hbox_in_leftlist)
def get_name(self):
print(self.leftlist_in_leftlist.currentRow()) # always give me -1
# print(self.leftlist_in_leftlist.currentItem().text()) # can't not print, it's will get wrong.
if __name__ == '__main__':
app = QApplication(sys.argv)
m = MainTest()
sys.exit(app.exec_())

Set selection of item in QStandarItemModel with Qlistview

I have a Qlistview with some items. I want to set selection at first item at startup of window. selectionModel().selectedRows() returns the selectet item. But
QAbstractItemModel().setCurrentIndex(0) does't select the item. How could that be done like setSelection(INDEX).
self.listView = QtWidgets.QListView()
self.entry = QtGui.QStandardItemModel()
self.listView.setModel(self.entry)
----------
self.listView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
self.listView.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.listView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
----------
self.listView.QAbstractItemModel().setCurrentIndex(0) #<------ Not really working
selection = self.listView.selectionModel().selectedRows()
print(selection)
If you want to select an item then you must use the select() method of selectionModel():
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.listView = QtWidgets.QListView(
editTriggers=QtWidgets.QAbstractItemView.NoEditTriggers,
selectionMode=QtWidgets.QAbstractItemView.SingleSelection,
selectionBehavior=QtWidgets.QAbstractItemView.SelectRows,
)
self.entry = QtGui.QStandardItemModel()
self.listView.setModel(self.entry)
for letter in list("abcdefghijklmnopqrstuvwxyz"):
it = QtGui.QStandardItem(letter)
self.entry.appendRow(it)
ix = self.entry.index(0, 0)
sm = self.listView.selectionModel()
sm.select(ix, QtCore.QItemSelectionModel.Select)
self.setCentralWidget(self.listView)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())

Interactive interaction of selections with checkboxes

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'}}

How do I get a clicked item in a QTreeWidget?

I have a two-dimensional QTreeWidget, how can I get an Item by clicking on it? I use PyQt5. I have this part of the code, but it gets only the first item of the selected row (or any other by changing the baseNode.text(#))
...
self.treeWidget.itemSelectionChanged.connect(lambda: loadAllMessages())
def loadAllMessages():
getSelected = self.treeWidget.selectedItems()
if getSelected:
baseNode = getSelected[0]
getChildNode = baseNode.text(0)
print(getChildNode)
...
You just have to use the itemClicked signal sent by the QTreeWidgetItem and the clicked column:
Example:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
tree = QtWidgets.QTreeWidget()
tree.setColumnCount(2)
lay.addWidget(tree)
for i in range(4):
parent_it = QtWidgets.QTreeWidgetItem(["{}-{}".format(i, l) for l in range(2)])
tree.addTopLevelItem(parent_it)
for j in range(5):
it = QtWidgets.QTreeWidgetItem(["{}-{}-{}".format(i, j, l) for l in range(2)])
parent_it.addChild(it)
tree.expandAll()
tree.itemClicked.connect(self.onItemClicked)
#QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int)
def onItemClicked(self, it, col):
print(it, col, it.text(col))
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
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.

Categories

Resources