PyQt高级界面控件
通过前面几节的讲述说明,相信小伙伴对PyQt的应用有了较为详细的认识,估计现在完成简单的应用程序没啥问题,那这就够了吗?当然不,撇开复杂的布局不说,因为这部分通过后面讲解的Qt Dedigner插件帮我们实现,我们还得了解PyQt高级界面控件
一、表格与树结构
表格与树解决的问题是如何在一个控件中有规律地呈现更多的数据。PyQt提供了两种控件类用于解决该问题,其中一种是表格结构的控件类;另一种是树形结构的控件类。
1、表格
在通常情况下,一个应用需要和一批数据(比如数组、列表)进行交互,然后以表格的形式输出这些信息,这时就要用到QTableView类了。在QtableView中可以使用自定义的数据模型来显示内容,通过setModel来绑定数据源。
1.代码示例
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Table(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('QTableView表格视图的例子')
self.resize(500,300)
self.model=QStandardItemModel(4,4)
self.model.setHorizontalHeaderLabels(['标题1','标题2','标题3','标题4'])
for row in range(4):
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)
layout=QVBoxLayout()
layout.addWidget(self.tableView)
self.setLayout(layout)
if __name__ == '__main__':
app=QApplication(sys.argv)
table=Table()
table.show()
app.exec_()
2.代码解析:
self.model=QStandardItemModel(4,4)
设置数据层次结构,4行4列
self.model.setHorizontalHeaderLabels(['标题1','标题2','标题3','标题4'])
设置水平方向四个表头文本内容
item=QStandardItem('row %s,column %s'%(row,column))
self.model.setItem(row,column,item)
设置每个位置的文本值
self.tableView=QTableView()
self.tableView.setModel(self.model)
实例化表格视图,设置模型为自定义的模型
3.补充说明属性参数
属性方法 | 说明 |
---|---|
setHorizontalHeaderLabels() | 设置QTableWidget表格控件的水平标签 |
setVerticalHeaderLabels() | 设置QTableWidget表格控件的垂直标签 |
setItem(int ,int ,QTableWidgetItem) | 在QTableWidget表格控件的每个单元控件内添加值(行号、列号、值) |
setEditTriggers(EditTriggers triggers) | 设置表格是否可以编辑,参数:QAbstractItemView.NoEditTriggers;QAbstractItemView.CurrentChanged;QAbstractItemView.DoubleClicked;QAbstractItemView.SelectedClicked;QAbstractItemView.EditKeyPressed;QAbstractItemView.AnyKeyPressed;QAbstractItemView.AllEditTriggers![]() |
setSelectionBehavior | 设置表格的选择行为。参数![]() |
setShowGrid() | 设置网格的显示,默认True |
setSpan(int row,int column,int rowSpanCount,int columnSpanCount) | 合并单元格,要改变单元格的第row行,column列,要合并rowSpancount行数和columnSpanCount列数 |
以上属性是QTableView组件的,其子控件QTableWidget属性更加多,设置更加详细。
2、树结构
1.自定义树结构
QTreeWidget类实现了树形结构,主要用于类似目录的展示。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QIcon, QBrush, QColor
from PyQt5.QtCore import Qt
class TreeWidgetDemo(QMainWindow):
def __init__(self, parent=None):
super(TreeWidgetDemo, self).__init__(parent)
self.setWindowTitle('TreeWidget 例子')
self.tree=QTreeWidget()
#设置列数
self.tree.setColumnCount(2)
#设置树形控件头部的标题
self.tree.setHeaderLabels(['Key','Value'])
#设置根节点
root=QTreeWidgetItem(self.tree)
root.setText(0,'Root')# 第一个参数指定列,第二个参数指定值
# 设置根节点的背景颜色
brush_red=QBrush(Qt.red)
root.setBackground(0,brush_red)
brush_blue=QBrush(Qt.blue)
root.setBackground(1,brush_blue)
#设置树形控件的列的宽度
self.tree.setColumnWidth(0,150)
#设置子节点1
child1=QTreeWidgetItem()
child1.setText(0,'child1')
child1.setText(1,'ios')
#优化1 设置节点的状态
child1.setCheckState(0,Qt.Checked)
root.addChild(child1)
#设置子节点2
child2=QTreeWidgetItem(root)
child2.setText(0,'child2')
child2.setText(1,'')
#设置子节点3
child3=QTreeWidgetItem(child2)
child3.setText(0,'child3')
child3.setText(1,'android')
#加载根节点的所有属性与子控件
self.tree.addTopLevelItem(root)
# 给节点添加响应事件
self.tree.clicked.connect(self.onClicked)
#节点全部展开
self.tree.expandAll()
self.setCentralWidget(self.tree)
def onClicked(self,qmodeLindex):
item=self.tree.currentItem()
print('Key=%s,value=%s'%(item.text(0),item.text(1)))
if __name__ == '__main__':
app = QApplication(sys.argv)
tree = TreeWidgetDemo()
tree.show()
app.exec_()
2.系统定制模式
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
if __name__ == '__main__':
app=QApplication(sys.argv)
# window系统提供的模式
model=QDirModel()
# 创建一个QTreeView的控件
tree=QTreeView()
#为控件添加模式
tree.setModel(model)
tree.setWindowTitle('QTreeView例子')
tree.resize(640,480)
tree.show()
app.exec_()
tree.setRootIndex(model.index(r'C:\Users\admin\Desktop'))
可以通过上述代码指定路径,不指定时,是运行代码的整个系统路径。
二、QStackedWidget组件
QStackedWidget是一个堆栈窗口控件,可以填充一些小控件,但同一时间只有一个小控件可以显示。QStackedWidget使用QStackedLayout布局。
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class StackedExample(QWidget):
def __init__(self):
super(StackedExample, self).__init__()
#设置窗口初始位置和大小
self.setGeometry(300,50,10,10)
self.setWindowTitle('StackedWidget 例子')
self.leftlist=QListWidget()
self.leftlist.insertItem(0,'联系方式')
self.leftlist.insertItem(1,'个人信息')
self.leftlist.insertItem(2,'教育程度')
#创建三个小控件和QStackedWidget对象,并把三个控件绑定到QStackedWidget对象
self.stack1=QWidget()
self.stack2=QWidget()
self.stack3=QWidget()
self.stack=QStackedWidget(self)
self.stack.addWidget(self.stack1)
self.stack.addWidget(self.stack2)
self.stack.addWidget(self.stack3)
#水平布局,添加部件到布局中
HBox=QHBoxLayout()
HBox.addWidget(self.leftlist)
HBox.addWidget(self.stack)
self.setLayout(HBox)
self.leftlist.currentRowChanged.connect(self.display)
self.stack1UI()
self.stack2UI()
self.stack3UI()
def stack1UI(self):
layout=QFormLayout()
layout.addRow('姓名',QLineEdit())
layout.addRow('地址',QLineEdit())
self.stack1.setLayout(layout)
def stack2UI(self):
# zhu表单布局,次水平布局
layout = QFormLayout()
sex = QHBoxLayout()
# 水平布局添加单选按钮
sex.addWidget(QRadioButton('男'))
sex.addWidget(QRadioButton('女'))
# 表单布局添加控件
layout.addRow(QLabel('性别'), sex)
layout.addRow('生日', QLineEdit())
self.stack2.setLayout(layout)
def stack3UI(self):
# 水平布局
layout = QHBoxLayout()
# 添加控件到布局中
layout.addWidget(QLabel('科目'))
layout.addWidget(QCheckBox('物理'))
layout.addWidget(QCheckBox('高数'))
self.stack3.setLayout(layout)
def display(self,i):
#设置当前可见的选项卡的索引
self.stack.setCurrentIndex(i)
if __name__ == '__main__':
app=QApplication(sys.argv)
demo=StackedExample()
demo.show()
sys.exit(app.exec_())
self.leftlist=QListWidget()
self.leftlist.insertItem(0,'联系方式')
self.leftlist.insertItem(1,'个人信息')
self.leftlist.insertItem(2,'教育程度')
创建列表窗口,并添加条目。
def display(self,i):
#设置当前可见的选项卡的索引
self.stack.setCurrentIndex(i)
将QListWidget的currentRowChanged信号与display()槽函数相关联,从而改变堆叠控件的视图。
三、QTabWidget
QTabWidget控件提供了一个选项卡和一个页面区域,默认显示第一个选项卡的页面。通过单击各选项卡可以查看对应的页面。如果在一个窗口中显示的输入字段很多,则可以对这些字段进行拆分,分别放置在不同页面的选项卡中。QTabWidget空件与QStackedWidget类似,可以有效地显示窗口中的控件。
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class TabDemo(QTabWidget):
def __init__(self,parent=None):
super(TabDemo, self).__init__(parent)
#创建3个选项卡小控件窗口
self.tab1=QWidget()
self.tab2=QWidget()
self.tab3=QWidget()
#将三个选项卡添加到顶层窗口中
self.addTab(self.tab1, "Tab 1")
self.addTab(self.tab2, "Tab 2")
self.addTab(self.tab3, "Tab 3")
self.setGeometry(100,100,400,300)
#每个选项卡自定义的内容
self.tab1UI()
self.tab2UI()
self.tab3UI()
def tab1UI(self):
#表单布局
layout=QFormLayout()
#添加姓名,地址的单行文本输入框
layout.addRow('姓名',QLineEdit())
layout.addRow('地址',QLineEdit())
#设置选项卡的小标题与布局方式
self.setTabText(0,'联系方式')
self.tab1.setLayout(layout)
def tab2UI(self):
#zhu表单布局,次水平布局
layout=QFormLayout()
sex=QHBoxLayout()
#水平布局添加单选按钮
sex.addWidget(QRadioButton('男'))
sex.addWidget(QRadioButton('女'))
#表单布局添加控件
layout.addRow(QLabel('性别'),sex)
layout.addRow('生日',QLineEdit())
#设置标题与布局
self.setTabText(1,'个人详细信息')
self.tab2.setLayout(layout)
def tab3UI(self):
#水平布局
layout=QHBoxLayout()
#添加控件到布局中
layout.addWidget(QLabel('科目'))
layout.addWidget(QCheckBox('物理'))
layout.addWidget(QCheckBox('高数'))
#设置小标题与布局方式
self.setTabText(2,'教育程度')
self.tab3.setLayout(layout)
if __name__ == '__main__':
app=QApplication(sys.argv)
demo=TabDemo()
demo.show()
sys.exit(app.exec_())
四、多窗口模式 QMidArea
一个典型的GUI应用程序可能有多个窗口,选项卡控件和堆栈窗口控件允许一次使用其中的一个窗口。然而,很多时候这种方法不是很有用,因为其他窗口的视图是隐藏的。所以有了QMdiArea。
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
count=0
def __init__(self,parent=None):
super(MainWindow, self).__init__(parent)
#实例化Qmidarea区域
self.mdi=QMdiArea()
#设置为中间控件
self.setCentralWidget(self.mdi)
#实例化菜单栏
bar=self.menuBar()
#添加主菜单
file=bar.addMenu('File')
#添加子菜单
file.addAction('New')
file.addAction('cascade')
file.addAction('Tiled')
#点击QAction绑定自定义的槽函数(传递有值【QAction】)
file.triggered[QAction].connect(self.windowaction)
#设置主窗口的标题
self.setWindowTitle("MDI demo")
def windowaction(self,q):
if q.text()=='New':
#子窗口增加一个
MainWindow.count=MainWindow.count+1
#实例化多文档界面对象
sub=QMdiSubWindow()
#向sub内添加内部控件
sub.setWidget(QTextEdit())
#设置新建子窗口的标题
sub.setWindowTitle('subWindow'+str(MainWindow.count))
#将子窗口添加到Mdi区域
self.mdi.addSubWindow(sub)
#子窗口显示
sub.show()
if q.text()=='cascade':
#cascadeSubWindows():安排子窗口在Mdi区域级联显示
self.mdi.cascadeSubWindows()
if q.text()=='Tiled':
#tileSubWindow():安排子窗口在Mdi区域平铺显示
self.mdi.tileSubWindow()
if __name__ == '__main__':
app=QApplication(sys.argv)
demo=MainWindow()
demo.show()
sys.exit(app.exec_())
其更多属性如下:
五、窗口停靠 QDockWidget
QDockWidget是一个可以停靠在QMainWindow内的窗口控件,它可以保持在浮动状态或者在指定位置作为子窗口附加到主窗口中。QMainWindow类的主窗口对象保留有一个用于停靠窗口的区域,这个区域在控件的中央四周。
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class DockDemo(QMainWindow):
def __init__(self,parent=None):
super(DockDemo, self).__init__(parent)
#设置水平布局
layout=QHBoxLayout()
#实例化菜单栏
bar=self.menuBar()
#创建主菜单file,在其中添加子菜单
file=bar.addMenu('File')
file.addAction('New')
file.addAction('Save')
file.addAction('quit')
#创建QDockWidget窗口(标题,自身窗口)
self.items=QDockWidget('Dockable',self)
#实例化列表窗口,添加几个条目
self.listWidget=QListWidget()
self.listWidget.addItem('Item1')
self.listWidget.addItem('Item2')
self.listWidget.addItem('Item3')
self.listWidget.addItem('Item4')
#在窗口区域设置QWidget,添加列表控件
self.items.setWidget(self.listWidget)
#设置dock窗口是否可以浮动,True,运行浮动在外面,自动与主界面脱离,False,默认浮动主窗口内,可以手动脱离
self.items.setFloating(False)
#设置QTextEdit为中央小控件
self.setCentralWidget(QTextEdit())
#将窗口放置在中央小控件的右侧
self.addDockWidget(Qt.RightDockWidgetArea,self.items)
self.setLayout(layout)
self.setWindowTitle('Dock 例子')
if __name__ == '__main__':
app=QApplication(sys.argv)
demo=DockDemo()
demo.show()
sys.exit(app.exec_())
六、调用其他应用 QAxWidget
import sys
from PyQt5.QAxContainer import QAxWidget
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QPushButton, QFileDialog
from PyQt5.QtCore import Qt
class Window(QWidget):
def __init__(self, *args, **kwargs):
super(Window, self).__init__(*args, **kwargs)
self.setFixedSize(900, 600)
layout = QVBoxLayout(self)
self.axWidget = QAxWidget("ObjectName", self)
layout.addWidget(self.axWidget)
layout.addWidget(QPushButton('打开word', self, clicked=self.onOpenWord))
self.axWidget.setControl("{8856F961-340A-11D0-A96B-00C04FD705A2}")
# self.axWidget.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.axWidget.setProperty("DisplayAlerts", False)
self.axWidget.setProperty("DisplayScrollBars", True)
self.axWidget.dynamicCall("Navigate(const QString&)", "https://map.baidu.com/@13523265.31,3641114.64,12z")
def onOpenWord(self):
path, _ = QFileDialog.getOpenFileName(self, '请选择Word文件', '', 'word(*.docx *.doc)')
if not path:
return
# 不显示窗体
# self.axWidget.resetControl()
self.axWidget.setControl(path)
self.axWidget.dynamicCall('SetVisible (bool Visible)', 'false')
# self.axWidget.setProperty('DisplayAlerts', False)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()
七、QWebEngineView
PyQt5使用QWebEngineView控件来展示HTML页面,对老版本的QWebView类不在进行维护,因为QWebEngineView使用CHromium内核可以给用户带来更好的体验。
QWebEngineView控件使用load()函数加载一个Web页面,实际上就是使用HTTP Get方法加载web页面,这个控件可以加载本地的web页面,也可以加载外部的WEb页面,其核心代码如下
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.webEngineView = QtWebEngineWidgets.QWebEngineView(self.centralwidget)
self.webEngineView.setUrl(QtCore.QUrl("https://www.baidu.com/"))
self.webEngineView.setZoomFactor(1.0)
self.webEngineView.setObjectName("webEngineView")
self.horizontalLayout.addWidget(self.webEngineView)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 23))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
from PyQt5 import QtWebEngineWidgets
class Example(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
if __name__ == '__main__':
app = QtWidgets.QApplication([])
win = Example()
win.show()
app.exit(app.exec_())
上述代码有个问题,就是没法在加载的网页里面再点击,所以,这里网上说要重构 QtWebEngineWidgets,代码如下:
import sys
import os
import datetime
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtWebEngineWidgets import QWebEngineView,QWebEngineSettings
################################################
#######创建主窗口
################################################
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowTitle('My Browser')
self.showMaximized() # 最大化窗口
# self.setWindowFlags(Qt.FramelessWindowHint) # 窗口架构格式,这里是无上面的关闭等按钮的
#####创建tabwidget
self.tabWidget = QTabWidget()
self.tabWidget.setTabShape(0)# 设置标签的样式,0代表圆形标签,1代表三角形
self.tabWidget.setDocumentMode(True) # 文档模式,打开会正常显示图片等
self.tabWidget.setMovable(True)# tab窗口可移动
self.tabWidget.setTabsClosable(True)# 开启tab窗口关闭按钮
self.tabWidget.tabCloseRequested.connect(self.close_Tab)
self.setCentralWidget(self.tabWidget)
####第一个tab
self.webview = WebEngineView(self) #self必须要有,是将主窗口作为参数,传给浏览器
self.webview.load(QUrl("http://www.baidu.com"))
self.create_tab(self.webview)
#创建tab
def create_tab(self,webview):
self.tab = QWidget()
self.tabWidget.addTab(self.tab, "新标签页")
self.tabWidget.setCurrentWidget(self.tab)
#####
self.Layout = QHBoxLayout(self.tab)
self.Layout.setContentsMargins(0, 0, 0, 0)
self.Layout.addWidget(webview)
#关闭tab
def close_Tab(self,index):
if self.tabWidget.count()>1:
self.tabWidget.removeTab(index)
else:
self.close() # 当只有1个tab时,关闭主窗口
################################################
#######创建浏览器
################################################
class WebEngineView(QWebEngineView):
def __init__(self,mainwindow,parent=None):
super(WebEngineView, self).__init__(parent)
self.mainwindow = mainwindow
##############
self.settings().setAttribute(QWebEngineSettings.PluginsEnabled, True) #支持视频播放
self.page().windowCloseRequested.connect(self.on_windowCloseRequested) #页面关闭请求
self.page().profile().downloadRequested.connect(self.on_downloadRequested) #页面下载请求
# 支持页面关闭请求
def on_windowCloseRequested(self):
the_index = self.mainwindow.tabWidget.currentIndex()
self.mainwindow.tabWidget.removeTab(the_index)
# 支持页面下载按钮
def on_downloadRequested(self,downloadItem):
if downloadItem.isFinished()==False and downloadItem.state()==0:
###生成文件存储地址
the_filename = downloadItem.url().fileName()
if len(the_filename) == 0 or "." not in the_filename:
cur_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
the_filename = "下载文件" + cur_time + ".xls"
the_sourceFile = os.path.join(os.getcwd(), the_filename)
###下载文件
# downloadItem.setSavePageFormat(QWebEngineDownloadItem.CompleteHtmlSaveFormat)
downloadItem.setPath(the_sourceFile)
downloadItem.accept()
downloadItem.finished.connect(self.on_downloadfinished)
# 下载结束触发函数
def on_downloadfinished(self):
js_string = '''
alert("下载成功,请到软件同目录下,查找下载文件!");
'''
self.page().runJavaScript(js_string)
# 重写createwindow()
def createWindow(self, QWebEnginePage_WebWindowType):
new_webview = WebEngineView(self.mainwindow)
self.mainwindow.create_tab(new_webview)
return new_webview
################################################
#######程序入门
################################################
if __name__ == "__main__":
app = QApplication(sys.argv)
QCoreApplication.setAttribute(Qt.AA_UseSoftwareOpenGL) #这句解决错误警告:ERROR:gl_context_wgl.cc(78)] Could not share GL contexts.
the_mainwindow = MainWindow()
the_mainwindow.show()
app.exec_()
讲解到这里,估计对PyQt的更高级的应用有了认识,持续更新中…
👊🙈 …