前言 昨天终于把软件给写完了,算是我有史以来做过的最大的项目了吧,今天终于能有空闲时间思考一下这个系列的博客该怎么填了。我打算贴出部分代码来展示如何从头写出一个完整的 GUI 程序,但是由于我们需要进行软著和专利的申请,绘图的核心代码就不放出了。但是各位看官放心😋,贴出的代码足够编译出一个完整的 GUI 程序。
温馨提示:文章的最末尾有本节的完整代码
UI 设计 UI 设计上我把重心放到了 PPI 以及 RHI 两个子页面上,主页面 (form.ui) 仅仅展示了三个按钮
使用 Qt Creator
设计起来非常简单,仅需要几个按钮以及数个弹簧 (spacer
) 即可
按钮和弹簧全部添加完成以后,将按钮与垂直弹簧间隔排列,并全部选中,对其使用垂直布局
然后水平排列水平弹簧和垂直布局,全选,使用水平布局
最后选中最高级的 MainWindow
使用栅格布局 ,即可让所有部件对齐并平铺填满窗口。
这里添加弹簧的原因是要让弹簧来填满按钮与按钮、按钮与边界之间的间隙,如不加弹簧在使用栅格布局时,按钮的长和宽会被自动拉长以填满窗口,使得 UI 异常难看。
使用水平或垂直布局会使同一布局内的部件有相同的长或者宽 (除非你设置了部件的最大长度和宽度),对于布局外的部件来讲,会将一个布局视为一个整体,因此这里设置水平布局时只添加了两个水平弹簧。
弹簧会最大压缩部件的尺寸,所以这里垂直布局的水平边长几乎与第一个按钮的长度一致。(几乎 是因为布局的长宽会略微大于部件)
在 Qt Creator
右侧设置好部件的类名称后可使用 pyuic5
命令将 form.ui
文件转换为 form.py
文件,方便后续使用。
1 pyuic5 form.ui -o form.py
这里我设置的类名分别为
请忽略这里驼峰和蛇形命名的混用,我也不知道我为什么命名会这么混乱🐶
按钮
类名
PPI及组合反射率绘制
PPIPlotButton
RHI 及任意剖面绘制
RHIPlotButton
关闭程序
exit_button
初始化界面 由 .ui
生成的文件我们不好直接修改 (会被覆盖掉),若要进一步设置各种事件,需要新定义一个类。这里不会使用类的继承 ,而是使用 Qt
中的函数 setupUi
。
首先新建一个 mainwindow.py
文件,然后导入 form.py
中的类,最后创建一个类并继承与 form.py
中的类相同的 Qt
类,并使用 setupUi
初始化 UI
1 2 3 4 5 6 7 8 from PyQt5.QtWidgets import QMainWindowfrom form import Ui_MainWindowclass MainWindow (QMainWindow ): def __init__ (self ): super (MainWindow, self ).__init__() self .ui = Ui_MainWindow() self .ui.setupUi(self )
若要运行程序,需要继续加上以下代码
1 2 3 4 5 6 7 8 if __name__ == "__main__" : import sys from PyQt5.QtWidgets import QApplication app = QApplication([]) widget = MainWindow() widget.show() sys.exit(app.exec ())
现在程序可以运行了,但是点击按钮不会触发任何事件,因为我们还没有为按钮绑定槽函数。
绑定槽函数 首先新建两个新的 UI 界面吧,分别当作 PPI 和 RHI 的绘图界面。整个文件夹分布如下
1 2 3 4 5 6 -- -- -- . --- -- . -- . . --- .
同样使用 pyuic
命令将 .ui
文件转换成 .py
文件,然后分别新建 ppi.py
以及 rhi.py
对界面进行初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from PyQt5.QtWidgets import QMainWindowfrom src.ppi.ppi_form import Ui_PPIWindowclass PPIWindow (QMainWindow ): def __init__ (self ): super (PPIWindow, self ).__init__() self .ui = Ui_PPIWindow() self .ui.setupUi(self ) from PyQt5.QtWidgets import QMainWindowfrom src.rhi.rhi_form import Ui_RHIWindowclass RHIWindow (QMainWindow ): def __init__ (self ): super (RHIWindow, self ).__init__() self .ui = Ui_RHIWindow() self .ui.setupUi(self )
ppi.py
以及 rhi.py
两个文件其实分别在 src/ppi
和 src/rhi
中,但是这里 import 的方式仍为 from src.xxx.xxx import xxx
,这与 Python 的 模块缓存 有一定联系,这样写的好处是仅搜索 sys.modules
即可找到对应的包,并且由于 src
模块已经被导入过 (第一次被导入将在 mainwindow.py
中发生),这样可以保证 import 不会出错。
现在我们可以在 mainwindow.py
中引入这两个部件并配置按钮的槽函数了,以下是配置过后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 from PyQt5.QtWidgets import QMainWindowfrom form import Ui_MainWindowfrom src.ppi.ppi import PPIWindow from src.rhi.rhi import RHIWindowclass MainWindow (QMainWindow ): def __init__ (self ): super (MainWindow, self ).__init__() self .ui = Ui_MainWindow() self .ui.setupUi(self ) self .ppi_window = None self .rhi_window = None self .init_slot() def init_slot (self ): """ bind function to slot :return: """ self .ui.PPIPlotButton.clicked.connect(self .show_ppi) self .ui.RHIPlotButton.clicked.connect(self .show_rhi) self .ui.exit_button.clicked.connect(self .close) def show_ppi (self ): """ create ppi window and show it :return: """ if self .ppi_window is not None : self .ppi_window.deleteLater() self .ppi_window = PPIWindow() self .ppi_window.show() def show_rhi (self ): """ create rhi window and show it :return: """ if self .rhi_window is not None : self .rhi_window.deleteLater() self .rhi_window = RHIWindow() self .rhi_window.show() if __name__ == "__main__" : import sys from PyQt5.QtWidgets import QApplication app = QApplication([]) widget = MainWindow() widget.show() sys.exit(app.exec ())
现在运行程序,点击按钮就可触发对应事件了。
快捷键及其他事件 接下来我们为应用绑定快捷键,方便程序的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from PyQt5.QtGui import QKeySequencefrom PyQt5.QtWidgets import QShortcutclass MainWindow (QMainWindow ): def __init__ (self ): self .bind_short_cut() def bind_short_cut (self ): """ bing shortcut to app :return: """ self .close_short_cut = QShortcut(QKeySequence('Ctrl+Q' ), self ) self .close_short_cut.activated.connect(self .close)
ppi.py
和 rhi.py
的快捷键同理,这里不再详述。运行程序,使用对应快捷键,窗口会随之关闭。为了防止用户误触快捷键导致所做的修改丢失,这里使用 QMessageBox
加一个确认框 用来确认关闭窗口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from PyQt5.QtWidgets import QMessageBoxdef close_confirm_box (parent ): """ return True if Yes or False if No. be careful! close window will lose all results :return: """ res = QMessageBox.question(parent, '关闭窗口' , '关闭窗口会导致所有结果丢失,确认关闭?' , QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if res == QMessageBox.Yes: return True else : return False class MainWindow (QMainWindow ): ... ... def closeEvent (self, QCloseEvent ): if close_confirm_box(self ): QCloseEvent.accept() else : QCloseEvent.ignore()
现在重新使用快捷键或者直接尝试关闭窗口时,将弹出确认对话框进行确认
全部代码 mainwindow.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 from PyQt5.QtWidgets import QMainWindow, QShortcut, QMessageBoxfrom PyQt5.QtGui import QKeySequencefrom form import Ui_MainWindowfrom src.ppi.ppi import PPIWindow from src.rhi.rhi import RHIWindowdef close_confirm_box (parent ): """ return True if Yes or False if No. be careful! close window will lose all results :return: """ res = QMessageBox.question(parent, '关闭窗口' , '关闭窗口会导致所有结果丢失,确认关闭?' , QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if res == QMessageBox.Yes: return True else : return False class MainWindow (QMainWindow ): def __init__ (self ): super (MainWindow, self ).__init__() self .close_short_cut = None self .ui = Ui_MainWindow() self .ui.setupUi(self ) self .ppi_window = None self .rhi_window = None self .init_slot() self .bind_short_cut() def init_slot (self ): """ bind function to slot :return: """ self .ui.PPIPlotButton.clicked.connect(self .show_ppi) self .ui.RHIPlotButton.clicked.connect(self .show_rhi) self .ui.exit_button.clicked.connect(self .close) def show_ppi (self ): """ create ppi window and show it :return: """ if self .ppi_window is not None : self .ppi_window.deleteLater() self .ppi_window = PPIWindow() self .ppi_window.show() def show_rhi (self ): """ create rhi window and show it :return: """ if self .rhi_window is not None : self .rhi_window.deleteLater() self .rhi_window = RHIWindow() self .rhi_window.show() def bind_short_cut (self ): """ bing shortcut to app :return: """ self .close_short_cut = QShortcut(QKeySequence('Ctrl+Q' ), self ) self .close_short_cut.activated.connect(self .close) def closeEvent (self, QCloseEvent ): if close_confirm_box(self ): QCloseEvent.accept() else : QCloseEvent.ignore() if __name__ == "__main__" : import sys from PyQt5.QtWidgets import QApplication app = QApplication([]) widget = MainWindow() widget.show() sys.exit(app.exec ())
ppi.py
1 2 3 4 5 6 7 8 9 from PyQt5.QtWidgets import QMainWindowfrom src.ppi.ppi_form import Ui_PPIWindowclass PPIWindow (QMainWindow ): def __init__ (self ): super (PPIWindow, self ).__init__() self .ui = Ui_PPIWindow() self .ui.setupUi(self )
rhi.py
1 2 3 4 5 6 7 8 9 from PyQt5.QtWidgets import QMainWindowfrom src.rhi.rhi_form import Ui_RHIWindowclass RHIWindow (QMainWindow ): def __init__ (self ): super (RHIWindow, self ).__init__() self .ui = Ui_RHIWindow() self .ui.setupUi(self )
Comments