To keep the GUI widgets number to minimum I need to find a way to give to user a choice of pull-down menu items that could be used to filter out the displayed in a listWidget items.
Let's say the listWidget lists 5 different categories of Items: "Cat A", "Cat B","Cat C","Cat D","Cat E". I could implement the radio or checkboxes for each item category. But then 5 radio buttons or checkboxes would take a lot of GUI space. A combobox with the checkable items seems to be a right choice. Any ideas?
from PyQt4 import QtGui, QtCore
import sys, os
class CheckableComboBox(QtGui.QComboBox):
def __init__(self):
super(CheckableComboBox, self).__init__()
def flags(self, index):
return Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.ComboBox = CheckableComboBox()
for i in range(3):
self.ComboBox.addItem("Combobox Item " + str(i))
myBoxLayout.addWidget(self.ComboBox)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
This idea of a multi-select combo has come up before, but I'm not sure that its the best solution. Really, all that's needed is a tool-button with a drop-down menu (similar to the history buttons in a web-browser).
Here's a basic demo that illustrates both options (button left, combo right):
PyQt5:
from PyQt5 import QtWidgets, QtGui, QtCore
class CheckableComboBox(QtWidgets.QComboBox):
def __init__(self):
super(CheckableComboBox, self).__init__()
self.view().pressed.connect(self.handleItemPressed)
self.setModel(QtGui.QStandardItemModel(self))
def handleItemPressed(self, index):
item = self.model().itemFromIndex(index)
if item.checkState() == QtCore.Qt.Checked:
item.setCheckState(QtCore.Qt.Unchecked)
else:
item.setCheckState(QtCore.Qt.Checked)
class Dialog_01(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
myQWidget = QtWidgets.QWidget()
myBoxLayout = QtWidgets.QHBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.ComboBox = CheckableComboBox()
self.toolbutton = QtWidgets.QToolButton(self)
self.toolbutton.setText('Categories ')
self.toolmenu = QtWidgets.QMenu(self)
for i in range(3):
self.ComboBox.addItem('Category %s' % i)
item = self.ComboBox.model().item(i, 0)
item.setCheckState(QtCore.Qt.Unchecked)
action = self.toolmenu.addAction('Category %s' % i)
action.setCheckable(True)
self.toolbutton.setMenu(self.toolmenu)
self.toolbutton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
myBoxLayout.addWidget(self.toolbutton)
myBoxLayout.addWidget(self.ComboBox)
if __name__ == '__main__':
app = QtWidgets.QApplication(['Test'])
dialog_1 = Dialog_01()
dialog_1.show()
app.exec_()
**PyQt4**:
from PyQt4 import QtGui, QtCore
class CheckableComboBox(QtGui.QComboBox):
def __init__(self):
super(CheckableComboBox, self).__init__()
self.view().pressed.connect(self.handleItemPressed)
self.setModel(QtGui.QStandardItemModel(self))
def handleItemPressed(self, index):
item = self.model().itemFromIndex(index)
if item.checkState() == QtCore.Qt.Checked:
item.setCheckState(QtCore.Qt.Unchecked)
else:
item.setCheckState(QtCore.Qt.Checked)
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow, self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QHBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.ComboBox = CheckableComboBox()
self.toolbutton = QtGui.QToolButton(self)
self.toolbutton.setText('Categories ')
self.toolmenu = QtGui.QMenu(self)
self.toolbutton.setMenu(self.toolmenu)
self.toolbutton.setPopupMode(QtGui.QToolButton.InstantPopup)
for i in range(3):
self.ComboBox.addItem('Category %s' % i)
item = self.ComboBox.model().item(i, 0)
item.setCheckState(QtCore.Qt.Unchecked)
action = self.toolmenu.addAction('Category %s' % i)
action.setCheckable(True)
myBoxLayout.addWidget(self.toolbutton)
myBoxLayout.addWidget(self.ComboBox)
if __name__ == '__main__':
app = QtGui.QApplication(['Test'])
dialog_1 = Dialog_01()
dialog_1.show()
app.exec_()
It is very easy. Just set the item Checkable using the flags() function in the model associated with the comboBox.
def flags(self, index):
return Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsEnabled
UPDATE
This may not be the best method, But it should sort your problem. Here is a something u may want look at , IF you have space restrictionn and cannot display the complete listView, Just resize it until it looks like a ComboBox.
(Not an answere to the question, hence I used most of the code from here)
I added a function and changed the name to RadioComboBox if anyone else wants to have a class for a RadioComboBox.
from PyQt4 import QtCore
from PyQt4.QtGui import QComboBox, QStandardItemModel
class RadioComboBox(QComboBox):
def __init__(self):
super(RadioComboBox, self).__init__()
self.view().pressed.connect(self.handle_item_pressed)
self.setModel(QStandardItemModel(self))
def handle_item_pressed(self, index):
item = self.model().itemFromIndex(index)
target_row = item.index().row()
if item.checkState() != QtCore.Qt.Checked:
item.setCheckState(QtCore.Qt.Checked)
self.check_others(target_row)
def check_others(self, target_row):
for i in range(self.model().rowCount()):
if i == target_row:
continue
else:
item = self.model().item(i)
item.setCheckState(QtCore.Qt.Unchecked)
Related
I have used the following code what it does is displays a list of values in the combo box but the difficulty that i am experiencing is that each and every time the value is check ed the drop down closes.
Is there a way possible where instead of the drop down all the menu is displayed in the main window as a list of all check boxes so that multiple values can be clicked at once.
Following is the code snippet.
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class CheckableComboBox(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(CheckableComboBox, self).__init__(parent)
self.setView(QtWidgets.QListView(self))
self.view().pressed.connect(self.handleItemPressed)
self.setModel(QtGui.QStandardItemModel(self))
def handleItemPressed(self, index):
item = self.model().itemFromIndex(index)
if item.checkState() == QtCore.Qt.Checked:
item.setCheckState(QtCore.Qt.Unchecked)
else:
item.setCheckState(QtCore.Qt.Checked)
def checkedItems(self):
checkedItems = []
for index in range(self.count()):
item = self.model().item(index)
if item.checkState() == QtCore.Qt.Checked:
checkedItems.append(item)
return checkedItems
class Ui_dialogCreateBatch(object):
def setupUi(self, dialogCreateBatch):
dialogCreateBatch.resize(400, 338)
dialogCreateBatch.setMouseTracking(True)
self.gridLayoutWidget = QtWidgets.QWidget(dialogCreateBatch)
self.gridLayoutWidget.setGeometry(QtCore.QRect(10, 10, 360, 115))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.cboItemList = CheckableComboBox(self.gridLayoutWidget)
self.cboItemList.setObjectName("cboItemList")
self.gridLayout.addWidget(self.cboItemList, 0, 0, 1, 1)
data = ('item1', 'item2', 'item3')
for index, element in enumerate(data):
self.cboItemList.addItem(element)
item = self.cboItemList.model().item(index, 0)
item.setCheckState(QtCore.Qt.Unchecked)
self.buttonBox = QtWidgets.QDialogButtonBox(dialogCreateBatch)
self.buttonBox.setGeometry(QtCore.QRect(100, 300, 156, 23))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.retranslateUi(dialogCreateBatch)
QtCore.QMetaObject.connectSlotsByName(dialogCreateBatch)
def retranslateUi(self, dialogCreateBatch):
_translate = QtCore.QCoreApplication.translate
dialogCreateBatch.setWindowTitle(_translate("dialogCreateBatch", "Create Item Batch"))
class DialogCreateBatch(QtWidgets.QDialog, Ui_dialogCreateBatch):
def __init__(self, parent=None):
QtWidgets.QDialog.__init__(self, parent)
self.setupUi(self)
self.buttonBox.accepted.connect(self.on_accepted)
self.buttonBox.rejected.connect(self.reject)
def on_accepted(self):
selectedItems = self.cboItemList.checkedItems()
print(selectedItems)
self.accept()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = DialogCreateBatch()
w.show()
sys.exit(app.exec_())
The following code implements a QDialog that shows a QListView with a model that has checkable items based on QStandardItemModel:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class DialogCreateBatch(QtWidgets.QDialog):
def __init__(self, parent=None):
super(DialogCreateBatch, self).__init__(parent)
self.model = QtGui.QStandardItemModel(self)
self.view = QtWidgets.QListView()
self.view.setModel(self.model)
self.button_box = QtWidgets.QDialogButtonBox()
self.button_box.setOrientation(QtCore.Qt.Horizontal)
self.button_box.setStandardButtons(
QtWidgets.QDialogButtonBox.Cancel | QtWidgets.QDialogButtonBox.Ok
)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.view)
lay.addWidget(self.button_box)
#property
def items(self):
items = []
for i in range(self.model.rowCount()):
it = self.model.item(i)
items.append(it.text())
return items
#items.setter
def items(self, items):
self.model.clear()
for item in items:
it = QtGui.QStandardItem(item)
it.setCheckable(True)
self.model.appendRow(it)
#property
def checked_items(self):
checked_items = []
for i in range(self.model.rowCount()):
it = self.model.item(i)
if it.data(QtCore.Qt.CheckStateRole) == QtCore.Qt.Checked:
checked_items.append(it.text())
return checked_items
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = DialogCreateBatch()
w.setWindowTitle("Stack Overflow")
w.items = ("item1", "item2", "item3")
if w.exec_() == QtWidgets.QDialog.Accepted:
print(w.checked_items)
I have a simple .xlsx file and I load its content to my qtableview. My code likes this:
import xlrd
from PyQt5 import QtCore, QtGui, QtWidgets, uic
form_class = uic.loadUiType("SearchView.ui")[0] # Load the UI
app = None
myWindow = None
dic_messages = dict()
class MyWindowClass(QtWidgets.QMainWindow, form_class):
def __init__(self, fileName, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.gui = form_class
self.fileName = fileName
self.model = QtGui.QStandardItemModel(self)
self.tableView.setModel(self.model)
self.fn_load_messages()
def fn_load_messages(self):
workbook = xlrd.open_workbook('sample.xlsx')
worksheet = workbook.sheet_by_name('sample_sheet')
for i in range(0, worksheet.nrows):
dic_messages[i + 1] = (worksheet.cell(i, 0).value, worksheet.cell(i, 1).value, worksheet.cell(i, 2).value, worksheet.cell(i, 3).value, worksheet.cell(i, 4).value)
i += 1
rowPosition = self.tbl_messages.rowCount()
self.tbl_messages.insertRow(rowPosition)
return
def main():
global app, myWindow
app = QtWidgets.QApplication(sys.argv)
myWindow = MyWindowClass('sample.xlsx')
myWindow.show()
app.exec_()
if __name__ == '__main__':
main()
i want to add a checkbox column in each row and then i want to check or unchecked each row i want. i added below part to my code but the checkboxes are not active:
self.tableView.setItemDelegateForColumn(0 , CheckBoxDelegate(self))
class CheckBoxDelegate(QtWidgets.QStyledItemDelegate):
def paint(self, painter, option, index):
check_box_style_option = QtWidgets.QStyleOptionButton()
check_box_style_option.rect = self.getCheckBoxRect(option)
check_box_style_option.state = QtWidgets.QStyle.State_Enabled
QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_CheckBox,
check_box_style_option, painter)
def getCheckBoxRect(self, option):
check_box_style_option = QtWidgets.QStyleOptionButton()
check_box_rect = QtWidgets.QApplication.style().subElementRect(QtWidgets.QStyle.SE_CheckBoxIndicator, check_box_style_option, None)
check_box_point = QtCore.QPoint (option.rect.x() +
option.rect.width() / 2 -
check_box_rect.width() / 2,
option.rect.y() +
option.rect.height() / 2 -
check_box_rect.height() / 2)
return QtCore.QRect(check_box_point, check_box_rect.size())
how can I do this?
Thanks.
You don't set it in the view but in the items and model. Like this:
#!/usr/bin/env python
from PyQt5 import QtWidgets, QtGui
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.tableModel = QtGui.QStandardItemModel(self)
self.tableModel.itemChanged.connect(self.itemChanged)
item = QtGui.QStandardItem("Click me")
item.setCheckable(True)
self.tableModel.appendRow(item)
self.mainLayout = QtWidgets.QVBoxLayout()
self.setLayout(self.mainLayout)
self.tableView = QtWidgets.QTableView()
self.tableView.setModel(self.tableModel)
self.mainLayout.addWidget(self.tableView)
def itemChanged(self, item):
print("Item {!r} checkState: {}".format(item.text(), item.checkState()))
def main():
app = QtWidgets.QApplication([])
win = MyWidget()
win.show()
win.raise_()
app.exec_()
if __name__ == "__main__":
main()
The code below creates a single dialog window with three rows of widgets:
a combo, lineEdit and dateEdit. When combobox shows 'Show LineEdit' I would like lineEdit to be visible and dateEdit to be hidden. When "Show DateEdit" is selected I would like to hide LineEdit and show DateEdit instead. How to achieve it?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setLayout(QtGui.QVBoxLayout())
for i in range(3):
row = QtGui.QHBoxLayout()
combo = QtGui.QComboBox()
combo.addItems(['Show LineEdit','Show DateEdit'])
combo.activated.connect(self.activated)
row.addWidget(combo)
self.lineEdit = QtGui.QLineEdit()
self.dateEdit = QtGui.QDateEdit()
self.dateEdit.setVisible(False)
row.addWidget(self.lineEdit)
row.addWidget(self.dateEdit)
self.layout().insertLayout(i, row)
def activated(self):
print self.sender()
panel=Dialog()
panel.show()
app.exec_()
The strategy is to create a dictionary that has as a key a combobox and values a dictionary of the corresponding other widget, then use the activated method that can return a text or a number.
from PyQt4 import QtGui
app = QtGui.QApplication([])
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setLayout(QtGui.QVBoxLayout())
self.widgets = {}
for i in range(3):
row = QtGui.QHBoxLayout()
combo = QtGui.QComboBox()
combo.addItems(['Show LineEdit', 'Show DateEdit'])
combo.activated[str].connect(self.activated)
row.addWidget(combo)
lineEdit = QtGui.QLineEdit()
dateEdit = QtGui.QDateEdit()
self.widgets[combo] = [lineEdit, dateEdit]
self.changeWidget(combo.currentText(), lineEdit, dateEdit)
row.addWidget(lineEdit)
row.addWidget(dateEdit)
self.layout().insertLayout(i, row)
def activated(self, text):
linedit, dateEdit = self.widgets[self.sender()]
self.changeWidget(text, linedit, dateEdit)
def changeWidget(self, text, linedit, dateEdit):
if text == 'Show LineEdit':
linedit.setVisible(True)
dateEdit.setVisible(False)
elif text == 'Show DateEdit':
linedit.setVisible(False)
dateEdit.setVisible(True)
panel = Dialog()
panel.show()
app.exec_()
Inspired by eyllanesc answer posted above but with no dictionary.
from PyQt4 import QtGui
app = QtGui.QApplication([])
class Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setLayout(QtGui.QVBoxLayout())
for i in range(3):
row = QtGui.QHBoxLayout()
combo = QtGui.QComboBox()
combo.addItems(['Show LineEdit', 'Show DateEdit'])
combo.activated[str].connect(self.activated)
row.addWidget(combo)
combo.lineEdit = QtGui.QLineEdit()
combo.dateEdit = QtGui.QDateEdit()
row.addWidget(combo.lineEdit)
row.addWidget(combo.dateEdit)
self.layout().insertLayout(i, row)
def activated(self, title):
combo = self.sender()
if title == 'Show LineEdit':
combo.lineEdit.setVisible(True)
combo.dateEdit.setVisible(False)
else:
combo.lineEdit.setVisible(False)
combo.dateEdit.setVisible(True)
panel = Dialog()
panel.show()
app.exec_()
Could someone help me create a recursive function which loops through the treeview QStandardItemModel and collects all items which are 'checked true'
I'm not entirely clear on how to go about doing this myself.
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.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):
parent = Person("Kevin", [
Person("Tom", [Person("Sally"), Person("Susan")]),
Person("Snappy", [Person("John"), Person("Kimmy"),
Person("Joe")]),
Person("Chester", [Person("Danny"), Person("Colleen")])
]
)
self.create_nodes(parent, 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..."
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
It can be done with the model's match method:
def get_checked(self):
model = self.treeview.model()
checked = model.match(
model.index(0, 0), QtCore.Qt.CheckStateRole,
QtCore.Qt.Checked, -1,
QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive)
for index in checked:
item = model.itemFromIndex(index)
print(item.text())
A following code creates a simple ComboBox with 12 pull-down items. To each item there was assigned an instance of MyClass() - the variable myObject using
self.ComboBox.addItem( name, myObject ).
The ComboBox itself was set to be "editable" using
self.ComboBox.setEditable(True)
Since the combobox is editable the user can double click straight into the combobox and enter a new text entry which becomes a new Combobox's pull-down item. The problem is that the text typed into the Combobox is only a string (while all other combobox items have .setData() processed. Is there any work around to make sure that even "typed in" combobox items would have myClass instance assigned?
from PyQt4 import QtGui, QtCore
import sys, os
class MyClass(object):
def __init__(self):
super(MyClass, self).__init__()
self.myAttr=None
def getTime(self):
import datetime
return datetime.datetime.now()
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.ComboBox = QtGui.QComboBox()
self.ComboBox.setEditable(True)
for i in range(12):
name='Item '+str(i)
myObject=MyClass()
self.ComboBox.addItem( name, myObject )
self.ComboBox.currentIndexChanged.connect(self.combobox_selected)
myBoxLayout.addWidget(self.ComboBox)
def combobox_selected(self, index):
myObject=self.ComboBox.itemData(index).toPyObject()
print myObject.getTime()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
Here is the working solution that worked for me.
from PyQt4 import QtGui, QtCore
import sys, os
class MyClass(object):
def __init__(self):
super(MyClass, self).__init__()
self.myAttr=None
def getTime(self):
import datetime
return datetime.datetime.now()
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.ComboBox = QtGui.QComboBox()
self.ComboBox.setEditable(True)
for i in range(12):
name='Item '+str(i)
myObject=MyClass()
self.ComboBox.addItem( name, myObject )
self.ComboBox.currentIndexChanged.connect(self.combobox_selected)
myBoxLayout.addWidget(self.ComboBox)
def combobox_selected(self, index):
itemName=self.ComboBox.currentText()
myObject=self.ComboBox.itemData(index).toPyObject()
if not hasattr(myObject, 'getTime'):
result=self.ComboBox.blockSignals(True)
self.ComboBox.removeItem(index)
myObject=MyClass()
self.ComboBox.addItem( itemName, myObject )
self.ComboBox.setCurrentIndex( index )
self.ComboBox.blockSignals(False)
print myObject.getTime()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())