实战PyQt5: 051-多窗口文档控件QMdiArea

MDI应用程序

在传统的GUI应用程序设计中,有一类称作多文档接口(Multi-Document Interface, 简称MDI)应用程序,在MDI程序中,一个显示区域维护多个窗口文件(就像Windows在一个屏幕上维护多个应用程序窗口一样)。

PyQt为设计MDI应用程序提供了支持,通过控件QMdiArea提供了一个可以同时显示多个文档的区域。本质上说,QMdiArea就像是多文档窗口的窗口管理器。例如,它将它所管理的窗口绘制在它上面,并且以级联或者平铺的方式放置这些窗口。通常情况下,QMdiArea用作QMainWindow的中心控件,以此来创建一个MDI应用程序。但是,它也可以被放置在任何布局类中。

QMdiArea中的子窗口是QMdiSubWindow的实例化对象。通过调用addSubWindow来将他们加入MDI区域。通常情况下是传递一个QWidget作为内部控件给这个函数。但是直接传递QMdiSubWindow也是可以的。

子窗口在获得键盘焦点或者调用setFocus时变为激活状态。用户通常通过移动焦点的方式来激活窗口,当活动窗口发生改变时,MDI区域发出subWindowActive信号。

QMdiArea

QMdiArea常用方法有:

  • addSubWindow(): 添加子窗口;
  • removeSubWindow(): 移除子窗口;
  • setBackground(): 设置工作区背景颜色,默认为深灰色;
  • setViewMode(): 设置视图模式: QMdiArea.SubWindowView: 0, 显示带有窗口框架的子窗口(默认)。QMdiArea::TabbedView: 1, 在选项卡栏中显示带有选项卡的子窗口;
  • setTabShape(): 设置标签页的形状,QTabWidget.Rounded圆角型;QTabWidget.Triangular三角形。
  • setTabsMovable(): 多个tab时用鼠标拖动摆放顺序;
  • setTabsCloseable(): 默认为否,设置为True时,会在tab上方形成一个小关闭按钮;
  • closeAllSubWindows(): 关闭所有子窗口;
  • closeActiveSubWidow(): 关闭当前处于激活状态的子窗口;
  • tileSubWindows(): 将所有子窗口平铺显示;
  • cascadeSubWindow(): 将所有子窗口级联显示;

QMdiArea常用信号:

  • subWindowActivated: 特定子窗口被激活时发射该信号

QMdiArea类继承关系:

实战PyQt5: 051-多窗口文档控件QMdiArea

QMdiSubWindow

QMdiSubWindow常用方法有:

  • setWidget(): 向窗口设置部件;
  • setSystemMenu(): 设置窗口的系统菜单;
  • widget(): 获得窗口设置的部件;
  • systemMenu(): 获得窗口的系统菜单;
  • mdiArea: 获得拥有该窗口的QMdiArea。

QMdiSubWindow常用信号:

  • aboutToActive: 窗口切换到活动状态时发射该信号;
  • windowStateChanged: 窗口的状态发生了改变时发射该信号。

QMdiSubWindow类继承关系:

实战PyQt5: 051-多窗口文档控件QMdiArea

MDI应用样例

现在我们使用QMdiArea 和 QMdiSubWindow来实现一个多文档文本编辑器,主窗口继承自QMainWindow, 其中心窗口设置为一个QMdiArea控件。提供一个菜单条和一个工具条来完成基本的交互操作。该程序的功能包括:

  • 可新建一个文本文件;
  • 可打开一个文本文件;
  • 可对文本进行基本编辑操作,包括剪切,复制和粘贴等功能;
  • 在工作区可以用不同的方式显示子窗口。

完整代码如下:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont, QIcon
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, 
                             QMenu, QAction, QActionGroup, QToolBar,
                             QStyle, QToolButton, QMdiArea, 
                             QMdiSubWindow, QPlainTextEdit,
                             QFileDialog, QMessageBox)
import resource_rc
 
