Wednesday, August 7, 2019

Learn PyQt: PyQt5 Widgets Overview

In Qt (and most User Interfaces) ‘widget’ is the name given to a component of the UI that the user can interact with. User interfaces are made up of multiple widgets, arranged within the window.

Qt comes with a large selection of widgets available, and even allows you to create your own custom and customised widgets.

Load up a fresh copy of MyApp_window.py and save it under a new name for this section.

Big ol' list of widgets

A full list of widgets is available on the Qt documentation, but let’s have a look at them quickly in action.

python
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

# Only needed for access to command line arguments
import sys


# Subclass QMainWindow to customise your application's main window
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")
        

        layout = QVBoxLayout()
        widgets = [QCheckBox,
            QComboBox,
            QDateEdit,
            QDateTimeEdit,
            QDial,
            QDoubleSpinBox,
            QFontComboBox,
            QLCDNumber,
            QLabel,
            QLineEdit,
            QProgressBar,
            QPushButton,
            QRadioButton,
            QSlider,
            QSpinBox,
            QTimeEdit]
        
        for w in widgets:
            layout.addWidget(w())
            
        
        widget = QWidget()
        widget.setLayout(layout)
        
        # Set the central widget of the Window. Widget will expand
        # to take up all the space in the window by default.
        self.setCentralWidget(widget)


# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) works too.
app = QApplication(sys.argv)

window = MainWindow()
window.show() # IMPORTANT!!!!! Windows are hidden by default.

# Start the event loop.
app.exec_()


# Your application won't reach here until you exit and the event 
# loop has stopped.
python
from PySide2.QtWidgets import *
from PySide2.QtCore import *
from PySide2.QtGui import *

# Only needed for access to command line arguments
import sys


# Subclass QMainWindow to customise your application's main window
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")
        

        layout = QVBoxLayout()
        widgets = [QCheckBox,
            QComboBox,
            QDateEdit,
            QDateTimeEdit,
            QDial,
            QDoubleSpinBox,
            QFontComboBox,
            QLCDNumber,
            QLabel,
            QLineEdit,
            QProgressBar,
            QPushButton,
            QRadioButton,
            QSlider,
            QSpinBox,
            QTimeEdit]
        
        for w in widgets:
            layout.addWidget(w())
            
        
        widget = QWidget()
        widget.setLayout(layout)
        
        # Set the central widget of the Window. Widget will expand
        # to take up all the space in the window by default.
        self.setCentralWidget(widget)


# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) works too.
app = QApplication(sys.argv)

window = MainWindow()
window.show() # IMPORTANT!!!!! Windows are hidden by default.

# Start the event loop.
app.exec_()


# Your application won't reach here until you exit and the event 
# loop has stopped.

To do this we’re going to take the skeleton of our application and replace the QLabel with a QWidget. This is the generic form of a Qt widget.

Here we’re not using it directly. We apply a list of widgets - in a layout, which we will cover shortly - and then add the QWidget as the central widget for the window. The result is that we fill the window with widgets, with the QWidget acting as a container.

Big ol' list of widgets. Big ol' list of widgets.

It’s possible to use this QWidget layout trick to create custom compound widgets. For example you can take a base QWidget and overlay a layout containing multiple widgets of different types. This 'widget' can then be inserted into other layouts as normal. We'll cover custom widgets in more detail later.

Lets have a look at all the example widgets, from top to bottom:

Widget What it does
QCheckbox A checkbox
QComboBox A dropdown list box
QDateEdit For editing dates and datetimes
QDateTimeEdit For editing dates and datetimes
QDial Rotateable dial
QDoubleSpinbox A number spinner for floats
QFontComboBox A list of fonts
QLCDNumber A quite ugly LCD display
QLabel Just a label, not interactive
QLineEdit Enter a line of text
QProgressBar A progress bar
QPushButton A button
QRadioButton A toggle set, with only one active item
QSlider A slider
QSpinBox An integer spinner
QTimeEdit For editing times

There are actually more widgets than this, but they don’t fit so well! You can see them all by checking the documentation. Here we’re going to take a closer look at the a subset of the most useful.

