PyQt5学习Ⅱ(菜单,工具栏和布局管理)
在PyQt5教程的这一部分中,我们创建了创建状态栏,菜单栏和工具栏。菜单是位于菜单栏中的一组命令。工具栏上有按钮,其中包含应用程序中的一些常用命令 状态栏显示状态信息,通常位于应用程序窗口的底部。
QMainWindow
状态栏
状态栏是用于显示状态信息的小组件。
状态栏是在QMainWindow
小部件的帮助下创建的。
self.statusBar().showMessage( 'ready')
要获取状态栏,我们调用类QtGui.QMainWindow的statusBar()
方法。方法的第一次调用会创建一个状态栏。后续调用将返回状态栏对象。在showMessage()
显示在状态栏的消息。
我现在还没有遇见必须要用sys才能实现的。这次我们用的不是QWidget类了,而是QMainWindow类,因为QWidget没有statusBar()
方法,widget的意思是窗口小部件的意思。
不过都需要show方法和QApplication才能显示出来。没有 app.exec_()这个主循环,窗口都是昙花一现。QMainWindow实际是继承于QWidget的。
简单的菜单
菜单栏是GUI应用程序的常见部分。它是位于各种菜单中的一组命令。(Mac OS对菜单栏的处理方式不同。为了获得类似的结果,我们可以添加以下行:menubar.setNativeMenuBar(False)
。),windows就不用。
QMainWindow.menubar内部用的其实是QMenuBar类,不过QMenuBar不需要导入,似乎是直接把代码移植过来了。
我实际测试了,其实退出的快捷键是ctrl+q就够了q不需要大写,也就是不需要shift,虽然按shift也可以(一般程序员大写用的都是shift,而不会用大写锁)。看到这个exit是直接退出整个QApplication了,并且这里也写的是直接连接的就是the quit method of the QApplication。
qApp是QApplication的一个实例化对象。我们完全可以直接用QApplication。
还需要说明的一点是triggered虽然没有语法高亮,但是这个在QAcation里面确实是有的。并且QAcation触发没有clicked这个方法。
self.statusBar()就是创建一个状态栏显示exitAct.setStatusTip里面的内容,没有这一句是显示不了里面内容的。
Exit和File前面的&就目前而言可以不加,看不出&的用处。下面来理解一下这个setShorcut,它只是在后面显示一下文字,退出的快捷键是pyqt默认的设置就是ctrl+q呢,还是说是货真价实地改了快捷键呢?现在地快捷键是ctrl+q,按q时不会退出的。
然后把快捷键改为Q。
看来是货真价实地改了快捷键了。
子菜单
子菜单是位于另一个菜单内的菜单。
In the example, we have two menu items; one is located in the File menu and the other one in the File's Import submenu.(在示例中,我们有两个菜单项,一个在文件菜单里,另一个在文件的导入菜单里)
impMenu = QMenu('Import', self)
New menu is created with QMenu
.(新的菜单是用QMenu创建的)。
impAct = QAction('Import mail', self)
impMenu.addAction(impAct)
An action is added to the submenu with addAction()
.(使用addAction()把一个动作加到了子菜单里面)。
下面的gif主要说明的是在代码里键入菜单的先后顺序决定了在菜单里的上下位置。
Check menu
在以下示例中,我们创建了一个可以选中和取消选中的菜单。
我们来试试上面所说的checkable和setchecked的作用。
把setchecked改为False会如何。
看到只是一开始,对号是默认没有勾的,并且这种情况下也可以看到状态栏,不过也仅限于第一次,后面就正常了,只有对号勾选了,才能看见状态栏。再改checkable呢?
看到没有对号勾选了,第一次显示了状态栏,后面就都不可见了。
Context menu
上下文菜单(也称为弹出菜单)是在某些上下文下显示的命令列表。例如,在Opera网页浏览器中,当我们右键单击网页时,我们会得到一个上下文菜单。在这里,我们可以重新加载页面,返回或查看页面源。如果我们右键单击工具栏,我们将获得另一个用于管理工具栏的上下文菜单。其实就和我们在桌面右键弹出菜单是一个道理。
我们点击new和open是没有用的,因为它们没有定义后续的判断操作。contextMenuEvent是QMainWindow的一个内置函数,这个函数名不可以写错的,和上一讲关闭消息框的closeEvent是一样的。
action = cmenu.exec_(self.mapToGlobal(event.pos()))里面的参数没有,context menu也会出来,不过位置就一直在左上角,而不是在鼠标右键的地方。
工具栏
菜单分组我们可以在应用程序中使用的所有命令。工具栏可以快速访问最常用的命令。
exitAct.triggered.connect(qApp.quit)括号里面一定是函数或方法的名字,后面不需要加括号。
不然就会报错。
把它放在一起
在本节的最后一个示例中,我们将创建一个菜单栏,工具栏和状态栏。我们还将创建一个中央小部件。
This code example creates a skeleton of a classic GUI application with a menubar, toolbar, and a statusbar.(这个代码实例创造了一个有菜单栏,工具栏和状态栏的经典GUI应用)
textEdit = QTextEdit() self.setCentralWidget(textEdit)
Here we create a text edit widget. We set it to be the central widget of the QMainWindow
. The central widget will occupy all space that is left.(这里创造了一个文本编辑部件,我们把它设为中心部件,中心部件会占据剩下的所有空间。)
后面把btn这个按钮作为中心部件,按钮就覆盖了文本框。
PyQt5中的布局管理
布局管理是我们将小部件放置在应用程序窗口上的方式。我们可以使用绝对定位或布局类来放置小部件。使用布局管理器管理布局是组织窗口小部件的首选方式。
绝对定位
程序员以像素为单位指定每个小部件的位置和大小。当您使用绝对定位时,我们必须了解以下限制:
-
The size and the position of a widget do not change if we resize a window
(如果我们改变窗口的大小,窗口部件的大小和位置不可以改变)
-
Applications might look different on various platforms(应用在不同的平台可能看起来不同)
-
Changing fonts in our application might spoil the layout(改变字体在应用中可能会破坏布局)
-
If we decide to change our layout, we must completely redo our layout, which is tedious and time consuming(如果我们决定改变我们的布局,我们必须完全重做布局,这是繁琐和耗时的)
以下示例将小部件放在绝对坐标中。
效果就是这样的。
我们使用该move()
方法来定位我们的小部件。在我们的例子中,这些是标签。我们通过提供x和y坐标来定位它们。坐标系的开头位于左上角。x值从左到右增长。y值从上到下增长。
lbl1 = QLabel('Zetcode',self)
lbl1.move(15,10)
标签小部件位于x=15
和 y=10
。
盒子布局
QHBoxLayout
并且QVBoxLayout
是水平和垂直排列小部件的基本布局类。
想象一下,我们想在右下角放置两个按钮。要创建这样的布局,我们使用一个水平和一个垂直框。为了创建必要的空间,我们添加了一个伸展因子。
self.setLayout(vbox)这句还是很重要的,没有它,按钮是显示不出来的,因为 okButton = QPushButton("OK")
cancelButton = QPushButton("Cancel")
hbox = QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)
vbox = QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)
这些按钮是添加在布局里面的。
我们看到无论你怎么拉伸,这两个按钮都在右下角,按钮的绝对位置会随着拉伸改变,因为绝对位置是相对于左上角的坐标嘛。和第一讲的
我们对比一下。
这个Button按钮的绝对位置不会随着伸缩而改变,把窗口向左拉,按钮跟着动还是要保持绝对位置不变,还是因为绝对位置是以左上角为原点或者说参考的。我们来研究一下伸缩因子到底干嘛用的。首先这里面传的参数必须是整数。
不然会报错。
不过在python里面。
这个里面的参数是负的貌似也每什么影响啊,但是如果把vbox的addStrech给注释了,发现按钮仍然会跟着伸缩改变绝对位置,但是位置是在垂直的中间,而不是下面,这个v是vertical,垂直的的意思。
把垂直布局的代码注释掉,效果和注释掉addStrech是一样的,然后我们把水平布局,h是herizonal的意思的addStrech注释掉,看到按钮出现在垂直的中间位置,这个没变,但是水平是贯穿整个窗口的,怎么伸缩都是。
QMainWindow好像是有自己的布局的,重新布局的是没有用的。
QGridLayout
QGridLayout
是最通用的布局类。它将空间划分为行和列。
效果:
我们来复习里面用到的一些知识,主要集中在
positions = [(i,j) for i in range(5) for j in range(4)]
for position, name in zip(positions, names):
if name == '':
continue
button = QPushButton(name)
grid.addWidget(button, *position)
这一段里面。首先positions的产生用的是列表推导式,列表里面的元素是元组。
元组没有推导式,那种形式叫做生成器,可以用next方法,for i in positions其实里面是调用了next方法的。这块是迭代器的内容。 关于zip,我们直接看几个例子。
zip的第一个参数必须支持迭代。然后就是把对应位置的元素打包成一个元组,元组的小括号有时候可以不写,这里就可以不写。 grid.addWidget(button, *position)这个*是什么意思呢?
*就相当于是把最外面的括号,无论是哪种括号,都去掉。在我们这个代码实例中,*postion就会出来两个int,符合addWidget的参数格式(可能两个int之间可以用空格隔开,不过下面看到不是这样的,不知道*是个什么机制,反正传参数就是可以传)。
好像是自动加上了,一样。
集合其实也有推导式,然后就是addWidget不传位置参数的话,默认会排一列。
Review example(复习示例)
窗口小部件可以跨越网格中的多个列或行。在下一个例子中,我们将说明这一点
LineEdit,顾名思义就是只能写一行文本的编辑器。TextEdit就没有限制必须是一行文本。
间隔必须要传参数。
可以传负的
grid.addWidget(reviewEdit, 3, 1, 5, 1)的第三个参数是跨越行数,第四个是跨越列数,值得指出的是每一个矩阵单元的大小是不一样的,不然的话review的宽应该是上面的两倍,但是明显不是的。
有的位置参数可以不传,有默认参数,LineEdit传四个参数看起来很乱。
截止目前,我们还是可以不导入sys模块, app = QApplication()甚至可以传一个空的列表,只要是列表都可以,别的类型目前来看是不行的。