SoFunction
Updated on 2025-04-23

A complete guide to the development of cross-platform desktop application

introduction

In today's software development, cross-platform application development is becoming increasingly important. Users hope to get a consistent application experience regardless of whether they use Windows, macOS or Linux systems. As a high-level programming language, Python has become an ideal choice for cross-platform desktop application development with its concise syntax and rich library ecosystem. This article will explore the main frameworks, tools, and best practices for cross-platform desktop application development using Python.

Overview of Python Cross-Platform Development

Python's "write once, run everywhere" feature makes it ideal for cross-platform development. The Python interpreter can run on all mainstream operating systems, which means that Python code can be executed on different platforms without modification. However, creating a true cross-platform desktop application requires the use of dedicated GUI frameworks and tools.

The main challenges of cross-platform development include:

  • Ensure a consistent user interface appearance and experience
  • Handle file system differences between different operating systems
  • Manage platform-specific features and APIs
  • Optimize performance on different platforms
  • Simplify the packaging and distribution process of applications

Python provides a variety of solutions to address these challenges through its rich library ecosystem.

Mainstream Python desktop application framework

Framework comparison

frame advantage shortcoming Applicable scenarios
PyQt/PySide Rich features, native appearance, powerful tool support Steep learning curve, commercial licensing may be charged Enterprise-level applications, complex UI
Tkinter Python standard library comes with it, easy to learn UI components are limited, with a relatively basic appearance Simple tools, fast prototypes
Kivy Supports touch interface, strong cross-platform capabilities Non-native appearance, medium learning curve Multi-platform application, touch interface
wxPython Native appearance, rich features Relatively few documents, updates are not as frequent as other frameworks Applications that require native appearance
DearPyGui High performance, simple and intuitive Relatively new, smaller community Data visualization, simple tools

Choosing the right framework depends on project requirements, development team experience, and target platform. The following will introduce the characteristics and usage methods of each framework in detail.

Detailed explanation of PyQt/PySide

PyQt and PySide (Qt for Python) are Python bindings based on the Qt framework, providing rich UI components and functions.

Installation and Setup

# Install PyQt5pip install PyQt5

# Or install PySide2pip install PySide2

# Qt6 versionpip install PyQt6
# orpip install PySide6

Basic application structure

import sys
from  import QApplication, QMainWindow, QLabel, QPushButton, QVBoxLayout, QWidget

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        ("PyQt Sample Application")
        (100, 100, 400, 300)
        
        # Create central components and layouts        central_widget = QWidget()
        (central_widget)
        layout = QVBoxLayout(central_widget)
        
        # Add tags        label = QLabel("Welcome to use PyQt cross-platform applications!")
        (label)
        
        # Add button        button = QPushButton("Click Me")
        (self.on_button_clicked)
        (button)
    
    def on_button_clicked(self):
        print("The button was clicked!")

if __name__ == "__main__":
    app = QApplication()
    window = MainWindow()
    ()
    (app.exec_())

The main features of PyQt/PySide

Rich UI components: Provides more than 200 UI classes, from basic buttons, labels to advanced tables, tree views, etc.

Signal and slot mechanism: Qt's signal-slot mechanism allows loosely coupled communication between components, making the interface respond to user operations.

Stylesheet support: QSS (Qt Style Sheets) allows you to customize the appearance of the application, similar to CSS.

Model-View Architecture: Provides a powerful model-view framework that simplifies data display and editing.

Multithreading support: built-in QThread class and worker thread mechanism to facilitate the creation of responsive interfaces.

Internationalization support: built-in translation tools and Unicode support to facilitate the creation of multilingual applications.

Graphics and animations: Provides rich drawing APIs and animation frameworks.

Qt Designer and Resource System

Qt Designer is a visual UI design tool that allows you to create interfaces by dragging and dropping, and then save them as .ui files.

# Example of loading .ui filefrom PyQt5 import uic

# Load UI fileui_file = "main_window.ui"
form_class = (ui_file)[0]

