PySide6 QTreeWidget Checkbox SingleSelection - checkbox

have a PySide6 QTreeWidget with some Elements and Checkboxes, very simple. What i cannot getting to work is how can i make s Single Selection with the Checkboxes? What works is SingleSelection without the Checkboxes, but not when i only use the Checkboxes itself. I wann use only Checkboxes and not Mouseclicks on the row, i did that with
tv.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
tv.setFocusPolicy(QtCore.Qt.NoFocus)
My Idea was set a itemChanged handler function for it and when a checkbox is clicked i run through all items, set all item checkboxes to uncheck with
child.setCheckState(0, QtCore.Qt.Unchecked)
and after that set the item that is selected from the handler to checked.
def handle_statechange(item):
selected_item = item.text(0)
for i in range(tv.invisibleRootItem().childCount()):
child = tv.invisibleRootItem().child(i)
child.setCheckState(0, QtCore.Qt.Unchecked)
item.setCheckState(0, QtCore.Qt.Checked)
But that doesnt work. How can i make this behavior? Thanks!
Here is small sample Code
import sys
from PySide6 import QtGui, QtCore, QtWidgets
testdict = {'TEST1': 'Testname1',
'TEST2': 'Testname2',
'TEST3': 'Testname3',
'TEST4': 'Testname4',
}
app = QtWidgets.QApplication(sys.argv)
widget = QtWidgets.QWidget()
widget.setWindowTitle("test")
widget.grid = QtWidgets.QGridLayout(widget)
widget.grid.setContentsMargins(5, 5, 5, 5)
widget.hide()
tv = QtWidgets.QTreeWidget()
tv.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
tv.setIndentation(0)
tv.setColumnCount(4)
tv.setFocusPolicy(QtCore.Qt.NoFocus)
tv.hideColumn(2)
tv.hideColumn(3)
tv.setHeaderLabels(['NORM', 'NAME'])
tv.header().setDefaultAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignLeft)
strlen_list = []
for k, v in testdict.items():
strlen_list.append(len(v))
TreeNodeItem = QtWidgets.QTreeWidgetItem
treeNode = TreeNodeItem(tv, k)
treeNode.setText( 0, k )
treeNode.setText( 1, v )
treeNode.setText( 2, '512' )
treeNode.setText( 3, '513' )
treeNode.setCheckState(0, QtCore.Qt.Unchecked)
widget.grid.addWidget(tv, 0, 0, QtCore.Qt.AlignTop)
label = QtWidgets.QLabel()
label.setText("Testlabel")
widget.grid.addWidget(label, 1, 0, QtCore.Qt.AlignTop)
button = QtWidgets.QPushButton('Testbutton', widget)
button.clicked.connect(test)
widget.grid.addWidget(button, 2, 0, QtCore.Qt.AlignLeft)
widget.show()

