欢迎光临
我们一直在努力

基于PANDAS的QAbstractTableModel实现高级TableView详细解析(三、如何装载懒加载)

一、如何关联到TableView

在前面的文章里我们用到了下面的几个参数

self._display_start = 0

self._display_end = 0

self._chunk_size = 50

很显然我们要实现懒加载很肯定是要基于这些参数去设定,展示形式有两种1.滚动加载 2.页码加载

二、实现设计

import os,sys
from PySide6 import QtCore, QtGui, QtWidgets
from app.shared.widgets.ComboBox import ComboBox

class Ui_Win(object):
def setupUi(self, Win):
Win.setObjectName("TabWd")
Win.resize(1400,600)

self.MianLayout = QtWidgets.QVBoxLayout()
self.MianLayout.setObjectName("main_layout")
self.MianLayout.setContentsMargins(0,0,0,0)
self.MianLayout.setSpacing(0)

self.tablelayout = QtWidgets.QHBoxLayout()
self.tablelayout.setContentsMargins(0,0,0,0)
self.tablelayout.setSpacing(0
)
self.tableView = QtWidgets.QTableView()
self.tableView.setObjectName("tableView")
self.tableView.verticalHeader().setVisible(False)
self.tableView.horizontalHeader().setHighlightSections(False) #设置水平表头的节在被选中或鼠标悬停时不突出显示
self.tableView.horizontalHeader().setSortIndicatorShown(True)
self.tableView.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.tableView.verticalScrollBar().setObjectName('AlarmTableScrollbar')
self.view_scrollbar = QtWidgets.QScrollBar()
self.tablelayout.addWidget(self.tableView,1)
self.tablelayout.addWidget(self.view_scrollbar,0)
self.view_scrollbar.setHidden(True)
self.MianLayout.addLayout(self.tablelayout)

##页码部件
self.PageRecrodWindget = QtWidgets.QWidget(Win)
self.PageRecrodWindget.setContentsMargins(0,1,0,0)
self.PageRecrodWindget.setObjectName("layoutWidget")

self.horizontalLayout = QtWidgets.QHBoxLayout(self.PageRecrodWindget)
self.horizontalLayout.setContentsMargins(5, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
#数据总数
self.totalRecordLabel = QtWidgets.QLabel()
self.totalRecordLabel.setObjectName("totalRecordLabel")
self.horizontalLayout.addWidget(self.totalRecordLabel)
#页数设置器
self.pagerecordcount = ComboBox()
self.pagerecordcount.addItems(['20','50','100'])
self.pagerecordcount.setCurrentText('50')
self.horizontalLayout.addWidget(self.pagerecordcount)

#上一页
self.prevButton = QtWidgets.QPushButton(self.PageRecrodWindget)
self.prevButton.setObjectName("btn")
self.horizontalLayout.addWidget(self.prevButton)
self.prevButton.setMinimumWidth(60)
#轮转页码
self.pageLayout = QtWidgets.QHBoxLayout()
self.OneButton = QtWidgets.QPushButton()
self.TwoButton = QtWidgets.QPushButton()
self.ThreeButton = QtWidgets.QPushButton()
self.FourButton = QtWidgets.QPushButton()
self.FiveButton = QtWidgets.QPushButton()
self.SixButton = QtWidgets.QPushButton()
self.SevenButton = QtWidgets.QPushButton()
self.EightyButton = QtWidgets.QPushButton()
self.pageButtons = [self.OneButton, self.TwoButton, self.ThreeButton,self.FourButton, self.FiveButton, self.SixButton, self.SevenButton, self.EightyButton]
self.pageLayout.setContentsMargins(0, 0, 0, 0) # 设置布局的外边距
self.pageLayout.setSpacing(4) # 设置布局内控件的间距
for btn in self.pageButtons:
btn.setObjectName('sp_btn')
self.pageLayout.addWidget(btn)
btn.setHidden(True)

self.horizontalLayout.addLayout(self.pageLayout)

#下一页
self.nextButton = QtWidgets.QPushButton(self.PageRecrodWindget)
self.nextButton.setObjectName("btn")
self.horizontalLayout.addWidget(self.nextButton)
self.nextButton.setMinimumWidth(60)
#跳转页面
self.label = QtWidgets.QLabel(self.PageRecrodWindget)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
validator = QtGui.QIntValidator()
validator.setRange(1,1000)
self.switchPageLineEdit = QtWidgets.QLineEdit(self.PageRecrodWindget)
self.switchPageLineEdit.setValidator(validator)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.switchPageLineEdit.sizePolicy().hasHeightForWidth())
self.switchPageLineEdit.setSizePolicy(sizePolicy)
self.switchPageLineEdit.setMaximumSize(QtCore.QSize(40, 16777215))
self.switchPageLineEdit.setObjectName("switchPageLineEdit")
self.horizontalLayout.addWidget(self.switchPageLineEdit)
self.label_2 = QtWidgets.QLabel(self.PageRecrodWindget)
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.horizontalLayout.addSpacerItem(QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy .Minimum))

