• Programming
  • Tutorial
  • System Administration
  • Gadgets
  • Phones
  • Web development
Friday, July 1, 2022
  • Login
No Result
View All Result
SkilledRoom
No Result
View All Result
SkilledRoom
No Result
View All Result
Home Tutorial

Python Desktop Application: UI and Signals

December 3, 2021
in Tutorial
Share on FacebookShare on Twitter

It is considered that Python is not the best choice for desktop applications. However, when I was about to move from website development to software in 2016, Google told me that you can build complex modern applications in Python. For example blender3d, which is written in Python.

But people, through no fault of their own, use ugly examples of GUIs that look too old and will not appeal to young people. I hope to change this opinion in my tutorial. Let’s start.

We’ll be using PyQt (pronounced “Pie Quote”). It is a Qt framework ported from C ++. Qt is known for being essential for C ++ developers. Blender3d, Tableau, Telegram, Anaconda Navigator, Ipython, Jupyter Notebook, VirtualBox, VLC and others are made with this framework. We’ll use it instead of the depressing Tkinter.

Requirements

  1. You should know the basics of Python
  2. You should know how to install packages and libraries using pip .
  3. You must have Python installed.

Installation

You only need to install PyQt. Open a terminal and enter the command:

>>> pip install PyQt5

We will be using PyQt version 5.15. Wait until the installation is complete, it will take a couple of minutes.

Hello, World!

Create a project folder, we’ll call it helloApp. Open your main.py file, it is better to do this vscode, and enter the following code:

import sys

from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine

app = QGuiApplication(sys.argv)

engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('./UI/main.qml')

sys.exit(app.exec())

This code calls QGuiApplication and  QQmlApplicationEngine which use Qml instead of QtWidget as the UI layer in the Qt application. Next, we attach the UI exit function to the main exit function of the application. They will now both close at the same time when the user clicks exit. Then, load the qml file for the Qml UI. The call to app.exec () starts the application, it sits inside sys.exit because it returns an exit code that is passed to sys.exit.

Add this code to main.qml:

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 600
    height: 500
    title: "HelloApp"

    Text {
        anchors.centerIn: parent
        text: "Hello, World"
        font.pixelSize: 24
    }

}

This code creates a window, makes it visible, with the specified dimensions and title. The Text object is displayed in the middle of the window.

Now let’s run the application:

>>> python main.py

You will see a window like this:

UI update

Let’s update the UI a bit, add a background image and time:

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 400
    height: 600
    title: "HelloApp"
    
Rectangle {
        anchors.fill: parent
        
Image {
            sourceSize.width: parent.width
            sourceSize.height: parent.height
            source: "./images/playas.jpg"
            fillMode: Image.PreserveAspectCrop
        }
        
Rectangle {
            anchors.fill: parent
            color: "transparent"
            Text {
                text: "16:38:33"
                font.pixelSize: 24
                color: "white"
            }
        }
    }
}

Inside the ApplicationWindow type is the content of the window, the Rectangle type fills the window space. Inside it is the Image type and another transparent Rectangle that will be displayed over the image.

If you start the application now, the text will appear in the upper left corner. But we want the bottom left corner, so we use padding:

Text {
                anchors {
                    bottom: parent.bottom
                    bottomMargin: 12
                    left: parent.left
                    leftMargin: 12
                }
                text: "16:38:33"
                font.pixelSize: 24
                ...
            }

Once launched, you will see the following:

Show the current time

The gmtime module allows you to use a structure over time, and strftime allows you to convert it to a string. We import them:

import sys
from time import strftime, gmtime

Now we can get a string with the current time:

curr_time = strftime("%H:%M:%S", gmtime())

The string "%H:%M:%S"means that we will get the time in 24 hour format, with hours, minutes and seconds ( more about strtime ).

Let’s create a property in a qml file to store the time. We’ll call it currTime.

property string currTime: "00:00:00"

Now let’s replace the text with our variable:

Text {
    ...
    text: currTime  // used to be; text: "16:38:33"
    font.pixelSize: 48
    color: "white"
}

Now, let’s pass the curr_time variable from pyhton to qml:

engine.load('./UI/main.qml')
engine.rootObjects()[0].setProperty('currTime', curr_time)

This is one of the ways to transfer information from Python to UI.

Run the application and you will see the current time.

Time update

In order to update the time, we need to use streams. For this, I suggest using signals.

To use signals we need a QObject subclass. Let’s call it Backend.

...
from PyQt5.QtCore import QObject, pyqtSignal

class Backend(QObject):
    def __init__(self):
        QObject.__init__(self)
...

We already have properties for the string with curr_time, now let’s create a backend property of type QtObject in the main.qml file.

property string currTime: "00:00:00"
property QtObject backend

Let’s transfer data from Python to qml:

engine.load('./UI/main.qml')
back_end = Backend()
engine.rootObjects()[0].setProperty('backend', back_end)

In a qml file, a single QtObject can receive multiple functions (called signals) from Python.

Let’s create a Connections type and specify the backend in its target. Now inside this type there can be as many functions as we need to get in the backend.

...
Rectangle {
    anchors.fill: parent
    Image {
    ...
    }
    ...
}
Connections {
    target: backend
}
...

This will link qml and signals from Python.

We use streams to ensure that the UI is up to date. Let’s create two functions, one for managing flows and one for performing actions. It is good practice to use _ in the name of one of the functions.

...
import threading
from time import sleep
...
class Backend(QObject):

    def __init__(self):
        QObject.__init__(self)
    def bootUp(self):
        t_thread = threading.Thread(target=self._bootUp)
        t_thread.daemon = True
        t_thread.start()
    def _bootUp(self):
        while True:
            curr_time = strftime("%H:%M:%S", gmtime())
            print(curr_time)
            sleep(1)
