How to save selected input from a drop-down box with checkboxes? My drop-down box is okay but the checkboxes are not working - python-3.x

Hi, I am trying to run a GUI based on wx. I made checkboxes in a drop-down list. But the checkboxes are not working. I want the checkboxes to work and save the selected input option names somewhere. Here is the code. With these selected input option names (one or more than one) I will filter a big string later. Thank you in advance :)
import wx
import wx.stc
from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):
def __init__(self, parent):
wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT |
wx.SUNKEN_BORDER)
CheckListCtrlMixin.__init__(self)
ListCtrlAutoWidthMixin.__init__(self)
self.SetSize(-1, -1, -1, 50)
def OnCheckItem(self, index, flag):
item = self.GetItem(index)
if flag:
what = "checked"
else:
what = "unchecked"
print(f'{item.GetText()} - {what}')
class ListViewComboPopup(wx.ComboPopup):
def __init__(self):
wx.ComboPopup.__init__(self)
self.lc = None
def AddItem(self, txt):
self.lc.InsertItem(0, txt)
def Init(self):
self.value = -1
self.curitem = -1
def Create(self, parent):
self.lc = CheckListCtrl(parent)
self.lc.InsertColumn(0, '', width=90)
return True
def GetControl(self):
return self.lc
def OnPopup(self):
wx.ComboPopup.OnPopup(self)
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
return wx.ComboPopup.GetAdjustedSize(
self, minWidth, 110, maxHeight)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Popup Menu Tutorial")
panel = wx.Panel(self)
comboCtrl = wx.ComboCtrl(panel, wx.ID_ANY, "Select filter")
popupCtrl = ListViewComboPopup()
comboCtrl.SetPopupControl(popupCtrl)
popupCtrl.AddItem("mango")
popupCtrl.AddItem("cat")
popupCtrl.AddItem("dog")
popupCtrl.AddItem("tiger")
popupCtrl.AddItem("three")
popupCtrl.AddItem("hat")
popupCtrl.AddItem("hot")
popupCtrl.AddItem("sweden")
popupCtrl.AddItem("kth")

You can saev them as variables in a .txt file with pickle:
pickle.dump(your_variable, open("path/to/your/file.txt", "wb"))
and to access them:
your_variable = pickle.load(open("path/to/your/file.txt", "rb"))

Related

PYQT QTableView Delegate can not show createEditor when applied with Proxy

I'm having problem to show the Editor Widget when Delegate is applied with the Proxy situation.
-> self.table.setModel(self.proxy)
If the Delegate is applied to the View/Model structure, then there is no problem at all.
-> #self.table.setModel(self.model)
Refer to: https://www.pythonfixing.com/2021/10/fixed-adding-row-to-qtableview-with.html
See the code below:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
comboBox.addItems(self.type_items)
return comboBox
# no need to check for the other columns, as Qt automatically creates a
# QLineEdit for string values and QTimeEdit for QTime values;
return super().createEditor(parent, option, index)
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def appendRowData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._data.append(data)
self.endInsertRows()
def data(self, index, role=Qt.DisplayRole):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
# allow editing of the index
return super().flags(index) | Qt.ItemIsEditable
class CustomProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filters = dict()
#property
def filters(self):
return self._filters
def setFilter(self, expresion, column):
if expresion:
self.filters[column] = expresion
elif column in self.filters:
del self.filters[column]
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
for column, expresion in self.filters.items():
text = self.sourceModel().index(source_row, column, source_parent).data()
regex = QRegExp(
expresion, Qt.CaseInsensitive, QRegExp.RegExp
)
if regex.indexIn(text) == -1:
return False
return True
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView(localWidget)
data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]
self.model = TableModel(data)
self.proxy = CustomProxyModel() # Customized Filter
self.proxy.setSourceModel(self.model)
#self.table.setModel(self.model) # Original code, for View/Model
self.table.setModel(self.proxy) # Revised code, for View/Proxy/Model
self.table.setItemDelegate(Delegate())
self.add_row = QPushButton("Add Row", localWidget)
self.add_row.clicked.connect(self.addRow)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.add_row)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def addRow(self):
row = self.model.rowCount()
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.appendRowData(new_row_data)
for i in range(self.model.columnCount()):
index = self.model.index(row, i)
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Test with View/Model, Widget Editor display.
Test with View/Proxy/Model, Widget Editor not display.
Any attempt to access the view indexes must use the view's model.
Your code doesn't work because the index you are providing belongs to another model, so the editor cannot be created because the view doesn't recognize the model of the index as its own: the view uses the proxy model, while you're trying to open an editor for the source model.
While in this case the simplest solution would be to use self.proxy.index(), the proper solution is to always refer to the view's model.
Change both self.model.index(...) to self.table.model().index(...).
Yes, thanks you very much.
Revised the code as below, now the Widget Editor display accordingly.
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
#index = self.model.index(row, column) # original code, which get the wrong index from the model
index = self.proxy.index(row, column) # revised code, get the correct index from the proxy
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor

validating specific column of QTreeWidget using QStyledItemDelegate not working properly

I have a QTreeWidget with 3 columns, only the 3rd column should have a double validator. My issue is that it somewhat works; The QLineEdit does not stop at 100 it just keeps going.
what I am missing; to make it where it doesn't let user input anything higher than 100 while still editing the lineEdit?
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class Delegate(QStyledItemDelegate):
'Changes number of decimal places in gas analysis self.chosen table'
def __init__(self, decimals, parent=None):
super().__init__(parent)
self.nDecimals = decimals
def createEditor(self, parent, option, index):
editor = QLineEdit(parent)
editor.setValidator(QDoubleValidator(0,100, 15))
return editor
def setEditorData(self, editor, index):
if index.column() == 2 and index.data() is not None:
editor.setText(str(float(index.data())))
class Widget(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent=None)
self.tree_widget = QTreeWidget()
self.tree_widget.setItemDelegate(Delegate(self.tree_widget))
self.tree_widget.setHeaderLabels(["Value1", "Value2", "Value3"])
self.setCentralWidget(self.tree_widget)
for vals in [("h", "20.0", "40.0"), ("k", "25.0", "50.0")]:
it = QTreeWidgetItem(vals)
it.setFlags(it.flags()| Qt.ItemIsEditable)
self.tree_widget.addTopLevelItem(it)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
The QDoubleValidator only tries to validate the text entered by the user, if it does not meet the requirement then the information will not be saved and that is what is observed in your case. If you want to define what values you want the user to use in your specific case, it is better to use the QDoubleSpinBox as editor:
class Delegate(QStyledItemDelegate):
def __init__(self, decimals, parent=None):
super().__init__(parent)
self.nDecimals = decimals
def createEditor(self, parent, option, index):
editor = QDoubleSpinBox(
parent, minimum=0, maximum=100.0, decimals=self.nDecimals
)
return editor
def setEditorData(self, editor, index):
if index.data() is not None:
try:
value = float(index.data())
except Exception as e:
print("error", e)
else:
editor.setValue(value)
class Widget(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent=None)
self.tree_widget = QTreeWidget()
self.tree_widget.setHeaderLabels(["Value1", "Value2", "Value3"])
self.tree_widget.setItemDelegateForColumn(2, Delegate(15, self.tree_widget))
self.setCentralWidget(self.tree_widget)
for vals in [("h", "20.0", "40.0"), ("k", "25.0", "50.0")]:
it = QTreeWidgetItem(vals)
it.setFlags(it.flags() | Qt.ItemIsEditable)
self.tree_widget.addTopLevelItem(it)
Note: I have modified the column selection since with the initial implementation of the OP the same editor is used in all the columns and that clearly contradicts its same requirement, so instead of using setItemDelegate() I have used setItemDelegateForColumn().

Using QComboBox in QTableView properly - issues with data being set and clearing QComboBoxes

