본문 바로가기
PyQt5_

Enums & the Qt Namespace

by 자동매매 2023. 3. 13.

35. Enums & the Qt Namespace

When you see a line like the following in your application, you might have wondered what the Qt.ItemDataRole.DisplayRole or Qt.ItemDataRole.CheckStateRole objects actually are.

ë

In earlier versions of PyQt there were also shortcut names such

as Qt.DisplayRole, so you may still see these in code. In PyQt6

you must always use the long-form. All examples in this book

use the fully-qualified names.

def data (self, role, index):

if role == Qt.ItemDataRole.DisplayRole:

# do something

Qt makes use of these types extensively for meaningful constants in code. Many of them are available in the Qt namespace , that is as Qt., although there are object-specific types such as QDialogButtonBox.StandardButton.Ok which work in exactly the same way.

But how do they work? In this chapter we’ll take a close look at how these constants are formed and how to work with them effectively. To do that we’ll need to touch on some fundamentals like binary numbers. But understanding these deeply isn’t necessary to get something from this chapter — as always we’ll focus on how we can apply things as we learn them.

It’s all just numbers

If you check the type() of a flag you’ll see the name of a class. These classes are the group that a given flag belongs to. For example, Qt.ItemDataRole.DecorationRole is of type Qt.ItemDataRole — you can see these groups in the Qt documentation.

ë

You can run the following code in a Python shell, just import the

Qt namespace first with from PyQt6.QtCore import Qt.

>>> type(Qt.ItemDataRole.DecorationRole)

<enum 'itemdatarole'="">

These types are enums — a type which restricts its values to a set of predefined values. In PyQt6 they are defined as Python Enum types.

Each of these values is actually a simple integer number. The value of Qt.ItemDataRole.DisplayRole is 0, while Qt.ItemDataRole.EditRole has a value of 2. The integer values themselves are meaningless but have a meaning in the particular context in which they are used.

>>> int(Qt.ItemDataRole.DecorationRole)

1

For example, would you expect the following to evaluate to True?

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

True

Probably not. But both Qt.ItemDataRole.DecorationRole and Qt.AlignmentFlag.AlignLeft have an integer value of 1 and so are numerically equal. These numeric values can usually be ignored. As long as you use the constants in their appropriate context they will always work as expected.

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

Identifier Value (hex) Value (decimal) Description

Qt.AlignmentFlag.A

lignLeft

0x0001 1 Aligns with the left

edge.

Identifier Value (hex) Value (decimal) Description

Qt.AlignmentFlag.A

lignRight

0x0002 2 Aligns with the

right edge.

Qt.AlignmentFlag.A

lignHCenter

0x0004 4 Centers

horizontally in the

available space.

Qt.AlignmentFlag.A

lignJustify

0x0008 8 Justifies the text in

the available

space.

Qt.AlignmentFlag.A

lignTop

0x0020 32 Aligns with the top.

Qt.AlignmentFlag.A

lignBottom

0x0040 64 Aligns with the

bottom.

Qt.AlignmentFlag.A

lignVCenter

0x0080 128 Centers vertically

in the available

space.

Qt.AlignmentFlag.A

lignBaseline

0x0100 256 Aligns with the

baseline.

If you look at the numbers in the table above you may notice something odd. Firstly, they don’t increase by 1 for each constant, but double each time. Secondly, the horizontal alignment hex numbers are all in one column, while the vertical alignment numbers are in another.

This pattern of numbers is intentional and it allows us to do something very neat — combine flags together to create compound flags. To understand this we’ll need to take a quick look at how integer numbers are represented by a computer.

Binary & Hexadecimal

When we count normally we use decimal a base-10 number system. It has 10

digits, from 0-9 and each digit in a decimal number is worth 10x that which preceded it. In the following example, our number 1251 is made up of 1x1000, 2x100, 5x10 and 1x1.

1000 100 10 1

1 2 5 1

Computers store data in binary, a series of on and off states represented in written form as 1s and 0s. Binary is a base-2 number system. It has 2 digits, from 0-1 and each digit in a binary number is worth 2x that which preceded it. In the following example, our number 5 is made up of 1x4 and 1x1.

8 4 2 1 Decimal

0 1 0 1 5

Writing binary numbers gets cumbersome quickly — 5893 in binary is 1011100000101 — but converting back and forward to decimal is not much better. To make it easier to work with binary numbers hexadecimal is frequently used in computing. This is a numeric system with 16 digits (0-9A-F). Each hexadecimal digit has a value between 0-15 (0-A) equivalent to 4 binary digits. This makes it straightforward to convert between the two.

The table below shows the numbers 0-15, together with the same value in binary and hexadecimal. The value of a given binary number can be calculated by adding up the numbers at the top of each column with a 1 in them.

8 4 2 1 Hex Dec

0 0 0 0 0 0

0 0 0 1 1 1

0 0 1 0 2 2

0 0 1 1 3 3

8 4 2 1 Hex Dec

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

This pattern continues for higher numbers. For example, below is the number 25 in binary, constructed from 16 x 1, 8 x 1 and 1 x 1.

16 8 4 2 1

1 1 0 0 1

Because each digit in a binary value is either a 1 or a 0 (True or False) we can use individual binary digits as boolean flags — state markers which are either on or off. A single integer value can store multiple flags, using unique binary digits for each. Each of these flags would have their own numerical value based on the position of the binary digit they set to 1.