class MainWindow(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        (self)  # Set up the UI        # Connect signals and slots        (self.on_button_clicked)

The Qt resource system allows the embedding of images, icons and other resources into the application:

<!--  -->
<!DOCTYPE RCC>
<RCC>
  <qresource>
    <file>images/</file>
  </qresource>
</RCC>
# Compile resource filespyrcc5  -o resources_rc.py
# Use resources in codeimport resources_rc
(QIcon(":/images/"))

Tkinter application development

Tkinter is a standard GUI library for Python, based on the Tcl/Tk toolkit, and is a built-in GUI development tool for Python without additional installation.

Advantages of Tkinter

Python Standard Library: As part of the Python Standard Library, no additional installation is required.

Simple and easy to learn: The API is simple and easy to get started.

Cross-platform compatibility: Consistent appearance and behavior on Windows, macOS, and Linux.

Lightweight: takes up less resources and starts quickly.

Basic application structure

import tkinter as tk
from tkinter import messagebox

​​​​​​​class TkinterApp:
    def __init__(self, root):
         = root
        ("Tkinter Sample Application")
        ("400x300")
        
        # Create tags        label = (root, text="Welcome to use Tkinter cross-platform application!", font=("Arial", 14))
        (pady=20)
        
        # Create button        button = (root, text="Click Me", command=self.on_button_click)
        (pady=10)
    
    def on_button_click(self):
        ("information", "The button was clicked!")

if __name__ == "__main__":
    root = ()
    app = TkinterApp(root)
    ()

The main components of Tkinter

Basic Components:

  • Label: Display text or image
  • Button: Clickable button
  • Entry: Single-line text input box
  • Text: Multi-line text input box
  • Checkbutton: Checkbox
  • Radiobutton: Radio button
  • Canvas: Drawing area

Layout Manager:

  • pack: Simple layout manager, sorting components in order of addition
  • grid: grid-based layout manager, more precise component positioning
  • place: Absolute positioning layout manager

Advanced Tkinter Apps

Using ttk theme components

The ttk module provides thematic Tkinter components with a more modern look:

import tkinter as tk
from tkinter import ttk

root = ()
("TTK Theme Example")

# Set the themestyle = ()
print(style.theme_names())  # View available topicsstyle.theme_use("clam")  # Use clam theme
# Use the ttk componentttk_button = (root, text="TTK Button")
ttk_button.pack(pady=10)

ttk_entry = (root)
ttk_entry.pack(pady=10)

()

Create a custom dialog box

import tkinter as tk
from tkinter import simpledialog

class CustomDialog():
    def __init__(self, parent, title):
         = None
        super().__init__(parent, title)
    
    def body(self, frame):
        (frame, text="Please enter your name:").grid(row=0, column=0, sticky="w")
         = (frame)
        (row=0, column=1, padx=5, pady=5)
        return   # Initial focus    
    def apply(self):
         = ()

​​​​​​​# Use custom dialogsroot = ()
()  # Hide the main windowdialog = CustomDialog(root, "Input Dialog")
print("Input result:", )

The limitations and solutions of Tkinter

Although Tkinter is simple and easy to use, it also has some limitations:

Limited UI components: fewer native components than other frameworks.

Solution: Use third-party libraries such as ttkwidgets, tkinter-tooltip and other extension components.

The appearance is relatively basic: the default appearance is not modern enough.

Solution: Use ttk themes and custom styles, or consider the additional themes provided by the ttkthemes library.

Limited Advanced Features Support: Some advanced UI features are missing.

Solution: Combining other libraries such as Pillow to process images, matplotlib to create charts, etc.

# Use ttkthemes to improve appearancefrom ttkthemes import ThemedTk

root = ThemedTk(theme="arc")  # Use arc theme("Beautified Tkinter Application")

​​​​​​​(root, text="Modern Style Button").pack(pady=10)
()

Kivy multi-platform application

Kivy is an open source Python library for developing multi-touch applications, with powerful cross-platform capabilities, supporting Windows, macOS, Linux, Android and iOS.

Installation and Setup

# Install Kivypip install kivy

# If additional features are required, install the full versionpip install kivy[full]

# For Android development, install buildozerpip install buildozer

Key Features of Kivy

Multi-touch support: natively supports multi-touch input, which is very suitable for touch screen applications.

Cross-platform capability: A set of code can run on multiple platforms, including mobile devices.

Custom UI: Use your own graphics engine, does not rely on native components, and has a consistent appearance.

KV language: A special markup language used to separate UI design and business logic.

GPU acceleration: Excellent performance with OpenGL ES 2 for graphics rendering.

Basic application structure

from  import App
from  import BoxLayout
from  import Label
from  import Button

class MyApp(App):
    def build(self):
        # Create a layout        layout = BoxLayout(orientation='vertical', padding=10, spacing=10)
        
        # Add tags        label = Label(text="Welcome to use Kivy cross-platform applications!", font_size=24)
        layout.add_widget(label)
        
        # Add button        button = Button(text="Click Me", size_hint=(None, None), size=(200, 50), pos_hint={'center_x': 0.5})
        (on_press=self.on_button_press)
        layout.add_widget(button)
        
        return layout
    
    def on_button_press(self, instance):
        print("The button was clicked!")

​​​​​​​if __name__ == "__main__":
    MyApp().run()

Spoken by KV language

Kivy provides KV language to separate UI design and business logic:

# 
from  import App
from  import BoxLayout

class MyLayout(BoxLayout):
    def on_button_press(self):
        print("The button was clicked!")

​​​​​​​class MyApp(App):
    def build(self):
        return MyLayout()

if __name__ == "__main__":
    MyApp().run()

#

MyLayout:
    orientation: 'vertical'
    padding: 10
    spacing: 10
    
    Label:
        text: 'Welcome to use Kivy cross-platform applications!  '
        font_size: 24
    
    Button:
        text: 'Click me'
        size_hint: None, None
        size: 200, 50
        pos_hint: {'center_x': 0.5}
        on_press: root.on_button_press()

Mobile application development

One of the main advantages of Kivy is the ability to develop mobile applications. Use the Buildozer tool to package Kivy apps as Android APK or iOS IPA files.

# File example[app]
title = My Kivy App
 = myapp
 = 
 = .
source.include_exts = py,png,jpg,kv,atlas
version = 0.1
requirements = python3,kivy
orientation = portrait
osx.python_version = 3
osx.kivy_version = 1.9.1
fullscreen = 0

​​​​​​​[buildozer]
log_level = 2
# Build Android APKbuildozer android debug

# Build iOS apps (requires macOS environment)buildozer ios debug

Advanced features of Kivy

Custom Components

from  import Widget
from  import NumericProperty, ReferenceListProperty
from  import Vector
from  import Clock

class Ball(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)
    
    def move(self):
         = Vector(*) + 

​​​​​​​class Game(Widget):
    def __init__(self, **kwargs):
        super(Game, self).__init__(**kwargs)
         = Ball()
        self.add_widget()
         = Vector(4, 0).rotate(randint(0, 360))
        Clock.schedule_interval(, 1.0/60.0)
    
    def update(self, dt):
        ()

Animation and transition

from  import Animation

# Create animationanim = Animation(x=100, y=100, duration=1) + Animation(size=(200, 200), duration=0.5)
(widget)

Pros and disadvantages of Kivy and applicable scenarios

advantage:

  • True cross-platform, including mobile devices
  • Native support for multi-touch
  • Custom UI looks consistent
  • GPU accelerated rendering
  • Active community

shortcoming:

  • Non-native appearance, inconsistent with system style
  • The application package size is relatively large
  • The learning curve is relatively steep

Applicable scenarios:

  • Applications that require both desktop and mobile platforms
  • Games and interactive applications
  • Applications that require custom UI and special effects
  • Touch screen application

wxPython application development

wxPython is a Python binding based on the wxWidgets C++ library. It provides a set of native appearance GUI components, which can present the platform's native appearance and behavior on each platform.

Installation and Setup

# Install wxPythonpip install wxPython

Main features of wxPython

Native Appearance: The platform-native appearance and behavior are adopted on each platform.

Rich component set: Provides a large number of UI components, from basic controls to advanced components.

Event-driven model: adopts an event-driven programming model, similar to other modern GUI frameworks.

Stability and maturity: The wxWidgets library has a history of many years and is very stable and mature.

Basic application structure

import wx

class MyFrame():
    def __init__(self, parent, title):
        super(MyFrame, self).__init__(parent, title=title, size=(400, 300))
        
        # Create a panel        panel = (self)
        
        # Create a vertical box layout        vbox = ()
        
        # Add text tags        st = (panel, label="Welcome to cross-platform application with wxPython!")
        font = ()
         += 4
         = wx.FONTWEIGHT_BOLD
        (font)
        (st, flag= | wx.ALIGN_CENTER, border=20)
        
        # Add button        btn = (panel, label="Click Me")
        (wx.EVT_BUTTON, self.on_button_click)
        (btn, flag= | wx.ALIGN_CENTER, border=10)
        
        (vbox)
        ()
        (True)
    
    def on_button_click(self, event):
        ("The button was clicked!", "information",  | wx.ICON_INFORMATION)

​​​​​​​if __name__ == "__main__":
    app = ()
    frame = MyFrame(None, "My wxPython application")
    ()

wxPython's main components

Basic Components:

  • : Main window frame
  • : Panel container
  • : Button
  • : Text tags
  • : Text input box
  • : Check box
  • : Radio button

Layout Manager:

  • : Box layout, can be arranged horizontally or vertically
  • : grid layout, equal sized cells
  • : Flexible grid layout, allowing rows and columns of different sizes
  • : The most flexible grid layout allows components to cross rows and columns

Advanced wxPython application

Advanced Components Using wxPython

import wx
import 

class AdvancedFrame():
    def __init__(self, parent, title):
        super(AdvancedFrame, self).__init__(parent, title=title, size=(600, 400))
        
        # Create notebook controls        notebook = (self)
        
        # Create a panel        panel1 = (notebook)
        panel2 = (notebook)
        
        # Add a page in your notebook        (panel1, "Table Page")
        (panel2, "Control Page")
        
        # Create a table on the first panel        grid = (panel1)
        (10, 5)
        
        # Set column labels        for col in range(5):
            (col, f"List {col+1}")
        
        # Populate some data        for row in range(10):
            for col in range(5):
                (row, col, f"Cell {row+1},{col+1}")
        
        # Layout the first panel        sizer1 = ()
        (grid, 1,  | , 5)
        (sizer1)
        
        # Create various controls on the second panel        sizer2 = ()
        
        # Add a tree control        tree = (panel2, style=wx.TR_DEFAULT_STYLE | wx.TR_HAS_BUTTONS)
        root = ("Root Node")
        child1 = (root, "Sub Node 1")
        child2 = (root, "Sub Node 2")
        (child1, "Sub-node 1.1")
        (child1, "Sub Node 1.2")
        (root)
        (child1)
        
        (tree, 1,  | , 5)
        (sizer2)
        
        ()
        (True)

if __name__ == "__main__":
    app = ()
    frame = AdvancedFrame(None, "Advanced wxPython Application")
    ()

Visual design using wxGlade

wxGlade is a visual designer that can generate wxPython code:

# Example of code generated using wxGlade#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import wx

class MyFrame():
    def __init__(self, *args, **kwds):
        # The generated code begins        kwds["style"] = ("style", 0) | wx.DEFAULT_FRAME_STYLE
        .__init__(self, *args, **kwds)
        ((400, 300))
        ("Application generated by wxGlade")
        
        self.panel_1 = (self, wx.ID_ANY)
        
        sizer_1 = ()
        
        label_1 = (self.panel_1, wx.ID_ANY, "Interface generated using wxGlade")
        label_1.SetFont((14, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, ""))
        sizer_1.Add(label_1, 0, wx.ALIGN_CENTER | , 10)
        
        self.button_1 = (self.panel_1, wx.ID_ANY, "Click Me")
        sizer_1.Add(self.button_1, 0, wx.ALIGN_CENTER | , 10)
        
        self.panel_1.SetSizer(sizer_1)
        
        ()
        ()
        
        (wx.EVT_BUTTON, self.on_button_click, self.button_1)
    
    def on_button_click(self, event):
        ("The button was clicked!", "information",  | wx.ICON_INFORMATION)

class MyApp():
    def OnInit(self):
         = MyFrame(None, wx.ID_ANY, "")
        ()
        ()
        return True

if __name__ == "__main__":
    app = MyApp(0)
    ()

Pros and Disadvantages and Applicable Scenarios of WxPython

advantage:

  • Native appearance, consistent with operating system style
  • Rich component set, including advanced controls
  • High stability and maturity
  • Documentation and Community Support

shortcoming:

  • The API is relatively complex and the learning curve is steep
  • Large installation package
  • Update frequency is not as high as other frameworks

Applicable scenarios:

  • Enterprise-level applications that require native appearance
  • Complex data entry and presentation applications
  • Applications that require advanced UI components
  • Cross-platform desktop application, but no mobile support is required

DearPyGui Rapid Development

DearPyGui is a relatively new Python GUI framework developed based on the Dear ImGui library, mainly targeting rapid development and high-performance applications, and is particularly suitable for data visualization and tool development.

Installation and Setup

# Install DearPyGuipip install dearpygui

The main features of DearPyGui

High performance: GPU-accelerated real-time mode rendering, very smooth.

Simple and intuitive: Use the context manager structure, the code is simple and easy to understand.

Rich widgets: Built-in widgets, from basic controls to complex charts.

Built-in Themes and Styles: A variety of built-in Themes and Style Customization options are available.

Cross-platform: Supports Windows, macOS and Linux.

Basic application structure

import  as dpg

# Create a contextdpg.create_context()

# Create the main windowdpg.create_viewport(title="DearPyGui Sample Application", width=600, height=400)

# Set the main window to the current render objectdpg.setup_dearpygui()

# Create the main windowwith (label="Main Window", width=580, height=380):
    dpg.add_text("Welcome to use DearPyGui cross-platform application!")
    dpg.add_separator()
    
    # Add button    def button_callback():
        print("The button was clicked!")
        dpg.set_value("output", "The button was clicked!")
    
    dpg.add_button(label="Click Me", callback=button_callback)
    dpg.add_separator()
    
    # Add output text    dpg.add_text("Output:")
    dpg.add_text("", tag="output")

# Show viewportdpg.show_viewport()

# Start the main loopdpg.start_dearpygui()

# Clean up the contextdpg.destroy_context()

The main components of DearPyGui

Basic Components:

  • add_text: text tag
  • add_button: button
  • add_input_text: text input box
  • add_slider_float/int: slider
  • add_checkbox: checkbox
  • add_radio_button: radio button

Layout Components:

  • add_group: Combine multiple widgets
  • add_tab_bar and add_tab: Tab page
  • add_collapse_header: Collapsed title
  • add_child_window: child window

Data Visualization Components:

  • add_plot: draw a chart
  • add_line_series: add line chart
  • add_bar_series: add a bar chart
  • add_scatter_series: add scatter plot

Advanced DearPyGui Application

Data visualization example

import  as dpg
import math
import numpy as np

dpg.create_context()
dpg.create_viewport(title="DearPyGui Data Visualization", width=800, height=600)
dpg.setup_dearpygui()

# Generate datax = (0, 10, 100)
y1 = (x)
y2 = (x)

with (label="Data Visualization Example", width=780, height=580):
    # Create a chart    with (label="Sine and Cosine Functions", height=400, width=750):
        # Add axes        dpg.add_plot_legend()
        dpg.add_plot_axis(, label="X-axis")
        
        # Add Y axis        with dpg.plot_axis(, label="Y-axis"):
            # Add a sine curve            dpg.add_line_series((), (), label="sin(x)")
            # Add cosine curve            dpg.add_line_series((), (), label="cos(x)")
    
    dpg.add_separator()
    
    # Add interactive controls    dpg.add_text("Adjust parameters:")
    
    def update_plot(sender, app_data):
        # Get the current parameter value        freq = dpg.get_value("freq_slider")
        amplitude = dpg.get_value("amp_slider")
        
        # Recalculate the data        new_y1 = amplitude * (freq * x)
        new_y2 = amplitude * (freq * x)
        
        # Update chart data        dpg.set_value("sin_series", [(), new_y1.tolist()])
        dpg.set_value("cos_series", [(), new_y2.tolist()])
    
    # Add frequency slider    dpg.add_slider_float(label="frequency", default_value=1.0, min_value=0.1, max_value=5.0, 
                         callback=update_plot, tag="freq_slider")
    
    # Add an amplitude slider    dpg.add_slider_float(label="amplitude", default_value=1.0, min_value=0.1, max_value=2.0, 
                         callback=update_plot, tag="amp_slider")

dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

Theme and style customization

import  as dpg

dpg.create_context()
dpg.create_viewport(title="DearPyGui Theme Example", width=600, height=400)
dpg.setup_dearpygui()

# Create a topicwith () as global_theme:
    with dpg.theme_component():
        # Set text color        dpg.add_theme_color(dpg.mvThemeCol_Text, [255, 255, 0])
        # Set the window background color        dpg.add_theme_color(dpg.mvThemeCol_WindowBg, [50, 50, 50])
        # Set button color        dpg.add_theme_color(dpg.mvThemeCol_Button, [100, 100, 150])
        dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, [150, 150, 200])
        # Set rounded corners        dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 5.0)
        dpg.add_theme_style(dpg.mvStyleVar_WindowRounding, 5.0)

# Apply Topicsdpg.bind_theme(global_theme)

# Create a windowwith (label="Custom Theme Example", width=580, height=380):
    dpg.add_text("This is a custom theme DearPyGui app")
    dpg.add_separator()
    
    # Add button    dpg.add_button(label="Button 1", width=120, height=30)
    dpg.add_button(label="Button 2", width=120, height=30)
    
    # Create different themes for specific controls    with () as button_theme:
        with dpg.theme_component():
            dpg.add_theme_color(dpg.mvThemeCol_Button, [200, 50, 50])
            dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, [250, 100, 100])
    
    # Create a button that uses a specific topic    button = dpg.add_button(label="Special Button", width=120, height=30)
    dpg.bind_item_theme(button, button_theme)

dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

Pros and disadvantages of DearPyGui and applicable scenarios

advantage:

  • High performance, real-time mode rendering
  • Simple and intuitive API
  • Powerful built-in data visualization
  • Lightweight, less dependency
  • The context manager is clear structure

shortcoming:

  • Relatively new, relatively little community and documentation
  • Non-native appearance, inconsistent with operating system style
  • No mobile platform supported

Applicable scenarios:

  • Data visualization tools
  • Rapid prototyping
  • Science and engineering applications
  • Debugging and development tools

Application Packaging and Distribution

The developed Python desktop application needs to be packaged into an executable file so that users can run without installing the Python environment. The following are several mainstream packaging tools.

PyInstaller

PyInstaller is one of the most popular Python application packaging tools, which can package Python applications into single-file executables or directories.

Installation and basic use

# Install PyInstallerpip install pyinstaller

# Basic Packaging Commandpyinstaller 

# Package as a single filepyinstaller --onefile 

# Specify iconpyinstaller --onefile --icon=app_icon.ico 

# Do not display the console windowpyinstaller --onefile --windowed 

Advanced configuration: .spec file

The .spec file generated by PyInstaller allows for finer configuration:

# 
block_cipher = None

a = Analysis([''],
             pathex=['D:\\MyProject'],
             binaries=[],
             datas=[('resources', 'resources')],  # Include extra files             hiddenimports=[''],  # Hide import             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)

pyz = PYZ(, a.zipped_data,
          cipher=block_cipher)

exe = EXE(pyz,
          ,
          ,
          ,
          ,
          [],
          name='MyApp',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=False,
          icon='app_icon.ico')
# Use .spec file to packagepyinstaller 

cx_Freeze

cx_Freeze is another popular packaging tool that is especially suitable for creating cross-platform packages.

Installation and basic use

# Install cx_Freezepip install cx_Freeze
# 
import sys
from cx_Freeze import setup, Executable

build_exe_options = {
    "packages": ["os", "numpy"],
    "excludes": ["tkinter"],
    "include_files": [("resources/", "resources/")]
}

base = None
if  == "win32":
    base = "Win32GUI"  # For Windows GUI applications
setup(
    name="MyApp",
    version="0.1",
    description="My GUI Application",
    options={"build_exe": build_exe_options},
    executables=[Executable("", base=base, icon="app_icon.ico")]
)
# Execute packagepython  build

# Create an installerpython  bdist_msi  # Windows
python  bdist_dmg  # macOS
python  bdist_rpm  # Linux

Nuitka

Nuitka is a Python to C++ compiler that compiles Python code into executables, and its performance is usually faster than the interpreter.

# Install Nuitkapip install nuitka

# Basic Compilationpython -m nuitka --follow-imports 

# Independent compilation (including all dependencies)python -m nuitka --standalone --follow-imports 

# Create application without console windows for Windowspython -m nuitka --standalone --windows-disable-console --follow-imports 

Auto-Py-To-Exe

For users who like graphical interfaces, Auto-Py-To-Exe provides a graphical interface packaging for PyInstaller.

# Install Auto-Py-To-Exepip install auto-py-to-exe

# Start the graphical interfaceauto-py-to-exe

Cross-platform packaging strategy

For cross-platform applications, the best practice is to package on the target platform:

Windows Packaging: Create .exe files using PyInstaller or cx_Freeze on Windows systems.

macOS Packaging: Create .app packages using PyInstaller on macOS, or use py2app.

Linux Packaging: Use PyInstaller or cx_Freeze on Linux, or create DEB/RPM packages.

Application signature and notarization

For commercial applications, signing your application can increase user trust:

Windows code signing: Use SignTool and code signing certificates.

macOS code signing: use Apple developer certificate and codesign tools.

Notarization distribution: Consider using digital signature and checksum mechanisms.

Automated construction and release

Automate the construction and release process using CI/CD processes:

GitHub Actions: Workflows can be configured to automatically build multi-platform distributions.

# .github/workflows/
name: Build

on:
  push:
    tags:
      - 'v*'

jobs:
  build-windows:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.9'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pyinstaller
          pip install -r 
      - name: Build with PyInstaller
        run: pyinstaller --onefile --windowed --icon=app_icon.ico 
      - name: Upload artifacts
        uses: actions/upload-artifact@v2
        with:
          name: windows-build
          path: dist/

  build-macos:
    runs-on: macos-latest
    # Similar steps...
  build-linux:
    runs-on: ubuntu-latest
    # Similar steps...

Automatic Publishing: Configure GitHub Releases or other platforms to automatically publish built applications.

FAQs and Solutions

Missing dependencies: Use --hidden-import to specify hidden dependencies.

Resource file not found: Use --add-data to add the resource file and modify the path reference in the code.

Application package is too large: Use UPX to compress or exclude unnecessary libraries.

Anti-virus false positives: Submit a false positive sample to the anti-virus software provider.

Practical case: Cross-platform file manager

To show how to develop practical cross-platform applications, we will create a simple file manager application. This application will be implemented using PyQt5 and includes basic file operation functions.

Project structure

file_manager/
├──           # Main program entry
├── file_manager.py # File manager class
├── file_operations.py # file operation function
├── resources/            # Resource folder
│   ├── icons/       #Icon
│   └── styles/       # Stylesheet
├── # Dependencies
└──         # Project Description

Dependencies

# 
PyQt5==5.15.6
pyqt5-tools==5.15.4.3.2

Main program entry

# 
import sys
from  import QApplication
from file_manager import FileManagerApp

def main():
    app = QApplication()
    window = FileManagerApp()
    ()
    (app.exec_())

if __name__ == "__main__":
    main()

File Manager Class

# file_manager.py
import os
import shutil
from  import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                             QListWidget, QListWidgetItem, QPushButton, QFileDialog, 
                             QInputDialog, QMessageBox, QLabel, QMenu, QAction, QToolBar)