The check state change of items has absolutely nothing to do with the selection and is not directly correlated to mouse clicks, since the user could click on an item but not on its checkbox, or could press the space-bar to toggle the check state of the current item.
Assuming that you toggle the check state of items ONLY using mouse/keyboard and you will always have only one checked item at most, you can connect to the itemChanged signal and verify if any of its columns has a checked state.
Then you have to store the checked item and column so that you can later verify if any other item has changed and it's different from the previously set checked item.
class ToggleTree(QtWidgets.QTreeWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.lastToggled = None
self.itemChanged.connect(self.checkToggled)
def checkToggled(self, item):
for column in range(item.columnCount()):
if item.checkState(column):
break
else:
if self.lastToggled and self.lastToggled[0] == item:
self.lastToggled = None
return
if self.lastToggled:
oldItem, oldColumn = self.lastToggled
if oldItem != item:
# prevent recursion
self.itemChanged.disconnect(self.checkToggled)
oldItem.setCheckState(oldColumn, 0)
self.itemChanged.connect(self.checkToggled)
self.lastToggled = item, column
# ...
tv = ToggleTree()
Note: if you only need a 2-dimensional model like in your example, don't use QTreeWidget, but QTableWidget.

Related

Is there a way to use/get the value from a current entry window

I'm trying to get the variable that's entered in an entry widget on the Return key pressed event, but struggling a bit. What I have tried has always produced a blank result.
This code may look messy and hap-hazard, but it's only going to be a template that I'll be using on a current project!
I've tried that many things to get it to work, I can't remember what I have tried!
from collections import OrderedDict
try:
import tkinter as tk
except:
import Tkinter as tk
root = tk.Tk()
labelLIST = OrderedDict([
('Temp ID', 'tempID'),
('PO Number', "poNumber"),
('Reference', "reference"),
('Cut/Sample Date', "csDate"),
('Cut Number', "cut")
])
i = 0
e_loops = len(labelLIST)
print (e_loops)
def bval1(event=None):
for i in range(e_loops):
print (entries[i].get())
entries[0].delete(0, tk.END)
entries[0].insert(0, 'DISABLED')
entries[0].configure(state='disabled')
def bval2():
entries[0].configure(state='normal')
for i in range(e_loops):
entries[i].delete(0, tk.END)
entries[0].focus()
def onClick(event):
ent = event.widget # event.widget is the widget that called the event
print(ent.cget("text")) # Print the text for the selected button
event.widget.tk_focusNext().focus()
def enterEV(event):
# print(entries[].get())
event.widget.tk_focusNext().focus()
entries = []
for key, value in labelLIST.items():
label = tk.Label(root, text=key)
label.grid(row=i, column=0, sticky="ew", padx=1, pady=1)
entry = tk.Entry(root, width=10)
entry.grid(row=i, column=1, sticky="ew", padx=5, pady=5)
if value == "cut":
entry.bind('<Return>', bval1)
else:
# entry.bind('<Return>', enterEV)
entry.bind('<Return>', onClick)
entries.append(entry)
i = i+1
button = tk.Button(root, text="Submit", command=bval1)
button.grid(row=0, column=2, columnspan=9, sticky="ew")
button = tk.Button(root, text="Clear", command=bval2)
button.grid(row=1, column=2, columnspan=9, sticky="ew")
entries[0].focus()
tk.mainloop()
When enter/return is pressed, I want the value that is the entry box to be printed to terminal via the onClick event. But the output is always empty.
def onClick(event):
ent = event.widget # event.widget is the widget that called the event
print(ent.cget("text")) # Print the text for the selected button
event.widget.tk_focusNext().focus()
You don't use the text attribute to get the value in an Entry widget. Using cget("text") returns the value for the textvariable attribute. Since you haven't set that attribute, it will always be the empty string.
Instead, you need to call the get method:
print(ent.get())

How to remove the text from label when checkbutton is not being checked?

i'm working on this code.i want to make it display the text from the checkbutton on the label, and also remove the text when the checkbutton is not being checked.the first question is i can't remove it since the system responses
TypeError: list indices must be integers or slices, not str
the other question is i want to make a shuffle button which can shuffle the text in the label.i tried random.shuffle() but it seems doesn't work. thanks in advance!!
import tkinter as tk
import random
window = tk.Tk()
checkbutton_frame = tk.Frame(window)
checkbutton_frame.grid(column=0, row=1)
contentvar = tk.StringVar()
label = tk.Label(window, textvariable=contentvar,
bg='white', font=('Arial', 10), width=20, height=20, wraplength=50)
label.grid(column=6, row=1, padx=20,
pady=20, columnspan=2)
cb_list = ['ray', 'kevin', 'jacky']
cb_vars = []
checked = []
check_list = 1
def display():
for text, var in zip(cb_list, cb_vars):
if var.get():
checked.append(text)
contentvar.set(list(set(checked)))
else:
for i in checked:
del checked[i]
def shuffle():
random.shuffle(checked)
for r, element in enumerate(cb_list):
var = tk.BooleanVar(window, False)
cb = tk.Checkbutton(checkbutton_frame, variable=var,
text=element, command=display)
cb.grid(column=check_list, row=r, sticky='w')
cb_vars.append(var)
shuffle_button = tk.Button(window, text='SHUFFLE', command=shuffle)
shuffle_button.grid(column=8, row=2)
window.mainloop()
In the function display() you have:
else:
for i in checked:
del checked[i]
but checked is a string, so i will be a string. Try:
for i, dummy in enumerate(checked):
There were a few more things: the function display() appends to the list checked for checked items even if they are already there. This makes it look like the delete does not work. You can't see that you have to many of any item as you put them through a set before you load them into the label as set doesn't allow identical items.
Then you should set the contentvar after all processing, i.e. last in the function. Here's a version that works:
def display():
for text, var in zip(cb_list, cb_vars):
if var.get():
if text not in checked:
checked.append(text)
else:
if text in checked:
checked.remove(text)
contentvar.set(checked)
Now the texts in the label appear in the order you put them there.
The function shuffle() works just fine and shuffles the list checked. But you will have to load it into the label as well:
def shuffle():
random.shuffle(checked)
contentvar.set(checked)

How to pass input data from a tablewidget to a combo box by clicking pushbutton?

I want to input data in my table and then have it appear in my combo box after pressing 'ok' pushbutton in pyqt. May I know how to go about doing that? Whenever I run my code it just says there is an error at self.comboBox.addItem(item). I don't know which other command to use. Here is part of my code:
def setup(self, Dialog):
...
...
self.comboBox = QtGui.QComboBox(Dialog)
self.comboBox.setGeometry(QtCore.QRect(20, 100, 431, 22))
self.comboBox.setObjectName(_fromUtf8("comboBox"))
self.tableWidget = QtGui.QTableWidget(Dialog)
self.tableWidget.setGeometry(QtCore.QRect(20, 470, 651, 71))
self.tableWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.tableWidget.setTextElideMode(QtCore.Qt.ElideRight)
self.tableWidget.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerItem)
self.tableWidget.setRowCount(1)
self.tableWidget.setColumnCount(129)
self.tableWidget.setObjectName(_fromUtf8("tableWidget"))
self.tableWidget.horizontalHeader().setVisible(True)
self.tableWidget.horizontalHeader().setDefaultSectionSize(25)
self.tableWidget.horizontalHeader().setMinimumSectionSize(26)
self.tableWidget.verticalHeader().setDefaultSectionSize(25)
self.tableWidget.verticalHeader().setHighlightSections(True)
self.pushButton_7 = QtGui.QPushButton(Dialog)
self.pushButton_7.setGeometry(QtCore.QRect(220, 650, 75, 23))
self.pushButton_7.setObjectName(_fromUtf8("pushButton_7"))
self.pushButton_7.clicked.connect(self.additem)
def retranslateUi(self, Dialog):
...
...
self.tableWidget.setSortingEnabled(False)
def additem(self):
item = self.tableWidget.item(0,0)
self.comboBox.addItem(item)
UPDATE: The solution only works for the first box of my tablewidget. I tried to do it like this:
def additem(self):
while true:
item = self.tableWidget.item(0, 0).text()
self.comboBox.addItem(item)
item1 = self.tableWidget.item(0, 1).text()
self.comboBox.addItem(item1)
However, I just keep getting the error that 'NoneType' object has no attribute 'text'
UPDATE: I tried your suggestion. I tried to upload images so it would be clearer but I need 10 reputation in order to do so. Anyway, I typed this in the tablewidget: 11 22 33 44 each one in one box for four boxes. But only 11 appeared in combobox after pressing 'ok'. It doesn't work even if I typed my value on the other boxes, it only works for the first box. What I need is for them to appear as '11, 22, 33, 44' in my combo box. Would it be possible to do this? As well as for all 128 columns of my tablewidget?
in addItem(self) item is tableWidgetItem type. What you need is QString to add as comboBox item so try to correct this line only:
item = self.tableWidget.item(0,0).text()
here is working example:
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt
class Widget(QtGui.QWidget):
def __init__(self):
super(Widget, self).__init__()
self.layout = QtGui.QVBoxLayout(self)
self.comboBox = QtGui.QComboBox(self)
self.comboBox.setGeometry(QtCore.QRect(20, 100, 431, 22))
self.tableWidget = QtGui.QTableWidget(self)
self.tableWidget.setGeometry(QtCore.QRect(20, 470, 651, 71))
self.tableWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.tableWidget.setTextElideMode(QtCore.Qt.ElideRight)
self.tableWidget.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerItem)
self.tableWidget.setRowCount(1)
self.tableWidget.setColumnCount(3)
self.tableWidget.horizontalHeader().setVisible(True)
self.tableWidget.horizontalHeader().setDefaultSectionSize(25)
self.tableWidget.horizontalHeader().setMinimumSectionSize(26)
self.tableWidget.verticalHeader().setDefaultSectionSize(25)
self.tableWidget.verticalHeader().setHighlightSections(True)
self.tableWidget.setItem(0, 0, QtGui.QTableWidgetItem("first"))
self.tableWidget.setItem(0, 1, QtGui.QTableWidgetItem("second"))
self.tableWidget.setItem(0, 2, QtGui.QTableWidgetItem("third"))
self.tableWidget.resizeColumnsToContents()
self.pushButton_7 = QtGui.QPushButton("Add item 0.0 to combo")
self.pushButton_7.setGeometry(QtCore.QRect(220, 650, 75, 23))
self.pushButton_7.clicked.connect(self.additem)
self.layout.addWidget(self.comboBox)
self.layout.addWidget(self.tableWidget)
self.layout.addWidget(self.pushButton_7)
def additem(self):
item = self.tableWidget.item(0,0)#.text()
self.comboBox.addItem(item)
if __name__ == '__main__':
app = QtGui.QApplication([])
w = Widget()
w.show()
sys.exit(app.exec_())
EDIT
if you want to add all cells from table row to combo box, your additem should look like this:
def additem(self):
for i in range(0, self.tableWidget.columnCount()):
item = self.tableWidget.item(0,i)
if item != None:
text = item.text()
self.comboBox.addItem(text)
Keep in mind that if table cell is empty (item == None) nothing will be added to combo box