That is exactly how the Qt flags work. Looking at our alignment flags again, we can now see why the numbers were chosen — each flag is a unique non- overlapping bit. The values of the flags come from the binary digit the flag has set

to 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

When testing these flags directly with == you don’t need to worry about all this. But this arrangement of values unlocks the ability to combine the flags together to create compound flags which represent more than one state at the same time. This allows you to have a single flag variable representing, for example, left & bottom aligned.

Bitwise OR ( | ) combination

Any two numbers, with non-overlapping binary representations can be added together while leaving their original binary digits in place. For example, below we add 1 and 2 together, to get 3 —

Table 9. Add

(^0011) (^010) + 2 (^011) = 3 The 1 digits in the original numbers are preserved in the output. In contrast, if we add together 1 and 3 to get 4, the 1 digits of the original numbers are not in the result — both are now zero.

(^0011) (^011) + 3 (^100) = 4 Z You can see the same effect in decimal — compare adding 100 and 50 to give 150 vs. adding 161 and 50 to give 211. Since we’re using 1 values in specific binary positions to mean something, this poses a problem. For example, if we added the value of an alignment flag twice, we would get something else both entirely right (mathematically) and entirely wrong (in meaning). 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 For this reason, when working with binary flags we combine them using a bitwise OR — which is performed in Python using the | (pipe) operator. In a bitwise OR you combine two numbers together by comparing them at the binary level. The result is a new number, where binary digits are set to 1 if they were 1 in either of the inputs. But importantly, digits are not carried and do not affect adjacent digits. Z When you have non-overlapping digits bitwise OR is the same as

add (+).

Qt.AlignmentFlag.A

lignLeft

00000001

Qt.AlignmentFlag.A

lignTop

00100000

Taking the two alignment constants above, we can combine their values together using a bitwise OR to produce the output to give align top left.

Table 11. Bitwise OR

00000001 1 Qt.AlignmentFlag.AlignLeft

(^00100000) OR 32 | Qt.AlignmentFlag.AlignTop (^00100001) = 33 Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop

int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) 33 So, if we combine 32 with 1 we get 33. This should hopefully not be too surprising. But what if we accidentally add Qt.AlignmentFlag.AlignLeft multiple times? int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft | Qt .AlignmentFlag.AlignTop) 33 The same result! The bitwise OR outputs a 1 in a binary position if there is a 1 in any of the inputs. It doesn’t add them up, carry or overflow anything into other digits — meaning you can | the same value together multiple times and you just end up with what you started with.

>>> int(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft | Qt

.AlignmentFlag.AlignLeft)

1

Or, in binary —

Table 12. Bitwise OR

00000001 1 Qt.AlignmentFlag.AlignLeft

(^00000001) OR 1 | Qt.AlignmentFlag.AlignLeft (^00000001) = 1 = Qt.AlignmentFlag.AlignLeft And finally, comparing the values.

Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft == Qt .AlignmentFlag.AlignLeft True Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignLeft == Qt .AlignmentFlag.AlignRight False Checking compound flags We can check simple flags by comparing against the flag itself, as we’ve already seen — align = Qt.AlignmentFlag.AlignLeft align == Qt.AlignmentFlag.AlignLeft True For combined flags we can also check equality with the combination of flags —

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

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

True

But sometimes, you want to know if a given variable contains a specific flag. For example, perhaps we want to know if align has the align left flag set, regardless of any other alignment state.

How can we check that an element has Qt.AlignmentFlag.AlignLeft applied, once it’s been combined with another? In this case a == comparison will not work, since they are not numerically equal.

>> alignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop

>> alignment == Qt.AlignmentFlag.AlignLeft # 33 == 1

False

We need a way to compare the Qt.AlignmentFlag.AlignLeft flag against the bits of our compound flag. For this we can use a bitwise AND.

Bitwise AND ( & ) checks

In Python, bitwise AND operations are performed using the & operator.

In the previous step we combined together Qt.AlignmentFlag.AlignLeft (1) and Qt.AlignmentFlag.AlignTop (32) to produce "Top Left" (33). Now we want to check if the resulting combined flag has the align left flag set. To test we need to use bitwise AND which checks bit by bit to see if both input values are 1, returning a 1 in that place if it is true.

Table 13. Bitwise AND

00100001 33 Qt.AlignmentFlag.AlignLeft |

Qt.AlignmentFlag.AlignTop

(^00000001) AND 1 & Qt.AlignmentFlag.AlignLeft

(^00000001) = 1 = Qt.AlignmentFlag.AlignLeft This has the effect of filtering the bits in our input variable to only those that are set in our target flag Qt.AlignmentFlag.AlignLeft. If this one bit is set, the result is non-zero, if it is unset the result is 0.

int(alignment & Qt.AlignmentFlag.AlignLeft) 1 # result is the numerical value of the flag, here 1. For example, if we tested our alignment variable against Qt.AlignmentFlag.AlignRight the result is 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 Because in Python 0 is equal to False and any other value is True. This means that when testing two numbers against one another with bitwise AND, if any bits are in common the result will be > 0, and be True. With a combination of bitwise OR and AND you should be able to achieve everything you need with the Qt flags.

'PyQt5_' 카테고리의 다른 글

Packaging with PyInstaller  (0) 2023.03.13
Working with command-line arguments  (0) 2023.03.13
System tray & macOS menus  (0) 2023.03.13
Working with Relative Paths  (0) 2023.03.13
Extending Signals  (0) 2023.03.13

댓글