《快速掌握PyQt5》第二十三章 主窗口QMainWindow

第二十三章 主窗口QMainWindow

23.1 记事本应用

23.3 程序启动画面QSplashScreen

23.2 小结


《快速掌握PyQt5》专栏已整理成书出版,书名为《PyQt编程快速上手》,详情请见该链接。感谢大家一直以来的支持!祝大家PyQt用得越来越顺!

在较为大型复杂,功能较多的应用程序中,我们通常继承QMainWindow类来进行开发。该主窗口为搭建应用用户界面提供了非常好的框架,请看下图:

可以看出该主窗口类为我们提供了菜单栏(Menu Bar)、工具栏(Tool Bar)、控件停靠区域(Dock Widgets)和状态栏(Status Bar),我们可以往其中加入很多自己想要的东西,这也使得我们可以快速地开发一个功能复杂并且界面友好的应用程序。

本章主要介绍菜单栏、工具栏和状态栏,控件停靠区域会放在后续章节。

23.1 记事本应用

接下来我们完成一个简易的记事本应用来了解一下QMainWindow的用法(其实就是对上一章22.2节中的程序再进行扩展):

(Mac上的菜单栏在屏幕左上方)

为保证代码敲击流畅,请先在程序中导入以下模块:

import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QMimeData
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QAction, QFileDialog, QMessageBox,\
                            QFontDialog, QColorDialog

在类的开始处我们设了几个类变量,分别用于判断文本是否已经保存,是否是第一次保存以及用于文件路径保存(相信看过第二十二章的小伙伴已经知道这点了):

class Demo(QMainWindow):
    is_saved = True
    is_saved_first = True
    path = ''

    def __init__(self):
        super(Demo, self).__init__()

接着我们开始添加菜单栏、工具栏和状态栏:

class Demo(QMainWindow):
    is_saved = True
    is_saved_first = True
    path = ''

    def __init__(self):
        super(Demo, self).__init__()
        self.file_menu = self.menuBar().addMenu('File')
        self.edit_menu = self.menuBar().addMenu('Edit')
        self.help_menu = self.menuBar().addMenu('Help')

        self.file_toolbar = self.addToolBar('File')
        self.edit_toolbar = self.addToolBar('Edit')

        self.status_bar = self.statusBar()

可以看出添加的方法非常简单,接下来我们只需要往菜单栏和工具栏上面添加各种动作——也就是QAction,该类通常与菜单栏和工具栏搭配使用。可以将一个动作看作一种命令,每当用户点击某个动作时,就会触发某种命令,程序从而执行相应的命令。那现在我们就要实例化几个动作:

class Demo(QMainWindow):
    is_saved = True
    is_saved_first = True
    path = ''

    def __init__(self):
        super(Demo, self).__init__()
        self.file_menu = self.menuBar().addMenu('File')
        self.edit_menu = self.menuBar().addMenu('Edit')
        self.help_menu = self.menuBar().addMenu('Help')

        self.file_toolbar = self.addToolBar('File')
        self.edit_toolbar = self.addToolBar('Edit')

        self.status_bar = self.statusBar()

        self.new_action = QAction('New', self)
        self.open_action = QAction('Open', self)
        self.save_action = QAction('Save', self)
        self.save_as_action = QAction('Save As', self)
        self.close_action = QAction('Close', self)
        self.cut_action = QAction('Cut', self)
        self.copy_action = QAction('Copy', self)
        self.paste_action = QAction('Paste', self)
        self.font_action = QAction('Font', self)
        self.color_action = QAction('Color', self)
        self.about_action = QAction('Qt', self)

这里一共实例化了11个动作,分别为:新建new_action、打开open_action、保存save_action、另存为save_as_action、关闭close_action、剪切cut_action、复制copy_action、粘贴paste_action、字体改变font_action、颜色改变color_action以及关于about_action。

既然是记事本的话,那肯定要有文本编辑器,所以在类的初始化函数中当然还要实例化一个QTextEdit控件:

self.text_edit = QTextEdit(self)

有剪切复制粘贴功能的话就让我们自然而然的想到了剪贴板以及QMimeData类 :

self.mime_data = QMimeData()
self.clipboard = QApplication.clipboard()

