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