🧱 Building QRForge PRO with PySide6 (Beginner-Friendly Guide)

Published: (January 16, 2026 at 12:59 AM EST)
5 min read
Source: Dev.to

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

Preview

QR code design

Back to Blog

Related posts

Read more »