面向对象编程更符合人类思维。python 中orm要比sql更常用,因而在tableview中显示对象数组是个自然需求,网上这方面资料很少。这里是个完整的对象数组显示方案,再结合sqlalchmy就不难了。
# import copy
from datetime import datetime
from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex
from PyQt5.QtWidgets import QTableView, QMenu, QInputDialog, QErrorMessage, QDialog, QDialogButtonBox, QVBoxLayout
import traceback
import logging
from PyQt5 import QtCore
from MyTableView import MyTableView
def getAttrRecursive(obj, attr):
""" Recursive introspection (i.e. get the member 'b' of a member 'a' by name as 'a.b').
"""
return getattr(obj, attr)
# try:
# p = attr.index(".")
# obj = getattr(obj, attr[0:p])
# return getAttrRecursive(obj, attr[p+1:])
# except ValueError:
# return getattr(obj, attr)
def setAttrRecursive(obj, attr, value):
""" Recursive introspection (i.e. set the member 'b' of a member 'a' by name as 'a.b').
"""
setattr(obj, attr, value)
# try:
# p = attr.index(".")
# obj = getattr(obj, attr[0:p])
# setAttrRecursive(obj, attr[p+1:], value)
# except ValueError:
# setattr(obj, attr, value)
class ObjectModel(QAbstractTableModel):
def __init__(self, parent=None):
QAbstractTableModel.__init__(self, parent)
self.objects = []
self.properties = []
def getObject(self, index):
if not index.isValid():
return None
objectIndex = index.row()
try:
return self.objects[objectIndex]
except IndexError:
return None
def getObjectRow(self, row):
try:
return self.objects[row]
except IndexError:
return None
def getProperty(self, index):
if not index.isValid():
return None
propertyIndex = index.column()
try:
return self.properties[propertyIndex]
except IndexError:
return None
def rowCount(self, parent=None, *args, **kwargs):
return len(self.objects)
def columnCount(self, parent=None, *args, **kwargs):
return len(self.properties)
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
# elif role != Qt.DisplayRole:
# return None
obj = self.getObject(index)
prop = self.getProperty(index)
if (obj is None) or (prop is None):
return None
try:
if role in [Qt.DisplayRole, Qt.EditRole]:
return getAttrRecursive(obj, prop['attr'])
except:
return None
return None
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
logging.info("index invalid")
return False
obj = self.getObject(index)
prop = self.getProperty(index)
if (obj is None) or (prop is None):
logging.info("obj is None or prop is None")
return None
try:
if role == Qt.EditRole:
logging.info(type(value))
logging.info([obj,prop['attr'],value])
setAttrRecursive(obj, prop['attr'], value)
return True
except:
traceback.print_exc()
return False
return False
def flags(self, index):
flags = QAbstractTableModel.flags(self, index)
if not index.isValid():
return flags
prop = self.getProperty(index)
if prop is None:
return flags
flags |= Qt.ItemIsEnabled
flags |= Qt.ItemIsSelectable
mode = prop.get('mode', "Read/Write")
if "Write" in mode:
flags |= Qt.ItemIsEditable
return flags
def headerData(self, col, orientation, role= Qt.DisplayRole):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if col<len(self.properties):
p=self.properties[col]
if p!=None:
return QtCore.QVariant(p["header"])
else:
return QtCore.QVariant()
# return QtCore.QVariant(self.properties[col]['header'])
return QtCore.QVariant()
def setObjects(self,objects,properties):
self.beginResetModel()
self.objects=objects
self.properties=properties
self.endResetModel()
def clearObjects(self):
if len(self.objects):
self.beginResetModel()
del self.objects[:]
self.endResetModel()
def sort(self, Ncol, order):
"""Sort table by given column number.
"""
self.layoutAboutToBeChanged.emit()#SIGNAL("layoutAboutToBeChanged()"))
print(Ncol)
# self.objects =sorted(self.objects, key=operator.itemgetter(Ncol))
try:
self.objects =sorted(self.objects,key=lambda x: getattr(x,self.properties[Ncol]["attr"]))
except TypeError:
pass
if order == Qt.DescendingOrder:
self.objects.reverse()
self.layoutChanged.emit()#SIGNAL("layoutChanged()"))
class ObjectView(MyTableView):
def __init__(self, model, parent=None):
super().__init__(parent)
def setModel(self, model):
QTableView.setModel(self, model)
for i, prop in enumerate(model.properties):
# logging.info([i,prop])
if prop.get('visible',True)==True:
self.setColumnHidden(i,False)
else:
self.setColumnHidden(i,True)
width=prop.get('width',100)
self.setColumnWidth(i,width)
# self.setItemDelegateForColumn(i, prop.get('delegate'))
def selectedRows(self):
selectedIndexes = self.selectionModel().selection().indexes()
rows = set()
for index in selectedIndexes:
rows.add(index.row())
return sorted(list(rows))
def selectedColumns(self):
selectedIndexes = self.selectionModel().selection().indexes()
columns = set()
for index in selectedIndexes:
columns.add(index.column())
return sorted(list(columns))
def clearObjects(self):
self.model().clearObjects()
if __name__ == "__main__":
import sys
from PyQt5.QtWidgets import QApplication
# We'll create a table model/view for a list of these objects.
class MyObject(object):
def __init__(self, name="New Obj", s="", i=0, f=0.0, b=True, hasChild=True):
self.name = name
self.strValue = s
self.intValue = i
self.floatValue = f
self.boolValue = b
self.dateValue = datetime.now()
self._fileName = ""
if hasChild:
self.child = MyObject(name, s, i, f, b, False)
# We'll have the model/view access the fileName property
# rather than the _fileName attribute so that we
# run our custom code whenever the fileName is changed.
@property
def fileName(self):
return self._fileName
@fileName.setter
def fileName(self, fileName):
if len(fileName) and (fileName != self._fileName):
print("Setting file name for " +
self.name + " to " + fileName + ".")
self._fileName = fileName
# We'll have the model/view call this
# when a button is clicked in the object's row/column.
def clicked(self):
print(self.name + " was clicked.")
# Create the QApplication.
app = QApplication(sys.argv)
# Create our object list.
a = MyObject("obj A", "a str", 3, 0.042, True)
b = MyObject("obj B", "b str", -1, -10.069, False)
objects = [a, b]
# Specify the properties to display in the model/view.
properties = [
{'width':90,'visible':True,'attr': "name", 'header': "Read Only Name", 'mode': "Read Only"},
{'attr': "strValue", 'header': "String"},
{'attr': "intValue", 'header': "Integer"},
{'attr': "floatValue", 'header': "Float"},
{'attr': "boolValue", 'header': "Bool"},
{'attr': "dateValue", 'header': "Date/Time", 'text': "%c"},
{'attr': "fileName", 'header': "File Name",
'action': "fileDialog"},
{'attr': "clicked", 'header': "Button",
'action': "button", 'text': "Click Me!"},
{'attr': "child.intValue", 'header': "Child Int"},
{'attr': "strValue", 'header': "String Combo Box",
'choices': ['First Choice', 'Second Choice']},
{'attr': "child.intValue",
'header': "Child Int Combo Box", 'choices': [42, 82]},
{'attr': "floatValue", 'header': "Float Combo Box", 'choices': [('PI', 3.14), ('-PI', -3.14)]}]
# Print property names/values/types prior to editing.
print("---------- BEFORE EDITING ----------")
for obj in objects:
for prop in properties:
attr = prop['attr']
try:
value = getattr(obj, attr)
print(attr, value, type(value))
except:
pass
# Create the model/view.
model = ObjectModel()
view = ObjectView(model)
model.setObjects(objects,properties)
view.setModel(model)
# Show the model/view and run the application.
view.setAttribute(Qt.WA_DeleteOnClose)
view.show()
status = app.exec()
# Print property names/values/types post editing.
print("---------- AFTER EDITING ----------")
for obj in objects:
for prop in properties:
attr = prop['attr']
try:
value = getattr(obj, attr)
print(attr, value, type(value))
except:
pass
sys.exit(status)