接下来我们往初始化函数中再添加以下代码:

self.setCentralWidget(self.text_edit)    # 1
self.resize(450, 600)

self.menu_init()                         # 2
self.toolbar_init()
self.status_bar_init()
self.action_init()
self.text_edit_int()

1. 调用QMainWindow的setCentralWidget()方法可以设置主窗口的中央控件,这里我们将文本编辑框text_edit设置为中央控件。调用resize()方法将窗口设置到合适的大小;

2. 在代码量比较多的情况下,将各个对象分开来设置会让代码更加清晰,如果都同时挤在__init__()中的话会显得非常混乱,也不方便日后维护。

我们接下来看一下menu_init()函数,在这里我们将相应的动作添加到了菜单栏中:

def menu_init(self):
    self.file_menu.addAction(self.new_action)
    self.file_menu.addAction(self.open_action)
    self.file_menu.addAction(self.save_action)
    self.file_menu.addAction(self.save_as_action)
    self.file_menu.addSeparator()
    self.file_menu.addAction(self.close_action)

    self.edit_menu.addAction(self.cut_action)
    self.edit_menu.addAction(self.copy_action)
    self.edit_menu.addAction(self.paste_action)
    self.edit_menu.addSeparator()
    self.edit_menu.addAction(self.font_action)
    self.edit_menu.addAction(self.color_action)

    self.help_menu.addAction(self.about_action)

可以看出我们调用addAction()方法就可以将动作添加进去。addSeparator()方法顾名思义就是加一个分割条,也就是让close_action和上面四种动作分隔开来,这样看起来更加有条理。

菜单栏设置好了之后我们就可以设置工具栏了,方法相同:

def toolbar_init(self):
    self.file_toolbar.addAction(self.new_action)
    self.file_toolbar.addAction(self.open_action)
    self.file_toolbar.addAction(self.save_action)
    self.file_toolbar.addAction(self.save_as_action)

    self.edit_toolbar.addAction(self.cut_action)
    self.edit_toolbar.addAction(self.copy_action)
    self.edit_toolbar.addAction(self.paste_action)
    self.edit_toolbar.addAction(self.font_action)
    self.edit_toolbar.addAction(self.color_action)

接着是状态栏,状态栏设置非常简单,我们只需要调用showMessage()方法,传入程序打开时想要显示的状态即可(读者可以回到上方看看状态栏刚开始的样子):

def status_bar_init(self):
    self.status_bar.showMessage('Ready to compose')

菜单栏、工具栏和状态栏都设置好了,但是各个动作还只是个空壳,现在点击动作后不会有任何反应,所以接下来我们要在action_init()中设置动作:

def action_init(self):
    self.new_action.setIcon(QIcon('images/new.ico'))            # 1
    self.new_action.setShortcut('Ctrl+N')
    self.new_action.setToolTip('Create a new file')
    self.new_action.setStatusTip('Create a new file')
    self.new_action.triggered.connect(self.new_func)

    self.open_action.setIcon(QIcon('images/open.ico'))          # 2
    self.open_action.setShortcut('Ctrl+O')
    self.open_action.setToolTip('Open an existing file')
    self.open_action.setStatusTip('Open an existing file')
    self.open_action.triggered.connect(self.open_file_func)

    self.save_action.setIcon(QIcon('images/save.ico'))          # 3
    self.save_action.setShortcut('Ctrl+S')
    self.save_action.setToolTip('Save the file')
    self.save_action.setStatusTip('Save the file')
    self.save_action.triggered.connect(lambda: self.save_func(self.text_edit.toHtml()))

    self.save_as_action.setIcon(QIcon('images/save_as.ico'))    # 4
    self.save_as_action.setShortcut('Ctrl+A')
    self.save_as_action.setToolTip('Save the file to a specified location')
    self.save_as_action.setStatusTip('Save the file to a specified location')
    self.save_as_action.triggered.connect(lambda: self.save_as_func(self.text_edit.toHtml()))

    self.close_action.setIcon(QIcon('images/close.ico'))        # 5
    self.close_action.setShortcut('Ctrl+E')
    self.close_action.setToolTip('Close the window')
    self.close_action.setStatusTip('Close the window')
    self.close_action.triggered.connect(self.close_func)

    self.cut_action.setIcon(QIcon('images/cut.ico'))            # 6
    self.cut_action.setShortcut('Ctrl+X')
    self.cut_action.setToolTip('Cut the text to clipboard')
    self.cut_action.setStatusTip('Cut the text')
    self.cut_action.triggered.connect(self.cut_func)

    self.copy_action.setIcon(QIcon('images/copy.ico'))          # 7
    self.copy_action.setShortcut('Ctrl+C')
    self.copy_action.setToolTip('Copy the text')
    self.copy_action.setStatusTip('Copy the text')
    self.copy_action.triggered.connect(self.copy_func)

    self.paste_action.setIcon(QIcon('images/paste.ico'))        # 8
    self.paste_action.setShortcut('Ctrl+V')
    self.paste_action.setToolTip('Paste the text')
    self.paste_action.setStatusTip('Paste the text')
    self.paste_action.triggered.connect(self.paste_func)

    self.font_action.setIcon(QIcon('images/font.ico'))          # 9
    self.font_action.setShortcut('Ctrl+T')
    self.font_action.setToolTip('Change the font')
    self.font_action.setStatusTip('Change the font')
    self.font_action.triggered.connect(self.font_func)

    self.color_action.setIcon(QIcon('images/color.ico'))        # 10
    self.color_action.setShortcut('Ctrl+R')
    self.color_action.setToolTip('Change the color')
    self.color_action.setStatusTip('Change the color')
    self.color_action.triggered.connect(self.color_func)

    self.about_action.setIcon(QIcon('images/about.ico'))        # 11
    self.about_action.setShortcut('Ctrl+Q')
    self.about_action.setToolTip('What is Qt?')
    self.about_action.setStatusTip('What is Qt?')
    self.about_action.triggered.connect(self.about_func)

可以看出各个动作设置方法非常类似:

所有图片我已经放在了百度云中,读者可以自行下载:

链接: https://pan.baidu.com/s/1Rp2-G_8PdvFfDIoIesMNEg 提取码: ag8w

1. 通过setIcon()方法传入QIcon参数来设置动作的图标。setShortCut()方法就是用来设置快捷键的,这里我们将新建动作的快捷键设置为Ctrl+N,那么按下Ctrl+N就相当于点击了这个动作了(不管什么平台,这里统一用Ctrl,即便是在Mac上,不过程序运行后,使用Mac的用户要按下的是Command+N)。setToolTip()方法可以用来设置小气泡提示,当鼠标停留在该动作上时,就会显示相应的提示(当然我们也可以对其他对象使用该方法,比如QPushButton)。setStatusTip()就是设置状态栏信息,当鼠标停留在该动作上时,状态栏会显示相应的信息。最后我们将new_action的triggered信号与自定义的槽函数连接起来:

def new_func(self):
    if not self.is_saved and self.text_edit.toPlainText():
        choice = QMessageBox.question(self, '', 'Do you want to save the text?',
                                      QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if choice == QMessageBox.Yes:
            self.save_func(self.text_edit.toHtml())
            self.text_edit.clear()
            self.is_saved_first = True
        elif choice == QMessageBox.No:
            self.text_edit.clear()
        else:
            pass
    else:
        self.text_edit.clear()
        self.is_saved = False
        self.is_saved_first = True

新建前我们要判断当前文本是否有保存,如果没有的话,那就出现弹框询问是否要保存,按Yes的话就调用save_func()函数进行保存(save_func()等函数笔者已经在上一章中详细讲解了,这里不再复述),保存好了当然要将当前的文本编辑框清空好方便让用户重新写作。若按No不进行保存的话,就直接清空。若按下Cancel取消的话,则不进行任何动作;如果已经保存了,那么就直接清空文本编辑框,并设置相应变量就行了;

2. open_action动作设置方法类似,我们主要来看下连接的槽函数:

def open_file_func(self):
    if not self.is_saved:
        choice = QMessageBox.question(self, '', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if choice == QMessageBox.Yes:
            self.save_func(self.text_edit.toHtml())
            file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)')
            if file:
                with open(file, 'r') as f:
                    self.text_edit.clear()
                    self.text_edit.setText(f.read())
                    self.is_saved = True
        elif choice == QMessageBox.No:
            file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)')
            if file:
                with open(file, 'r') as f:
                    self.text_edit.clear()
                    self.text_edit.setText(f.read())
                    self.is_saved = True
        else:
            pass
    else:
        file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)')
        if file:
            with open(file, 'r') as f:
                self.text_edit.clear()
                self.text_edit.setText(f.read())
                self.is_saved = True