#———–迷你布局———————
self.PageRecrodWindget2 = QtWidgets.QWidget()
self.PageRecrodWindget2.setContentsMargins(0,1,0,0)
self.PageRecrodWindget2.setObjectName("layoutWidget")

self.horizontalLayout2 = QtWidgets.QHBoxLayout(self.PageRecrodWindget2)
self.horizontalLayout2.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
self.horizontalLayout2.setSpacing(0)
self.horizontalLayout2.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout2.setObjectName("horizontalLayout")

self.prevButton2 = QtWidgets.QPushButton('<',self.PageRecrodWindget2)
self.prevButton2.setFixedWidth(30)
self.prevButton2.setObjectName("btn")

self.nextButton2 = QtWidgets.QPushButton('>',self.PageRecrodWindget2)
self.nextButton2.setFixedWidth(30)
self.nextButton2.setObjectName("btn")

self.okButton = QtWidgets.QPushButton('确定',self.PageRecrodWindget2)
self.calButton = QtWidgets.QPushButton('重置',self.PageRecrodWindget2)

self.label_3 = QtWidgets.QLabel(self.PageRecrodWindget2)
self.label_3.setObjectName("label_2")
self.switchPageLineEdit2 = QtWidgets.QLineEdit(self.PageRecrodWindget2)
self.switchPageLineEdit2.setValidator(validator)
sizePolicy2 = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.switchPageLineEdit2.sizePolicy().hasHeightForWidth())
self.switchPageLineEdit2.setSizePolicy(sizePolicy2)
self.switchPageLineEdit2.setMaximumSize(QtCore.QSize(40, 16777215))
self.switchPageLineEdit2.setObjectName("switchPageLineEdit")

self.horizontalLayout2.addWidget(self.prevButton2)
self.horizontalLayout2.addWidget(self.switchPageLineEdit2)
self.horizontalLayout2.addWidget(self.label_3)
self.horizontalLayout2.addWidget(self.nextButton2)
self.horizontalLayout2.addSpacerItem(QtWidgets.QSpacerItem(1,1,QtWidgets.QSizePolicy.Policy.Expanding,QtWidgets.QSizePolicy.Policy.Minimum))
self.horizontalLayout2.addWidget(self.okButton)
self.horizontalLayout2.addWidget(self.calButton)

self.pageContainer = QtWidgets.QWidget(self)
self.pageContainer.setFixedHeight(25)
self.pageStack = QtWidgets.QStackedLayout(self.pageContainer)
self.pageStack.setContentsMargins(0, 5, 0, 0)
self.pageStack.addWidget(self.PageRecrodWindget) # normal
self.pageStack.addWidget(self.PageRecrodWindget2) # mini

self.MianLayout.addWidget(self.pageContainer)

Win.setLayout(self.MianLayout)
self.retranslateUi(Win)
QtCore.QMetaObject.connectSlotsByName(Win)

