🧱 Building QRForge PRO with PySide6 (Beginner-Friendly Guide)
Source: Dev.to
QRForge PRO v2.0.0 – Step‑by‑Step Desktop Tutorial
Dev.to‑style walkthrough
What you’ll build – a professional QR‑code design studio that can:
- Generate QR codes from Text, URLs, and Wi‑Fi credentials
- Customize fill colour, background colour, and transparency
- Move, rotate, and scale QR codes visually
- Export print‑ready PNG, SVG, and PDF files
Full source code:
0️⃣ What You’ll Build
By the end of this tutorial you’ll have a fully‑featured Qt application that lets you design QR codes the way a graphic‑designer would.
1️⃣ Project Setup
Install dependencies
pip install PySide6 qrcode pillow requests
File structure
qrforge/
├─ main.py
├─ logo.ico
2️⃣ Importing What We Need
# Standard library
import sys, os, math, requests
# QR helpers
import qrcode
from PIL import Image
# Qt imports
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
from PySide6.QtSvg import QSvgGenerator
from PySide6.QtGui import QPdfWriter, QPageSize
These imports give us:
- Windows & dialogs – via
QtWidgets - Vector drawing – via
QtGui&QtSvg - Scene‑based rendering – via
QGraphicsScene/View - SVG/PDF export – via
QSvgGenerator&QPdfWriter
3️⃣ App Metadata
Centralising metadata keeps the code clean and reusable.
APP_NAME = "QRForge PRO"
APP_VERSION = "2.0.0"
APP_AUTHOR = "Mate Technologies"
APP_URL = "https://matetools.gumroad.com"
4️⃣ Utility Functions
# Resource loader (for PyInstaller compatibility)
def resource_path(name):
base = getattr(sys, "_MEIPASS", os.path.dirname(__file__))
return os.path.join(base, name)
# URL shortener (TinyURL API)
def shorten_url(url):
try:
r = requests.get(
f"https://tinyurl.com/api-create.php?url={url}",
timeout=5,
)
return r.text if r.status_code == 200 else url
except Exception:
return url
# Wi‑Fi QR payload format
def wifi_payload(ssid, pwd, enc):
# enc can be "WPA", "WEP", or "NONE"
return f"WIFI:S:{ssid};T:{enc if enc!='NONE' else ''};P:{pwd};;"
5️⃣ Creating a QR Code Graphics Item
Instead of rendering images directly we create a custom QGraphicsItem.
That gives us dragging, rotation, and selection outlines.
QRItem class skeleton
class QRItem(QGraphicsItem):
def __init__(self, data):
super().__init__()
self.data = data
self.size = 300 # default pixel size
self.rotation_angle = 0
self.fill = QColor("black") # QR fill colour
self.bg = QColor("white") # background colour
self.transparent = False
self.generate()
Generating the QR image
def generate(self):
qr = qrcode.QRCode(border=1)
qr.add_data(self.data)
qr.make(fit=True)
back = None if self.transparent else self.bg.name()
img = qr.make_image(
fill_color=self.fill.name(),
back_color=back,
).convert("RGBA")
# Convert PIL → QImage
self.qimage = QImage(
img.tobytes("raw", "RGBA"),
img.width,
img.height,
QImage.Format_RGBA8888,
)
Painting & selection outline
def paint(self, painter, *_):
painter.save()
painter.rotate(self.rotation_angle)
painter.drawImage(self.boundingRect(), self.qimage)
painter.restore()
if self.isSelected():
painter.setPen(QPen(QColor("#00E676"), 2, Qt.DashLine))
painter.drawRect(self.boundingRect())
6️⃣ Canvas: Zoom, Drag, Rotate
We extend QGraphicsView to handle mouse‑wheel zoom and ALT + mouse rotation.
class Canvas(QGraphicsView):
def __init__(self, scene, parent=None):
super().__init__(scene, parent)
self.setRenderHint(QPainter.Antialiasing)
self.rotating = False
# ---------- Zoom ----------
def wheelEvent(self, event):
factor = 1.15 if event.angleDelta().y() > 0 else 0.85
self.scale(factor, factor)
# ---------- Rotation ----------
def mousePressEvent(self, event):
if event.modifiers() & Qt.AltModifier and event.button() == Qt.LeftButton:
self.rotating = True
super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
self.rotating = False
super().mouseReleaseEvent(event)
def mouseMoveEvent(self, event):
if self.rotating:
item = self.itemAt(event.position().toPoint())
if isinstance(item, QRItem):
dx = event.scenePosition().x() - item.scenePos().x()
dy = event.scenePosition().y() - item.scenePos().y()
item.rotation_angle = math.degrees(math.atan2(dy, dx))
item.update()
else:
super().mouseMoveEvent(event)
7️⃣ Main Window (QMainWindow)
All UI pieces are assembled here.
class QRForgeStudio(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle(f"{APP_NAME} v{APP_VERSION}")
# Scene & canvas
self.scene = QGraphicsScene(-5000, -5000, 10000, 10000)
self.canvas = Canvas(self.scene)
self.setCentralWidget(self.canvas)
# UI – left dock
self._setup_left_dock()
8️⃣ Left Control Panel (DockWidget)
Mode selector
self.mode = QComboBox()
self.mode.addItems(["Text", "URL", "Wi‑Fi"])
Content input
self.text = QTextEdit()
self.text.setPlaceholderText("Enter content…")
Colour & background controls
self.fill_btn = QPushButton("🎨 Fill Colour")
self.bg_btn = QPushButton("🖌 Background")
self.trans = QCheckBox("Transparent Background")
(Additional Wi‑Fi fields – self.ssid, self.pwd, self.enc – are created in the same dock.)
9️⃣ Adding a QR to the Canvas
def add_qr(self):
mode = self.mode.currentText()
if mode == "Text":
data = self.text.toPlainText()
elif mode == "URL":
data = shorten_url(self.text.toPlainText())
else: # Wi‑Fi
data = wifi_payload(
self.ssid.text(),
self.pwd.text(),
self.enc.currentText(),
)
item = QRItem(data)
self.scene.addItem(item)
item.setPos(0, 0) # centre of the canvas
🔟 Exporting (PNG, SVG, PDF)
PNG export (raster)
def export_png(self, path, width=2000, height=2000):
img = QImage(width, height, QImage.Format_ARGB32)
img.fill(Qt.transparent)
painter = QPainter(img)
self.scene.render(painter)
painter.end()
img.save(path)
SVG export (vector)
def export_svg(self, path, rect=None):
generator = QSvgGenerator()
generator.setFileName(path)
generator.setSize(QSize(2000, 2000))
generator.setViewBox(rect or self.scene.sceneRect())
generator.setTitle(APP_NAME)
generator.setDescription("QRForge PRO SVG export")
painter = QPainter(generator)
self.scene.render(painter)
painter.end()
PDF export (vector)
def export_pdf(self, path, rect=None):
writer = QPdfWriter(path)
writer.setPageSize(QPageSize(QPageSize.A4))
writer.setResolution(300)
painter = QPainter(writer)
self.scene.render(painter, target=writer.pageRect(), source=rect or self.scene.sceneRect())
painter.end()
🎉 Wrap‑Up
You now have a complete, interactive QR‑code design studio built with Python, PySide6, and qrcode. Feel free to:
- Add more effects (gradients, shadows)
- Implement undo/redo with
QUndoStack - Package the app with PyInstaller (
pyinstaller --onefile main.py)
Happy coding! 🚀
QRForge PRO – A Beginner‑Friendly PySide6 QR‑Code Studio
PDF Export (Print‑Ready)
pdf = QPdfWriter(path)
pdf.setPageSize(QPageSize.A4)
pdf.setResolution(300)
Each QR gets its own page.
1️⃣ Entry Point
if __name__ == "__main__":
app = QApplication(sys.argv)
win = QRForgeStudio()
win.show()
sys.exit(app.exec())
🎉 Final Result
You now have:
- A designer‑friendly QR studio
- Vector & print exports
- Interactive canvas controls
- Clean PySide6 architecture
👉 Full repository:
🚀 What to Build Next
- Preset templates
- Logo embedding
- Batch QR generation
- PyInstaller packaging
Happy building! 🧡
About This Tutorial
I’ve created a Dev.to‑style, beginner‑friendly, step‑by‑step tutorial that walks through QRForge PRO from zero to a complete app.
What this version does differently (on purpose)
- ✅ Breaks the project into small, digestible sections
- ✅ Uses short code blocks with explanations right after
- ✅ Explains why each Qt/QR concept exists, not just what it does
- ✅ Keeps it friendly for developers new to PySide6 + GraphicsView
- ✅ Links to the full source‑code clone without dumping unreadable walls of code
Perfect for
- Dev.to publishing
- Medium / Hashnode cross‑posts
- Open‑source project documentation
- Teaching PySide6 visually
