I have a QTableView with the following code below. It runs fine, but when I am typing the search field is always the first column, which is the number and primary key.
How can I change that the standard search field is another field, second (name) for example, even with select column on the name column or with setsort on name the standard search when typing is the first number field?
When the code is running, I can change the search column by left-clicking in the second column, but I want to achieve this programmatically.
class KL_browse(QDialog, ui_kl_browse.Ui_kl_browse):
def __init__(self):
super(KL_browse, self).__init__()
query = QSqlQuery()
query.exec_("""SELECT * FROM klanten""")
self.setupUi(self)
self.model = QSqlTableModel(self)
self.model.setTable("klanten")
self.model.setSort(1, Qt.AscendingOrder)
self.model.select()
self.tableView.setModel(self.model)
self.tableView.setSelectionMode(QTableView.SingleSelection)
self.tableView.setSelectionBehavior(QTableView.SelectRows)
# self.view.setColumnHidden(ID, True)
self.tableView.horizontalHeader().setSectionsMovable(True)
self.tableView.horizontalHeader().setDragEnabled(True)
self.tableView.horizontalHeader().setDragDropMode(QAbstractItemView.InternalMove)
self.tableView.horizontalHeader().setSortIndicator(1,0)
self.tableView.selectRow(0)
self.tableView.selectColumn(1)
You need to change the index as follows:
# index: the index of a cell in the desired column
index = tableView.model().index(0, 1)
tableView.selectionModel().setCurrentIndex(index, QItemSelectionModel.NoUpdate);
Related
I made PyQt5 APP which create SQlite3 DB and show data from it in QTableView widget.
I have a problem with editing rows in a widget. There are 3 buttons "Add", "Change" and "Delete" that should delete, modify and add new rows to the widget, as well as edit the database itself, but the buttons do not work properly.
"Add" button - after clicking add button, when all new data inputted and Enter clicked, all values is gone and "!" symbol is showing. I need press Add button, input new data in cells, click Enter and all data must save in SQL DB and displayed in QTableView widget in live time.
"Change" button - when change clicked and Enter pressed after cell editing, all data gone. All data must be save in real time is SQL DB after change button press.
3)"Delete" button - this button don't delete row. There is no error, no any answer from app.
How to program the buttons correctly ?
Do I need to use a delegate?
Which programming approach is more preferable when working with a graphical interface and SQL database?
I made a reproducible example. At startup, a database with 1 table and 2 rows will be created.
import sys, os, sqlite3
from datetime import datetime
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtSql import *
from PyQt5.QtCore import *
CONFIG_NAME = 'config.ini'
DB_NAME = 'nsi.db'
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__()
self.window_pref()
self.show_widgets()
def window_pref(self):
self.setWindowTitle('PyQt5 APP')
self.def_width = 800
self.def_height = 400
self.def_size = self.setMinimumSize(self.def_width, self.def_height)
def show_widgets(self):
self.createConnection()
self.fillDB()
self.setupMainWidgets()
def createConnection(self):
db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName(DB_NAME)
if not db.open():
QMessageBox.warning(self, 'PyQt5 APP',
'Error:{}'.format(db.lastError().text()))
sys.exit(1)
def fillDB(self):
query = QSqlQuery()
query.exec_("""\
CREATE TABLE sprav (
id_nsi INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
nsi_name TEXT UNIQUE NOT NULL,
file_date TEXT NOT NULL,
file_name TEXT NOT NULL)
""")
query.prepare("""\
INSERT INTO sprav (nsi_name, file_date, file_name)VALUES (?, ?, ?)
""")
sample_list = (('nsi1', 'january', 'file1'), ('nsi2', 'may', 'file2'))
for i in sample_list:
query.addBindValue(i[0])
query.addBindValue(i[1])
query.addBindValue(i[2])
query.exec_()
def setupMainWidgets(self):
mw_widget = QWidget()
main_panel = QHBoxLayout(mw_widget)
# SQL Table
self.modelSql = QSqlTableModel()
self.modelSql.setTable('sprav')
self.modelSql.setQuery(QSqlQuery(
'SELECT nsi_name, file_date, file_name FROM sprav'))
self.modelSql.setHeaderData(self.modelSql.fieldIndex('nsi_name'),
Qt.Horizontal, 'Name')
self.modelSql.setHeaderData(self.modelSql.fieldIndex('file_date'),
Qt.Horizontal, 'Date')
self.modelSql.setHeaderData(self.modelSql.fieldIndex('file_name'),
Qt.Horizontal, 'File')
self.modelSql.setEditStrategy(QSqlTableModel.OnFieldChange)
self.modelSql.select()
# QTableView()
self.table_view = QTableView()
self.table_view.setSelectionBehavior(1)
self.table_view.setAlternatingRowColors(True)
self.table_view.setModel(self.modelSql)
self.table_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
main_panel.addWidget(self.table_view)
# QVBoxLayout()
right_panel = QVBoxLayout()
line = QFrame()
line.setFrameShape(QFrame.HLine)
self.add_record = QPushButton('Add', self)
self.add_record.clicked.connect(self.addRecord)
self.change_record = QPushButton('Change', self)
self.change_record.clicked.connect(self.changeRecord)
self.delete_record = QPushButton('Delete', self)
self.delete_record.clicked.connect(self.delRecord)
right_panel.addSpacing(20)
right_panel.addWidget(line)
right_panel.addWidget(self.add_record)
right_panel.addWidget(self.change_record)
right_panel.addWidget(self.delete_record)
right_panel.addStretch()
main_panel.addLayout(right_panel)
self.setCentralWidget(mw_widget)
def addRecord(self):
row = self.modelSql.rowCount()
self.modelSql.insertRow(row)
index = self.modelSql.index(row, 0)
self.table_view.setCurrentIndex(index)
self.table_view.edit(index)
def delRecord(self):
cur_item = self.table_view.selectedIndexes()
for index in cur_item:
self.modelSql.removeRow(index.row())
self.modelSql.select()
def changeRecord(self):
self.table_view.edit(self.table_view.currentIndex())
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
There are two problems with your code.
First of all, if you use QSqlTableModel, you should not call setQuery():
This function simply calls QSqlQueryModel::setQuery(query). You should normally not call it on a QSqlTableModel. Instead, use setTable(), setSort(), setFilter(), etc., to set up the query.
This is the main reason for which your row addition/deletion/editing didn't work: the model became partially invalid, and any submission was discarded because columns didn't match the records of the table model, which is very important also because the model requires an incremental key that QSqlTableModel is able to use properly on its own.
Remove the setQuery() from your code, and consider that if you did that only to hide a column, then you just have to hide that column:
self.table_view.setColumnHidden(0, True)
Obviously, you have to keep in mind that all column indexes you will use, will now start from 1, since the model also includes the id:
def addRecord(self):
# ...
index = self.modelSql.index(row, 1)
self.table_view.setCurrentIndex(index)
self.table_view.edit(index)
The other problem was the deletion of rows: even after fixing what described above, the number of rows and its order would have been wrong:
when you cycle through the selectedIndexes() you're getting the same row for each selected item: since you used the SelectRows selection behavior, the for loop would have called removeRow() three times the same row, for each selected row;
removal of indexes should always be in reverse, sorted order; consider if you try to remove row 0 and 1: the first iteration would remove row 0, but at that point the previous row 1 would have become the new row 0, so the next iteration would actually delete the row that previously was the third;
The solution is to have a sorted list of unique row numbers, and cycle through them in reversed order:
def delRecord(self):
# create a set of unique row numbers
rows = set([i.row() for i in self.table_view.selectedIndexes()])
# cycle through them in reversed sorting order
for row in sorted(rows, reverse=True):
self.modelSql.removeRow(row)
self.modelSql.select()
Remember to call select() (I know you did, but better safe than sorry), as explained in the documentation about removeRows():
Deletions are submitted immediately to the database. The model retains a blank row for successfully deleted row until refreshed with select().
Unrelated notes: 1. avoid unnecessary and confusing imports: since you're already importing QtWidgets with wildcard, there's no point for from PyQt5 import QtWidgets; you either import the submodule, or its classes; 2. setMinimumSize returns nothing, so self.def_size will be None; if you want to keep a variable for the default size, use self.def_size = QSize(self.def_width, self.def_height) then self.setMinimumSize(self.def_size); 3. do not use sys.exit() inside a Qt app (and from a QWidget class), instead use QApplication.exit(1); 4. use more verbose names that also clarify their type: you have almost identical names for buttons and functions (add_record and addRecord), which is a poor choice in naming (also considering the different writing style): a better choice would be to name the button like add_record_btn, or addRecordBtn to follow the Qt convention;
I’ve got a QTableView based on a QSqlTableModel.
I want to filter displayed information: a user chooses one year in a combobox, and one or more perons in a multiple choices list. The QTableView data is filtered by these two widgets. My problem is that the data is correctly filtered on year, but only on the first personn selected. Other people's data are not displayed.
This is the code which creates the widget and the model, fills it, and filters it to display information in the QTableView :
self.model = QtSql.QSqlTableModel(self, self.db)
self.model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
self.ui.tbv_suivtemp.setModel(self.model)
self.model.setTable("bdsuivis.t_suivprev_tablo")
self.model.select()
self.model.setHeaderData(22, QtCore.Qt.Horizontal, "Annee")
self.model.setHeaderData(21, QtCore.Qt.Horizontal, "Salarie")
annee = self.ui.cbx_channee.itemText(self.ui.cbx_channee.currentIndex())
if len(annee) == 0 and len(self.text_sal) == 0:
self.model.setFilter("")
else:
filtre = "annee = '%s' AND salaries IN ('%s')" % (annee, self.text_sal)
print filtre
self.model.setFilter(filtre)
This is the code to create self.text_sal:
def choisal(self):
list_sal = []
for item in xrange (len(self.ui.lst_salaries.selectedItems())):
salarie = self.ui.lst_salaries.selectedItems()[item].text().replace("\'","\'\'")
list_sal.append(salarie)
self.text_sal = ", ".join((str(x) for x in list_sal)).replace(",","\',\'")
print filtre gives :
annee = '2014' AND salaries IN ('Dupont Albert','Durant Hector')
Which represents the WHERE clause from a SQL query.
My problem is: the year is correctly filtered, but only for "Albert Dupont" - the data for "Hector Durant" is not displayed. If, as a user, I click for the first time on "Hector Durand", in my multiple choices list, then on "Albert Dupont", the data for "Hector Durand" in 2014 is displayed.
Is this normal behaviour for a QSqlTableModel filter? Is there a way to obtain data for the two persons?
I am trying to create a table, where it has 2 columns and several rows.
Column1 will be listing all the available mesh/geos in the scene while Column2 will be in the form of combo box per cell where it contains several options - depending on the mesh/geos from Column1, it will lists different shader options in the combobox as it will be reading off from a file. Meaning to say in the table, each item is on a per-row basis.
I am currently having issues with populating the list of mesh/geos into Column1. Suppose my scene has 5 geos - pCube1, pCube2, pCube3, pCube4, pCube5, in my table, I would be expecting the Column0 of its 5 rows to be populated with pCube#, however instead of that, I got pCube5 as my output result instead.
Please see the following code:
from PyQt4 import QtGui, QtCore
from functools import partial
import maya.cmds as cmds
class combo_box( QtGui.QComboBox ):
# For combox
def __init__( self, *args, **kwargs ):
super( combo_box, self ).__init__( *args, **kwargs)
def get_all_geos():
all_geos = cmds.ls(type='mesh')
return all_geos
class TestTable( QtGui.QWidget ):
def __init__( self, parent=None ):
QtGui.QWidget.__init__( self, parent )
self.setLayout( QtGui.QVBoxLayout() )
self.resize( 600, 300 )
self.myTable = QtGui.QTableWidget()
self.myTable.setColumnCount( 2 )
rowCount = len(get_all_geos())
self.myTable.setRowCount(rowCount)
self.setTable()
self.layout().addWidget(self.myTable)
self.myTable.cellChanged.connect(self.update)
def setTable(self):
# Adding the list of mesh found in scene into first column
for geo in get_all_geos():
item = cmds.listRelatives(geo, parent=True)[0]
for i in range(0, self.myTable.rowCount()):
# instead of being populated with the list of items, I got the same name for the entire column
self.myTable.setItem(i, 0, QtGui.QTableWidgetItem(item))
# sets the combobox into the second column
box = combo_box()
nameList = ("test1","test2","test3")
box.addItems(nameList)
self.myTable.setCellWidget(i,1,box)
box.currentIndexChanged.connect(partial(self.tmp, i))
def tmp(self, rowIndex, comboBoxIndex):
item = "item " + str(comboBoxIndex)
self.myTable.setItem(rowIndex, 2, QtGui.QTableWidgetItem(item))
if __name__ == "__main__":
tableView = TestTable()
tableView.show()
In my setTable function, the item is not being processed correctly? when I am trying to add it into the QTableWidget. Can someone advise?
Additionally, if anyone could answers, does the format I have used, would it be applicable for the scenario I am trying to achieve as I mentioned at the start of the post?
In your setTable() method, you are looping through the geometries, then you are looping through the rows. Since each geometry represents a row you only really need to loop through them and remove the other loop.
Modifying it like so fixes the output:
def setTable(self):
# Adding the list of mesh found in scene into first column
geos = get_all_geos()
for i in range(0, len(geos)):
item = cmds.listRelatives(geos[i], parent=True)[0]
# instead of being populated with the list of items, I got the same name for the entire column
self.myTable.setItem(i, 0, QtGui.QTableWidgetItem(item))
# sets the combobox into the second column
box = combo_box()
nameList = ("test1","test2","test3")
box.addItems(nameList)
self.myTable.setCellWidget(i,1,box)
box.currentIndexChanged.connect(partial(self.tmp, i))
The reason it was failing was because your second loop kept overriding the rows with the last geo in the list.
I'm doing a form that contains a QListView. It's populated from database with this code:
model = QStandardItemModel(self.listUser)
for row in self.SELECT_USERS_ACCOUNTS():
item = QStandardItem(str(row[1]))
model.appendRow(item)
self.listUser.setModel(model)
My QListView is named listUser and SELECT_USERS_ACCOUNTS() function returns data created by select id,name from table_user, I need to store row[0] (id column in the table_user) too because when the user will click an item into the QListView. I want to capture this Id. Thanks in advance.
In QStandardItem data for different roles can be stored by setData(data, role).
Stored data are returned by data(role).
Roles >= 256 (0x100) are UserRoles, see documentation
so you can store the id by
item.setData(row[0],256)
and ask it by
item.data(256)
edit 31.03.2015:
for row in self.SELECT_USERS_ACCOUNTS():
item = QStandardItem(str(row[1]))
# add userData to item
item.setData(row[0],256)
# item.setData(row[2], 257) and so on if there are further columns in row
model.appendRow(item)
self.listUser.setModel(model)
self.listUser.clicked.connect(self.getUserData)
def getUserData(self,index):
item = self.model().item(index.row(),index.column())
iId = item.data(256)
# xyz = item.data(257) for further columns
print(iId) # or do anything else
I'm trying to show several rows from database in a TreeView but all I am getting are some dummy rows as you can see in the image below.
class SettingsDialog(gtk.Dialog):
def __init__(self):
gtk.Dialog.__init__(self, "Server Settings", self, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
# Instantiate ServerManager
self.server_manager = ServerManager()
# Create TreeStore for Server list
self.liststore = gtk.ListStore(str, str)
self.treeview = gtk.TreeView(self.liststore)
# Create TreeViewColumns to display data
cell = gtk.CellRendererText()
col = gtk.TreeViewColumn("Name")
col.pack_start(cell, True)
self.treeview.append_column(col)
cell = gtk.CellRendererText()
col = gtk.TreeViewColumn("URL")
col.pack_start(cell, True)
self.treeview.append_column(col)
self.vbox.pack_start(self.treeview)
self.resize(500,350)
self.set_position(gtk.WIN_POS_CENTER)
self.show_all()
self.load_server_list()
def load_server_list(self):
self.liststore.clear()
servers = self.server_manager.list()
for name, url in servers.iteritems():
self.liststore.append([name, url])
self.show_all()
Data returned from self.server_manager.list() is valid an added to the list store perfectly. There seems to be something wrong with the CellRenderers but I wasn't able to find the error.
You have to set an attribute mapping on the column. For example, the cellrenderer's text attribute value will be displayed in the treeview cell. It is taken from the values on the data model (self.liststore). The column number on the model where the value is taken from is specified in the attribute mapping.
## Take value for *text* attribute of the cell renderer from the model's 3rd column
col = gtk.TreeViewColumn(title, cellrenderer, text=2)