def retranslateUi(self, Win):
_translate = QtCore.QCoreApplication.translate
Win.setWindowTitle(_translate("TabWd", "Form"))

font_1 = QtGui.QFont()# 设置字体
font_1.setFamily("Arial")
font_1.setPointSize(10)
font_1.setBold(True)

self.totalRecordLabel.setText(_translate("TabWd", "共0条"))
self.prevButton.setText(_translate("TabWd", "前一页"))
self.nextButton.setText(_translate("TabWd", "后一页"))
self.label.setText(_translate("TabWd", "转到第"))
self.label_2.setText(_translate("TabWd", "页"))
self.label_3.setText(_translate("TabWd", "/ ? 页"))
for index,btn in enumerate(self.pageButtons):
btn.setText(_translate("TabWd", str(index)))

三、定义视图

import sys,pandas as pd
from PySide6.QtGui import QFont,QKeySequence,QColor,QPainter
from PySide6.QtCore import Qt,Signal,QPoint
from PySide6.QtWidgets import (QApplication, QLabel,QFrame,QHBoxLayout,QSpacerItem,QSizePolicy,QWidget,QPushButton,QGridLayout,
QScrollArea,QAbstractItemView)
from app.shared.views.switch_page.TableView_ui import Ui_Win
from enum import Enum

class DisplayMode(Enum):
PAGINATION = 0
MINI_PAGINATION = 1
HIDDEN = 2
SCROLL = 3

class TableView(QWidget,Ui_Win):
StatusBarSignal = Signal(str, str)
update_count = Signal(int)
operating_signal = Signal(str,list) #操作按钮名称,返回的信息
copy_text,paste_text = Signal(),Signal()
undo,redo,delete = Signal(),Signal(),Signal()
get_checked_info = Signal() #传递信号到控制器
reset_checked_state = Signal()
page_record_count_change = Signal()
errorOccurred = Signal(str)
interface_resize = Signal()

def __init__(self):
super().__init__()
Ui_Win.__init__(self)
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
self.setAttribute(Qt.WA_StyledBackground, True)
self.displayMode = DisplayMode.PAGINATION

self.restrictions_flag,self.customMenu = False,True

self.create_standard_table_view()

def create_standard_table_view(self):#初始化参数
"""基础设置"""
self.setupUi(self)
self.setObjectName("SwitchPageView")
self.tableView.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
self.tableView.setSortingEnabled(True)
self.tableView.copy_text.connect(self.copy_text)
self.okButton.clicked.connect(self.get_checked_info)
self.calButton.clicked.connect(self.reset_checked_state)

def setEditTriggers(self,EditTriggers:QAbstractItemView.EditTrigger):
self.tableView.setEditTriggers(EditTriggers)

def setColumnHidden(self,index:int,hidden:bool):
if 0 <= index < self.tableView.model().columnCount() and isinstance(hidden,bool) and self.tableView.model() is not None:
self.tableView.setColumnHidden(index,hidden)

def setDisplayMode(self, mode: DisplayMode):
if self.displayMode == mode:
return

self.displayMode = mode

#恢复状态
if mode != DisplayMode.HIDDEN:
self.pageContainer.setHidden(False)

if mode != DisplayMode.SCROLL:
self.view_scrollbar.setHidden(True)
self.tableView.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)

#设定显示
if mode in [DisplayMode.PAGINATION,DisplayMode.MINI_PAGINATION]:
self.pageStack.setCurrentIndex(mode.value)
elif mode == DisplayMode.SCROLL:
self.pageContainer.setHidden(True)
self.view_scrollbar.setHidden(False)
self.tableView.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
elif mode == DisplayMode.HIDDEN:
self.pageContainer.setHidden(True)

def set_item_delegate(self,delegate):#设置代理模式
self.tableView.setItemDelegate(delegate)

