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)
Related
I'm trying to implement a button in PyQt5 which acts identically to pressing the Tab-Key. For this, I get the focused item, and call nextInFocusChain() to get the next item in tab order and set it to focus. If it is not a QLineEdit object, I repeat.
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
Sadly this snipped does not wrap around. After the last QLineEdit, it gets stuck inside the loop while continuously printing
...
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
<PyQt5.QtWidgets.QLabel object at 0x1114d2b80><PyQt5.QtWidgets.QFocusFrame object at 0x1114d2ca0>
Here a reproducing example...
from collections import OrderedDict
from PyQt5.QtWidgets import QDialog, QFormLayout, QLineEdit, QCheckBox, QDialogButtonBox, QVBoxLayout, QPushButton
class AddressEditor(QDialog):
def __init__(self,fields:OrderedDict,parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout(self)
self.inputs = OrderedDict()
last = None
for k,v in fields.items():
self.inputs[k] = QLineEdit(v)
if last is not None:
self.setTabOrder(self.inputs[last],self.inputs[k])
last = k
form.addRow(k, self.inputs[k])
layout.addLayout(form)
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
def tabpressed(self):
self.focusWidget().nextInFocusChain().setFocus()
while type(self.focusWidget()) != QLineEdit:
print(str(self.focusWidget()) + "" + str(self.focusWidget().nextInFocusChain()))
self.focusWidget().nextInFocusChain().setFocus()
if __name__ == '__main__':
from PyQt5.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
dialog = AddressEditor({"Text":"Default","Empty":"","True":"True","False":"False"})
if dialog.exec():
pass
exit(0)
There are two important aspects to consider when trying to achieve "manual" focus navigation:
the focus chain considers all widgets, not only those that can "visibly" accept focus; this includes widgets that have a NoFocus policy, the parent widget (including the top level window), hidden widgets, and any other widget that can be "injected" by the style, including "helpers" like QFocusFrame;
widgets can have a focus proxy which can "push back" the focus to the previous widget, and this can cause recursion issues like in your case;
Besides that, there are other issues with your implementation:
the button accepts focus, so whenever it's pressed it resets the focus chain;
to compare the class you should use isinstance and not type;
form should not have any argument since it's going to be added as a nested layout, and in any case it shouldn't be self since a layout has already been set;
the tab order must be set after both widgets are added to parents that share the same a common ancestor widget/window;
the tab order is generally automatically set based on when/where widgets are added to the parent: for a form layout is generally unnecessary as long as all fields are inserted in order;
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
super(AddressEditor, self).__init__(parent=parent)
layout = QVBoxLayout(self)
form = QFormLayout() # <- no argument
layout.addLayout(form)
self.inputs = OrderedDict()
last = None
for k, v in fields.items():
new = self.inputs[k] = QLineEdit(v)
form.addRow(k, new) # <- add the widget *before* setTabOrder
if last is not None:
self.setTabOrder(last, new)
last = new
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
button.setFocusPolicy(Qt.NoFocus) # <- disable focus for the button
def tabpressed(self):
nextWidget = self.focusWidget().nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()
If you want to keep the focus policy for the button so that it can be reached through Tab, the only possibility is to keep track of the focus change of the application, since as soon as the button is pressed with the mouse button it will already have received focus:
class AddressEditor(QDialog):
def __init__(self, fields:OrderedDict, parent=None):
# ...
button = QPushButton("TAB")
layout.addWidget(button)
button.clicked.connect(self.tabpressed)
QApplication.instance().focusChanged.connect(self.checkField)
self.lastField = tuple(self.inputs.values())[0]
def checkField(self, old, new):
if isinstance(new, QLineEdit) and self.isAncestorOf(new):
self.lastField = new
def tabpressed(self):
nextWidget = self.lastField.nextInFocusChain()
while not isinstance(nextWidget, QLineEdit) or not nextWidget.isVisible():
nextWidget = nextWidget.nextInFocusChain()
nextWidget.setFocus()
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)
I was recently coding PyQt with a QComboBox in a QTable. The QComboBox has autocomplete on by default.
I was wanting to try and reproduce this in Python3 with Gtk3. I came across this example:
Gtk.Entry in Gtk.TreeView (CellRenderer)
that seems to have successfully added an autocompletion to a ComboBox in a Treeview. The example is not complete and I was hoping someone could give me a complete working example. I don't know how I 'connect' the CellRendererAutoComplete class to one of the columns in a treeview.
What I really want is to have AutoCompletion in an editable TreeView cell (with or without a ComboBox), but I have never come across this in the past in Gtk.
Thank you.
Here is a code example:
# https://stackoverflow.com/questions/13756787/
# https://python-gtk-3-tutorial.readthedocs.io/en/latest/cellrenderers.html
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
#######################################################################
class CellRendererAutoComplete(Gtk.CellRendererText):
""" Text entry cell which accepts a Gtk.EntryCompletion object """
__gtype_name__ = 'CellRendererAutoComplete'
def __init__(self, completion):
self.completion = completion
Gtk.CellRendererText.__init__(self)
def do_start_editing(
self, event, treeview, path, background_area, cell_area, flags):
if not self.get_property('editable'):
return
entry = Gtk.Entry()
entry.set_completion(self.completion)
entry.connect('editing-done', self.editing_done, path)
entry.show()
entry.grab_focus()
return entry
def editing_done(self, entry, path):
self.emit('edited', path, entry.get_text())
#######################################################################
class CellRendererTextWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="CellRendererText Example")
self.set_default_size(200, 200)
self.liststore = Gtk.ListStore(str, str)
self.liststore.append(["Fedora", "http://fedoraproject.org/"])
self.liststore.append(["Slackware", "http://www.slackware.com/"])
self.liststore.append(["Sidux", "http://sidux.com/"])
treeview = Gtk.TreeView(model=self.liststore)
renderer_text = Gtk.CellRendererText()
column_text = Gtk.TreeViewColumn("Text", renderer_text, text=0)
treeview.append_column(column_text)
renderer_editabletext = Gtk.CellRendererText()
renderer_editabletext.set_property("editable", True)
########
# This is the problem area, I suppose, but I'm not sure
x = CellRendererAutoComplete()
renderer_editabletext.connect('on_edit',x(renderer_editabletext))
########
column_editabletext = Gtk.TreeViewColumn("Editable Text",renderer_editabletext, text=1)
treeview.append_column(column_editabletext)
renderer_editabletext.connect("edited", self.text_edited)
self.add(treeview)
def text_edited(self, widget, path, text):
self.liststore[path][1] = text
win = CellRendererTextWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
So here is an example showing a CellRendererText and a CellRendererCombo using an EntryCompletion.
Because of the complexity of the Treeview, where one ListStore can be the model behind the Completion, Combo, and Entry, and another model can be behind the Treeview, you should have a very good grasp of Gtk.Treeview to understand this example. Note that this example only uses one Liststore, and only one editable column used by both the CellRendererText and the CellRendererColumn. This makes the example confusing, but it the simplest I can come up with since I do not know the intended use for this Treeview.
#!/usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class CellRendererTextWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="CellRendererText Example")
self.set_default_size(200, 200)
self.liststore = Gtk.ListStore(str, str)
self.liststore.append(["Fedora", "http://fedoraproject.org/"])
self.liststore.append(["Slackware", "http://www.slackware.com/"])
self.liststore.append(["Sidux", "http://sidux.com/"])
treeview = Gtk.TreeView(model=self.liststore)
self.completion = Gtk.EntryCompletion(model = self.liststore)
self.completion.set_text_column(1)
self.completion.connect('match-selected', self.renderer_match_selected)
renderer_text = Gtk.CellRendererText()
column_text = Gtk.TreeViewColumn("Text", renderer_text, text=0)
treeview.append_column(column_text)
######## CellRendererText with EntryCompletion example
renderer_text = Gtk.CellRendererText()
renderer_text.connect('editing-started', self.renderer_text_editing_started)
renderer_text.connect('edited', self.text_edited)
renderer_text.set_property("editable", True)
column_text_autocomplete = Gtk.TreeViewColumn("Editable Text", renderer_text, text=1)
treeview.append_column(column_text_autocomplete)
######## CellRendererCombo with EntryCompletion example
renderer_combo = Gtk.CellRendererCombo(model = self.liststore)
renderer_combo.set_property("text-column", 1)
renderer_combo.connect('editing-started', self.renderer_combo_editing_started)
renderer_combo.connect('changed', self.combo_changed)
renderer_combo.set_property("editable", True)
column_combo_autocomplete = Gtk.TreeViewColumn("Editable Combo", renderer_combo, text=1)
treeview.append_column(column_combo_autocomplete)
self.add(treeview)
def renderer_match_selected (self, completion, model, tree_iter):
''' beware ! the model and tree_iter passed in here are the model from the
EntryCompletion, which may or may not be the same as the model of the Treeview '''
text_match = model[tree_iter][1]
self.liststore[self.path][1] = text_match
def renderer_text_editing_started (self, renderer, editable, path):
''' since the editable widget gets created for every edit, we need to
connect the completion to every editable upon creation '''
editable.set_completion(self.completion)
self.path = path # save the path for later usage
def text_edited(self, widget, path, text):
self.liststore[path][1] = text
def renderer_combo_editing_started (self, renderer, combo, path):
''' since the editable widget gets created for every edit, we need to
connect the completion to every editable upon creation '''
editable = combo.get_child() # get the entry of the combobox
editable.set_completion(self.completion)
self.path = path # save the path for later usage
def combo_changed (self, combo, path, tree_iter):
''' the path is from the treeview and the tree_iter is from the model
of the combobox which may or may not be the same model as the treeview'''
combo_model = combo.get_property('model')
text = combo_model[tree_iter][1]
self.liststore[path][1] = text
win = CellRendererTextWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
In the official docs, it is explicitly stated that the purpose of editing-started is to add a EntryCompletion and etc. I also subclassed Gtk.CellRendererText before I found that little hint in the docs.
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.
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.