from  import Qt, QSize
from  import QIcon
from file_operations import get_file_size, get_file_type, copy_file, move_file, delete_file

​​​​​​​class FileManagerApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.current_path = ("~")  # Start as the user home directory        self.update_file_list()
        
    def init_ui(self):
        # Set window properties        ("Cross-platform file manager")
        (100, 100, 800, 600)
        
        # Create a central component        central_widget = QWidget()
        (central_widget)
        main_layout = QVBoxLayout(central_widget)
        
        # Create a toolbar        toolbar = QToolBar("Main Toolbar")
        (toolbar)
        
        # Add navigation buttons        back_action = QAction(QIcon("resources/icons/"), "return", self)
        back_action.(self.navigate_back)
        (back_action)
        
        up_action = QAction(QIcon("resources/icons/"), "Advanced Directory", self)
        up_action.(self.navigate_up)
        (up_action)
        
        home_action = QAction(QIcon("resources/icons/"), "Home Directory", self)
        home_action.(self.navigate_home)
        (home_action)
        
        # Add the current path to display        self.path_label = QLabel()
        main_layout.addWidget(self.path_label)
        
        # Add a file list        self.file_list = QListWidget()
        self.file_list.setIconSize(QSize(24, 24))
        self.file_list.(self.on_item_double_clicked)
        self.file_list.setContextMenuPolicy()
        self.file_list.(self.show_context_menu)
        main_layout.addWidget(self.file_list)
        
        # Add bottom button        button_layout = QHBoxLayout()
        
        self.new_folder_btn = QPushButton("Create a new folder")
        self.new_folder_btn.(self.create_new_folder)
        button_layout.addWidget(self.new_folder_btn)
        
        self.refresh_btn = QPushButton("refresh")
        self.refresh_btn.(self.update_file_list)
        button_layout.addWidget(self.refresh_btn)
        
        main_layout.addLayout(button_layout)
        
        #History         = []
        self.history_position = -1
    
    def update_file_list(self):
        self.file_list.clear()
        self.path_label.setText(self.current_path)
        
        try:
            # Add a directory            for item in sorted([d for d in (self.current_path) 
                                if ((self.current_path, d))]):
                list_item = QListWidgetItem(QIcon("resources/icons/"), item)
                list_item.setData(, "dir")
                self.file_list.addItem(list_item)
            
            # Add a file            for item in sorted([f for f in (self.current_path) 
                                if ((self.current_path, f))]):
                file_path = (self.current_path, item)
                file_type = get_file_type(file_path)
                file_size = get_file_size(file_path)
                
                # Select the appropriate icon                icon_name = ""
                if file_type == "image":
                    icon_name = ""
                elif file_type == "text":
                    icon_name = ""
                
                list_item = QListWidgetItem(QIcon(f"resources/icons/{icon_name}"), 
                                          f"{item} ({file_size})")
                list_item.setData(, "file")
                self.file_list.addItem(list_item)
        
        except PermissionError:
            (self, "Permission Error", "No permission to access this directory")
            self.navigate_back()
        except Exception as e:
            (self, "mistake", f"An error occurred while loading the directory: {str(e)}")
    
    def on_item_double_clicked(self, item):
        item_name = ().split(" (")[0]  # Remove file size information        item_type = ()
        
        if item_type == "dir":
            # Add the current path to history            self.add_to_history(self.current_path)
            
            # Navigate to the new directory            self.current_path = (self.current_path, item_name)
            self.update_file_list()
        else:
            # Open the file            file_path = (self.current_path, item_name)
            try:
                (file_path)  # Windows
            except AttributeError:
                import subprocess
                # macOS or Linux                if  == "darwin":
                    (["open", file_path])
                else:  # Linux
                    (["xdg-open", file_path])
    
    def show_context_menu(self, position):
        item = self.file_list.currentItem()
        if not item:
            return
            
        context_menu = QMenu()
        
        # Add context menu item        copy_action = context_menu.addAction("copy")
        move_action = context_menu.addAction("move")
        rename_action = context_menu.addAction("Rename")
        delete_action = context_menu.addAction("delete")
        
        # Show menu and get user selection        action = context_menu.exec_(self.file_list.mapToGlobal(position))
        
        item_name = ().split(" (")[0]
        item_path = (self.current_path, item_name)
        
        if action == copy_action:
            target_dir = (self, "Select the target directory", "")
            if target_dir:
                try:
                    copy_file(item_path, target_dir)
                    (self, "success", f"The file has been copied to {target_dir}")
                except Exception as e:
                    (self, "mistake", f"Failed to copy the file: {str(e)}")
        
        elif action == move_action:
            target_dir = (self, "Select the target directory", "")
            if target_dir:
                try:
                    move_file(item_path, target_dir)
                    (self, "success", f"File has been moved to {target_dir}")
                    self.update_file_list()
                except Exception as e:
                    (self, "mistake", f"Failed to move files: {str(e)}")
        
        elif action == rename_action:
            new_name, ok = (self, "Rename", "Enter a new name:", text=item_name)
            if ok and new_name:
                new_path = (self.current_path, new_name)
                try:
                    (item_path, new_path)
                    self.update_file_list()
                except Exception as e:
                    (self, "mistake", f"Rename failed: {str(e)}")
        
        elif action == delete_action:
            reply = (self, "Confirm Delete", 
                                       f"You are sure to delete {item_name} Is it?",
                                        | )
            if reply == :
                try:
                    delete_file(item_path)
                    self.update_file_list()
                except Exception as e:
                    (self, "mistake", f"Deletion failed: {str(e)}")
    
    def create_new_folder(self):
        folder_name, ok = (self, "Create a new folder", "Enter folder name:")
        if ok and folder_name:
            new_folder_path = (self.current_path, folder_name)
            try:
                (new_folder_path, exist_ok=True)
                self.update_file_list()
            except Exception as e:
                (self, "mistake", f"Failed to create a folder: {str(e)}")
    
    def navigate_back(self):
        if self.history_position &gt; 0:
            self.history_position -= 1
            self.current_path = [self.history_position]
            self.update_file_list()
    
    def navigate_up(self):
        parent_dir = (self.current_path)
        if parent_dir != self.current_path:  # Make sure it is not the root directory            self.add_to_history(self.current_path)
            self.current_path = parent_dir
            self.update_file_list()
    
    def navigate_home(self):
        self.add_to_history(self.current_path)
        self.current_path = ("~")
        self.update_file_list()
    
    def add_to_history(self, path):
        # If it is not currently at the end of the history, clear the subsequent history        if self.history_position &lt; len() - 1:
             = [:self.history_position + 1]
        
        (path)
        self.history_position = len() - 1