How to update a combo box with a list of items

I am trying to update the list of items in one combobox2 depending on the item selected in another - combobox1.
For example, if the user selects a file.mp3 in combobox1 then combobox2 will display a list of audio extension (.aac, .wav, .wma). However, if the user selects a file.flv from combobox1, combobox2 will display a list of video extensions (.mpg, mp4, .avi, .mov).
I initially thought I could accomplish this with if statements. The initial selection works, but there after, if you continue to choose different files, the combobox2 is not updated. I tried using an Event, but it didn't work.
Below if a very stripped-down version of the code so that you can get the gist:
import wx
import os
import sys
import time
from wx.lib.delayedresult import startWorker
class udCombo(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'd-Converter', size=(500, 310))
panel = wx.Panel(self, wx.ID_ANY)#Creates a panel over the widget
toolbar = self.CreateToolBar()
toolbar.Realize()
font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD)
font2 = wx.Font(7, wx.DECORATIVE, wx.NORMAL, wx.NORMAL)
directory = wx.StaticText(panel, -1, 'Path to media files: c:\\ffmpeg\\bin', (300, 13))
directory.SetFont(font2)
convertfile = wx.StaticText(panel, -1, 'File:', (270, 53))
convertfile.SetFont(font)
convertfile2 = wx.StaticText(panel, -1, 'Format:', (245, 83))
#Select Media
os.chdir("c:\\ffmpeg\\bin")
wrkdir = os.getcwd()
filelist = os.listdir(wrkdir)
self.formats1 = []
for filename in filelist:
(head, filename) = os.path.split(filename)
if filename.endswith(".avi") or filename.endswith(".mp4") or filename.endswith(".flv") or filename.endswith(".mov") or filename.endswith(".mpeg4") or filename.endswith(".mpeg") or filename.endswith(".mpg2") or filename.endswith(".wav") or filename.endswith(".mp3"):
self.formats1.append(filename)
self.format_combo1=wx.ComboBox(panel, size=(140, -1),value='Select Media', choices=self.formats1, style=wx.CB_DROPDOWN, pos=(300,50))
self.Bind(wx.EVT_COMBOBOX, self.fileFormats, self.format_combo1)
self.format_combo2=wx.ComboBox(panel, size=(100, -1),pos=(300,81))
self.Bind(wx.EVT_COMBOBOX, self.fileFormats, self.format_combo2)
def fileFormats(self, e):
myFormats = {'audio': ('.wav', '.wma', '.mp3'), 'video': ('.mpg', '.mp4', '.mpeg')}
bad_file = ['Media not supported']
myFile = self.format_combo1.GetValue()
f_exten = [x for x in myFormats['audio'] or myFormats['video'] if myFile.endswith(x)]
if f_exten[0] in myFormats['audio']:
self.format_combo2.SetItems(myFormats['audio'])
elif f_exten[0] in myFormats['video']:
self.format_combo2.SetItems(myFormats['video'])
else:
self.format_combo2.SetItems(bad_file)
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = udCombo()
frame.SetSizeHints(500,310,500,310)
frame.Show()
app.MainLoop()
Traceback error:
Traceback (most recent call last):
File "C:\Users\GVRSQA004\Desktop\udCombo.py", line 86, in fileFormats
if f_exten[0] in myFormats['audio']:
IndexError: list index out of range
Use a dictionary to hold the two lists. Then when the user clicks something in the first widget, you can call the second combobox's SetItems(myDict[selection]) method or something along those lines. The error message is because you're trying to do something with a CommandEvent that it doesn't support. They don't have an "rfind" attribute, for example.
EDIT: The new code the OP posted doesn't work because it's only running the list comprehension against the first half of the OR statement. It never runs against the "video" portion, so it returns an empty list if the user chooses anything with a video format extension. It WILL work if you select an audio selection.
Personally, I would recommend creating a video extension list and an audio list. That would be easier to understand in the future should you need to fix it later.