class DemoMdi(QMainWindow):
    def __init__(self, parent=None):
        super(DemoMdi, self).__init__(parent)   
        
         # 设置窗口标题
        self.setWindowTitle('实战PyQt5: MDI多文档接口程序 演示')      
        # 设置窗口大小
        self.resize(480, 360)
      
        self.initUi()
        
    def initUi(self):        
        self.initMenuBar()
        self.initToolBar()
        
        self.mdiArea = QMdiArea(self)
        self.setCentralWidget(self.mdiArea)
        
        self.newDocIndex = 1
 
    def initMenuBar(self):
        menuBar = self.menuBar()    
        style = QApplication.style()
        
        #==== 文件 ====#
        fileMenu = menuBar.addMenu('文件')
        
        #新建一个文档
        aFileNew = QAction('新建文档', self)
        aFileNew.setIcon(style.standardIcon(QStyle.SP_FileIcon))
        aFileNew.triggered.connect(self.onFileNew)
        fileMenu.addAction(aFileNew)
        
        #打开一个文档
        aFileOpen = QAction('打开文档', self)
        aFileOpen.setIcon(style.standardIcon(QStyle.SP_DialogOpenButton))
        aFileOpen.triggered.connect(self.onFileOpen)
        fileMenu.addAction(aFileOpen)
        
        #关闭一个文档
        aFileCloseAll = QAction('关闭全部', self)
        aFileCloseAll.setIcon(style.standardIcon(QStyle.SP_DialogCloseButton))
        aFileOpen.triggered.connect(self.onFileCloseAll)
        fileMenu.addAction(aFileCloseAll)
        
        #添加分割线
        fileMenu.addSeparator()
        
        #退出
        aFileExit = QAction('退出', self)
        aFileExit.triggered.connect(self.close)
        fileMenu.addAction(aFileExit)
        
        #==== 编辑 ====#
        editMenu = menuBar.addMenu('编辑')
        
        #剪切
        aEditCut = QAction('剪切', self)
        aEditCut.setIcon(QIcon(':/ico/cut.png'))
        aEditCut.triggered.connect(self.onEditCut)
        editMenu.addAction(aEditCut)
        
        #复制
        aEditCopy = QAction('复制', self)
        aEditCopy.setIcon(QIcon(':/ico/copy.png'))
        aEditCopy.triggered.connect(self.onEditCopy)
        editMenu.addAction(aEditCopy)
        
        #粘贴
        aEditPaste = QAction('粘贴', self)
        aEditPaste.setIcon(QIcon(':/ico/paste.png'))
        aEditPaste.triggered.connect(self.onEditPaste)
        editMenu.addAction(aEditPaste)
        
        #==== 窗口排列方式 ====#
        windowMenu = menuBar.addMenu('窗口')
        
        #子窗口模式
        aWndSubView = QAction('子窗口模式', self)
        aWndSubView.triggered.connect(lambda:self.onWinowdMode(0))
        windowMenu.addAction(aWndSubView)
        #标签页模式
        aWndTab = QAction('标签页模式', self)
        aWndTab.triggered.connect(lambda:self.onWinowdMode(1))
        windowMenu.addAction(aWndTab)
        
        windowMenu.addSeparator()
        
        #平铺模式
        aWndTile = QAction('平铺模式', self)
        aWndTile.triggered.connect(lambda:self.onWinowdMode(2))
        windowMenu.addAction(aWndTile)
        #窗口级联模式
        aWndCascade = QAction('窗口级联模式', self)
        aWndCascade.triggered.connect(lambda:self.onWinowdMode(3))
        windowMenu.addAction(aWndCascade)
        
    def initToolBar(self):
        toolBar = self.addToolBar('') 
        style = QApplication.style()
        
        min_width = 64
        
        btnFileNew = QToolButton(self)
        btnFileNew.setText('新建文档')
        btnFileNew.setMinimumWidth(min_width)
        btnFileNew.setIcon(style.standardIcon(QStyle.SP_FileIcon))
        btnFileNew.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        btnFileNew.clicked.connect(self.onFileNew)
        toolBar.addWidget(btnFileNew)
        
        btnFileOpen = QToolButton(self)
        btnFileOpen.setText('打开文档')
        btnFileOpen.setMinimumWidth(min_width)
        btnFileOpen.setIcon(style.standardIcon(QStyle.SP_DialogOpenButton))
        btnFileOpen.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        btnFileOpen.clicked.connect(self.onFileOpen)
        toolBar.addWidget(btnFileOpen)
        
        btnFileCloseAll = QToolButton(self)
        btnFileCloseAll.setText('关闭全部')
        btnFileCloseAll.setMinimumWidth(min_width)
        btnFileCloseAll.setIcon(style.standardIcon(QStyle.SP_DialogCloseButton))
        btnFileCloseAll.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        btnFileCloseAll.clicked.connect(self.onFileCloseAll)
        toolBar.addWidget(btnFileCloseAll)
        
        toolBar.addSeparator()
        
        btnEditCut = QToolButton(self)
        btnEditCut.setText('剪切')
        btnEditCut.setMinimumWidth(64)
        btnEditCut.setIcon(QIcon(':/ico/cut.png'))
        btnEditCut.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        btnEditCut.clicked.connect(self.onEditCut)
        toolBar.addWidget(btnEditCut)
        
        btnEditCopy = QToolButton(self)
        btnEditCopy.setText('复制')
        btnEditCopy.setMinimumWidth(64)
        btnEditCopy.setIcon(QIcon(':/ico/copy.png'))
        btnEditCopy.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        btnEditCopy.clicked.connect(self.onEditCopy)
        toolBar.addWidget(btnEditCopy)
        
        btnEditPaste = QToolButton(self)
        btnEditPaste.setText('粘贴')
        btnEditPaste.setMinimumWidth(64)
        btnEditPaste.setIcon(QIcon(':/ico/paste.png'))
        btnEditPaste.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        btnEditPaste.clicked.connect(self.onEditPaste)
        toolBar.addWidget(btnEditPaste)
    
    def msgCritical(self, strInfo):
        dlg = QMessageBox(self)
        dlg.setIcon(QMessageBox.Critical)
        dlg.setText(strInfo)
        dlg.show()
        
    def onFileNew(self):
        newDoc = QMdiSubWindow(self)
        newDoc.setWindowTitle('新文档 ' + str(self.newDocIndex))
        self.newDocIndex += 1
        newDoc.setWidget(QPlainTextEdit(newDoc))
        self.mdiArea.addSubWindow(newDoc)
        newDoc.show()      
    
    def onFileOpen(self):
        path, _ = QFileDialog.getOpenFileName(self, '打开文件', '', '文本文件 (*.txt)')   
        if path:
            try:
                with open(path, 'rU') as f:
                    text = f.read()
            except Exception as e:
                self.msgCritical(str(e))
            else:
                openDoc = QMdiSubWindow(self)
                openDoc.setWindowTitle(path)
                txtEdit = QPlainTextEdit(openDoc)
                txtEdit.setPlainText(text)
                openDoc.setWidget(txtEdit)
                self.mdiArea.addSubWindow(openDoc)
                openDoc.show()
    
    def onFileCloseAll(self):
        self.mdiArea.closeAllSubWindows()
    
    def onEditCut(self):
        txtEdit = self.mdiArea.activeSubWindow().widget()
        txtEdit.cut()
    
    def onEditCopy(self):
        txtEdit = self.mdiArea.activeSubWindow().widget()
        txtEdit.copy()
    
    def onEditPaste(self):
        txtEdit = self.mdiArea.activeSubWindow().widget()
        txtEdit.paste()
    
    def onWinowdMode(self, index):
        if index == 3:
            self.mdiArea.cascadeSubWindows()
        elif index == 2:
            self.mdiArea.tileSubWindows()
        elif index == 1:
            self.mdiArea.setViewMode(QMdiArea.TabbedView)
        else:
            self.mdiArea.setViewMode(QMdiArea.SubWindowView)
            
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DemoMdi()
    window.show()
    sys.exit(app.exec()) 

运行结果如下图:

实战PyQt5: 051-多窗口文档控件QMdiArea

一个简单的多文档接口模式的文本编辑器

本文知识点

  • MdiArea添加子窗口;
  • QMdiSubWindow添加部件;
  • 获取当前活动窗口的部件;
  • QFileDialog的使用和加载文本文件;
  • 工具条QToolBar上添加设定格式的QToolButton按钮;
  • QMdiArea 工作区子窗口的排列模式。

前一篇:   实战PyQt5: 050-选项卡控件QTabWidget

  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值