def setupTabs(self): """ Setup the various tabs in the AddressWidget. """ groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"] for group in groups: proxyModel = QSortFilterProxyModel(self) proxyModel.setSourceModel(self.tableModel) proxyModel.setDynamicSortFilter(True) tableView = QTableView() tableView.setModel(proxyModel) tableView.setSortingEnabled(True) tableView.setSelectionBehavior(QAbstractItemView.SelectRows) tableView.horizontalHeader().setStretchLastSection(True) tableView.verticalHeader().hide() tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) tableView.setSelectionMode(QAbstractItemView.SingleSelection) # This here be the magic: we use the group name (e.g. "ABC") to # build the regex for the QSortFilterProxyModel for the group's # tab. The regex will end up looking like "^[ABC].*", only # allowing this tab to display items where the name starts with # "A", "B", or "C". Notice that we set it to be case-insensitive. re = QRegularExpression("^[{}].*".format(group)) assert re.isValid() re.setPatternOptions(QRegularExpression.CaseInsensitiveOption) proxyModel.setFilterRegularExpression(re) proxyModel.setFilterKeyColumn(0) # Filter on the "name" column proxyModel.sort(0, Qt.AscendingOrder) # This prevents an application crash (see: http://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash) viewselectionmodel = tableView.selectionModel() tableView.selectionModel().selectionChanged.connect(self.selectionChanged) self.addTab(tableView, group)
class Table(QWidget): def __init__(self, arg=None): super(Table, self).__init__(arg) # 设置窗体的标题和初始大小 self.setWindowTitle("QTableView表格视图控件的例子") self.resize(500, 300) # 准备数据模型,设置数据层次结构为5行4列 self.model = QStandardItemModel(5, 4) # 设置数据栏名称 self.model.setHorizontalHeaderLabels(['标题1', '标题2', '标题3', '标题4']) for row in range(5): for column in range(4): item = QStandardItem("row %s, column %s" % (row, column)) self.model.setItem(row, column, item) # 实例化表格视图,设置模型为自定义的模型 self.tableView = QTableView() self.tableView.setModel(self.model) # 下面代码让表格 100% 的填满窗口 self.tableView.horizontalHeader().setStretchLastSection(True) self.tableView.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) # 下面代码让表格禁止编辑 self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) # 下面代码让表格设置选中背景色 self.tableView.setStyleSheet("selection-background-color:lightblue;") # 下面代码要限定只能选择整行 # self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) # 设置只能选中整行 # self.tableView.setSelectionMode(QAbstractItemView.SingleSelection) # 设置只能选中一行 # 下面代码可以选中表格的多行 self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) # 设置只能选中整行 self.tableView.setSelectionMode(QAbstractItemView.ExtendedSelection) # 设置只能选中多行 # 下面代码可以打印表格的1行2列数据 data = self.tableView.model().index(1, 2).data() print(data) # 下面代码去掉左边表格的行号 # headerView = self.tableView.verticalHeader() # headerView.setHidden(True) # 局部布局 vboxLayout = QVBoxLayout() vboxLayout.addWidget(self.tableView) self.add_btn = QPushButton("添加记录") # 连接信号槽,点击按钮 add_btn 绑定槽事件 self.add_btn.clicked.connect(self.add_records_btn_click) self.del_btn = QPushButton("删除多行记录") # 连接信号槽,点击按钮 del_btn 绑定槽事件 self.del_btn.clicked.connect(self.del_records_btn_click) # 局部布局 hboxLayout = QHBoxLayout() hboxLayout.addWidget(self.add_btn) hboxLayout.addWidget(self.del_btn) # 全局布局 wl = QVBoxLayout(self) wl.addLayout(vboxLayout) wl.addLayout(hboxLayout) # 点击删除按钮响应方法, 删除选中的单行数据 def del_record_btn_click(self): # 第一种方法: 删除单行数据 indices = self.tableView.selectionModel().selectedRows() for index in sorted(indices): self.model.removeRow(index.row()) # 第二种方法: 删除单行数据 # index = self.tableView.currentIndex() # 取得当前选中行的index # if -1 == index.row(): # return # self.model.removeRow(index.row()) # 通过index的row()操作得到行数进行删除 # 点击删除按钮响应方法, 删除选中的多行数据 def del_records_btn_click(self): # 第一种方法:删除多行数据 index_list = [] for model_index in self.tableView.selectionModel().selectedRows(): index = QtCore.QPersistentModelIndex(model_index) index_list.append(index) if index_list: for index in index_list: self.model.removeRow(index.row()) else: MessageBox = QMessageBox() MessageBox.information(self.tableView, "标题", "没有选中表格中要删除的行") # 第二种方法:删除多行数据 # indexs = self.tableView.selectionModel().selectedRows() # temp_list = [] # 创建一个空队列用于存放需要删除的行号 # for index in indexs: # temp_list.append(index.row()) # 队列中保存需要删除的行号 # temp_list.sort(key=int, reverse=True) # 用sort方法将队列进行降序排列 # print(temp_list) # # if temp_list: # for i in temp_list: # 按照队列删除对应的行 # self.model.removeRow(i) # else: # MessageBox = QMessageBox() # MessageBox.information(self.tableView, "标题", "没有选中表格中要删除的行") # 点击添加按钮相应方法,添加数据 def add_records_btn_click(self): self.model.appendRow([ QStandardItem("row %s, column %s" % (5, 0)), QStandardItem("row %s, column %s" % (6, 1)), QStandardItem("row %s, column %s" % (7, 2)), QStandardItem("row %s, column %s" % (8, 3)), ])
class XDFChunksDialog(QDialog): def __init__(self, parent, chunks, fname): super().__init__(parent) self.setWindowTitle(f"XDF Chunks ({fname})") self.chunks = chunks TAGS = { 1: "FileHeader", 2: "StreamHeader", 3: "Samples", 4: "ClockOffset", 5: "Boundary", 6: "StreamFooter" } self.model = QStandardItemModel() self.model.setHorizontalHeaderLabels( ["#", "Bytes", "Tag", "Stream ID"]) for i, chunk in enumerate(chunks, start=1): row = [] row.append(_add_item(i)) row.append(_add_item(chunk["nbytes"])) row.append(_add_item(f"{chunk['tag']} ({TAGS[chunk['tag']]})")) row.append(_add_item(chunk.get("stream_id", ""))) self.model.appendRow(row) self.view = QTableView() self.view.setModel(self.model) self.view.verticalHeader().setVisible(False) self.view.horizontalHeader().setStretchLastSection(True) self.view.setShowGrid(False) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSortingEnabled(True) self.view.sortByColumn(0, Qt.AscendingOrder) self.view.setEditTriggers(QTableView.NoEditTriggers) self.view.setFixedWidth(450) self.details = QPlainTextEdit("") self.details.setFixedWidth(450) self.details.setReadOnly(True) self.details.setTabStopDistance(30) font = QFont() font.setFamily("monospace") font.setStyleHint(QFont.Monospace) self.details.setFont(font) self.details.setLineWrapMode(QPlainTextEdit.NoWrap) hbox = QHBoxLayout() hbox.addWidget(self.view) hbox.addWidget(self.details) vbox = QVBoxLayout(self) vbox.addLayout(hbox) self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok) vbox.addWidget(self.buttonbox) self.buttonbox.accepted.connect(self.accept) self.view.clicked.connect(self._update_details) self.setFixedSize(980, 650) self.view.setColumnWidth(0, 70) self.view.setColumnWidth(1, 80) self.view.setColumnWidth(2, 150) self.view.setColumnWidth(3, 70) @Slot() def _update_details(self): selection = self.view.selectionModel() if selection.hasSelection(): n = int(selection.selectedIndexes()[0].data()) self.details.setPlainText(self.chunks[n - 1].get("content", ""))
class IncomeSpendingWidget(AbstractOperationDetails): def __init__(self, parent=None): AbstractOperationDetails.__init__(self, parent) self.name = "Income/Spending" self.details_model = None self.category_delegate = CategorySelectorDelegate() self.tag_delegate = TagSelectorDelegate() self.float_delegate = FloatDelegate(2) self.date_label = QLabel(self) self.details_label = QLabel(self) self.account_label = QLabel(self) self.peer_label = QLabel(self) self.main_label.setText(self.tr("Income / Spending")) self.date_label.setText(self.tr("Date/Time")) self.details_label.setText(self.tr("Details")) self.account_label.setText(self.tr("Account")) self.peer_label.setText(self.tr("Peer")) self.timestamp_editor = QDateTimeEdit(self) self.timestamp_editor.setCalendarPopup(True) self.timestamp_editor.setTimeSpec(Qt.UTC) self.timestamp_editor.setFixedWidth( self.timestamp_editor.fontMetrics().horizontalAdvance( "00/00/0000 00:00:00") * 1.25) self.timestamp_editor.setDisplayFormat("dd/MM/yyyy hh:mm:ss") self.account_widget = AccountSelector(self) self.peer_widget = PeerSelector(self) self.a_currency = OptionalCurrencyComboBox(self) self.a_currency.setText(self.tr("Paid in foreign currency:")) self.add_button = QPushButton(load_icon("add.png"), '', self) self.add_button.setToolTip(self.tr("Add detail")) self.del_button = QPushButton(load_icon("remove.png"), '', self) self.del_button.setToolTip(self.tr("Remove detail")) self.copy_button = QPushButton(load_icon("copy.png"), '', self) self.copy_button.setToolTip(self.tr("Copy detail")) self.details_table = QTableView(self) self.details_table.horizontalHeader().setFont(self.bold_font) self.details_table.setAlternatingRowColors(True) self.details_table.verticalHeader().setVisible(False) self.details_table.verticalHeader().setMinimumSectionSize(20) self.details_table.verticalHeader().setDefaultSectionSize(20) self.layout.addWidget(self.date_label, 1, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.details_label, 2, 0, 1, 1, Qt.AlignLeft) self.layout.addWidget(self.timestamp_editor, 1, 1, 1, 4) self.layout.addWidget(self.add_button, 2, 1, 1, 1) self.layout.addWidget(self.copy_button, 2, 2, 1, 1) self.layout.addWidget(self.del_button, 2, 3, 1, 1) self.layout.addWidget(self.account_label, 1, 5, 1, 1, Qt.AlignRight) self.layout.addWidget(self.peer_label, 2, 5, 1, 1, Qt.AlignRight) self.layout.addWidget(self.account_widget, 1, 6, 1, 1) self.layout.addWidget(self.peer_widget, 2, 6, 1, 1) self.layout.addWidget(self.a_currency, 1, 7, 1, 1) self.layout.addWidget(self.commit_button, 0, 9, 1, 1) self.layout.addWidget(self.revert_button, 0, 10, 1, 1) self.layout.addWidget(self.details_table, 4, 0, 1, 11) self.layout.addItem(self.horizontalSpacer, 1, 8, 1, 1) self.add_button.clicked.connect(self.addChild) self.copy_button.clicked.connect(self.copyChild) self.del_button.clicked.connect(self.delChild) super()._init_db("actions") self.model.beforeInsert.connect(self.before_record_insert) self.model.beforeUpdate.connect(self.before_record_update) self.mapper.setItemDelegate(IncomeSpendingWidgetDelegate(self.mapper)) self.details_model = DetailsModel(self.details_table, db_connection()) self.details_model.setTable("action_details") self.details_model.setEditStrategy(QSqlTableModel.OnManualSubmit) self.details_table.setModel(self.details_model) self.details_model.dataChanged.connect(self.onDataChange) self.account_widget.changed.connect(self.mapper.submit) self.peer_widget.changed.connect(self.mapper.submit) self.a_currency.changed.connect(self.mapper.submit) self.a_currency.name_updated.connect(self.details_model.setAltCurrency) self.mapper.addMapping(self.timestamp_editor, self.model.fieldIndex("timestamp")) self.mapper.addMapping(self.account_widget, self.model.fieldIndex("account_id")) self.mapper.addMapping(self.peer_widget, self.model.fieldIndex("peer_id")) self.mapper.addMapping(self.a_currency, self.model.fieldIndex("alt_currency_id")) self.details_table.setItemDelegateForColumn(2, self.category_delegate) self.details_table.setItemDelegateForColumn(3, self.tag_delegate) self.details_table.setItemDelegateForColumn(4, self.float_delegate) self.details_table.setItemDelegateForColumn(5, self.float_delegate) self.model.select() self.details_model.select() self.details_model.configureView() def setId(self, id): super().setId(id) self.details_model.setFilter(f"action_details.pid = {id}") @Slot() def addChild(self): new_record = self.details_model.record() new_record.setNull("tag_id") new_record.setValue("amount", 0) new_record.setValue("amount_alt", 0) if not self.details_model.insertRecord(-1, new_record): logging.fatal( self.tr("Failed to add new record: ") + self.details_model.lastError().text()) return @Slot() def copyChild(self): idx = self.details_table.selectionModel().selection().indexes() src_record = self.details_model.record(idx[0].row()) new_record = self.details_model.record() new_record.setValue("category_id", src_record.value("category_id")) if src_record.value("tag_id"): new_record.setValue("tag_id", src_record.value("tag_id")) else: new_record.setNull("tag_id") new_record.setValue("amount", src_record.value("amount")) new_record.setValue("amount_alt", src_record.value("amount_alt")) new_record.setValue("note", src_record.value("note")) if not self.details_model.insertRecord(-1, new_record): logging.fatal( self.tr("Failed to add new record: ") + self.details_model.lastError().text()) return @Slot() def delChild(self): selection = self.details_table.selectionModel().selection().indexes() for idx in selection: self.details_model.removeRow(idx.row()) self.onDataChange(idx, idx, None) @Slot() def saveChanges(self): if not self.model.submitAll(): logging.fatal( self.tr("Operation submit failed: ") + self.model.lastError().text()) return pid = self.model.data(self.model.index(0, self.model.fieldIndex("id"))) if pid is None: # we just have saved new action record and need last inserted id pid = self.model.query().lastInsertId() for row in range(self.details_model.rowCount()): self.details_model.setData( self.details_model.index(row, self.details_model.fieldIndex("pid")), pid) if not self.details_model.submitAll(): logging.fatal( self.tr("Operation details submit failed: ") + self.details_model.lastError().text()) return self.modified = False self.commit_button.setEnabled(False) self.revert_button.setEnabled(False) self.dbUpdated.emit() @Slot() def revertChanges(self): self.model.revertAll() self.details_model.revertAll() self.modified = False self.commit_button.setEnabled(False) self.revert_button.setEnabled(False) def createNew(self, account_id=0): super().createNew(account_id) self.details_model.setFilter(f"action_details.pid = 0") def prepareNew(self, account_id): new_record = self.model.record() new_record.setNull("id") new_record.setValue( "timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) new_record.setValue("account_id", account_id) new_record.setValue("peer_id", 0) new_record.setValue("alt_currency_id", None) return new_record def copyNew(self): old_id = self.model.record(self.mapper.currentIndex()).value(0) super().copyNew() self.details_model.setFilter(f"action_details.pid = 0") query = executeSQL( "SELECT * FROM action_details WHERE pid = :pid ORDER BY id DESC", [(":pid", old_id)]) while query.next(): new_record = query.record() new_record.setNull("id") new_record.setNull("pid") assert self.details_model.insertRows(0, 1) self.details_model.setRecord(0, new_record) def copyToNew(self, row): new_record = self.model.record(row) new_record.setNull("id") new_record.setValue( "timestamp", int(datetime.now().replace(tzinfo=tz.tzutc()).timestamp())) return new_record def before_record_insert(self, record): if record.value("alt_currency_id") == 0: record.setNull("alt_currency_id") def before_record_update(self, _row, record): self.before_record_insert( record) # processing is the same as before insert