35. 枚举与 Qt 命名空间
当您在应用程序中看到类似以下的代码时,您可能会想知道 Qt.ItemDataRole.DisplayRole
或 Qt.ItemDataRole.CheckStateRole
对象究竟是什么。
在 PyQt 的早期版本中,也存在诸如
Qt.DisplayRole
之类的快捷名称,因此您在代码中仍可能看到这些名称。在 PyQt6 中,您必须始终使用完整名称。本书中的所有示例均使用完全限定名称。
def data(self, role, index):
if role == Qt.ItemDataRole.DisplayRole:
# 做些什么
Qt 在代码中广泛使用这些类型来表示有意义的常量。其中许多在 Qt 命名空间中可用,即 Qt.<something>
,尽管还有一些对象特定的类型,例如 QDialogButtonBox.StandardButton.Ok
,它们的工作方式完全相同。
但它们是如何工作的呢?在本章中,我们将详细探讨这些常量是如何形成的,以及如何有效地使用它们。为此,我们需要涉及一些基础知识,如二进制数。但深入理解这些内容并非本章的必要条件——正如往常一样,我们将重点放在如何在学习过程中应用所学知识上。
这不过是一些数字而已
如果您查看一个标志的类型(type()
),您会看到一个类的名称。这些类是该标志所属的组。例如,Qt.ItemDataRole.DecorationRole
的类型是 Qt.ItemDataRole
—— 您可以在 Qt 文档中看到这些组。
您可以在 Python Shell 中运行以下代码,只需先使用
from PyQt6.QtCore import Qt
导入 Qt 命名空间即可。
>>> type(Qt.ItemDataRole.DecorationRole)
<enum 'ItemDataRole'>
这些类型是枚举类型——一种将值限制为一组预定义值的类型。在 PyQt6 中,它们被定义为 Python 枚举 (Enum
) 类型。
这些值实际上都是简单的整数。Qt.ItemDataRole.DisplayRole
的值为 0,而 Qt.ItemDataRole.EditRole
的值为 2。这些整数值本身没有意义,但在它们被使用的特定上下文中具有意义。
>>> int(Qt.ItemDataRole.DecorationRole)
1
例如,您是否认为以下代码会评估为 True?
>>> Qt.ItemDataRole.DecorationRole == Qt.AlignmentFlag.AlignLeft
True
可能不是。但 Qt.ItemDataRole.DecorationRole
和 Qt.AlignmentFlag.AlignLeft
的整数值均为 1,因此在数值上是相等的。这些数值通常可以忽略。只要在适当的上下文中使用这些常量,它们就会始终按预期工作。
Table 8. Values given in the documentation can be in decimal or binary
标识符 | 值(十六进制) | 值(十进制) | 描述 |
---|---|---|---|
Qt.AlignmentFlag.AlignLeft | 0x0001 | 1 | 与左侧对齐 |
Qt.AlignmentFlag.AlignRight | 0x0002 | 2 | 与右侧对齐 |
Qt.AlignmentFlag.AlignHCenter | 0x0004 | 4 | 在可用空间中水平居中 |
Qt.AlignmentFlag.AlignJustify | 0x0008 | 8 | 在可用空间内对齐文本。 |
Qt.AlignmentFlag.AlignTop | 0x0020 | 32 | 与顶部对齐 |
Qt.AlignmentFlag.AlignBottom | 0x0040 | 64 | 与底部对齐 |
Qt.AlignmentFlag.AlignVCenter | 0x0080 | 128 | 在可用空间中垂直居中 |
Qt.AlignmentFlag.AlignBaseline | 0x0100 | 256 | 与基准线对齐 |
如果您查看上表中的数字,可能会发现一些异常。首先,这些数字并非每次增加1,而是每次翻倍。其次,水平对齐的十六进制数字都集中在同一列,而垂直对齐的数字则分布在另一列。
这种数字模式是故意设计的,它使我们能够做一件非常巧妙的事情——将标志位组合在一起以创建复合标志位。要理解这一点,我们需要快速了解计算机如何表示整数。
二进制与十六进制
当我们进行常规计数时,使用的是十进制(基数为10)的数字系统。该系统包含10个数字,从0到9,且十进制数字中的每个数字的值是其前一个数字的10倍。例如,数字1251由1×1000、2×100、5×10和1×1组成。
1000 | 100 | 10 | 1 |
---|---|---|---|
1 | 2 | 5 | 1 |
计算机以二进制形式存储数据,即一系列开和关的状态,以1和0的形式表示。二进制是一种以2为基数的数制。它有2个数字,从0到1,二进制数中的每个数字的值是前一个数字的2倍。在以下示例中,数字5由1×4和1×1组成。
8 | 4 | 2 | 1 | 十进制 |
---|---|---|---|---|
0 | 1 | 0 | 1 | 5 |
二进制数书写起来很快就会变得繁琐——5893的二进制形式是 1011100000101
——但将其与十进制数来回转换也并不方便。为了更方便地处理二进制数,十六进制在计算机领域中被广泛使用。这是一种由16个数字(0-9A-F)组成的数制。每个十六进制数字的值在0-15(0-A)之间,相当于4个二进制位。这使得在两者之间进行转换变得直观。
下表显示了数字0-15,以及它们在二进制和十六进制中的对应值。一个给定的二进制数的值可以通过将每列顶部含有1的数字相加来计算。
8 | 4 | 2 | 1 | 十六进制 | 十进制 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 1 | 1 | 1 |
0 | 0 | 1 | 0 | 2 | 2 |
0 | 0 | 1 | 1 | 3 | 3 |
0 | 1 | 0 | 0 | 4 | 4 |
0 | 1 | 0 | 1 | 5 | 5 |
0 | 1 | 1 | 0 | 6 | 6 |
0 | 1 | 1 | 1 | 7 | 7 |
1 | 0 | 0 | 0 | 8 | 8 |
1 | 0 | 0 | 1 | 9 | 9 |
1 | 0 | 1 | 0 | A | 10 |
1 | 0 | 1 | 1 | B | 11 |
1 | 1 | 0 | 0 | C | 12 |
1 | 1 | 0 | 1 | D | 13 |
1 | 1 | 1 | 0 | E | 14 |
1 | 1 | 1 | 1 | F | 15 |
这种模式在更大的数字中继续适用。例如,下图是数字25的二进制表示,由16×1、8×1和1×1组成。
16 | 8 | 4 | 2 | 1 |
---|---|---|---|---|
1 | 1 | 0 | 0 | 1 |
由于二进制值中的每个数字要么是1,要么是0(True
或 False
),我们可以将二进制数字用作布尔标志——状态标记,这些标记要么处于开启状态,要么处于关闭状态。一个整数值可以存储多个标志,每个标志使用唯一的二进制数字。每个标志都会根据其设置为1的二进制数字的位置拥有自己的数值。
这就是 Qt 标志的工作原理。再次查看我们的对齐标志,我们现在可以理解为什么选择这些数字——每个标志都是一个唯一的、不重叠的位。标志的值来自标志设置为 1 的二进制位。
Qt.AlignmentFlag.AlignLeft | 1 | 00000001 |
---|---|---|
Qt.AlignmentFlag.AlignRight | 2 | 00000010 |
Qt.AlignmentFlag.AlignHCenter | 4 | 00000100 |
Qt.AlignmentFlag.AlignJustify | 8 | 00001000 |
Qt.AlignmentFlag.AlignTop | 32 | 00100000 |
Qt.AlignmentFlag.AlignBottom | 64 | 01000000 |
Qt.AlignmentFlag.AlignVCenter | 128 | 10000000 |
当直接使用 ==
运算符测试这些标志时,您无需担心这些问题。但这种值的排列方式解锁了将标志组合在一起的能力,以创建复合标志,这些标志同时代表多个状态。这使您能够使用单个标志变量表示,例如,左对齐和底对齐。
位或运算(|
)组合
任何两个二进制表示不重叠的数字都可以相加,同时保持其原有的二进制位不变。例如,下图中我们对1和2进行相加,得到3 —
Table 9. Add
001 | 1 |
---|---|
010 | + 2 |
011 | = 3 |
原始数字中的1位数在输出中得以保留。相比之下,如果我们将1和3相加得到4,原始数字中的1位数在结果中不存在——两者现在都是零。
001 | 1 |
---|---|
011 | + 3 |
100 | = 4 |
在十进制中也能观察到相同的效果——您可以比较将100和50相加得到150,与将161和50相加得到211的情况。
由于我们在特定二进制位置使用1值来表示某种含义,这会造成问题。例如,如果我们将对齐标志的值添加两次,我们将得到一个在数学上完全正确但意义上完全错误的结果。
Table 10. Add
00000001 | 1 | Qt.AlignmentFlag.AlignLeft |
---|---|---|
00000001 | + 1 | + Qt.AlignmentFlag.AlignLeft |
00000010 | = 2 | = Qt.AlignmentFlag.AlignRight |
>>> Qt.AlignmentFlag.AlignLeft + Qt.AlignmentFlag.AlignLeft == Qt.AlignmentFlag.AlignRight
True
因此,在处理二进制标志时,我们使用位或运算将它们组合起来。在 Python 中,位或运算使用 |
(管道)运算符实现。在位或运算中,我们通过在二进制级别比较两个数来将它们组合在一起。结果是一个新数,其中二进制位如果在任一输入中为 1,则设置为 1。但重要的是,位不被传递,也不影响相邻位。
当数字不重叠时,按位或运算等同于加法(+)。
Qt.AlignmentFlag.AlignLeft | 00000001 |
---|---|
Qt.AlignmentFlag.AlignTop | 00100000 |
使用上述两个对齐常量,我们可以将它们的值结合起来,使用位或运算生成输出,以实现顶部左对齐。
Table 11. Bitwise OR
00000001 | 1 | Qt.AlignmentFlag.AlignLeft |
---|---|---|
00100000 | OR 32 | ` |
00100001 | = 33 | `Qt.AlignmentFlag.AlignLeft |
>>> int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
33
因此,如果我们将 32 与 1 相加,结果是 33。这应该不会太令人惊讶。但是,如果我们不小心多次添加 Qt.AlignmentFlag.AlignLeft
,会发生什么情况?
>>> int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
33
我们得到了相同的结果!位或运算在二进制位置上输出1,只要输入中任何一个位置有1。它不会将它们相加,也不会将任何内容进位或溢出到其他位——这意味着您可以多次对同一个值进行位或运算,最终得到的只是您最初的值。
>>> int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft)
1
或者,用二进制表示——
Table 12. Bitwise OR
00000001 | 1 | Qt.AlignmentFlag.AlignLeft |
---|---|---|
00000001 | OR 1 | ` |
00000001 | = 1 | = Qt.AlignmentFlag.AlignLeft |
最后,比较这些值。
>>> Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft == Qt.AlignmentFlag.AlignLeft
True
>>> Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft == Qt.AlignmentFlag.AlignRight
False
检查组合标志
我们可以直接比较标志本身来检查简单的标志,正如我们已经看到的——
>>> align = Qt.AlignmentFlag.AlignLeft
>>> align == Qt.AlignmentFlag.AlignLeft
True
对于组合标志,我们还可以检查与标志组合的相等性
>>> align = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
>>> align == Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
True
但有时,您可能想知道某个变量是否包含特定标志。例如,我们可能想知道 align
是否设置了 align left
标志,而与其他对齐状态无关。
一旦与另一个元素合并后,如何判断一个元素是否应用了 Qt.AlignmentFlag.AlignLeft
属性?在这种情况下,使用 a ==
比较不会生效,因为它们在数值上并不相等。
>> alignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
>> alignment == Qt.AlignmentFlag.AlignLeft # 33 == 1
False
我们需要一种方法来比较 Qt.AlignmentFlag.AlignLeft
标志与我们的复合标志的位。为此,我们可以使用位与运算。
位与运算(&
)检查
在 Python 中,位与运算使用 & 运算符进行。
在上一步骤中,我们结合了 Qt.AlignmentFlag.AlignLeft
(1) 和 Qt.AlignmentFlag.AlignTop
(32) 以生成 “Top Left” (33)。现在,我们需要检查组合后的对齐标志是否设置了左对齐标志。为了进行测试,我们需要使用位与运算,该运算逐位检查两个输入值是否均为 1,如果为真,则在该位置返回 1。
Table 13. Bitwise AND
| 00100001
| 33 | Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
| | ---------- | ----- | -------------------------------------------------------- | | 00100001
| AND 1 | & Qt.AlignmentFlag.AlignLeft
| | 00100001
| = 1 | = Qt.AlignmentFlag.AlignLeft
|
这会过滤输入变量中的位,仅保留那些在目标标志 Qt.AlignmentFlag.AlignLeft
中设置的位。如果该位被设置,结果为非零值;如果未设置,结果为 0。
>>> int(alignment & Qt.AlignmentFlag.AlignLeft)
1 # 结果是标志的数值,这里是1.
例如,如果我们将对齐变量与 Qt.AlignmentFlag.AlignRight
进行测试,结果为 0
| 00100001
| 33 | Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
| | ---------- | ---- | -------------------------------------------------------- | | 00000010
| 2 | & Qt.AlignmentFlag.AlignRight
| | 00000000
| 0 | = Qt.AlignmentFlag.AlignLeft
|
>>> int(alignment & Qt.AlignmentFlag.AlignRight)
0
因为在 Python 中,0 等于 False
,而其他任何值都等于 True
。这意味着,当使用位与运算符对两个数字进行比较时,如果它们有任何位是相同的,结果将大于 0,并且为 True
。
通过结合位运算的或运算和与运算,您应该能够利用Qt标志实现所需的所有功能。