Skip to content

35. 枚举与 Qt 命名空间

当您在应用程序中看到类似以下的代码时,您可能会想知道 Qt.ItemDataRole.DisplayRoleQt.ItemDataRole.CheckStateRole 对象究竟是什么。

tips

在 PyQt 的早期版本中,也存在诸如 Qt.DisplayRole 之类的快捷名称,因此您在代码中仍可能看到这些名称。在 PyQt6 中,您必须始终使用完整名称。本书中的所有示例均使用完全限定名称。

python
    def data(self, role, index):
        
        if role == Qt.ItemDataRole.DisplayRole:
            # 做些什么

Qt 在代码中广泛使用这些类型来表示有意义的常量。其中许多在 Qt 命名空间中可用,即 Qt.<something>,尽管还有一些对象特定的类型,例如 QDialogButtonBox.StandardButton.Ok,它们的工作方式完全相同。

但它们是如何工作的呢?在本章中,我们将详细探讨这些常量是如何形成的,以及如何有效地使用它们。为此,我们需要涉及一些基础知识,如二进制数。但深入理解这些内容并非本章的必要条件——正如往常一样,我们将重点放在如何在学习过程中应用所学知识上。

这不过是一些数字而已

如果您查看一个标志的类型(type()),您会看到一个类的名称。这些类是该标志所属的组。例如,Qt.ItemDataRole.DecorationRole 的类型是 Qt.ItemDataRole—— 您可以在 Qt 文档中看到这些组。

tips

您可以在 Python Shell 中运行以下代码,只需先使用 from PyQt6.QtCore import Qt 导入 Qt 命名空间即可。

bash
>>> type(Qt.ItemDataRole.DecorationRole)
<enum 'ItemDataRole'>

这些类型是枚举类型——一种将值限制为一组预定义值的类型。在 PyQt6 中,它们被定义为 Python 枚举 (Enum) 类型。

这些值实际上都是简单的整数。Qt.ItemDataRole.DisplayRole 的值为 0,而 Qt.ItemDataRole.EditRole 的值为 2。这些整数值本身没有意义,但在它们被使用的特定上下文中具有意义。

bash
>>> int(Qt.ItemDataRole.DecorationRole)
1

例如,您是否认为以下代码会评估为 True?

bash
>>> Qt.ItemDataRole.DecorationRole == Qt.AlignmentFlag.AlignLeft
True

可能不是。但 Qt.ItemDataRole.DecorationRoleQt.AlignmentFlag.AlignLeft 的整数值均为 1,因此在数值上是相等的。这些数值通常可以忽略。只要在适当的上下文中使用这些常量,它们就会始终按预期工作。

Table 8. Values given in the documentation can be in decimal or binary

标识符值(十六进制)值(十进制)描述
Qt.AlignmentFlag.AlignLeft0x00011与左侧对齐
Qt.AlignmentFlag.AlignRight0x00022与右侧对齐
Qt.AlignmentFlag.AlignHCenter0x00044在可用空间中水平居中
Qt.AlignmentFlag.AlignJustify0x00088在可用空间内对齐文本。
Qt.AlignmentFlag.AlignTop0x002032与顶部对齐
Qt.AlignmentFlag.AlignBottom0x004064与底部对齐
Qt.AlignmentFlag.AlignVCenter0x0080128在可用空间中垂直居中
Qt.AlignmentFlag.AlignBaseline0x0100256与基准线对齐

如果您查看上表中的数字,可能会发现一些异常。首先,这些数字并非每次增加1,而是每次翻倍。其次,水平对齐的十六进制数字都集中在同一列,而垂直对齐的数字则分布在另一列。

这种数字模式是故意设计的,它使我们能够做一件非常巧妙的事情——将标志位组合在一起以创建复合标志位。要理解这一点,我们需要快速了解计算机如何表示整数。

二进制与十六进制

当我们进行常规计数时,使用的是十进制(基数为10)的数字系统。该系统包含10个数字,从0到9,且十进制数字中的每个数字的值是其前一个数字的10倍。例如,数字1251由1×1000、2×100、5×10和1×1组成。

1000100101
1251

计算机以二进制形式存储数据,即一系列开和关的状态,以1和0的形式表示。二进制是一种以2为基数的数制。它有2个数字,从0到1,二进制数中的每个数字的值是前一个数字的2倍。在以下示例中,数字5由1×4和1×1组成。

8421十进制
01015

二进制数书写起来很快就会变得繁琐——5893的二进制形式是 1011100000101 ——但将其与十进制数来回转换也并不方便。为了更方便地处理二进制数,十六进制在计算机领域中被广泛使用。这是一种由16个数字(0-9A-F)组成的数制。每个十六进制数字的值在0-15(0-A)之间,相当于4个二进制位。这使得在两者之间进行转换变得直观。

下表显示了数字0-15,以及它们在二进制和十六进制中的对应值。一个给定的二进制数的值可以通过将每列顶部含有1的数字相加来计算。

8421十六进制十进制
000000
000111
001022
001133
010044
010155
011066
011177
100088
100199
1010A10
1011B11
1100C12
1101D13
1110E14
1111F15

这种模式在更大的数字中继续适用。例如,下图是数字25的二进制表示,由16×1、8×1和1×1组成。

168421
11001