File operation module

# file_operations.py
import os
import shutil
import platform

def get_file_size(file_path):
    """Get file size and format"""
    try:
        size_bytes = (file_path)
        
        # Format file size        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size_bytes &lt; 1024.0 or unit == 'TB':
                break
            size_bytes /= 1024.0
        
        return f"{size_bytes:.2f} {unit}"
    except Exception:
        return "Unknown size"

def get_file_type(file_path):
    """Determine file type based on file extension"""
    _, ext = (file_path.lower())
    
    # Image File    if ext in [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp"]:
        return "image"
    
    # Text file    if ext in [".txt", ".md", ".py", ".java", ".c", ".cpp", ".h", ".html", ".css", ".js", ".json", ".xml"]:
        return "text"
    
    # Audio file    if ext in [".mp3", ".wav", ".ogg", ".flac", ".aac"]:
        return "audio"
    
    # Video File    if ext in [".mp4", ".avi", ".mov", ".mkv", ".wmv", ".flv"]:
        return "video"
    
    # Document File    if ext in [".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"]:
        return "document"
    
    # Other types    return "other"

def copy_file(source_path, target_dir):
    """Copy file or directory to target directory"""
    file_name = (source_path)
    target_path = (target_dir, file_name)
    
    # If the target already exists, add a numeric suffix    if (target_path):
        base, ext = (file_name)
        i = 1
        while ((target_dir, f"{base}_{i}{ext}")):
            i += 1
        target_path = (target_dir, f"{base}_{i}{ext}")
    
    if (source_path):
        (source_path, target_path)
    else:
        shutil.copy2(source_path, target_path)
    
    return target_path