In my application im using a QTableView, QStandardItemModel and a QSortFilterProxyModel in between for filtering.
The content is updated via a method for columns 1 & 2, and I want there to be a 3rd column for user to select options. I would prefer to use a QComboBox.
I've got everything pretty much working, except that when I select the item from the QComboBox in any of the cells in column 3, it doesn't populate. Does it have something to do with my setModelData() method?
I also have a clear button that I would like to reset all of the QComboBoxes to the first item which is an empty entry. I am not sure how to tackle this, i've found such things as using deleteLater() or setting the QTableView's setItemDelegateForColumn() to None and re-apply.
Obviously these are not the most efficient. What am I missing?
Working example:
import win32com.client
from PyQt5 import QtCore, QtGui, QtWidgets
outApp = win32com.client.gencache.EnsureDispatch("Outlook.Application")
outGAL = outApp.Session.GetGlobalAddressList()
entries = outGAL.AddressEntries
class ComboDelegate(QtWidgets.QItemDelegate):
def __init__(self,parent=None):
super().__init__(parent)
self.items = ['','To', 'CC']
def createEditor(self, widget, option, index):
editor = QtWidgets.QComboBox(widget)
editor.addItems(self.items)
return editor
def setEditorData(self, editor, index):
if index.column() == 2:
editor.blockSignals(True)
text = index.model().data(index, QtCore.Qt.EditRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
editor.blockSignals(False)
else:
QtWidgets.QItemDelegate.setModelData(editor,model,index)
def setModelData(self, editor, model, index):
if index.column() == 2:
model.setData(index, editor.currentText())
else:
QtWidgets.QItemDelegate.setModelData(editor,model,index)
def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect)
def paint(self, painter, option, index):
QtWidgets.QApplication.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, option, painter)
class App(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
"""This method creates our GUI"""
self.centralwidget = QtWidgets.QWidget()
self.setCentralWidget(self.centralwidget)
self.lay = QtWidgets.QVBoxLayout(self.centralwidget)
self.filterEdit = QtWidgets.QLineEdit()
self.filterEdit.setPlaceholderText("Type to filter name.")
self.label = QtWidgets.QLabel("Select an option for each person:")
self.button = QtWidgets.QPushButton("Test Button")
self.button.clicked.connect(self.runButton)
self.resetbutton = QtWidgets.QPushButton("Clear")
self.resetbutton.clicked.connect(self.clear)
self.lay.addWidget(self.filterEdit)
self.lay.addWidget(self.label)
self.tableview=QtWidgets.QTableView(self.centralwidget)
self.model=QtGui.QStandardItemModel()
self.model.setHorizontalHeaderLabels(['Name','Address','Option'])
self.tableview.verticalHeader().hide()
self.tableview.setSelectionBehavior(QtWidgets.QTableView.SelectRows)
self.tableview.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
self.proxyModel = QtCore.QSortFilterProxyModel(self)
self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.proxyModel.setSourceModel(self.model)
self.proxyModel.sort(0,QtCore.Qt.AscendingOrder)
self.proxyModel.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.tableview.setModel(self.proxyModel)
self.model.insertRow(self.model.rowCount(QtCore.QModelIndex()))
#self.fillModel(self.model) #uncomment if you have outlook
self.tableview.resizeColumnsToContents()
self.tableview.verticalHeader().setDefaultSectionSize(10)
self.filterEdit.textChanged.connect(self.onTextChanged)
self.lay.addWidget(self.tableview)
self.delegate = ComboDelegate()
self.tableview.setItemDelegateForColumn(2, self.delegate)
self.lay.addWidget(self.button)
self.lay.addWidget(self.resetbutton)
self.setMinimumSize(450, 200)
self.setMaximumSize(1500, 200)
self.setWindowTitle('Application')
def clear(self):
###clear tableview comboboxes in column 3
print("clear")
def runButton(self,index):
print("Do stuff")
def fillModel(self,model):
"""Fills model from outlook address book """
nameList = []
addressList = []
for row,entry in enumerate(entries):
if entry.Type == "EX":
user = entry.GetExchangeUser()
if user is not None:
if len(user.FirstName) > 0 and len(user.LastName) > 0:
nameItem = QtGui.QStandardItem(str(user.Name))
emailItem = QtGui.QStandardItem(str(user.PrimarySmtpAddress))
nameItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
emailItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
model.appendRow([nameItem,emailItem])
#QtCore.pyqtSlot(str)
def onTextChanged(self, text):
self.proxyModel.setFilterRegExp(text)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = App()
w.show()
sys.exit(app.exec_())
The problem is that you override the paint method unnecessarily since you don't want to customize anything. Before override I recommend you understand what it does and for this you can use the docs or the source code. But to summarize, in the case of the QItemDelegate the paint method establishes the information of the roles in the "option" and then just paints, and within that information is the text. But in your case it is not necessary so there is no need to override. On the other hand, if your delegate has the sole function of establishing a QComboBox then you don't have to verify the columns. Considering all of the above, I have simplified your delegate to:
class ComboDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self.items = ["", "To", "CC"]
def createEditor(self, widget, option, index):
editor = QtWidgets.QComboBox(widget)
editor.addItems(self.items)
return editor
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, QtCore.Qt.EditRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentText())
On the other hand, the QItemEditorFactory uses the qproperty user as the parameter for the update, and in the case of the QComboBox it is the "currentText", so it can be further simplified using that information:
class ComboDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
self.items = ["", "To", "CC"]
def createEditor(self, widget, option, index):
editor = QtWidgets.QComboBox(widget)
editor.addItems(self.items)
return editor
For the clear method is simple: Iterate over all the rows of the third column and set the empty text:
def clear(self):
# clear tableview comboboxes in column 3
for i in range(self.model.rowCount()):
index = self.model.index(i, 2)
self.model.setData(index, "")

