Related
DESCRIPTION
I have a pyqt5 UI which has a QTableWidget with a dynamic row count; there is a button that adds rows. When a row is added, some of the cells contain QSpinBox(s) and one contains a QComboBox. The program also has a Save and a Restore QPushButton(s) that when selected, saves down all the widgets to an ini file located next to the py file. The save and restore methods, created by #eyllanesc and found here are pretty much universally used from what I have found on SO.
PROBLEM
The code below saves down the QSpinBox(s) and QComboBox fine. They are in the ini file. The restore function does not recover these widgets in to the QTableWidget. The row count is recovered, but no input text or widgets are inside the cells they were placed in.
WHAT I HAVE TRIED
I named the dynamically created widgets with a prefix_<row>_<column> name. I tried creating these in the initialisation of the UI, thinking there might be a link to the qApp.allWidgets() and the startup, but this didn't work. I was just guessing.
Reading #zythyr's post here, I thought I might have to add the widget to the QTableWidget in a parent<>child sort of arrangement (this is outside my knowledge), but the QTableWidget doesn't have the addWidget() method. I then tried *.setParent on the QComboBox (commented out in code below) and that didn't work either.
QUESTION
How do you save and restore user-input (typed in empty cell) data as well as QWidgets (namely QSpinBox and QComboBox) that are in a QTableWidget?
CODE
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import QSettings, QFileInfo
from PyQt5.QtWidgets import (QApplication, qApp, QMainWindow, QWidget,
QVBoxLayout, QTableWidget, QTableWidgetItem,
QHeaderView, QPushButton, QLineEdit, QSpinBox,
QComboBox)
def value_is_valid(val):
#https://stackoverflow.com/a/60028282/4988010
if isinstance(val, QPixmap):
return not val.isNull()
return True
def restore(settings):
#https://stackoverflow.com/a/60028282/4988010
finfo = QFileInfo(settings.fileName())
if finfo.exists() and finfo.isFile():
for w in qApp.allWidgets():
if w.objectName():
mo = w.metaObject()
for i in range(mo.propertyCount()):
prop = mo.property(i)
name = prop.name()
last_value = w.property(name)
key = "{}/{}".format(w.objectName(), name)
if not settings.contains(key):
continue
val = settings.value(key, type=type(last_value),)
if (
val != last_value
and value_is_valid(val)
and prop.isValid()
and prop.isWritable()
):
w.setProperty(name, val)
def save(settings):
#https://stackoverflow.com/a/60028282/4988010
for w in qApp.allWidgets():
if w.objectName():
mo = w.metaObject()
for i in range(mo.propertyCount()):
prop = mo.property(i)
name = prop.name()
key = "{}/{}".format(w.objectName(), name)
val = w.property(name)
if value_is_valid(val) and prop.isValid() and prop.isWritable():
settings.setValue(key, w.property(name))
# custom spin box allowing for optional maximum
class CustomSpinBox(QSpinBox):
def __init__(self, sb_value=1, sb_step=1, sb_min=1, *args):
super(CustomSpinBox, self).__init__()
self.setValue(sb_value)
self.setSingleStep(sb_step)
self.setMinimum(sb_min)
if len(args) > 0:
sb_max = args[0]
self.setMaximum(sb_max)
class MainWindow(QMainWindow):
# save settings alongside *py file
settings = QSettings("temp.ini", QSettings.IniFormat)
def __init__(self):
super().__init__()
self.initUI()
self.initSignals()
def initUI(self):
# standar UI stuff
self.setObjectName('MainWindow')
self.setWindowTitle('Program Title')
self.setGeometry(400, 400, 400, 100)
wid = QWidget(self)
self.setCentralWidget(wid)
# create some widgets
self.pb_add_row = QPushButton('Add Row')
self.pb_remove_row = QPushButton('Remove Selected Row')
self.pb_save = QPushButton('Save')
self.pb_restore = QPushButton('Restore')
self.le = QLineEdit()
self.le.setObjectName('le')
self.tbl = QTableWidget()
self.tbl.setObjectName('r_tbl')
header = self.tbl.horizontalHeader()
self.tbl.setRowCount(0)
self.tbl.setColumnCount(4)
input_header = ['Label', 'X', 'Y', 'Comment']
self.tbl.setHorizontalHeaderLabels(input_header)
header.setSectionResizeMode(QHeaderView.Stretch)
# add widgets to UI
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.le)
self.vbox.addWidget(self.tbl)
self.vbox.addWidget(self.pb_add_row)
self.vbox.addWidget(self.pb_remove_row)
self.vbox.addWidget(self.pb_save)
self.vbox.addWidget(self.pb_restore)
wid.setLayout(self.vbox)
# restore previous settings from *.ini file
restore(self.settings)
# pb signals
def initSignals(self):
self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
self.pb_save.clicked.connect(self.pb_save_clicked)
self.pb_restore.clicked.connect(self.pb_restore_clicked)
# add a new row to the end - add spin boxes and a combobox
def pb_add_row_clicked(self):
current_row_count = self.tbl.rowCount()
row_count = current_row_count + 1
self.tbl.setRowCount(row_count)
self.sb_1 = CustomSpinBox(1, 1, 1)
self.sb_1.setObjectName(f'sb_{row_count}_1')
self.sb_2 = CustomSpinBox(3, 1, 2, 6)
self.sb_2.setObjectName(f'sb_{row_count}_2')
self.cmb = QComboBox()
choices = ['choice_1', 'choice_2', 'choice_3']
self.cmb.addItems(choices)
self.cmb.setObjectName(f'cmb_{row_count}_0')
self.tbl.setCellWidget(current_row_count, 0, self.cmb)
self.tbl.setCellWidget(current_row_count, 1, self.sb_1)
self.tbl.setCellWidget(current_row_count, 2, self.sb_2)
#self.cmb.setParent(self.tbl) # <<<< this didn't work
def pb_remove_row_clicked(self):
self.tbl.removeRow(self.tbl.currentRow())
def pb_save_clicked(self):
print(f'{self.pb_save.text()} clicked')
save(self.settings)
def pb_restore_clicked(self):
print(f'{self.pb_restore.text()} clicked')
restore(self.settings)
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
EDIT1
...removed as it didn't help and just made it more confusing...
EDIT2
Excluding the widgets, I have worked out how to use QSettings to save and restore user-entered cell data from a QTableWidget. Hope the following code helps someone. I have no doubt it could be done better and I welcome improvement suggestions. I'll update if I can work out the addition of widgets (QSpinBoxes and QComboBoxes) in to to cells.
import sys
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QTableWidget, QTableWidgetItem,
QHeaderView, QPushButton)
class MainWindow(QMainWindow):
# save settings alongside *py file
settings = QSettings("temp.ini", QSettings.IniFormat)
def __init__(self):
super().__init__()
self.initUI()
self.initSignals()
self.restore_settings()
def initUI(self):
# standar UI stuff
self.setObjectName('MainWindow')
self.setWindowTitle('Program Title')
self.setGeometry(400, 400, 500, 300)
wid = QWidget(self)
self.setCentralWidget(wid)
# create some widgets
self.pb_add_row = QPushButton('Add Row')
self.pb_remove_row = QPushButton('Remove Selected Row')
self.pb_save = QPushButton('Save')
self.pb_restore = QPushButton('Restore')
self.tbl = QTableWidget(0, 4, self)
# config up the table
header = self.tbl.horizontalHeader()
input_header = ['Label', 'X', 'Y', 'Comment']
self.tbl.setHorizontalHeaderLabels(input_header)
header.setSectionResizeMode(QHeaderView.Stretch)
# add widgets to UI
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.tbl)
self.vbox.addWidget(self.pb_add_row)
self.vbox.addWidget(self.pb_remove_row)
self.vbox.addWidget(self.pb_save)
self.vbox.addWidget(self.pb_restore)
wid.setLayout(self.vbox)
# pb signals
def initSignals(self):#
self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
self.pb_save.clicked.connect(self.pb_save_clicked)
self.pb_restore.clicked.connect(self.pb_restore_clicked)
# reads in the ini file adn re-generate the table contents
def restore_settings(self):
self.setting_value = self.settings.value('table')
self.setting_row = self.settings.value('rows')
self.setting_col = self.settings.value('columns')
print(f'RESTORE: {self.setting_value}')
# change the table row/columns, create a dictionary out of the saved table
try:
self.tbl.setRowCount(int(self.setting_row))
self.tbl.setColumnCount(int(self.setting_col))
self.my_dict = dict(eval(self.setting_value))
except TypeError:
print(f'RESTORE: No ini file, resulting in no rows/columns')
# loop over each table cell and populate with old values
for row in range(self.tbl.rowCount()):
for col in range(self.tbl.columnCount()):
try:
if col == 0: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['Label'][row]))
if col == 1: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['X'][row]))
if col == 2: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['Y'][row]))
if col == 3: self.tbl.setItem(row, col, QTableWidgetItem(self.my_dict['Comment'][row]))
except IndexError:
print(f'INDEX ERROR')
# add a new row to the end
def pb_add_row_clicked(self):
current_row_count = self.tbl.rowCount()
row_count = current_row_count + 1
self.tbl.setRowCount(row_count)
# remove selected row
def pb_remove_row_clicked(self):
self.tbl.removeRow(self.tbl.currentRow())
# save the table contents and table row/column to the ini file
def pb_save_clicked(self):
# create an empty dictionary
self.tbl_dict = {'Label':[], 'X':[], 'Y':[], 'Comment':[]}
# loop over the cells and add to the table
for column in range(self.tbl.columnCount()):
for row in range(self.tbl.rowCount()):
itm = self.tbl.item(row, column)
try:
text = itm.text()
except AttributeError: # happens when the cell is empty
text = ''
if column == 0: self.tbl_dict['Label'].append(text)
if column == 1: self.tbl_dict['X'].append(text)
if column == 2: self.tbl_dict['Y'].append(text)
if column == 3: self.tbl_dict['Comment'].append(text)
# write values to ini file
self.settings.setValue('table', str(self.tbl_dict))
self.settings.setValue('rows', self.tbl.rowCount())
self.settings.setValue('columns', self.tbl.columnCount())
print(f'WRITE: {self.tbl_dict}')
def pb_restore_clicked(self):
self.restore_settings()
def closeEvent(self, event):
self.pb_save_clicked()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
CHALLENGE
I am sure there is a better way than this and I challenge those who are well versed in Qt (pyqt5) to find a better solution.
ANSWER
In the mean-time, I hope the below helps someone as I could not find this information anywhere. I can't really read the C++ Qt stuff and I am no pyqt5 Harry Potter, so this took some effort.
The general gist here, is the widgets were never saved. The values were saved to a dictionary and then to the QSettings ini file as a string. On boot, the ini file was parsed and the table rebuilt - rows and columns. The widgets were then built from scratch and re-inserted in to the table. The dictionary from the ini file was then parsed and the values applied to the widgets or the blank cells as required.
WISH
I really wish there was a method inline with the save / restore methods as per OP links. I found that if I included those save / restore methods in the init, it sometimes messed the table up completely. As a result, for my greater program, it looks like I am going to have to save and restore all settings manually.
MRE CODE
import sys
from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QTableWidget, QTableWidgetItem,
QHeaderView, QPushButton, QComboBox, QSpinBox)
# custom spin box allowing for optional maximum
class CustomSpinBox(QSpinBox):
def __init__(self, sb_value=1, sb_step=1, sb_min=1, *args):
super(CustomSpinBox, self).__init__()
self.setValue(sb_value)
self.setSingleStep(sb_step)
self.setMinimum(sb_min)
if len(args) > 0:
sb_max = args[0]
self.setMaximum(sb_max)
class MainWindow(QMainWindow):
# save settings alongside *py file
settings = QSettings("temp.ini", QSettings.IniFormat)
def __init__(self):
super().__init__()
self.initUI()
self.initSignals()
self.restore_settings()
def initUI(self):
# standar UI stuff
self.setObjectName('MainWindow')
self.setWindowTitle('Program Title')
self.setGeometry(400, 400, 500, 300)
wid = QWidget(self)
self.setCentralWidget(wid)
# create some widgets
self.pb_add_row = QPushButton('Add Row')
self.pb_remove_row = QPushButton('Remove Selected Row')
self.pb_save = QPushButton('Save')
self.pb_restore = QPushButton('Restore')
self.tbl = QTableWidget(0, 4, self)
# config up the table
header = self.tbl.horizontalHeader()
input_header = ['Label', 'X', 'Y', 'Comment']
self.tbl.setHorizontalHeaderLabels(input_header)
header.setSectionResizeMode(QHeaderView.Stretch)
# name the table for QSettings
self.tbl.setObjectName('input_table')
# add widgets to UI
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.tbl)
self.vbox.addWidget(self.pb_add_row)
self.vbox.addWidget(self.pb_remove_row)
self.vbox.addWidget(self.pb_save)
wid.setLayout(self.vbox)
# create an empty dictionary to house the table widgets
self.table_widgets = {'cmb':[], 'sb_1':[], 'sb_2':[]}
# combobox values
self.choices = ['choice_1', 'choice_2', 'choice_3']
# pb signals
def initSignals(self):#
self.pb_add_row.clicked.connect(self.pb_add_row_clicked)
self.pb_remove_row.clicked.connect(self.pb_remove_row_clicked)
self.pb_save.clicked.connect(self.pb_save_clicked)
# reads in the ini file and re-generate the table contents
def restore_settings(self):
try:
self.setting_tbl = self.settings.value('table')
self.setting_row = self.settings.value('rows')
self.setting_col = self.settings.value('columns')
self.my_dict = dict(eval(self.setting_tbl))
# need to rebuild the table first
self.tbl.setRowCount(int(self.setting_row))
self.tbl.setColumnCount(int(self.setting_col))
print(f'RESTORE: row:{self.setting_row} and col:{self.setting_col} and table:{self.setting_tbl}')
# probably don't need to build and return values from the dictionary
for row in range(int(self.setting_row)):
self.table_widgets['cmb'].append(QComboBox())
self.table_widgets['sb_1'].append(CustomSpinBox(1, 1, 1))
self.table_widgets['sb_2'].append(CustomSpinBox(3, 1, 2, 6))
self.table_widgets['cmb'][row].addItems(self.choices)
self.tbl.setCellWidget(row, 0, self.table_widgets['cmb'][row])
self.tbl.setCellWidget(row, 1, self.table_widgets['sb_1'][row])
self.tbl.setCellWidget(row, 2, self.table_widgets['sb_2'][row])
self.tbl.cellWidget(row, 0).setCurrentText(self.my_dict['Label'][row])
self.tbl.cellWidget(row, 1).setValue(self.my_dict['X'][row])
self.tbl.cellWidget(row, 2).setValue(self.my_dict['Y'][row])
self.tbl.setItem(row, 3, QTableWidgetItem(self.my_dict['Comment'][row]))
except TypeError:
print('NO INI FILE PRESENT')
# add a new row to the end
def pb_add_row_clicked(self):
current_row_count = self.tbl.rowCount()
row_count = current_row_count + 1
self.tbl.setRowCount(row_count)
self.table_widgets['cmb'].append(QComboBox())
self.table_widgets['sb_1'].append(CustomSpinBox(1, 1, 1))
self.table_widgets['sb_2'].append(CustomSpinBox(3, 1, 2, 6))
self.table_widgets['cmb'][-1].addItems(self.choices)
self.tbl.setCellWidget(current_row_count, 0, self.table_widgets['cmb'][current_row_count])
self.tbl.setCellWidget(current_row_count, 1, self.table_widgets['sb_1'][current_row_count])
self.tbl.setCellWidget(current_row_count, 2, self.table_widgets['sb_2'][current_row_count])
# save the table contents and table row/column to the ini file
def pb_save_clicked(self):
#save(self.settings)
# create an empty dictionary
self.tbl_dict = {'Label':[], 'X':[], 'Y':[], 'Comment':[]}
# loop over the cells and add to the dictionary
for row in range(self.tbl.rowCount()):
cmb_text = self.tbl.cellWidget(row, 0).currentText()
sb_1_value = self.tbl.cellWidget(row, 1).value()
sb_2_value = self.tbl.cellWidget(row, 2).value()
comment_text = self.tbl.item(row, 3)
try:
comment_text = comment_text.text()
except AttributeError: # happens when the cell is empty or a widget
comment_text = ''
self.tbl_dict['Label'].append(cmb_text)
self.tbl_dict['X'].append(sb_1_value)
self.tbl_dict['Y'].append(sb_2_value)
self.tbl_dict['Comment'].append(comment_text)
# write values to ini file
self.settings.setValue('table', str(self.tbl_dict))
self.settings.setValue('rows', self.tbl.rowCount())
self.settings.setValue('columns', self.tbl.columnCount())
print(f'WRITE TO INI FILE: {self.tbl_dict}')
# remove selected row
def pb_remove_row_clicked(self):
self.tbl.removeRow(self.tbl.currentRow())
def closeEvent(self, event):
self.pb_save_clicked()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
I'm not well versed in PyQt by any means, but if I'm interpreting your problem correctly I believe PyQtConfig may help since nothing else has been suggested for some time. Here is the github version link.
Tucked away in a dark corner of github, this is not nearly as popular as it should be. A snippet from the introduction:
The core of the API is a ConfigManager instance that holds configuration settings (either as a Python dict, or a QSettings instance) and provides standard methods to get and set values.
Configuration parameters can have Qt widgets attached as handlers. Once attached the widget and the configuration value will be kept in sync. Setting the value on the ConfigManager will update any attached widgets and changes to the value on the widget will be reflected immediately in the ConfigManager. Qt signals are emitted on each update.
This should allow you to save the state of widgets and I would imagine the widgets inside them separately. However, you will need to map the necessary events to update the config file, then build the widgets "up". If it's still clearing the cache--as it seems to act similar to a dict--when pulling from the config file, this functionality might be easier if other languages were used.
Integrating database storage with java fetching methods might be overkill, but SQL for python would be a middle ground. You can have tables for parent and child widgets as well as parameters, then link them by unique id's. Using python to fetch these then build the widget on startup would be a great alternative, and would actively keep widgets organized. There is also the added benefit of being able to more easily take the application online at some point with MySQL or Oracle.
Please be kind. This is my first question. I have included a minimum listing that illustrates the problem.
from PyQt5 import QtWidgets, QtCore, QtGui, uic, QtSql
from PyQt5.QtCore import Qt
qtCreatorFile = "QuotesGui.ui"
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class QuotesUI(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, model):
QtWidgets.QMainWindow.__init__(self)
uic.loadUi(qtCreatorFile,self)
self.setupUi(self)
self.model = model # View owns the model
self.tableView.setModel(self.model._formModel)
self.setWidgetMapping() # Map model to view widgets
authorSRD = QtSql.QSqlRelationalDelegate(self.authorComboBox)
self.authorComboBox.setModel(self.model._authorModel)
self.authorComboBox.setItemDelegate(authorSRD)
self.authorComboBox.setModelColumn(
self.model._formModel.fieldIndex("authorName"))
citationSRD = QtSql.QSqlRelationalDelegate(self.citationComboBox)
self.citationComboBox.setModel(self.model._citationModel)
self.citationComboBox.setItemDelegate(citationSRD)
self.citationComboBox.setModelColumn(
self.model._formModel.fieldIndex("citationText"))
self.mainMapper.toFirst()
self._updateButtons(0) # To disable the previous button at start up
self.saveBtn.setEnabled(False) # Since we can't edit, we can't save
self._connectSignals(self.model)
def setWidgetMapping(self):
self.mainMapper = QtWidgets.QDataWidgetMapper(self)
self.mainMapper.setModel(self.model._formModel)
self.mainMapper.setItemDelegate(QtSql.QSqlRelationalDelegate(self))
self.mainMapper.addMapping(self.authorComboBox,
self.model._formModel.fieldIndex("authorName"))
self.mainMapper.addMapping(self.citationComboBox,
self.model._formModel.fieldIndex("citationText"))
self.mainMapper.addMapping(self.quoteText,
self.model._formModel.fieldIndex("quoteText"))
def _connectSignals(self, model):
self.lastBtn.clicked.connect(self.mainMapper.toLast)
self.nextBtn.clicked.connect(self.mainMapper.toNext)
self.prevBtn.clicked.connect(self.mainMapper.toPrevious)
self.firstBtn.clicked.connect(self.mainMapper.toFirst)
self.mainMapper.currentIndexChanged.connect(
self._updateButtons)
def _updateButtons(self, row):
self.firstBtn.setEnabled(row > 0)
self.prevBtn.setEnabled(row > 0)
self.nextBtn.setEnabled(row < self.model._formModel.rowCount() - 1)
self.lastBtn.setEnabled(row < self.model._formModel.rowCount() - 1)
class QuoteModel():
def __init__(self, data):
# Make a database connection
self.db = self.createConnection(data)
# set models
self._setFormModel()
self._setAuthorComboTableLink()
self._setCitationComboTableLink()
# Connect signals
self._connectSignals()
# Define a model for the form
def _setFormModel(self):
self._formModel = QtSql.QSqlRelationalTableModel(
parent = None, db = self.db)
self._formModel.setEditStrategy(
QtSql.QSqlTableModel.OnManualSubmit)
self._formModel.setTable("Quote")
authorIdx = self._formModel.fieldIndex("authorId")
citationIdx = self._formModel.fieldIndex("citationId")
self._formModel.setJoinMode(1) # Left Join
self._formModel.setRelation(authorIdx, QtSql.QSqlRelation(
"Author", "authorId", "authorName"))
self._formModel.setRelation(citationIdx, QtSql.QSqlRelation(
"Citation", "citationId", "citationText"))
self._formModel.select()
# Define models and link tables for the two comboboxes
def _setAuthorComboTableLink(self):
self._authorModel = QtSql.QSqlTableModel(
parent = None, db = self.db)
self._authorModel.setTable("Author")
self._authorModel.setEditStrategy(
QtSql.QSqlTableModel.OnManualSubmit)
self._authorModel.select()
self._authorModel.sort(1,Qt.AscendingOrder)
def _setCitationComboTableLink(self):
self._citationModel = QtSql.QSqlTableModel(
parent = None, db = self.db)
self._citationModel.setTable("Citation")
self._citationModel.setEditStrategy(
QtSql.QSqlTableModel.OnManualSubmit)
self._citationModel.select()
self._citationModel.sort(1,Qt.AscendingOrder)
def _newData(self):
print('in _newData')
def _connectSignals(self):
self._authorModel.rowsInserted.connect(self._newData)
#######################################################
# A function to connect to a named database
def createConnection(self,dbfile):
db = QtSql.QSqlDatabase.addDatabase('QSQLITE')
db.setDatabaseName(dbfile)
db.open()
if not db.open():
QtWidgets.QMessageBox.critical(
None, QtWidgets.qApp.tr("Cannot open database"),
QtWidgets.qApp.tr("Unable to establish a database connection.\n"),
QtWidgets.QMessageBox.Cancel,
)
return False
return db
dbFile = "Quotations.db"
if __name__=='__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
m = QuoteModel(dbFile)
w = QuotesUI(m)
w.show()
sys.exit(app.exec_())
Still requires the upload of a 8kb .ui file and a 70kb sample database to run. So far I haven't been able to figure out how to do that....
When this is run, incorporating the rest of the necessary code, the author combobox works as expected, i.e.: the dropdown shows all the values in the author table and when I step the the records in the model, the correct author is displayed in the combobox.
The citation combobox does NOT function. As written here it neither shows values from the citation table, nor is it connected to the quotes table. Basically it shows nothing.
I think I need, somehow, a second delegate on the model. But I don't know how.
Any ideas how to get this functioning??
To make this work I had to change:
self.authorComboBox.setModel(self.model._authorModel)
to:
authorRel = self.model._formModel.relationModel(self.model.authorIdx)
self.authorComboBox.setModel(authorRel)
The QSqlRelationalTableModel has a virtual function "relationModel" that returns a QSqlTableModel. When you use this model as the model for the QComboBox, everything works.
This is a repost of my previous post which I deleted. In the first post I asked if someone knows a way of creating bookmarks with PyQT5, however, originally, I did not post what issues I am having now, with my way. I have 3 toolbars, in my web browser.
For additional buttons as - exit, minimize, maximize and etc.
For navigation in the web.
Bookmarks all are created with the instance of QToolBar().
Bookmarks toolbar code
self.bookmark_bar = QToolBar('Bookmark')
self.bookmark_bar.setIconSize(QSize(12, 12))
self.bookmark_bar.setMovable(False)
self.addToolBar(self.bookmark_bar)
After the tool bars are created the buttons are added, as there is a lot of code there I will show just the final result as a screenshot and the last few lines of code in init function.
The last few lines of code:
self.add_new_tab(QUrl('http://www.google.com'), 'Home Page')
self.bookmarks_load()
self.show()
self.setWindowTitle('Tempo')
Everything is fine with the Web Browser itself, as everything is working except the bookmarks. The line of code 'bookmarks_load()' load the bookmarks from a .txt file. As for now in the .txt document the bookmark is youtube.com (does not matter which link is used). The bookmarks functioncs:
Add a website to a bookmark.txt
def Bookmark(self):
try:
qurl = QUrl(self.urlbar.text())
print('Here we are using the QUrl toString method: %s ---> Type: %s' % (qurl.toString(), type(qurl)))
url = qurl.toString()
print('Here we are storing the new string to a new variable: %s ---> Type: %s' % (url, type(url)))
b = open(os.path.join('bookmarks', 'bookmarks.txt'), "wb")
self.bookmarks_write = pickle.dump(url, b)
b.close()
except:
print("Crash - Bookmarks not stored")
self.bookmark_btn.setText("★")
Load bookmarks from the document file.
def bookmarks_load(self):
try:
bookmarks_open = open(os.path.join('bookmarks', 'bookmarks.txt'), 'rb')
self.bookmarks_write = pickle.load(bookmarks_open)
bookmarks_open.close()
self.create_bookmarks()
except:
bookmarks_open = open(os.path.join('bookmarks', 'bookmarks.txt'), 'wb')
bookmarks = 'http://www.stackoverflow.com'
self.bookmarks_write = pickle.dump(bookmarks, bookmarks_open)
bookmarks_open.close()
self.create_bookmarks()
print('Crash - Did not load bookmarks')
Create the bookmarks 'button', when pressed open a new tab with that website.
def create_bookmarks(self):
bookmark_list = []
try:
for book in self.bookmarks_write.split():
print(book)
bookmark_list.append(book)
print(bookmark_list)
except:
print("Something went wrong with the list")
try:
button = QAction(QIcon(os.path.join('images', 'tab_icon.PNG')), 'Open bookmark', self)
button.triggered.connect(self.add_new_tab(QUrl(bookmark_list[0]), 'New'))
self.bookmark_bar.addAction(button)
except:
print('Button is causing the error')
Now this is the part which I start having issues. If I remove the - triggered.connect line and I do not add any functionality to that 'button' everything launches and works, without any errors. It stores and can load the bookmarks. However, when that line is added, it crashes and the button is not created(The app does not exit as there is an except statement which catches the error - which pyqt does not show what error it is.). This is the add_new_tab() function:
def add_new_tab(self, qurl=None, label='Blank'):
if qurl is None:
qurl = QUrl('')
web_browser = QWebEngineView()
web_browser.setUrl(qurl)
web_browser.adjustSize()
index = self.tabs.addTab(web_browser, label)
self.tabs.setCurrentIndex(index)
web_browser.urlChanged.connect(lambda qurl, web_browser=web_browser:
self.update_urlbar(qurl, web_browser))
web_browser.loadFinished.connect(lambda _, i=index, web_browser=web_browser:
self.tabs.setTabText(i, web_browser.page().title()))
Originally I open tabs by 'double clicking' on the tab bar with this function:
def tab_open_doubleclick(self, index):
if index == -1:
self.add_new_tab()
As you can see on the trigger - I do pass the link as QUrl and I do add a test label. The problem I have it somehow does not want to work and I can not find why, because Python PyQT5 does not show an error, it just closes with the return code.
Screenshots as explanation:
Link not added to a bookmarks.txt
Link added to bookmarks.txt
Pickle does dump the link in .txt
The "except" statement is run, while the triggered.connect line is not commented out.
The app continues to run, but the loaded bookmarks buttons are not there.
In the following example I show how to add the functionality of the BookMark, for it use QSettings to store the data but for this you must use setOrganizationName(), setOrganizationDomain() and setApplicationName(). Also I added the functionality of obtaining the title of the web page. The BookMarkToolBar class has the bookmarkClicked method that returns the url associated with the BookMark and the title.
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
class BookMarkToolBar(QtWidgets.QToolBar):
bookmarkClicked = QtCore.pyqtSignal(QtCore.QUrl, str)
def __init__(self, parent=None):
super(BookMarkToolBar, self).__init__(parent)
self.actionTriggered.connect(self.onActionTriggered)
self.bookmark_list = []
def setBoorkMarks(self, bookmarks):
for bookmark in bookmarks:
self.addBookMarkAction(bookmark["title"], bookmark["url"])
def addBookMarkAction(self, title, url):
bookmark = {"title": title, "url": url}
fm = QtGui.QFontMetrics(self.font())
if bookmark not in self.bookmark_list:
text = fm.elidedText(title, QtCore.Qt.ElideRight, 150)
action = self.addAction(text)
action.setData(bookmark)
self.bookmark_list.append(bookmark)
#QtCore.pyqtSlot(QtWidgets.QAction)
def onActionTriggered(self, action):
bookmark = action.data()
self.bookmarkClicked.emit(bookmark["url"], bookmark["title"])
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.defaultUrl = QtCore.QUrl()
self.tabs = QtWidgets.QTabWidget()
self.tabs.setTabsClosable(True)
self.setCentralWidget(self.tabs)
self.urlLe = QtWidgets.QLineEdit()
self.urlLe.returnPressed.connect(self.onReturnPressed)
self.favoriteButton = QtWidgets.QToolButton()
self.favoriteButton.setIcon(QtGui.QIcon("images/star.png"))
self.favoriteButton.clicked.connect(self.addFavoriteClicked)
toolbar = self.addToolBar("")
toolbar.addWidget(self.urlLe)
toolbar.addWidget(self.favoriteButton)
self.addToolBarBreak()
self.bookmarkToolbar = BookMarkToolBar()
self.bookmarkToolbar.bookmarkClicked.connect(self.add_new_tab)
self.addToolBar(self.bookmarkToolbar)
self.readSettings()
def onReturnPressed(self):
self.tabs.currentWidget().setUrl(QtCore.QUrl.fromUserInput(self.urlLe.text()))
def addFavoriteClicked(self):
loop = QtCore.QEventLoop()
def callback(resp):
setattr(self, "title", resp)
loop.quit()
web_browser = self.tabs.currentWidget()
web_browser.page().runJavaScript("(function() { return document.title;})();", callback)
url = web_browser.url()
loop.exec_()
self.bookmarkToolbar.addBookMarkAction(getattr(self, "title"), url)
def add_new_tab(self, qurl=QtCore.QUrl(), label='Blank'):
web_browser = QtWebEngineWidgets.QWebEngineView()
web_browser.setUrl(qurl)
web_browser.adjustSize()
web_browser.urlChanged.connect(self.updateUlrLe)
index = self.tabs.addTab(web_browser, label)
self.tabs.setCurrentIndex(index)
self.urlLe.setText(qurl.toString())
def updateUlrLe(self, url):
self.urlLe.setText(url.toDisplayString())
def readSettings(self):
setting = QtCore.QSettings()
self.defaultUrl = setting.value("defaultUrl", QtCore.QUrl('http://www.google.com'))
self.add_new_tab(self.defaultUrl, 'Home Page')
self.bookmarkToolbar.setBoorkMarks(setting.value("bookmarks", []))
def saveSettins(self):
settings = QtCore.QSettings()
settings.setValue("defaultUrl", self.defaultUrl)
settings.setValue("bookmarks", self.bookmarkToolbar.bookmark_list)
def closeEvent(self, event):
self.saveSettins()
super(MainWindow, self).closeEvent(event)
if __name__ == '__main__':
import sys
QtCore.QCoreApplication.setOrganizationName("eyllanesc.org")
QtCore.QCoreApplication.setOrganizationDomain("www.eyllanesc.com")
QtCore.QCoreApplication.setApplicationName("MyApp")
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I would like to insert a list of users from a file into the first row of a tablewidget or tableview. I am currently trying it out with a tablewidget. So far, the code below is what I came up with after having seen the answer from this post. Basically if you look at the image, I'm trying to do exactly that then later I'll add an ok button to perform the actions.
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self, rows, columns):
QtGui.QWidget.__init__(self)
self.table = QtGui.QTableWidget(rows, columns, self)
self.table.setHorizontalHeaderItem(0, QtGui.QTableWidgetItem("Users"))
self.table.setHorizontalHeaderItem(1, QtGui.QTableWidgetItem("Delete User"))
self.table.setHorizontalHeaderItem(2, QtGui.QTableWidgetItem("Delete User and Home"))
self.table.verticalHeader().hide()
header = self.table.horizontalHeader()
header.setStretchLastSection(True)
for column in range(columns):
if column == 0:
with open("users") as input:
if input is not None:
users = input.readlines()
for line in users:
users = QtGui.QTableWidgetItem(line)
print line
input.close()
for row in range(rows):
if column % 3:
item = QtGui.QTableWidgetItem('%d' % column)
item.setFlags(QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsEnabled)
item.setCheckState(QtCore.Qt.Unchecked)
self.table.setItem(row, column, item)
self.table.itemClicked.connect(self.handleItemClicked)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
self._list = []
def handleItemClicked(self, item):
if item.checkState() == QtCore.Qt.Checked:
print('"%s" Checked' % item.text())
self._list.append(item.row())
print(self._list)
else:
print('"%s" Clicked' % item.text())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window(6, 3)
window.resize(400, 400)
window.show()
sys.exit(app.exec_())
#How do I insert data from file in the first column of a qtablewidget or qtableview?
#This is the example code for insert data to Qtablewidget
#Please not the print result. I hope you can understand how to use columns and row values.
#I used the input data from "dictionary variable - self.input". You can replace this input variable to your input data.
#if any doubts regarding this below code, please let me know.
#If your are not expecting this answer, sorry.
#Thanks, Subin
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
class Window (QtGui.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
#Read user text file and append to dictionary variable
userDataPath = 'C:/Users/Subin/Desktop/userList.txt'
readUserData = open (userDataPath, 'r')
userList = readUserData.readlines ()
#self.input = {'bruno':[0,1], 'shelly':[0,0], 'nobody':[1,1]}
self.input = {}
for eachUser in userList :
if eachUser.strip() :
self.input.setdefault (eachUser.strip(), [1, 1])
self.rows = 3
self.columns = len(self.input)
self.tableWidget = QtGui.QTableWidget (self)
self.tableWidget.setGeometry (QtCore.QRect(10, 10, 500, 180))
self.tableWidget.setObjectName ('tableWidget')
self.tableWidget.setColumnCount(self.rows)
self.tableWidget.setRowCount(self.columns)
print '\t\tcolumns rows\n'
cLoop = 0
for eachInput in self.input :
self.item_name = QtGui.QTableWidgetItem ()
self.tableWidget.setItem (cLoop, 0, self.item_name)
self.item_name.setText (eachInput)
print 'name\t\tcolumns ', cLoop, ' rows ', 0
rLoop = 0
for eachAttri in self.input[eachInput] :
self.item_attri = QtGui.QTableWidgetItem ()
self.tableWidget.setItem (cLoop, rLoop+1, self.item_attri)
self.item_attri.setFlags (QtCore.Qt.ItemIsUserCheckable|QtCore.Qt.ItemIsEnabled)
self.item_attri.setCheckState (QtCore.Qt.Unchecked)
if eachAttri==1 :
self.item_attri.setCheckState (QtCore.Qt.Checked)
print 'attributes\tcolumns ', cLoop, ' rows ', rLoop+1
rLoop+=1
cLoop+=1
print '\n'
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
wind = Window ()
wind.show()
sys.exit(app.exec_())
The following code may do what you need. It assumes that you have a file users.txt, which consists of one name per row, like
Harry
Sally
Wang
Jon
Leona
You then need to read that file and get rid of the line break character.
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
with open("users.txt") as f:
users = f.readlines()
users = [user.split("\n")[0] for user in users]
self.table = QtGui.QTableWidget(len(users), 2, self)
self.table.setHorizontalHeaderLabels(["Delete User", "Delete Home"])
self.table.setVerticalHeaderLabels(users)
for column in range(2):
for row in range(len(users)):
item = QtGui.QTableWidgetItem("")
item.setFlags(QtCore.Qt.ItemIsUserCheckable |
QtCore.Qt.ItemIsEnabled)
item.setCheckState(QtCore.Qt.Unchecked)
self.table.setItem(row, column, item)
self.table.itemClicked.connect(self.handleItemClicked)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.table)
self._list = []
def handleItemClicked(self, item):
if item.checkState() == QtCore.Qt.Checked:
print('"%s" Checked' % item.text())
self._list.append(item.row())
print(self._list)
else:
print('"%s" Clicked' % item.text())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(350, 300)
window.show()
sys.exit(app.exec_())
I'm trying to append nodes of namedtuple object to a treeView but I'm not sure how to subclass QAbstractItem (if that's even the proper way). I'm still very new to Python so this is confusing to me. Here is my problem code:
Exercise = namedtuple('Exercise','name html list')
e_list = []
for i in range(1,6,1):
dummy_list = [1,2,3,'a','b','c']
ntup = Exercise("exercise{0}".format(i),'html here',dummy_list)
e_list.append(ntup)
for e in e_list:
item = QtGui.QStandardItem(e) # gives error
self.tree_model.appendRow(item) # doesnt execute
And here is the the whole program:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from collections import namedtuple
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.pushButton = QtGui.QPushButton(self)
self.pushButton.setText("Test 1 - doesn't work")
self.pushButton.clicked.connect(self.on_pushbutton)
self.pushButton2 = QtGui.QPushButton(self)
self.pushButton2.setText("Test 2 - works")
self.pushButton2.clicked.connect(self.on_pushbutton2)
self.treeView = QtGui.QTreeView(self)
self.treeView.clicked[QModelIndex].connect(self.on_clickitem)
self.tree_model = QtGui.QStandardItemModel()
self.treeView.setModel(self.tree_model)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.pushButton)
self.layoutVertical.addWidget(self.pushButton2)
self.layoutVertical.addWidget(self.treeView)
def on_pushbutton(self):
Exercise = namedtuple('Exercise','name html list')
e_list = []
for i in range(1,3,1):
dummy_list = [1,2,3,'a','b','c']
ntup = Exercise("exercise{}".format(i),'html here',dummy_list)
e_list.append(ntup)
for e in e_list:
item = QtGui.QStandardItem(e) # gives error
self.tree_model.appendRow(item) # never occurs
def on_pushbutton2(self):
txt = 'hello world'
item = QtGui.QStandardItem(txt)
self.tree_model.appendRow(item)
def on_clickitem(self,index):
item = self.tree_model.itemFromIndex(index) # doesn't work
print "item name:",item.getName() # doesn't work
print "item html:",item.getHtml() # doesn't work
print "item list:",item.getList() # doesn't work
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
I want to append the nodes into a tree and when I click on an item I want to get the values of the namedtuple (i.e. the values of 'name', 'html', and 'alist'). Thanks for your help.
Paul
I just ended up using QTreeWidget and setting the tree item data like this:
item = QtGui.QTreeWidgetItem()
item.mydata = my_namedtuple
I found the answer here: QtGui QTreeWidgetItem setData holding a float number
I didn't know you could just dynamically make an attribute for the tree item.