def keyPressEvent(self, event):
if event.matches(QKeySequence.StandardKey.Paste):
self.paste_text.emit()
elif event.matches(QKeySequence.StandardKey.Copy):
self.copy_text.emit()
elif event.matches(QKeySequence.StandardKey.Undo):
self.undo.emit()
elif event.matches(QKeySequence.StandardKey.Redo):
self.redo.emit()
elif event.matches(QKeySequence.StandardKey.Delete):
self.delete.emit()
else:
super().keyPressEvent(event)

def wheelEvent(self, event):
# 获取滚轮滚动的角度(以15度为单位)
delta = event.angleDelta().y() / 15

scroll_rows = int(- delta / 8) # 调整这个系数可以控制灵敏度

# 更新滚动条的值
current_value = self.view_scrollbar.value()
new_value = max(0, min(current_value + scroll_rows, self.view_scrollbar.maximum()))
self.view_scrollbar.setValue(new_value)

# 阻止事件继续传播
event.accept()

def resizeEvent(self, event):
self.interface_resize.emit()
super().resizeEvent(event)

四、定义控制器关联模型

class NavigationState:

def __init__(self):

self.mode = DisplayMode.PAGINATION

self.total_rows = 0 #总行数

self.page_size = 50 #显示行数

self.total_page = 0 #总页码

self.current_page = 1 #当前页码

self.scroll_value = 0 #滚动条数职

self.visible_count = 50 #滚动条可视行数

class NavigationController(QtCore.QObject):

displayRangeChanged = QtCore.Signal(int,int)
pageChanged = QtCore.Signal(int)
modeChanged = QtCore.Signal(DisplayMode)
pageSizeChanged = QtCore.Signal(int)

def __init__(self,view:TableView,model:ShowDataModel,parent=None):

super().__init__(parent)

self.view = view

self.model = model

self.state = NavigationState()

self._connect_signals()

def _connect_signals(self):

self.view.prevButton.clicked.connect(self.prev_page)
self.view.nextButton.clicked.connect(self.next_page)
self.view.prevButton2.clicked.connect(self.prev_page)
self.view.nextButton2.clicked.connect(self.next_page)
for button in self.view.pageButtons:
button.clicked.connect(self.page_btn_click)
self.view.switchPageLineEdit.editingFinished.connect(self.onSwitchPageEditingFinished)
self.view.switchPageLineEdit2.editingFinished.connect(self.onSwitchPageEditingFinished)
self.view.pagerecordcount.currentTextChanged.connect(self.page_record_count_changed)
#滚动页面逻辑
self.view.interface_resize.connect(self._update_scrollbar)
self.view.view_scrollbar.valueChanged.connect(self.on_scroll_changed)

self.model.total_recrod_count_change.connect(self.update_total_rows)

def set_display_mode(self,mode:DisplayMode):
"设定显示模式"
self.state.mode = mode
self.view.setDisplayMode(mode)
self.refresh_display_range()

def refresh_display_range(self):
"刷新显示范围"
start,end = self.calculate_display_range()
self.model.set_display_range(start,end)

def calculate_display_range(self):
"计算显示范围"
if self.state.mode in (DisplayMode.PAGINATION,DisplayMode.MINI_PAGINATION):
start = (self.state.current_page – 1) * self.state.page_size
end = min(start + self.state.page_size,self.state.total_rows)
elif self.state.mode == DisplayMode.SCROLL:
start = self.state.scroll_value
end = min(start + self.state.visible_count,self.state.total_rows)
else:
start = 0
end = self.state.page_size

return start,end

def reset(self):

self.state.current_page = 1
self.state.scroll_value = 0

self.updatePageButtons()
self.refresh_display_range()

## 分页
def update_total_rows(self,total_rows):
"模型触发,更新显示分页更新按钮"
self.state.total_rows = total_rows
self._update_pagination_info()
self.updatePageButtons() #刷新换页按钮

self.state.current_page = min(self.state.current_page,self.state.total_page)
self._update_scrollbar()
self.refresh_display_range()

## ———滚动页面逻辑
def on_scroll_changed(self,scroll_value:int):
self.state.scroll_value = scroll_value
self.refresh_display_range()