QLabel

We'll start the tour with QLabel, arguably one of the simplest widgets available in the Qt toolbox. This is a simple one-line piece of text that you can position in your application. You can set the text by passing in a str as you create it:

widget = QLabel("Hello")

Or, by using the .setText() method:

widget = QLabel("1")  # The label is created with the text 1.
widget.setText("2")   # The label now shows 2.

You can also adjust font parameters, such as the size of the font or the alignment of text in the widget.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")
        
        widget = QLabel("Hello")
        font = widget.font()
        font.setPointSize(30)
        widget.setFont(font)
        widget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        
        self.setCentralWidget(widget)

Font tip Note that if you want to change the properties of a widget font it is usually better to get the current font, update it and then apply it back. This ensures the font face remains in keeping with the desktop conventions.

The alignment is specified by using a flag from the Qt. namespace. The flags available for horizontal alignment are:

Flag Behaviour
Qt.AlignLeft Aligns with the left edge.
Qt.AlignRight Aligns with the right edge.
Qt.AlignHCenter Centers horizontally in the available space.
Qt.AlignJustify Justifies the text in the available space.

The flags available for vertical alignment are:

Flag Behaviour
Qt.AlignTop Aligns with the top.
Qt.AlignBottom Aligns with the bottom.
Qt.AlignVCenter Centers vertically in the available space.

You can combine flags together using pipes (|), however note that you can only use vertical or horizontal alignment flag at a time.

align_top_left = Qt.AlignLeft | Qt.AlignTop

Note that you use an OR pipe (`|`) to combine the two flags (not A & B). This is because the flags are non-overlapping bitmasks. e.g. Qt.AlignLeft has the hexadecimal value 0x0001, while Qt.AlignBottom is 0x0040.

By ORing together we get the value 0x0041 representing 'bottom left'. This principle applies to all other combinatorial Qt flags.

If this is gibberish to you, feel free to ignore and move on. Just remember to use |

Finally, there is also a shorthand flag that centers in both directions simultaneously:

Flag Behaviour
Qt.AlignCenter Centers horizontally and vertically

Weirdly, you can also use QLabel to display an image using .setPixmap(). This accepts an pixmap, which you can create by passing an image filename to QPixmap. In the example files provided with this book you can find a file otje.jpg which you can display in your window as follows:

widget.setPixMap(QPixmap('otje.jpg'))
Otgon Otgon "Otje" Ginge the cat.

What a lovely face. By default the image scales while maintaining its aspect ratio. If you want it to stretch and scale to fit the window completely you can set .setScaledContents(True) on the QLabel.

widget.setScaledContents(True)

QCheckBox

The next widget to look at is QCheckBox() which, as the name suggests, presents a checkable box to the user. However, as with all Qt widgets there are number of configurable options to change the widget behaviours.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")
        
        widget = QCheckBox()
        widget.setCheckState(Qt.Checked)

        # For tristate: widget.setCheckState(Qt.PartiallyChecked)
        # Or: widget.setTriState(True)
        widget.stateChanged.connect(self.show_state)

        self.setCentralWidget(widget)
        
        
    def show_state(self, s):
        print(s == Qt.Checked)
        print(s)

You can set a checkbox state programmatically using .setChecked or .setCheckState. The former accepts either True or False representing checked or unchecked respectively. However, with .setCheckState you also specify a particular checked state using a Qt. namespace flag:

Flag Behaviour
Qt.Unchecked Item is unchecked
Qt.PartiallyChecked Item is partially checked
Qt.Checked Item is unchecked

A checkbox that supports a partially-checked (Qt.PartiallyChecked) state is commonly referred to as 'tri-state', that is being neither on nor off. A checkbox in this state is commonly shown as a greyed out checkbox, and is commonly used in hierarchical checkbox arrangements where sub-items are linked to parent checkboxes.

If you set the value to Qt.PartiallyChecked the checkbox will become tristate. You can also .setTriState(True) to set tristate support on a You can also set a checkbox to be tri-state without setting the current state to partially checked by using .setTriState(True)

