PySide6
Cross-platform GUI framework for Python based on Qt 6. Official Qt Company support with LGPL license. Easier for enterprise adoption than PyQt due to commercial usability. Features rich widgets and modern UI development.
Framework
PySide6
Overview
PySide6 is a cross-platform GUI framework for Python based on Qt 6. With official Qt Company support and LGPL license, it enables commercial use and is more enterprise-friendly than PyQt. Features rich widgets and modern UI development capabilities.
Details
PySide6 has established itself as the mainstream choice for Python desktop development in 2025. Qt 6 support provides latest UI features and performance improvements, with commercial project adoption rate exceeding PyQt. Demand is particularly increasing in finance and scientific fields.
With official support from The Qt Company and fewer commercial restrictions due to LGPL licensing, enterprise adoption is progressing. Comprehensive features for professional desktop application development are provided, including visual UI design with Qt Designer, QML integration, and rich widget libraries.
PySide6 is adopted in many prominent applications such as Maya, FreeCAD, Eric IDE, Anki, and Spyder, making it the standard choice for Python developers building professional desktop applications.
Pros and Cons
Pros
- Official Support: Official support and continuous development by Qt Company
- License Flexibility: LGPL license enables easier commercial use
- Pythonic API: Pythonic interface that's easy to learn
- Latest Qt 6 Features: Modern UI features and performance
- Rich Widgets: All functionality needed for desktop apps
- Qt Designer Integration: Visual UI design tools
- QML Support: Declarative UI and advanced animations
- Cross-platform: Windows, macOS, Linux support
- Comprehensive Documentation: Detailed docs and samples
Cons
- App Size: Large binary size due to Qt dependencies
- Memory Usage: High memory consumption due to rich GUI
- Distribution Complexity: Dependency management and packaging challenges
- Learning Curve: Understanding Qt-specific concepts and signals/slots required
- Package Size: Large download size during pip installation
Key Links
- PySide6 Official Site
- PySide6 Official Documentation
- PySide6 GitHub Repository
- Qt Creator IDE
- PySide6 Tutorials
- Qt Designer
Code Examples
Hello World Application
# hello_world.py
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel, QPushButton
from PySide6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PySide6 Hello World")
self.setGeometry(100, 100, 300, 200)
# Central widget
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Layout
layout = QVBoxLayout(central_widget)
# Label
self.label = QLabel("Hello, PySide6!", self)
self.label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.label)
# Button
button = QPushButton("Click me", self)
button.clicked.connect(self.on_button_clicked)
layout.addWidget(button)
self.click_count = 0
def on_button_clicked(self):
self.click_count += 1
self.label.setText(f"Clicked {self.click_count} times")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Signals and Slots
# signals_slots.py
import sys
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QSlider, QLineEdit)
from PySide6.QtCore import Qt, QTimer, Signal, QObject
class DataModel(QObject):
# Custom signals
value_changed = Signal(int)
message_updated = Signal(str)
def __init__(self):
super().__init__()
self._value = 0
self._timer = QTimer()
self._timer.timeout.connect(self.update_value)
def start_auto_update(self):
self._timer.start(1000) # 1 second interval
def stop_auto_update(self):
self._timer.stop()
def update_value(self):
self._value += 1
self.value_changed.emit(self._value)
self.message_updated.emit(f"Auto updated: {self._value}")
def set_manual_value(self, value):
self._value = value
self.value_changed.emit(self._value)
self.message_updated.emit(f"Manually set: {self._value}")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Signals and Slots Example")
self.setGeometry(100, 100, 400, 300)
# Data model
self.model = DataModel()
# UI setup
self.setup_ui()
# Connect signals
self.model.value_changed.connect(self.on_value_changed)
self.model.message_updated.connect(self.on_message_updated)
def setup_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Value display label
self.value_label = QLabel("Value: 0")
layout.addWidget(self.value_label)
# Message label
self.message_label = QLabel("Message: None")
layout.addWidget(self.message_label)
# Slider
slider_layout = QHBoxLayout()
slider_layout.addWidget(QLabel("Manual Set:"))
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 100)
self.slider.valueChanged.connect(self.model.set_manual_value)
slider_layout.addWidget(self.slider)
layout.addLayout(slider_layout)
# Buttons
button_layout = QHBoxLayout()
self.start_button = QPushButton("Start Auto Update")
self.start_button.clicked.connect(self.model.start_auto_update)
button_layout.addWidget(self.start_button)
self.stop_button = QPushButton("Stop Auto Update")
self.stop_button.clicked.connect(self.model.stop_auto_update)
button_layout.addWidget(self.stop_button)
layout.addLayout(button_layout)
def on_value_changed(self, value):
self.value_label.setText(f"Value: {value}")
# Update slider (temporarily block signals to avoid loops)
self.slider.blockSignals(True)
self.slider.setValue(min(value, 100))
self.slider.blockSignals(False)
def on_message_updated(self, message):
self.message_label.setText(f"Message: {message}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Qt Designer Integration
# designer_ui.py
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtUiTools import QUiLoader
from PySide6.QtCore import QFile, QIODevice
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.load_ui()
self.setup_connections()
def load_ui(self):
# Load .ui file
ui_file = QFile("mainwindow.ui")
if ui_file.open(QIODevice.ReadOnly):
loader = QUiLoader()
self.ui = loader.load(ui_file, self)
ui_file.close()
# Set UI as central widget
self.setCentralWidget(self.ui)
else:
print("Failed to load UI file")
def setup_connections(self):
# Get references to UI elements and connect signals
if hasattr(self.ui, 'pushButton'):
self.ui.pushButton.clicked.connect(self.on_button_clicked)
if hasattr(self.ui, 'lineEdit'):
self.ui.lineEdit.textChanged.connect(self.on_text_changed)
def on_button_clicked(self):
if hasattr(self.ui, 'label'):
self.ui.label.setText("Button clicked!")
def on_text_changed(self, text):
if hasattr(self.ui, 'label_2'):
self.ui.label_2.setText(f"Input: {text}")
# Alternative: Use UI converted to .py file
# pyside6-uic mainwindow.ui -o ui_mainwindow.py
# from ui_mainwindow import Ui_MainWindow
#
# class MainWindow(QMainWindow):
# def __init__(self):
# super().__init__()
# self.ui = Ui_MainWindow()
# self.ui.setupUi(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
QML Integration
# qml_integration.py
import sys
import os
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QmlElement, qmlRegisterType
from PySide6.QtQuick import QQuickView
from PySide6.QtCore import QObject, Signal, Slot, Property
# Python class for use in QML
QML_IMPORT_NAME = "DataModel"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
class DataModel(QObject):
# Signals
dataChanged = Signal(str)
def __init__(self):
super().__init__()
self._data = "Initial Data"
# Properties
def get_data(self):
return self._data
def set_data(self, data):
if self._data != data:
self._data = data
self.dataChanged.emit(data)
data = Property(str, get_data, set_data, notify=dataChanged)
# Slots (callable from QML)
@Slot()
def update_data(self):
import random
new_data = f"Random Data: {random.randint(1, 100)}"
self.set_data(new_data)
@Slot(str, result=str)
def process_input(self, input_text):
return f"Processed: {input_text.upper()}"
def main():
app = QGuiApplication(sys.argv)
# Register Python class with QML engine
qmlRegisterType(DataModel, "DataModel", 1, 0, "DataModel")
# Load QML file
view = QQuickView()
view.setSource("main.qml")
view.show()
return app.exec()
if __name__ == "__main__":
sys.exit(main())
Database Operations
# database_example.py
import sys
import sqlite3
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout, QTableWidget,
QTableWidgetItem, QPushButton, QLineEdit,
QLabel, QMessageBox)
from PySide6.QtCore import Qt
class DatabaseApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PySide6 Database Operations")
self.setGeometry(100, 100, 600, 400)
# Initialize database
self.init_database()
# Create UI
self.setup_ui()
# Load data
self.load_data()
def init_database(self):
self.conn = sqlite3.connect("users.db")
cursor = self.conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER
)
""")
self.conn.commit()
def setup_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Input form
form_layout = QHBoxLayout()
form_layout.addWidget(QLabel("Name:"))
self.name_input = QLineEdit()
form_layout.addWidget(self.name_input)
form_layout.addWidget(QLabel("Email:"))
self.email_input = QLineEdit()
form_layout.addWidget(self.email_input)
form_layout.addWidget(QLabel("Age:"))
self.age_input = QLineEdit()
form_layout.addWidget(self.age_input)
add_button = QPushButton("Add")
add_button.clicked.connect(self.add_user)
form_layout.addWidget(add_button)
layout.addLayout(form_layout)
# Table
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["ID", "Name", "Email", "Age"])
layout.addWidget(self.table)
# Buttons
button_layout = QHBoxLayout()
refresh_button = QPushButton("Refresh")
refresh_button.clicked.connect(self.load_data)
button_layout.addWidget(refresh_button)
delete_button = QPushButton("Delete")
delete_button.clicked.connect(self.delete_user)
button_layout.addWidget(delete_button)
layout.addLayout(button_layout)
def add_user(self):
name = self.name_input.text().strip()
email = self.email_input.text().strip()
age_text = self.age_input.text().strip()
if not name or not email:
QMessageBox.warning(self, "Warning", "Name and email are required")
return
try:
age = int(age_text) if age_text else None
cursor = self.conn.cursor()
cursor.execute(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
(name, email, age)
)
self.conn.commit()
# Clear input fields
self.name_input.clear()
self.email_input.clear()
self.age_input.clear()
# Update table
self.load_data()
except ValueError:
QMessageBox.warning(self, "Error", "Please enter a valid age")
except sqlite3.IntegrityError:
QMessageBox.warning(self, "Error", "This email already exists")
def load_data(self):
cursor = self.conn.cursor()
cursor.execute("SELECT id, name, email, age FROM users")
rows = cursor.fetchall()
self.table.setRowCount(len(rows))
for row_index, row_data in enumerate(rows):
for col_index, col_data in enumerate(row_data):
item = QTableWidgetItem(str(col_data if col_data is not None else ""))
self.table.setItem(row_index, col_index, item)
def delete_user(self):
current_row = self.table.currentRow()
if current_row < 0:
QMessageBox.warning(self, "Warning", "Please select a row to delete")
return
user_id = self.table.item(current_row, 0).text()
reply = QMessageBox.question(
self, "Confirm",
f"Delete user ID {user_id}?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
cursor = self.conn.cursor()
cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
self.conn.commit()
self.load_data()
def closeEvent(self, event):
self.conn.close()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DatabaseApp()
window.show()
sys.exit(app.exec())
Project Setup and Execution
# Create virtual environment (recommended)
python -m venv pyside6_env
source pyside6_env/bin/activate # Linux/macOS
# or
pyside6_env\Scripts\activate # Windows
# Install PySide6
pip install PySide6
# Use Qt Designer
designer # Launch Qt Designer
# Convert .ui file to .py file
pyside6-uic mainwindow.ui -o ui_mainwindow.py
# Convert resource files (.qrc)
pyside6-rcc resources.qrc -o resources_rc.py
# Run application
python hello_world.py
# Create standalone executable (using PyInstaller)
pip install pyinstaller
pyinstaller --onefile --windowed hello_world.py
# Create requirements.txt
pip freeze > requirements.txt
Example Project Structure
my_pyside6_app/
├── main.py # Main application
├── ui/
│ ├── __init__.py
│ ├── mainwindow.ui # Qt Designer file
│ └── ui_mainwindow.py # Converted UI file
├── resources/
│ ├── icons/
│ ├── images/
│ └── resources.qrc # Resource file
├── models/
│ ├── __init__.py
│ └── data_model.py # Data models
├── views/
│ ├── __init__.py
│ └── custom_widgets.py # Custom widgets
├── utils/
│ ├── __init__.py
│ └── database.py # Database utilities
├── requirements.txt
└── README.md