逻辑与新建动作非常相似,只不过就是将清空文本编辑框操作换成了打开文件对话框操作;

3-5. 这几个动作的槽函数都已在上一章中讲解过,相信读者已经完全可以理解了,这里就直接放出代码:

def save_func(self, text):
    if self.is_saved_first:
        self.save_as_func(text)
    else:
        with open(self.path, 'w') as f:
            f.write(text)
        self.is_saved = True
def save_as_func(self, text):
    self.path, _ = QFileDialog.getSaveFileName(self, 'Save File', './', 'Files (*.html *.txt *.log)')
    if self.path:
        with open(self.path, 'w') as f:
            f.write(text)
        self.is_saved = True
        self.is_saved_first = False

下方的close_func()跟我们在上一章中讲的窗口关闭事件实现方法类似,只不过就是将QCloseEvent.accept()换成了self.close()。QCloseEvent.ignoret()其实功能上就相当于pass:

def close_func(self):
    if not self.is_saved:
        choice = QMessageBox.question(self, 'Save File', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if choice == QMessageBox.Yes:
            self.save_func(self.text_edit.toHtml())
            self.close()
        elif choice == QMessageBox.No:
            self.close()
        else:
            pass

def closeEvent(self, QCloseEvent):
    if not self.is_saved:
        choice = QMessageBox.question(self, 'Save File', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if choice == QMessageBox.Yes:
            self.save_func(self.text_edit.toHtml())
            QCloseEvent.accept()
        elif choice == QMessageBox.No:
            QCloseEvent.accept()
        else:
            QCloseEvent.ignore()

请注意,由于我们的记事本涉及到颜色,所以不能调用QTextEdit的toPlainText()方法,因为该方法获取的是纯文本,所以颜色会丢失掉。应该要调用toHtml()方法保留颜色;

6. cut_action连接的槽函数如下:

def cut_func(self):
    self.mime_data.setHtml(self.text_edit.textCursor().selection().toHtml())
    self.clipboard.setMimeData(self.mime_data)
    self.text_edit.textCursor().removeSelectedText()

self.text_edit.textCursor()方法可以获取到文本编辑框当前的指针(类型为QTextCursor),此时再调用selection()方法可以获取到指针当前所选择的内容,但此时的类型为QTextDocumentFragment,我们需要再调用toHtml()方法来获取到文本内容。当用户进行剪切后,被剪切的文本肯定要消失,所以就调用QTextCursor的removeSelectedText()方法。

7. copy_action的槽函数如下,方法同理,只不过此时不需要将文本删除:

def copy_func(self):
    self.mime_data.setHtml(self.text_edit.textCursor().selection().toHtml())
    self.clipboard.setMimeData(self.mime_data)

8. 在paste_action的槽函数中,我们只需要调用insetHtml()方法将剪贴板中的文本插入即可(该方法会在指针位置插入文本):

def paste_func(self):
    self.text_edit.insertHtml(self.clipboard.mimeData().html())

9-10. font_action和color_action的槽函数在上一章中也已经讲过了,这里不再复述:

def font_func(self):
    font, ok = QFontDialog.getFont()
    if ok:
        self.text_edit.setFont(font)

def color_func(self):
    color = QColorDialog.getColor()
    if color.isValid():
        self.text_edit.setTextColor(color)

11. about_action所连接的槽函数最为简单,就是打开一个关于Qt的消息框即可:

def about_func(self):
    QMessageBox.aboutQt(self, 'About Qt')

最后我们只剩下QTextEdit文本编辑框没有设置了,同上一章一样,将textChanged信号和槽函数连接。在槽函数中,我们对self.is_saved变量进行设置即可:

def text_edit_int(self):
    self.text_edit.textChanged.connect(self.text_changed_func)

def text_changed_func(self):
    if self.text_edit.toPlainText():
        self.is_saved = False
    else:
        self.is_saved = True

运行截图如下,菜单栏截图:

注:

  • Mac上若QAction的文本设为Quit或者Exit时,无法被添加(文本无法随意定);
  • Mac上Edit菜单下会自动加入Start Dictation和Emoji&Symbols动作;
  • Mac上Help菜单下会自动加入Search搜索框。

Tool Tip和Status Tip:

完整代码如下:

import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QMimeData
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QAction, QFileDialog, QMessageBox,\
                            QFontDialog, QColorDialog


class Demo(QMainWindow):
    is_saved = True
    is_saved_first = True
    path = ''

    def __init__(self):
        super(Demo, self).__init__()
        self.file_menu = self.menuBar().addMenu('File')
        self.edit_menu = self.menuBar().addMenu('Edit')
        self.help_menu = self.menuBar().addMenu('Help')

        self.file_toolbar = self.addToolBar('File')
        self.edit_toolbar = self.addToolBar('Edit')

        self.status_bar = self.statusBar()

        self.new_action = QAction('New', self)
        self.open_action = QAction('Open', self)
        self.save_action = QAction('Save', self)
        self.save_as_action = QAction('Save As', self)
        self.close_action = QAction('Close', self)
        self.cut_action = QAction('Cut', self)
        self.copy_action = QAction('Copy', self)
        self.paste_action = QAction('Paste', self)
        self.font_action = QAction('Font', self)
        self.color_action = QAction('Color', self)
        self.about_action = QAction('Qt', self)

        self.text_edit = QTextEdit(self)

        self.mime_data = QMimeData()
        self.clipboard = QApplication.clipboard()

        self.setCentralWidget(self.text_edit)
        self.resize(450, 600)

        self.menu_init()
        self.toolbar_init()
        self.status_bar_init()
        self.action_init()
        self.text_edit_int()

    def menu_init(self):
        self.file_menu.addAction(self.new_action)
        self.file_menu.addAction(self.open_action)
        self.file_menu.addAction(self.save_action)
        self.file_menu.addAction(self.save_as_action)
        self.file_menu.addSeparator()
        self.file_menu.addAction(self.close_action)

        self.edit_menu.addAction(self.cut_action)
        self.edit_menu.addAction(self.copy_action)
        self.edit_menu.addAction(self.paste_action)
        self.edit_menu.addSeparator()
        self.edit_menu.addAction(self.font_action)
        self.edit_menu.addAction(self.color_action)

        self.help_menu.addAction(self.about_action)

    def toolbar_init(self):
        self.file_toolbar.addAction(self.new_action)
        self.file_toolbar.addAction(self.open_action)
        self.file_toolbar.addAction(self.save_action)
        self.file_toolbar.addAction(self.save_as_action)

        self.edit_toolbar.addAction(self.cut_action)
        self.edit_toolbar.addAction(self.copy_action)
        self.edit_toolbar.addAction(self.paste_action)
        self.edit_toolbar.addAction(self.font_action)
        self.edit_toolbar.addAction(self.color_action)

    def status_bar_init(self):
        self.status_bar.showMessage('Ready to compose')

    def action_init(self):
        self.new_action.setIcon(QIcon('images/new.ico'))
        self.new_action.setShortcut('Ctrl+N')
        self.new_action.setToolTip('Create a new file')
        self.new_action.setStatusTip('Create a new file')
        self.new_action.triggered.connect(self.new_func)

        self.open_action.setIcon(QIcon('images/open.ico'))
        self.open_action.setShortcut('Ctrl+O')
        self.open_action.setToolTip('Open an existing file')
        self.open_action.setStatusTip('Open an existing file')
        self.open_action.triggered.connect(self.open_file_func)

        self.save_action.setIcon(QIcon('images/save.ico'))
        self.save_action.setShortcut('Ctrl+S')
        self.save_action.setToolTip('Save the file')
        self.save_action.setStatusTip('Save the file')
        self.save_action.triggered.connect(lambda: self.save_func(self.text_edit.toHtml()))

        self.save_as_action.setIcon(QIcon('images/save_as.ico'))
        self.save_as_action.setShortcut('Ctrl+A')
        self.save_as_action.setToolTip('Save the file to a specified location')
        self.save_as_action.setStatusTip('Save the file to a specified location')
        self.save_as_action.triggered.connect(lambda: self.save_as_func(self.text_edit.toHtml()))

        self.close_action.setIcon(QIcon('images/close.ico'))
        self.close_action.setShortcut('Ctrl+E')
        self.close_action.setToolTip('Close the window')
        self.close_action.setStatusTip('Close the window')
        self.close_action.triggered.connect(self.close_func)

        self.cut_action.setIcon(QIcon('images/cut.ico'))
        self.cut_action.setShortcut('Ctrl+X')
        self.cut_action.setToolTip('Cut the text to clipboard')
        self.cut_action.setStatusTip('Cut the text')
        self.cut_action.triggered.connect(self.cut_func)

        self.copy_action.setIcon(QIcon('images/copy.ico'))
        self.copy_action.setShortcut('Ctrl+C')
        self.copy_action.setToolTip('Copy the text')
        self.copy_action.setStatusTip('Copy the text')
        self.copy_action.triggered.connect(self.copy_func)

        self.paste_action.setIcon(QIcon('images/paste.ico'))
        self.paste_action.setShortcut('Ctrl+V')
        self.paste_action.setToolTip('Paste the text')
        self.paste_action.setStatusTip('Paste the text')
        self.paste_action.triggered.connect(self.paste_func)

        self.font_action.setIcon(QIcon('images/font.ico'))
        self.font_action.setShortcut('Ctrl+T')
        self.font_action.setToolTip('Change the font')
        self.font_action.setStatusTip('Change the font')
        self.font_action.triggered.connect(self.font_func)

        self.color_action.setIcon(QIcon('images/color.ico'))
        self.color_action.setShortcut('Ctrl+R')
        self.color_action.setToolTip('Change the color')
        self.color_action.setStatusTip('Change the color')
        self.color_action.triggered.connect(self.color_func)

        self.about_action.setIcon(QIcon('images/about.ico'))
        self.about_action.setShortcut('Ctrl+Q')
        self.about_action.setToolTip('What is Qt?')
        self.about_action.setStatusTip('What is Qt?')
        self.about_action.triggered.connect(self.about_func)

    def text_edit_int(self):
        self.text_edit.textChanged.connect(self.text_changed_func)

    def text_changed_func(self):
        if self.text_edit.toPlainText():
            self.is_saved = False
        else:
            self.is_saved = True

    def new_func(self):
        if not self.is_saved and self.text_edit.toPlainText():
            choice = QMessageBox.question(self, '', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            if choice == QMessageBox.Yes:
                self.save_func(self.text_edit.toHtml())
                self.text_edit.clear()
                self.is_saved_first = True
            elif choice == QMessageBox.No:
                self.text_edit.clear()
            else:
                pass
        else:
            self.text_edit.clear()
            self.is_saved = False
            self.is_saved_first = True

    def open_file_func(self):
        if not self.is_saved:
            choice = QMessageBox.question(self, '', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            if choice == QMessageBox.Yes:
                self.save_func(self.text_edit.toHtml())
                file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)')
                if file:
                    with open(file, 'r') as f:
                        self.text_edit.clear()
                        self.text_edit.setText(f.read())
                        self.is_saved = True
            elif choice == QMessageBox.No:
                file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)')
                if file:
                    with open(file, 'r') as f:
                        self.text_edit.clear()
                        self.text_edit.setText(f.read())
                        self.is_saved = True
            else:
                pass
        else:
            file, _ = QFileDialog.getOpenFileName(self, 'Open File', './', 'Files (*.html *.txt *.log)')
            if file:
                with open(file, 'r') as f:
                    self.text_edit.clear()
                    self.text_edit.setText(f.read())
                    self.is_saved = True

    def save_func(self, text):
        if self.is_saved_first:
            self.save_as_func(text)
        else:
            with open(self.path, 'w') as f:
                f.write(text)
            self.is_saved = True

    def save_as_func(self, text):
        self.path, _ = QFileDialog.getSaveFileName(self, 'Save File', './', 'Files (*.html *.txt *.log)')
        if self.path:
            with open(self.path, 'w') as f:
                f.write(text)
            self.is_saved = True
            self.is_saved_first = False

    def close_func(self):
        if not self.is_saved:
            choice = QMessageBox.question(self, 'Save File', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            if choice == QMessageBox.Yes:
                self.save_func(self.text_edit.toHtml())
                self.close()
            elif choice == QMessageBox.No:
                self.close()
            else:
                pass

    def closeEvent(self, QCloseEvent):
        if not self.is_saved:
            choice = QMessageBox.question(self, 'Save File', 'Do you want to save the text?',
                                          QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            if choice == QMessageBox.Yes:
                self.save_func(self.text_edit.toHtml())
                QCloseEvent.accept()
            elif choice == QMessageBox.No:
                QCloseEvent.accept()
            else:
                QCloseEvent.ignore()

    def cut_func(self):
        self.mime_data.setHtml(self.text_edit.textCursor().selection().toHtml())
        self.clipboard.setMimeData(self.mime_data)
        self.text_edit.textCursor().removeSelectedText()

    def copy_func(self):
        self.mime_data.setHtml(self.text_edit.textCursor().selection().toHtml())
        self.clipboard.setMimeData(self.mime_data)

    def paste_func(self):
        self.text_edit.insertHtml(self.clipboard.mimeData().html())

    def font_func(self):
        font, ok = QFontDialog.getFont()
        if ok:
            self.text_edit.setFont(font)

    def color_func(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.text_edit.setTextColor(color)

    def about_func(self):
        QMessageBox.aboutQt(self, 'About Qt')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    sys.exit(app.exec_())

23.3 程序启动画面QSplashScreen

许多应用程序的主窗口在打开前都会先展示一个启动画面(例如PS),这是因为打开需要一定时间,所以用启动画面来显示模块加载进度,这也是一种增加程序使用友好度的方法。通常我们会将程序启动画面的代码放在程序入口中,位于sys.exit(app.exec_())之前。下面我们就给在上一节中的记事本应用加一个启动画面(当然这个记事本打开很快,根本不需要启动画面,这里只是举一下栗子~( ̄▽ ̄)~*)

我们只需要改动下程序入口中的代码即可,首先先更新下导入的模块,我们新增了time、QPixmap、Qt以及QSplashScreen:

import sys
import time
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import QMimeData, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QAction, QFileDialog, QMessageBox,\
                            QFontDialog, QColorDialog, QSplashScreen

接下来是程序入口处的代码:

if __name__ == '__main__':
    app = QApplication(sys.argv)

    splash = QSplashScreen()
    splash.setPixmap(QPixmap('images/splash.jpg'))
    splash.show()
    splash.showMessage('Welcome to Use This PyQt5-Made Notebook~', 
                       Qt.AlignBottom | Qt.AlignCenter, Qt.white)
    time.sleep(2)

    demo = Demo()
    demo.show()
    splash.finish(demo)

    sys.exit(app.exec_())

首先我们实例化一个QSplashScreen控件,并调用setPixmap()方法设置图片,接着调用show()方法进行显示。showMessage(str, alignment, color)方法在程序启动画面上显示文字。alignment为文字在启动画面上的位置,我们这里设为底部居中;color为文字的颜色,我们这里设为白色。由于程序启动并不耗时,启动画面会一闪而过,所以我们在这里加一行time.sleep(2)来暂停两秒看一下效果。当我们决定可以开始显示主窗口时,调用finish()传入主窗口实例就可以了。

启动画面图片splash.jpg和ico图标放在一起了。

运行截图如下:

23.2 小结

1. 通过上方实例我们知道可以往QMainWindow主窗口中添加很多功能,各个功能通过QAction动作对象来实现。而这写动作对象被整齐有序的添加到菜单栏和工具栏中。这就是为什么我们应该用QMainWindow类来实现较为复杂的应用程序;

2. 关键知识点:获取选中的文本 self.text_edit.textCursor().selection().toHtml(),读者可以在QtAssitant中查询下相应方法。

欢迎关注我的微信公众号,发现更多有趣内容:

  • 12
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

la_vie_est_belle

谢谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值