How to shift position of radio button and reverse radio button function when unchecking it?

I would like to ask 2 questions about my code. First, how do I shift the position of my radio button into the position that I want (I wrote it in the code)? And how do I reverse my LCD screen after unchecking it? Right now, it shows '02' when checked but I want to reverse the process when I uncheck it. Any solution?
class MyRadioButton(QtGui.QRadioButton):
def __init__(self):
super(MyRadioButton, self).__init__()
self.value = None
def SetValue(self, val):
self.value = val
def GetValue(self):
return self.value
class UserTool(QtGui.QDialog):
def __init__(self, parent = None):
super(UserTool, self).__init__()
self.layout = QtGui.QVBoxLayout(self)
self.setup(self)
def setup(self, Dialog):
Dialog.setObjectName(_fromUtf8("Dialog"))
self.resize(688, 677)
self.lcdNumber = QtGui.QLCDNumber(Dialog)
self.lcdNumber.setGeometry(QtCore.QRect(590, 10, 71, 23))
self.lcdNumber.setFrameShadow(QtGui.QFrame.Raised)
self.lcdNumber.setObjectName(_fromUtf8("lcdNumber"))
self.lcdNumber.setStyleSheet("* {background-color: black; color: white;}")
self.lcdNumber.display("00")
self.radioButton_8 = MyRadioButton()
self.radioButton_8.setText("A1")
self.radioButton_8.SetValue("02")
self.radioButton_8.toggled.connect(self.showValueFromRadioButtonToLCDNumber)
self.layout.addWidget(self.radioButton_8)
#self.radioButton_8 = QtGui.QRadioButton(Dialog)
self.radioButton_8.setGeometry(QtCore.QRect(460, 10, 82, 17))
self.radioButton_8.setChecked(False)
self.radioButton_8.setAutoExclusive(False)
self.radioButton_8.setObjectName(_fromUtf8("radioButton_8"))
def retranslateUi(self, Dialog):
self.radioButton_8.setText(_translate("Dialog", "A1", None))
def showValueFromRadioButtonToLCDNumber(self):
value = self.radioButton_8.GetValue()
if self.radioButton_8.isChecked():
self.lcdNumber.display(value)
Here is working example. In order to show previous value you need mechanism to cash that value before it is changed. To shift radioButton I use addSpacing()
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import Qt
class MyRadioButton(QtGui.QRadioButton):
def __init__(self):
super(MyRadioButton, self).__init__()
self.value = None
def SetValue(self, val):
self.value = val
def GetValue(self):
return self.value
class Widget(QtGui.QWidget):
def __init__(self):
super(Widget, self).__init__()
self.layout = QtGui.QVBoxLayout(self)
self.radioButton = MyRadioButton()
self.radioButton.setText("some text")
self.radioButton.SetValue("02")
self.radioButton.toggled.connect(self.showValueFromRadioButtonToLCDNumber)
self.lcdNumber = QtGui.QLCDNumber()
self.lcdNumber.display("-2")# previous value
self.layoutHorizontal = QtGui.QHBoxLayout(self)
self.layoutHorizontal.addSpacing(20)# add space before radioButton
self.layoutHorizontal.addWidget(self.radioButton)
self.layout.addLayout(self.layoutHorizontal)
self.layout.addWidget(self.lcdNumber)
self.previousValue = ""
def showValueFromRadioButtonToLCDNumber(self):
value = self.radioButton.GetValue()
if self.radioButton.isChecked():
self.previousValue = self.lcdNumber.value()# save previous value before it is changed
self.lcdNumber.display(value)
else:
self.lcdNumber.display(self.previousValue)

