Key event detecting in grid - wxPython - python-3.x

I created a grid using wxPython and I need to monitor the data inserted by the user into one of the cells in my grid. I need to have an event due to every key press in the keyboard (like EVT_KEY_DOWN) and I can't find a way to do that.
Right now I need to use a grid for this purpose so the solution must be something that can be integrated into wx.grid.
I tried to use GridCellEditor but it only gives the first key.
Is there a way to integrate TextCtrl into a grid's cell or something like that?

As far as I can tell, unless told differently, a grid is a collection of TextCtrl's, so the key is to bind wx.EVT_KEY_DOWN to them.
Here is one way to do it:
Note: I have added a few different element types for demonstration purposes.
Hopefully this is all that you require.
import wx
import wx.grid as gridlib
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "A key detecting grid", size=(1000,300))
panel = wx.Panel(self, wx.ID_ANY)
self.grid = gridlib.Grid(panel)
self.grid.CreateGrid(10, 8)
self.grid.Bind(wx.EVT_KEY_DOWN, self.OnKeyPress) #Required for initial key press
self.grid.Bind(gridlib.EVT_GRID_EDITOR_CREATED, self.onEditorCreated) # For subsequent key presses
# -- Additional bits only for demonstration of isolating Text fields
# Boolean field dislays as a CheckBox
crbool = wx.grid.GridCellBoolRenderer()
cebool = wx.grid.GridCellBoolEditor()
self.grid.SetCellRenderer(1, 1, crbool)
self.grid.SetCellEditor(1, 1, cebool)
# Choice field
cechoice = wx.grid.GridCellChoiceEditor(['Choice 1','Choice 2','Choice 3'], allowOthers=False)
self.grid.SetCellEditor(1, 2, cechoice)
#Load special fields
self.grid.SetCellValue(1, 1, '1')
self.grid.SetCellValue(1, 2, 'Choice 2')
self.grid.SetColSize(0,200)
self.grid.SetColSize(2,200)
# --
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.grid, 1, wx.EXPAND, 5)
panel.SetSizerAndFit(sizer)
self.Show()
def OnKeyPress(self, event):
uk = event.UnicodeKey
key = chr(event.UnicodeKey)
shift = event.shiftDown
if not shift:
key = key.lower()
print("Key", uk, key)
event.Skip()
def onEditorCreated(self,event):
#Set TextCtrl element to want all char/key events for all keys
self.cb = event.Control
if event.Control.ClassName == "wxTextCtrl":
self.cb.SetWindowStyle(wx.WANTS_CHARS) # BEWARE! - Returns Tab, Enter, Arrow keys etc
self.cb.Bind(wx.EVT_KEY_DOWN,self.OnKeyPress)
else:
print("Non text cell - bailing out")
event.Skip()
if __name__ == "__main__":
app = wx.App()
frame = MyForm()
app.MainLoop()

Related

Automating repetitive code in Python 3 (PyQt5)