selected item of comboBox in custom Delegate from QTableView

I use a custom delegate to display a column of comboBoxes in my QTableView.
The values are the same for all the comboBoxes so it's not really the population part that gives me trouble.
I want them to show as the selected item, some value that I can retrieve from a database. I have access to the database from the delegate, but in order to send my request, I need the row of the comboBox.
So I guess my question is : how can you iterate over all the rows of the table and do some action from inside the custom delegate ?
If it can help here is my custom delegate class :
class ComboBoxDelegate(QtGui.QItemDelegate):
def __init__(self, parent, itemslist):
QtGui.QItemDelegate.__init__(self, parent)
self.itemslist = itemslist
self.parent = parent
def paint(self, painter, option, index):
# Get Item Data
value = index.data(QtCore.Qt.DisplayRole).toInt()[0]
# value = self.itemslist[index.data(QtCore.Qt.DisplayRole).toInt()[0]]
# fill style options with item data
style = QtGui.QApplication.style()
opt = QtGui.QStyleOptionComboBox()
opt.currentText = str(self.itemslist[value])
opt.rect = option.rect
# draw item data as ComboBox
style.drawComplexControl(QtGui.QStyle.CC_ComboBox, opt, painter)
self.parent.openPersistentEditor(index)
def createEditor(self, parent, option, index):
##get the "check" value of the row
# for row in range(self.parent.model.rowCount(self.parent)):
# print row
self.editor = QtGui.QComboBox(parent)
self.editor.addItems(self.itemslist)
self.editor.setCurrentIndex(0)
self.editor.installEventFilter(self)
self.connect(self.editor, QtCore.SIGNAL("currentIndexChanged(int)"), self.editorChanged)
return self.editor
# def setEditorData(self, editor, index):
# value = index.data(QtCore.Qt.DisplayRole).toInt()[0]
# editor.setCurrentIndex(value)
def setEditorData(self, editor, index):
text = self.itemslist[index.data(QtCore.Qt.DisplayRole).toInt()[0]]
pos = self.editor.findText(text)
if pos == -1:
pos = 0
self.editor.setCurrentIndex(pos)
def setModelData(self,editor,model,index):
value = self.editor.currentIndex()
model.setData(index, QtCore.QVariant(value))
def updateEditorGeometry(self, editor, option, index):
self.editor.setGeometry(option.rect)
def editorChanged(self, index):
check = self.editor.itemText(index)
id_seq = self.parent.selectedIndexes[0][0]
update.updateCheckSeq(self.parent.db, id_seq, check)
And I call it fromthe QTableView like this :
self.setEditTriggers(QtGui.QAbstractItemView.CurrentChanged)
self.viewport().installEventFilter(self)
self.setItemDelegateForColumn(13,ComboBoxDelegate(self, self.checkValues))
Hope I was clear enough, thanks for your attention
Not sure if accessing the database from the delegate is a right thing to do. Your delegate can contain reference to the instance of QAbstractTableModel which the QTableView refers to. You can then use methods in the model to iterate over rows of the table.

Resources