由于二进制值中的每个数字要么是1,要么是0(TrueFalse),我们可以将二进制数字用作布尔标志——状态标记,这些标记要么处于开启状态,要么处于关闭状态。一个整数值可以存储多个标志,每个标志使用唯一的二进制数字。每个标志都会根据其设置为1的二进制数字的位置拥有自己的数值。

这就是 Qt 标志的工作原理。再次查看我们的对齐标志,我们现在可以理解为什么选择这些数字——每个标志都是一个唯一的、不重叠的位。标志的值来自标志设置为 1 的二进制位。

Qt.AlignmentFlag.AlignLeft100000001
Qt.AlignmentFlag.AlignRight200000010
Qt.AlignmentFlag.AlignHCenter400000100
Qt.AlignmentFlag.AlignJustify800001000
Qt.AlignmentFlag.AlignTop3200100000
Qt.AlignmentFlag.AlignBottom6401000000
Qt.AlignmentFlag.AlignVCenter12810000000

当直接使用 == 运算符测试这些标志时,您无需担心这些问题。但这种值的排列方式解锁了将标志组合在一起的能力,以创建复合标志,这些标志同时代表多个状态。这使您能够使用单个标志变量表示,例如,左对齐和底对齐。

位或运算(|)组合

任何两个二进制表示不重叠的数字都可以相加,同时保持其原有的二进制位不变。例如,下图中我们对1和2进行相加,得到3 —

Table 9. Add

0011
010+ 2
011= 3

原始数字中的1位数在输出中得以保留。相比之下,如果我们将1和3相加得到4,原始数字中的1位数在结果中不存在——两者现在都是零。

0011
011+ 3
100= 4

information

在十进制中也能观察到相同的效果——您可以比较将100和50相加得到150,与将161和50相加得到211的情况。

由于我们在特定二进制位置使用1值来表示某种含义,这会造成问题。例如,如果我们将对齐标志的值添加两次,我们将得到一个在数学上完全正确但意义上完全错误的结果。

Table 10. Add

000000011Qt.AlignmentFlag.AlignLeft
00000001+ 1+ Qt.AlignmentFlag.AlignLeft
00000010= 2= Qt.AlignmentFlag.AlignRight
bash
>>> Qt.AlignmentFlag.AlignLeft + Qt.AlignmentFlag.AlignLeft == Qt.AlignmentFlag.AlignRight
True

因此,在处理二进制标志时,我们使用位或运算将它们组合起来。在 Python 中,位或运算使用 |(管道)运算符实现。在位或运算中,我们通过在二进制级别比较两个数来将它们组合在一起。结果是一个新数,其中二进制位如果在任一输入中为 1,则设置为 1。但重要的是,位不被传递,也不影响相邻位。

information

当数字不重叠时,按位或运算等同于加法(+)。

Qt.AlignmentFlag.AlignLeft00000001
Qt.AlignmentFlag.AlignTop00100000

使用上述两个对齐常量,我们可以将它们的值结合起来,使用位或运算生成输出,以实现顶部左对齐。

Table 11. Bitwise OR

000000011Qt.AlignmentFlag.AlignLeft
00100000OR 32`
00100001= 33`Qt.AlignmentFlag.AlignLeft
bash
>>> int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
33

因此,如果我们将 32 与 1 相加,结果是 33。这应该不会太令人惊讶。但是,如果我们不小心多次添加 Qt.AlignmentFlag.AlignLeft,会发生什么情况?

bash
>>> int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
33

我们得到了相同的结果!位或运算在二进制位置上输出1,只要输入中任何一个位置有1。它不会将它们相加,也不会将任何内容进位或溢出到其他位——这意味着您可以多次对同一个值进行位或运算,最终得到的只是您最初的值。

bash
>>> int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft)
1

或者,用二进制表示——

Table 12. Bitwise OR

000000011Qt.AlignmentFlag.AlignLeft
00000001OR 1`
00000001= 1= Qt.AlignmentFlag.AlignLeft

最后,比较这些值。

bash
>>> Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft == Qt.AlignmentFlag.AlignLeft
True

>>> Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft == Qt.AlignmentFlag.AlignRight
False

检查组合标志

我们可以直接比较标志本身来检查简单的标志,正如我们已经看到的——

bash
>>> align = Qt.AlignmentFlag.AlignLeft
>>> align == Qt.AlignmentFlag.AlignLeft
True

对于组合标志,我们还可以检查与标志组合的相等性

bash
>>> align = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
>>> align == Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
True

但有时,您可能想知道某个变量是否包含特定标志。例如,我们可能想知道 align 是否设置了 align left 标志,而与其他对齐状态无关。

一旦与另一个元素合并后,如何判断一个元素是否应用了 Qt.AlignmentFlag.AlignLeft 属性?在这种情况下,使用 a == 比较不会生效,因为它们在数值上并不相等。

bash
>> 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。

bash
>>> 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 |

bash
>>> int(alignment & Qt.AlignmentFlag.AlignRight)
0

因为在 Python 中,0 等于 False,而其他任何值都等于 True。这意味着,当使用位与运算符对两个数字进行比较时,如果它们有任何位是相同的,结果将大于 0,并且为 True

通过结合位运算的或运算和与运算,您应该能够利用Qt标志实现所需的所有功能。