...

Create pyqtsignal and name it updated, then call it from the updater function.

...
from PyQt5.QtCore import QObject, pyqtSignal
...
    def __init__(self):
        QObject.__init__(self)
    updated = pyqtSignal(str, arguments=['updater'])
    def updater(self, curr_time):
        self.updated.emit(curr_time)
    ...

In this code, updated has an arguments parameter, which is a list containing the function name “updater”. Qml will receive data from this function. In the updater function, we call the emit method and pass the time data to it.

Let’s update qml, having received a signal, using a handler, the name of which consists of “on” and the name of the signal:

    target: backend
    function onUpdated(msg) {
        currTime = msg;
    }

Now we just need to call the updater function. In our small application, it is not necessary to use a separate function to trigger the signal. But it is recommended to do this in large programs. Change the delay to one tenth of a second.

curr_time = strftime("%H:%M:%S", gmtime())
            self.updater(curr_time)
            sleep(0.1)

The bootUp function should be called immediately after loading the UI:

engine.rootObjects()[0].setProperty('backend', back_end)
back_end.bootUp()
sys.exit(app.exec())

All is ready

Now you can start the program. The time will update correctly. To remove the border, you can add the following line to the qml file:

flags: Qt.FramelessWindowHint | Qt.Window

This is how the main.py file should look like:

import sys
from time import strftime, gmtime
import threading
from time import sleep
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import QObject, pyqtSignal

class Backend(QObject):

    def __init__(self):
        QObject.__init__(self)
    updated = pyqtSignal(str, arguments=['updater'])
    def updater(self, curr_time):
        self.updated.emit(curr_time)
    def bootUp(self):
        t_thread = threading.Thread(target=self._bootUp)
        t_thread.daemon = True
        t_thread.start()
    def _bootUp(self):
        while True:
            curr_time = strftime("%H:%M:%S", gmtime())
            self.updater(curr_time)
            sleep(0.1)

app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('./UI/main.qml')
back_end = Backend()
engine.rootObjects()[0].setProperty('backend', back_end)
back_end.bootUp()
sys.exit(app.exec())

Here is the content of the main.qml file:

import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
    visible: true
    width: 360
    height: 600
    x: screen.desktopAvailableWidth - width - 12
    y: screen.desktopAvailableHeight - height - 48
    title: "HelloApp"
    flags: Qt.FramelessWindowHint | Qt.Window
    property string currTime: "00:00:00"
    property QtObject backend
    Rectangle {
        anchors.fill: parent
        Image {
            sourceSize.width: parent.width
            sourceSize.height: parent.height
            source: "./images/playas.jpg"
            fillMode: Image.PreserveAspectFit
        }
        Text {
            anchors {
                bottom: parent.bottom
                bottomMargin: 12
                left: parent.left
                leftMargin: 12
            }
            text: currTime
            font.pixelSize: 48
            color: "white"
        }
    }

    Connections {
        target: backend
        function onUpdated(msg) {
            currTime = msg;
        }
    }
}

Build the application

To build a Python desktop application, we need pyinstaller.

>>> pip install pyinstaller

To add all the necessary resources to the assembly, create a spec file:

>>> pyi-makespec main.py

Spec file settings

The datas parameter can be used to include the file in the application. This is a list of tuples, each of which must have a target path (where to get the files from) and a destination path (where the application will be located). destination path must be relative. Use an empty string to place all resources in the same folder with exe-files.

Change the datas parameter to the path to your UI folder:

a = Analysis(['main.py'],
             ...
             datas=[('I:/path/to/helloApp/UI', 'UI')],
             hiddenimports=[],
...
exe = EXE(pyz,
          a.scripts,
          [],
          ...
          name='main',
          debug=False,
          ...
          console=False )
coll = COLLECT(exe,
               ...
               upx_exclude=[],
               name='main')

Let’s set the console parameter to false, because we do not have a console application.

The name parameter inside the call to Exe is the name of the executable file. name inside the Collect call, this is the name of the folder in which the finished application will appear. The names are created based on the file for which we created the spec – main.py.

Now you can start the build:

>>> pyinstaller main.spec

The main folder appears in the dist folder. To start the program, just run the main.exe file.

This is how the contents of the folder with the desktop application in Python will look:

Tags: Python
Previous Post

Python vs C ++ speed comparison

Next Post

Pip: how to install packages in Python

Related Posts

Best IDEs and Code Editors for Python

Best IDEs and Code Editors for Python

by skilled
December 2, 2021
0

Writing in Python using IDLE or the Python Shell is fine when it comes to simple things, but as projects...

23 sites for hacking practice

23 sites for hacking practice

by skilled
December 2, 2021
0

For newbies who don't know where to start, we present a selection of sites where you can acquire and improve...

What does a “white hacker” do? 10 Questions About Vulnerability Searchers

What does a “white hacker” do? 10 Questions About Vulnerability Searchers

by skilled
December 2, 2021
0

As polls show, hackers are most often looking not for benefits and easy money, but want to test their strengths,...

Next Post
Pip: how to install packages in Python

Pip: how to install packages in Python

40 JavaScript Techniques You Should Know

40 JavaScript Techniques You Should Know

5 Ways to Reduce JavaScript Package Size

5 Ways to Reduce JavaScript Package Size

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • Home
  • About Us
  • Advertise
  • Privacy & Policy
  • Contact Us

© 2017 JNews - Premium WordPress news & magazine theme by Jegtheme.

No Result
View All Result
  • Programming
  • Tutorial
  • System Administration
  • Gadgets
  • Phones
  • Web development

© 2017 JNews - Premium WordPress news & magazine theme by Jegtheme.

Welcome Back!

Login to your account below

Forgotten Password?

Create New Account!

Fill the forms below to register

All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In