def _update_scrollbar(self):
"更新滚动条显示范围"
total_rows = self.state.total_rows
total_height = self.view.tableView.height()
vertical_header_height = self.view.tableView.verticalHeader().sizeHint().height()
horizontal_header_height = self.view.tableView.horizontalHeader().sizeHint().height()
viewport_height = total_height – vertical_header_height – horizontal_header_height
self.state.visible_count = math.ceil(viewport_height / 30) #可视长度

# 设置滚动条范围(基于行索引)
max_value = max(0, total_rows – self.state.visible_count)
self.view.view_scrollbar.setRange(0, max_value)

# 设置步长参数
self.view.view_scrollbar.setSingleStep(1) # 滚轮每次滚动1行
self.view.view_scrollbar.setPageStep(self.state.visible_count) # PageUp/PageDown翻页

## ———页码页面逻辑
def page_record_count_changed(self,text):
"""分页状态,分页器触发"""
self.state.page_size = int(text)
self._update_pagination_info() # 设置总记录数
self.updatePageButtons() #刷新换页按钮
self.refresh_display_range()

def _update_pagination_info(self):
#分页计算
page_size = self.state.page_size

total_page = max(1,(self.state.total_rows + page_size – 1) // page_size)

self.state.total_page = total_page

self.view.totalRecordLabel.setText(f'共 {self.state.total_rows} 条')
self.view.label_3.setText(f'/ {total_page} 页')

def prev_page(self):
"""上一页"""
self._set_page(self.state.current_page – 1)

def next_page(self):
"""下一页"""
self._set_page(self.state.current_page + 1)

def page_btn_click(self):
page_text = self.sender().text()
if page_text != '…':
self._set_page(int(page_text))

def _set_page(self, page: int):
"""页码切换"""
if self.state.total_page <= 0:
return

page = max(1, min(page, self.state.total_page))

if page == self.state.current_page:
return

self.state.current_page = page

self.updatePageButtons()
self.refresh_display_range()

def updatePageButtons(self):
"""更新页码按钮"""
self.view.switchPageLineEdit.setText(str(self.state.current_page))
self.view.switchPageLineEdit2.setText(str(self.state.current_page))

max_btn = len(self.view.pageButtons)

def set_btn(btn, text, active=False):
btn.setProperty("active", False) # 清旧状态
btn.setText(str(text))
btn.setVisible(True)
btn.setEnabled(text != '…')
btn.setProperty("active", active)
btn.style().polish(btn)

if self.state.total_page <= max_btn:
pages = list(range(1, self.state.total_page + 1))
else:
if self.state.current_page <= 4:
pages = [1, 2, 3, 4, '…', self.state.total_page – 2, self.state.total_page – 1, self.state.total_page]
elif self.state.current_page >= self.state.total_page – 3:
pages = [1, 2, 3, '…',self.state.total_page – 3, self.state.total_page – 2,self.state.total_page – 1, self.state.total_page]
else:
pages = [1,2, '…',self.state.current_page – 1, self.state.current_page,self.state.current_page + 1,'…', self.state.total_page]

for i, btn in enumerate(self.view.pageButtons):
if i < len(pages):
page = pages[i]
set_btn(btn, page, page == self.state.current_page)
else:
btn.setVisible(False)

def onSwitchPageEditingFinished(self):
"""输入框跳转"""
model = self.model
if not model or model.rowCount() <= 0:
return

editor = self.sender()
text = editor.text().strip() if editor else ""

try:
page = int(text)
except ValueError:
page = self.state.current_page

self.view.switchPageLineEdit.setText(str(page))
self.view.switchPageLineEdit2.setText(str(page))
self._set_page(page)

五、最后

我在定义视图的时候添加上复制、黏贴等信号下一个篇章就来做模型赋值setData相关的内容(PS:滚动视图发生resizeEvent时需要重新计算显示高度,这个需要关联重算不然会出问题的)

赞(0)
未经允许不得转载:171主机测评 » 基于PANDAS的QAbstractTableModel实现高级TableView详细解析(三、如何装载懒加载)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址