You may notice that when the script is running the current state number is displayed as an int with checked = 2, unchecked = 0, and partially checked = 1. You don’t need to remember these values, the Qt.Checked namespace variable == 2 for example. This is the value of these state's respective flags. This means you can test state using state == Qt.Checked.

QComboBox

The QComboBox is a drop down list, closed by default with an arrow to open it. You can select a single item from the list, with the currently selected item being shown as a label on the widget. The combo box is suited to selection of a choice from a long list of options.

You have probably seen the combo box used for selection of font faces, or size, in word processing applications. Although Qt actually provides a specific font-selection combo box as QFontComboBox.

You can add items to a QComboBox by passing a list of strings to .addItems(). Items will be added in the order they are provided.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")
        
        widget = QComboBox()
        widget.addItems(["One", "Two", "Three"])

        # The default signal from currentIndexChanged sends the index
        widget.currentIndexChanged.connect( self.index_changed )
        
        # The same signal can send a text string
        widget.currentIndexChanged[str].connect( self.text_changed )

        self.setCentralWidget(widget)


    def index_changed(self, i): # i is an int
        print(i)
        
    def text_changed(self, s): # s is a str
        print(s)

The .currentIndexChanged signal is triggered when the currently selected item is updated, by default passing the index of the selected item in the list. However, when connecting to the signal you can also request an alternative version of the signal by appending [str] (think of the signal as a dict). This alternative interface instead provides the label of the currently selected item, which is often more useful.

QComboBox can also be editable, allowing users to enter values not currently in the list and either have them inserted, or simply used as a value. To make the box editable:

widget.setEditable(True)

You can also set a flag to determine how the insert is handled. These flags are stored on the QComboBox class itself and are listed below:

Flag Behaviour
QComboBox.NoInsert No insert
QComboBox.InsertAtTop Insert as first item
QComboBox.InsertAtCurrent Replace currently selected item
QComboBox.InsertAtBottom Insert after last item
QComboBox.InsertAfterCurrent Insert after current item
QComboBox.InsertBeforeCurrent Insert before current item
QComboBox.InsertAlphabetically Insert in alphabetical order

To use these, apply the flag as follows:

widget.setInsertPolicy(QComboBox.InsertAlphabetically)

You can also limit the number of items allowed in the box by using .setMaxCount, e.g.

widget.setMaxCount(10)

QListBox

Next QListBox. It’s very similar to QComboBox, differing mainly in the signals available.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")
        
        widget = QListWidget()
        widget.addItems(["One", "Two", "Three"])

        # In QListWidget there are two separate signals for the item, and the str
        widget.currentItemChanged.connect( self.index_changed )
        widget.currentTextChanged.connect( self.text_changed )

        self.setCentralWidget(widget)
        
        
    def index_changed(self, i): # Not an index, i is a QListItem
        print(i.text())
        
    def text_changed(self, s): # s is a str
        print(s)

QListBox offers an currentItemChanged signal which sends the QListItem (the element of the list box), and a currentTextChanged signal which sends the text.

QLineEdit

The QLineEdit widget is a simple single-line text editing box, into which users can type input. These are used for form fields, or settings where there is no restricted list of valid inputs. For example, when entering an email address, or computer name.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        
        self.setWindowTitle("My Awesome App")
        
        widget = QLineEdit()
        widget.setMaxLength(10)
        widget.setPlaceholderText("Enter your text")

        #widget.setReadOnly(True) # uncomment this to make readonly
        
        widget.returnPressed.connect(self.return_pressed)
        widget.selectionChanged.connect(self.selection_changed)
        widget.textChanged.connect(self.text_changed)
        widget.textEdited.connect(self.text_edited)

        self.setCentralWidget(widget)
        
        
    def return_pressed(self):
        print("Return pressed!")
        self.centralWidget().setText("BOOM!")

    def selection_changed(self):
        print("Selection changed")
        print(self.centralWidget().selectedText())
        
    def text_changed(self, s):
        print("Text changed...")
        print(s)
            
    def text_edited(self, s):
        print("Text edited...")
        print(s)

As demonstrated in the above code, you can set a maximum length for the text in a line edit.

Continue reading: “Dialogs and Alerts”



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...