I'm new to PyQt5 and pretty new to Python in general. I needed a keyboard layout, and in stead of manually creating a QPushButton for every letter and setting the text, coordinates and size for each i tried to automate it. My idea was to iterate through a dictionary to create a new name for each QPushButton. I then had to use something else than self.dict[x] as text for the QPushButton, because self.dict[x] was a QPushButton itself. I created a list with all the characters and used list[x] in stead. I would use the coords list to tweak the coordinates for each QPushButton through the iterations. My attempt looked like this:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
class Window(QMainWindow):
coords = [0, 200]
size = 50
count = 0
list = [chr(x) for x in range(65, 91)]
dict = {}
for x in range(len(list)):
dict[x] = list[x]
def __init__(self):
super(Window, self).__init__()
self.setGeometry(100, 100, 800, 500)
self.setWindowTitle('')
self.makeButtons()
def makeButtons(self):
for x in range(len(self.dict)):
self.dict[x] = QtWidgets.QPushButton(self)
self.dict[x].resize(self.size, self.size)
self.dict[x].move(self.coords[0], self.coords[1])
self.dict[x].setText(self.list[x])
self.coords[0] += self.size
self.count += 1
if self.count == 9:
self.coords = [0, 250]
if self.count == 18:
self.coords = [25, 300]
def window():
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec_())
window()
which does create the keyboard layout i want. However, I got stuck trying to create a method for each QPushButton, so I can't add any functionalty to the buttons. Is it possible to implement the automation of creating other metods in the makeButton method like I did with the QPushButtons themselves, or do i need another strategy to automate it?
tl;dr
Use lambda and emit signals manually:
class Window(QMainWindow):
keyClicked = QtCore.pyqtSignal(object)
# ...
def makeButtons(self):
# ...
for x in range(len(self.dict)):
self.dict[x] = button = QtWidgets.QPushButton(self)
# ...
button.clicked.connect(lambda _, key=self.list[x]: self.keyClicked.emit(key))
# ...
Explanation
Whenever you have lots of widgets that behave in the same way, you should provide a common interface for their behavior. lambda functions can help you with that, but you have to be aware about the scope the variables belong to (remember, as soon as a function exits/returns, any "anonymous" variable that has no persistent relation to other objects will be garbage collected by python, which practically means "deleted").
As you can see in the example above, I'm using two arguments.
The first is the one provided whenever the signal is emitted (any QAbstractButton descendant emits a clicked signal with a bool arguments that reports the new "checked" status, so if the button is checkable the argument will be the button checked state, otherwise it will always be False). We can just ignore that, but it has to be added as an argument of the lambda function.
The latter keyword argument is provided to keep consistence with the scope of the current cycle. In this way we can ensure that the key argument of the lambda function will always be that of the current cycle (otherwise it will always be the last for value assigned to that for cycle).
Addendum
This part of the answer is totally unrequested and unnecessary for the actual purpose of this question; nonetheless, I'm assuming you're new not only to Python and PyQt in general, but to the UX side that programming almost always has to relate to. As programmers, we have to be aware about that, since the majority of users that will finally use our products are not programmers.
Creating a keyboard GUI is not an easy task.
People are accustomed to the keyboards they use everyday (guess what, smartphones). Choosig an alphabet-order based layout for a keyboard that uses a standard "three rows" layout is not only an unpopular choice, but also a wrong one. We're (luckily) far away from that gap-time that was before the 2010's, where almost nobody knew how to type at all, but we also have to consider the results of this aspect.
To add even more mess to that, consider that even if QWERTY layout is almost standard, there are other layouts that differ partially (or completely) from it. For example, French people use AZERTY, while some Central Europe countries use QWERTZ, not to mention non-latin based writings.
And all of this only accounts for the standard alphabet letters.
Finally, the code
from string import ascii_uppercase
from PyQt5 import QtCore, QtWidgets
class Keyboard(QtWidgets.QWidget):
ASCII, QWERTY, DVORAK = 0, 1, 2
KeyLayoutLetters = {
ASCII: [ascii_uppercase[r*9:r*9+9] for r in range(3)],
QWERTY: ['QWERTYUIOP', 'ASDFGHJKL', 'ZXCVBNM'],
DVORAK: ['PYFGCRL', 'AOEUIDHTNS ', 'QJKXBMWVZ'],
}
# some default special stretch set for specific keyboard layouts
KeyStretch = {
QWERTY: [(1, 1), (1, 1), (1, 2)],
DVORAK: [(2, 1), (1, 2), (3, 1)],
}
keySize = 50
spacing = 2
letterClicked = QtCore.pyqtSignal(object)
def __init__(self):
super(Keyboard, self).__init__()
self.setKeyboardLayout()
def setKeyboardLayout(self, keyLayout=None):
keyLayout = keyLayout if keyLayout is not None else self.ASCII
if self.layout() is not None:
QtWidgets.QWidget().setLayout(self.layout())
layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(self.spacing)
stretches = self.KeyStretch.get(keyLayout, [(1, 1)] * 3)
for row, rowChars in enumerate(self.KeyLayoutLetters.get(keyLayout, self.KeyLayoutLetters[self.ASCII])):
rowLayout = QtWidgets.QHBoxLayout()
rowLayout.setSpacing(self.spacing)
layout.addLayout(rowLayout)
stretchLeft, stretchRight = stretches[row]
rowLayout.addStretch(stretchLeft)
for letter in rowChars:
if not letter.strip():
spacer = QtWidgets.QWidget()
rowLayout.addWidget(spacer)
spacer.setFixedSize(self.keySize * .5, self.keySize)
continue
letterButton = QtWidgets.QPushButton(letter)
rowLayout.addWidget(letterButton, stretch=0)
letterButton.setFixedSize(self.keySize, self.keySize)
letterButton.clicked.connect(lambda _, key=letter: self.letterClicked.emit(key))
rowLayout.addStretch(stretchRight)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
self.setCentralWidget(central)
layout = QtWidgets.QVBoxLayout(central)
# just a QLineEdit widget to test the text intertion
self.lineEdit = QtWidgets.QLineEdit()
layout.addWidget(self.lineEdit)
self.keyboard = Keyboard()
layout.addWidget(self.keyboard)
self.keyboard.setKeyboardLayout(Keyboard.DVORAK)
self.keyboard.letterClicked.connect(self.lineEdit.insert)
self.keySelector = QtWidgets.QComboBox()
layout.addWidget(self.keySelector)
self.keySelector.addItems(['ASCII', 'QWERTY', 'DVORAK'])
self.keySelector.currentIndexChanged.connect(self.keyboard.setKeyboardLayout)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
keyboard = MainWindow()
keyboard.show()
sys.exit(app.exec_())
Keep in mind that this is a really simple and raw implementation of what you're probably looking for. There is a moltitude of aspects you'd need to take care of (mostly related to special characters that are typical of the user's language).

how to save current values of qtablewidget? [duplicate]

This question already has an answer here:
How to save selected Items to Qsettings from QListWidget, QTableWidget
(1 answer)
Closed 3 years ago.
Need to save current values of QTableWidget. The QTableWidget will be iniside QTabWidget and there will be several adjacent tabs containing multiple tables in it. And inside the table there will be QCheckBox, QComoBox as cellwidgetitem which values also need to be saved. Beside this I also need to save value of QSpinBox and QDoubleSpinBox which are inside gridlayout. I am taking help of a code from https://gist.github.com/eyllanesc/be8a476bb7038c7579c58609d7d0f031 which saves values of QLineEdit inside a QFormLayout where the QFormLayout is inside a QTabWidget. If multiple tabs are instantiated then QLineEdit values inside the tabs cannot be saved. Moreover, if a QTableWidget is added below a QTabWidget then the values of the QTableWidget cannot be saved as well.
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtCore import QFileInfo, QSettings
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import qApp, QApplication, QMainWindow, QFormLayout, QLineEdit, QTabWidget, QWidget, QAction, QVBoxLayout, QTableWidget, QTableWidgetItem
def restore(settings):
finfo = QFileInfo(settings.fileName())
print(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in qApp.allWidgets():
mo = w.metaObject()
if w.objectName() != "":
for i in range(mo.propertyCount()):
name = mo.property(i).name()
val = settings.value("{}/{}".format(w.objectName(), name), w.property(name))
w.setProperty(name, val)
def save(settings):
for w in qApp.allWidgets():
mo = w.metaObject()
if w.objectName() != "":
for i in range(mo.propertyCount()):
name = mo.property(i).name()
settings.setValue("{}/{}".format(w.objectName(), name), w.property(name))
class mainwindow(QMainWindow):
settings = QSettings("gui.ng", QSettings.IniFormat)
def __init__(self, parent=None):
super(mainwindow, self).__init__(parent)
self.setObjectName("MainWindow")
self.initUI()
restore(self.settings)
def initUI(self):
exitAction = QAction(QIcon('icon\\exit.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(self.close)
self.toolbar = self.addToolBar('Exit')
self.toolbar.setMovable(False)
self.toolbar.addAction(exitAction)
self.tab_widget = QTabWidget(self) # add tab
self.tab_widget.setObjectName("tabWidget")
self.tab2 = QWidget()
self.tab2.setObjectName("tab2")
self.tab3 = QWidget()
self.tab3.setObjectName("tab3")
self.tab_widget.addTab(self.tab2, "Tab_2")
self.tab_widget.addTab(self.tab3, "Tab_3")
self.tab2UI()
self.tab3UI()
self.vlay = QVBoxLayout()
self.vlay.addWidget(self.tab_widget)
self.qtable = QTableWidget()
self.qtable.setRowCount(3)
self.qtable.setColumnCount(3)
self.qtable.setItem(0, 0, QTableWidgetItem("text1"))
self.qtable.setItem(0, 1, QTableWidgetItem("text1"))
self.qtable.setItem(0, 2, QTableWidgetItem("text1"))
self.qtable.setItem(1, 0, QTableWidgetItem("text2"))
self.qtable.setItem(1, 1, QTableWidgetItem("text2"))
self.qtable.setItem(1, 2, QTableWidgetItem("text2"))
self.qtable.setItem(2, 0, QTableWidgetItem("text3"))
self.qtable.setItem(2, 1, QTableWidgetItem("text3"))
self.qtable.setItem(2, 2, QTableWidgetItem("text3"))
self.vlay.addWidget(self.qtable)
self.qVlayWidget = QWidget()
self.qVlayWidget.setLayout(self.vlay)
self.setCentralWidget(self.qVlayWidget)
def tab2UI(self):
self.layout_2 = QFormLayout()
nameLe = QLineEdit(self)
nameLe.setObjectName("nameLe_2")
self.layout_2.addRow("Name_2", nameLe)
addressLe = QLineEdit()
addressLe.setObjectName("addressLe_2")
self.layout_2.addRow("Address_2", addressLe)
self.tab2.setLayout(self.layout_2)
def tab3UI(self):
self.layout_3 = QFormLayout()
nameLe = QLineEdit(self)
nameLe.setObjectName("nameLe_3")
self.layout_3.addRow("Name_3", nameLe)
addressLe = QLineEdit()
addressLe.setObjectName("addressLe_3")
self.layout_3.addRow("Address_3", addressLe)
self.tab3.setLayout(self.layout_3)
def closeEvent(self, event):
save(self.settings)
QMainWindow.closeEvent(self, event)
def main():
app = QApplication(sys.argv)
ex = mainwindow()
ex.setGeometry(100, 100, 1000, 600)
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The QLineEdit data is stored, the problem is that its not shown. The visible property of a widget depends on its ancestor[s]; since a QTabWidget has a QWidget for each of its tab, only the current one is visible, while the other (including all their children) are not visible. A QLineEdit is a complex widget, and if you manually unset the visible property to all its internal components, they won't be correctly restored again.
This becomes clear if you close the window with the second tab set as current: once you'll open it again, the second tab will be shown and its contents correctly displayed, while the first one QLineEdit won't.
A workaround for this is to check if the widget is "stored" as not visible and has a parent. If that's the case, just do not apply the visible property.
def restore(settings):
# ...
for w in qApp.allWidgets():
mo = w.metaObject()
parent = w.parent()
if w.objectName() != "":
for i in range(mo.propertyCount()):
name = mo.property(i).name()
val = settings.value("{}/{}".format(w.objectName(), name), w.property(name))
if name == 'visible' and val == 'false' and parent:
continue
w.setProperty(name, val)
That said, this is clearly not the good way to save the data of any widget, since it saves every property it has: the title of the example is "Functions to save and restore Widgets", not its editable data which is what you need.
Also, QTableWidget contents cannot be stored using this method, as its data is not part of the widget properties (and nothing else was saved anyway, because you didn't set its object name).
You have to find your own implementation to save those contents, which might be something similar to the following code.
Note that I'm using a QByteArray to store the table data. You create a QByteArray, then a QDataStream with it as argument, which is used for reading or writing. Note that both reading and writing are consequential, as data is always appended on writing or "popped-out" each time it has been read.
class mainwindow(QMainWindow):
# ...
def save(self):
# using findChildren is for simplicity, it's probably better to create
# your own list of widgets to cycle through
for w in self.findChildren((QLineEdit, QTableWidget)):
name = w.objectName()
if isinstance(w, QLineEdit):
self.settings.setValue("{}/text".format(name), w.text())
elif isinstance(w, QTableWidget):
# while we could add sub-setting keys for each combination of
# row/column items, it's better to store the data in a single
# "container"
data = QByteArray()
stream = QDataStream(data, QIODevice.WriteOnly)
rowCount = w.rowCount()
columnCount = w.columnCount()
# write the row and column count first
stream.writeInt(rowCount)
stream.writeInt(columnCount)
# then write the data
for row in range(rowCount):
for col in range(columnCount):
stream.writeQString(w.item(row, col).text())
self.settings.setValue("{}/data".format(name), data)
def restore(self):
for w in self.findChildren((QLineEdit, QTableWidget)):
name = w.objectName()
if isinstance(w, QLineEdit):
w.setText(self.settings.value("{}/text".format(name), w.text()))
elif isinstance(w, QTableWidget):
data = self.settings.value("{}/data".format(name))
if not data:
continue
stream = QDataStream(data, QIODevice.ReadOnly)
# read the row and column count first
rowCount = stream.readInt()
columnCount = stream.readInt()
w.setRowCount(rowCount)
w.setColumnCount(columnCount)
# then read the data
for row in range(rowCount):
for col in range(columnCount):
cellText = stream.readQString()
if cellText:
w.item(row, col).setText(cellText)

how to differentiate between color and value in send cellchanged signal

There is a table with some values in it. what I am trying to do is to capture changes of the table content and save them inside a SQLite database. so I connected table to cellChanged and itemSelectionChanged signals. when I try to change some cells content, it will check it to verify it does not contain letters, and it's a number. so if after changing the cell, it has letters inside, it will try to rechange the content to its previous value.
I also have a search toolbar to search in table for some keyword. what the search toolbar does, is to colorize the cells that matching that specific keyword. but in doing so, it emits cellChanged signal and not cellSelectionChanged signal. that causes an error (myapp object has no attribute previous_value), because I haven't selected any cell yet. But if I select a cell and then start seeking, writing anything inside search box will change the content of every cell, because it is triggering cellChanged signal. my question is how to differentiate between color and text changes in cellChanged function. thank you in advance.
here is the sample code:(python 3.7.4, pyqt5 5.13.0)
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class myapp(QWidget):
def __init__(self):
QWidget.__init__(self)
self.tableWidget = QTableWidget()
self.tableWidget.setSortingEnabled(True)
self.tableWidget.setRowCount(5)
self.tableWidget.setColumnCount(5)
for i in range(5):
for j in range(5):
item=QTableWidgetItem("cell["+str(i) + ","+ str(j) + "]")
self.tableWidget.setItem(i,j,item)
self.tableWidget.cellChanged.connect(self.on_tablewidget_cell_changed)
self.tableWidget.itemSelectionChanged.connect(self.on_tablewidget_item_selection_changed)
self.search_entry = QLineEdit()
self.search_entry.textChanged.connect(self.seek)
layout = QVBoxLayout()
layout.addWidget(self.tableWidget)
layout.addWidget(self.search_entry)
self.setLayout(layout)
def on_tablewidget_cell_changed(self,row,col):
value = self.tableWidget.item(row,col).text()
if not self.check_numeric(value):
self.tableWidget.item(row,col).setText(self.previous_value)
def on_tablewidget_item_selection_changed(self):
row = self.tableWidget.currentRow()
col = self.tableWidget.currentColumn()
self.previous_value = self.tableWidget.item(row,col).text()
def seek(self):
keyword = self.search_entry.text()
items = self.tableWidget.findItems(keyword, Qt.MatchContains)
for item in items:
item.setBackground(Qt.red)
def check_numeric(self,value):
value = value.strip()
tmp = "".join([i for i in value if i in "0123456789"])
return(len(value) == len(tmp))
app = QApplication(sys.argv)
win = myapp()
win.show()
sys.exit(app.exec_())
Instead of capturing cellChanged signals to check if the user input is valid, you could set an item delegate for the table widget, and check the user input in there before setting the model data. For example:
class IntItemDelegate(QItemDelegate):
def setModelData(self, editor, item_model, model_index):
# only update the model data if the user input consists of digits only.
text = editor.text()
if self.check_numeric(text):
item_model.setData(model_index, text)
def check_numeric(self, value):
return value.isdecimal()
class myapp(QWidget):
def __init__(self):
QWidget.__init__(self)
self.tableWidget = QTableWidget()
self.tableWidget.setSortingEnabled(True)
self.tableWidget.setRowCount(5)
self.tableWidget.setColumnCount(5)
for i in range(5):
for j in range(5):
item=QTableWidgetItem("cell["+str(i) + ","+ str(j) + "]")
self.tableWidget.setItem(i,j,item)
# set a custom item delegate which will take care of checking the user input
self.tableWidget.setItemDelegate( IntItemDelegate())
self.search_entry = QLineEdit()
self.search_entry.textChanged.connect(self.seek)
layout = QVBoxLayout()
layout.addWidget(self.tableWidget)
layout.addWidget(self.search_entry)
self.setLayout(layout)
def seek(self):
keyword = self.search_entry.text()
items = self.tableWidget.findItems(keyword, Qt.MatchContains)
for item in items:
item.setBackground(Qt.red)

PyQt - QCombobox in QTableview

I am displaying data from an SQLite database in a QTableView using a QSqlTableModel. Letting the user edit this data works fine. However, for some columns I want to use QComboboxes instead of free text cells, to restrict the list of possible answers.
I have found this SO answer and am trying to implement it on my model/view setting, but I'm running into problems (so this is a follow-up).
Here's a full mini-example:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QWidget, QTableView, QApplication, QHBoxLayout,
QItemDelegate, QComboBox)
from PyQt5.QtCore import pyqtSlot
import sys
class ComboDelegate(QItemDelegate):
"""
A delegate that places a fully functioning QComboBox in every
cell of the column to which it's applied
source: https://gist.github.com/Riateche/5984815
"""
def __init__(self, parent, items):
self.items = items
QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
combo = QComboBox(parent)
li = []
for item in self.items:
li.append(item)
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
# editor.setCurrentIndex(int(index.model().data(index))) #from original code
editor.setCurrentIndex(index.row()) # replacement
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentIndex())
#pyqtSlot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class Example(QWidget):
def __init__(self):
super().__init__()
self.resize(400, 150)
self.createConnection()
self.fillTable() # comment out to skip re-creating the SQL table
self.createModel()
self.initUI()
def createConnection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test.db")
if not self.db.open():
print("Cannot establish a database connection")
return False
def fillTable(self):
self.db.transaction()
q = QtSql.QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("CREATE TABLE Cars (Company TEXT, Model TEXT, Year NUMBER);")
q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 2009);")
q.exec_("INSERT INTO Cars VALUES ('VW', 'Golf', 2013);")
q.exec_("INSERT INTO Cars VALUES ('VW', 'Polo', 1999);")
self.db.commit()
def createModel(self):
self.model = QtSql.QSqlTableModel()
self.model.setTable("Cars")
self.model.select()
def initUI(self):
layout = QHBoxLayout()
self.setLayout(layout)
view = QTableView()
layout.addWidget(view)
view.setModel(self.model)
view.setItemDelegateForColumn(0, ComboDelegate(self, ["VW", "Honda"]))
for row in range(0, self.model.rowCount()):
view.openPersistentEditor(self.model.index(row, 0))
def closeEvent(self, e):
for row in range(self.model.rowCount()):
print("row {}: company = {}".format(row, self.model.data(self.model.index(row, 0))))
if (self.db.open()):
self.db.close()
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In this case, I want to use a QCombobox on the "Company" column. It should be displayed all the time, so I'm calling openPersistentEditor.
Problem 1: default values I would expect that this shows the non-edited field's content when not edited (i.e. the company as it is listed in the model), but instead it apparently shows the ith element of the combobox's choices.
How can I make each combobox show the model's actual content for this field by default?
Problem 2: editing When you comment out "self.fill_table()" you can check whether the edits arrive in the SQL database. I would expect that choosing any field in the dropdown list would replace the original value. But (a) I have to make every choice twice (the first time, the value displayed in the cell remains the same), and (b) the data appears in the model weirdly (changing the first column to 'VW', 'Honda', 'Honda' results in ('1', 'VW', '1' in the model). I think this is because the code uses editor.currentIndex() in the delegate's setModelData, but I have not found a way to use the editor's content instead. How can I make the code report the user's choices correctly back to the model? (And how do I make this work on first click, instead of needing 2 clicks?)
Any help greatly appreciated. (I have read the documentation on QAbstractItemDelegate, but I don't find it particularly helpful.)
Found the solution with the help of the book Rapid GUI Programming with Python and Qt:
createEditor and setEditorData do not work as I expected (I was misguided because the example code looked like it was using the text content but instead was dealing with index numbers). Instead, they should look like this:
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, Qt.DisplayRole)
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())
I hope this helps someone down the line.

How to store in variables current row selection within tree widget to query a database using python

I wonder if it is possible to store in variables the contents from a tree widget row (when it is selected with the mouse) see picture. Basically I want to sync my tree with a database, every time when I insert or delete an element in my tree, my database needs to auto update.
With the insert part it is not a problem , because I have entry widgets, but I don't know how to manage the delete part. Therefore, I wonder if it is possible to do this with some cursor selection function.
I have been trying for a very long time to find a solution for this, I would really appreciate if someone can help me with some hints
Code:
import tkinter
from tkinter import ttk
class cards(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self, parent)
self.parent=parent
self.parent.geometry("800x500")
self.initialize_user_interface()
def initialize_user_interface(self):
self.parent.title("cards")
self.parent.grid_rowconfigure(0,weight=1)
self.parent.grid_columnconfigure(0,weight=1)
self.parent.config(background="lavender")
self.Card_label = tkinter.Label(self.parent, text = "Card type:")
self.Card_entry = tkinter.Entry(self.parent)
self.Card_label.place(x=5,y=5)
self.Card_entry.place(x=70,y=5)
self.SN_label = tkinter.Label(self.parent, text = "SN:")
self.SN_entry = tkinter.Entry(self.parent)
self.SN_label.place(x=5,y=40)
self.SN_entry.place(x=70,y=40)
self.submit_button = tkinter.Button(self.parent, text = "Insert", command = self.insert_data)
self.submit_button.place(x=210,y=15)
self.exit_button = tkinter.Button(self.parent, text = "Exit", command = self.exit)
self.exit_button.place(x=270,y=15)
self.tree = ttk.Treeview( self.parent, columns=('Card Type', 'SN'))
self.tree.heading('#0', text='Nr.')
self.tree.heading('#1', text='Card Type')
self.tree.heading('#2', text='SN')
self.tree.column('#1', stretch=tkinter.YES)
self.tree.column('#2', stretch=tkinter.YES)
self.tree.column('#0', stretch=tkinter.YES)
self.tree.place(x=0,y=100)
self.treeview = self.tree
self.i = 1
def exit(self):
self.master.destroy()
def insert_data(self):
self.treeview.insert('', 'end', text=str(self.i), values=(self.Card_entry.get(), self.SN_entry.get()))
self.i = self.i + 1
def main():
root=tkinter.Tk()
d=cards(root)
root.mainloop()
if __name__=="__main__":
main()
You can use
for item in self.tree.selection():
print(self.tree.item(item, "text"))
print(self.tree.item(item, "values"))
#print(self.tree.item(item))
to see data from all selected rows - you can select more than one row.
You can use it in function assigned to button
or you can use bind() to assign function to mouse click on row.

Resources