def move_file(source_path, target_dir):
    """Move files or directories to target directory"""
    file_name = (source_path)
    target_path = (target_dir, file_name)
    
    # If the target already exists, add a numeric suffix    if (target_path):
        base, ext = (file_name)
        i = 1
        while ((target_dir, f"{base}_{i}{ext}")):
            i += 1
        target_path = (target_dir, f"{base}_{i}{ext}")
    
    (source_path, target_path)
    return target_path

def delete_file(file_path):
    """Delete a file or directory"""
    if (file_path):
        (file_path)
    else:
        (file_path)

Running effect

This file manager application has the following functions:

  • Browse and navigate file systems
  • Create a new folder
  • Copy, move, rename and delete files/folders
  • Open the file (using the system default application)
  • Navigation history

The application works properly on Windows, macOS and Linux, demonstrating the ability to develop cross-platform applications using PyQt5.

Performance optimization and best practices

There are some performance optimizations and best practices worth noting when developing cross-platform Python desktop applications.

Performance optimization tips

Asynchronous processing: Use multi-threading or asynchronous IO processing time-consuming operations to avoid interface lag.

# Use QThread for asynchronous processingfrom  import QThread, pyqtSignal

class WorkerThread(QThread):
    result_ready = pyqtSignal(object)
    error_occurred = pyqtSignal(str)
    
    def __init__(self, function, *args, **kwargs):
        super().__init__()
         = function
         = args
         = kwargs
    
    def run(self):
        try:
            result = (*, **)
            self.result_ready.emit(result)
        except Exception as e:
            self.error_occurred.emit(str(e))