PyQt QWidget in QAbstractListModel gets deleted with QSortFilterProxyModel

I need to populate a listview with widgets, then have a custom proxyfilter work with it.
Without the filter it works great, when active it seems to delete the widgets attach to the model.
It shows up fine showing all items, filtering works but when erasing the filter, when hidden widgets should be shown again following error gets thrown:
custom_widget.setGeometry(option.rect)
RuntimeError: underlying C/C++ object has been deleted
Tried not using QVariant and going the internalPointer route but breaks at the same spot.
Thanks for having a look!
Setup:
def __init__(self, *args):
QtGui.QWidget.__init__(self, *args)
# create temp data
self.list_data = []
for x in xrange(500):
widget = ListItemWidget(text=str(x), parent=self)
self.list_data.append((str(x), widget)) # testing to put in inmut tuple
# create listviewmodel
self.lm = ListViewModel(parent=self)
# create listview widget
self.lv = QtGui.QListView()
# create filter proxy
self.proxy_model = ListViewFilterProxyModel()
self.proxy_model.setFilterPattern('')
self.proxy_model.setSourceModel(self.lm)
# set model of listview to filter proxy
self.lv.setModel(self.proxy_model)
# set delegate for column 0
self.lv.setItemDelegateForColumn(0, CustomWidgetDelegate(self.lv))
self.lm.updateData(self.list_data)
self.proxy_model.invalidate()
self.connect(self.filter_edit, QtCore.SIGNAL("textChanged(QString)"), self.update_filter)
def update_filter(self, pattern):
self.proxy_model.setFilterPattern(pattern)
self.proxy_model.invalidate()
Custom widget
class ListItemWidget(QtGui.QWidget):
def __init__(self, text=None, parent=None):
QtGui.QWidget.__init__(self)
self.text = text
#QtCore.pyqtProperty(QtCore.QString)
def text(self):
return self.__text
#text.setter
def text(self, value):
self.__text = value
Delegate for painting the view
class CustomWidgetDelegate(QtGui.QItemDelegate):
def __init__(self, parent=None):
super(CustomWidgetDelegate, self).__init__(parent)
def paint(self, painter, option, index):
custom_widget = index.model().data(index, QtCore.Qt.DisplayRole).toPyObject()[1]
>>>>>> custom_widget.setGeometry(option.rect)
if not self.parent().indexWidget(index):
self.parent().setIndexWidget(index, custom_widget)
List view model:
class ListViewModel(QtCore.QAbstractListModel):
def __init__(self, parent=None, *args):
QtCore.QAbstractListModel.__init__(self, parent, *args)
self.listdata = []
def rowCount(self, parent=QtCore.QModelIndex()):
return len(self.listdata)
def data(self, index, role):
if role == QtCore.Qt.SizeHintRole:
return QtCore.QSize(80, 80)
if index.isValid() and role == QtCore.Qt.DisplayRole:
return QtCore.QVariant(self.listdata[index.row()]).toPyObject()
return QtCore.QVariant()
def updateData(self, listdata):
self.listdata = listdata
index = len(self.listdata)
return True
Finally the filter proxy model:
class ListViewFilterProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
self.filter_str = None
QtGui.QSortFilterProxyModel.__init__(self, parent)
def setFilterPattern(self, pattern):
self.filter_str = QtCore.QString(pattern)
def filterAcceptsRow(self, sourceRow, sourceParent):
if self.filter_str is None:
return True
index = self.sourceModel().index(sourceRow, 0, sourceParent)
# just testing on the str here...
text = index.data().toPyObject()[0]
if not str(self.filter_str) in text:
return False
return True

Resources