#User Exampledef some_long_operation():
    # Time-consuming operation    pass

​​​​​​​ = WorkerThread(some_long_operation)
.result_ready.connect(self.handle_result)
.error_occurred.connect(self.handle_error)
()

Resource Cache: Caches images and other resources to reduce duplicate loading.

class ResourceCache:
    def __init__(self):
         = {}
    
    def get_icon(self, path):
        if path not in :
            [path] = QIcon(path)
        return [path]

# Use cache = ResourceCache()
icon = .get_icon("path/to/")

Lazy Loading: For large lists or tree views, lazy loading or virtualization.

Reduce redraw: Avoid unnecessary UI redraws, use update() instead of repaint().

Cross-platform best practices

Use relative paths: Always use processing paths instead of hardcoded path separators.

# Error methodpath = "resources\\icons\\"  # Windows Specific
# The correct waypath = ("resources", "icons", "")

Processing platform-specific code: Use or () to detect the platform.

import sys
import platform

def open_file(file_path):
    if  == "win32":
        (file_path)  # Windows-specific    elif  == "darwin":
        import subprocess
        (["open", file_path])  # macOS
    else:  # Linux and others        import subprocess
        (["xdg-open", file_path])

Test all target platforms: Test the application on all target platforms before release.

Using a virtual environment: Use a virtual machine or container to test different platforms.

Adapt to screen resolution: Design an interface that can adapt to different screen resolutions.

# Get screen size and resize windowfrom  import QDesktopWidget

def center_window(window):
    screen = QDesktopWidget().screenGeometry()
    size = ()
    x = (() - ()) // 2
    y = (() - ()) // 2
    (x, y)

Adapt to high DPI display: Ensure that the application displays normally on high DPI displays.

# Enable high DPI scaling(Qt.AA_EnableHighDpiScaling, True)
(Qt.AA_UseHighDpiPixmaps, True)

Summary and prospect

Python provides a variety of powerful frameworks and tools to make cross-platform desktop application development simple and efficient. In this article, we explore the characteristics, advantages and disadvantages and applicable scenarios of several mainstream frameworks, and demonstrate through practical cases how to develop a practical cross-platform file manager.

With the development of technology, the field of cross-platform desktop application development of Python is also constantly improving. Here are some future trends worth watching:

The convergence of web technology and desktop applications: Python alternatives such as Electron (such as Pywebview) allow the use of web technology to develop desktop applications.

Improvements to the cross-platform UI component library: Existing frameworks are constantly improving to provide more modern UI components and a better user experience.

Enhanced support for mobile platforms: More frameworks are improving support for mobile platforms such as BeeWare and Kivy.

Performance optimization: New tools and technologies are improving the performance of Python desktop applications such as JIT compilers such as PyPy and Nuitka.

AI Integration: There is a growing trend to integrate machine learning and artificial intelligence capabilities into desktop applications.

Regardless of the framework you choose, Python offers a powerful tool set to develop feature-rich, beautifully-looking cross-platform desktop applications. As you accumulate practical experience, you will be able to select the frameworks and tools that best suit the needs of a specific project and develop professional cross-platform applications.

The above is the detailed content of the complete guide for Python to implement cross-platform desktop application development. For more information about cross-platform development of Python, please pay attention to my other related articles!