๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
IT

ํŒŒ์ด์ฌ ์›น ๋ธŒ๋ผ์šฐ์ € – PySide6์œผ๋กœ ์ฃผ์†Œ์ฐฝ๊ณผ ํ™•๋Œ€/์ถ•์†Œ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ

๋ฐ˜์‘ํ˜•

PySide6์œผ๋กœ ๋‚˜๋งŒ์˜ ํŒŒ์ด์ฌ ์›น๋ธŒ๋ผ์šฐ์ € ๋งŒ๋“ค๊ธฐ

ํŒŒ์ด์ฌ์œผ๋กœ ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ง์ ‘ ๋งŒ๋“ค๊ณ , ์›น ๋ธŒ๋ผ์šฐ์ € ์ƒ๋‹จ์— ์ฃผ์†Œ์ฐฝ๊ณผ ํ™•๋Œ€/์ถ•์†Œ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ ์ž๋ฃŒ์ž…๋‹ˆ๋‹ค. ์›น ๋ธŒ๋ผ์šฐ์ € ์ œ์ž‘ ์‹œ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ์ฝ”๋“œ์ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์›น ๋ธŒ๋ผ์šฐ์ €๋Š” ์šฐ๋ฆฌ๊ฐ€ ์ผ์ƒ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ํฌ๋กฌ(Chrome), ์—ฃ์ง€(Edge), ํŒŒ์ด์–ดํญ์Šค(Firefox)์™€ ๊ฐ™์€ ํ”„๋กœ๊ทธ๋žจ๋“ค์ž…๋‹ˆ๋‹ค.

์ด ํ”„๋กœ๊ทธ๋žจ๋“ค์€ ์›น ์„œ๋ฒ„์—์„œ ๋ฐ›์€ HTML, CSS, JavaScript๋ฅผ ํ•ด์„ํ•˜์—ฌ ์šฐ๋ฆฌ๊ฐ€ ๋ณด๋Š” ์›น ํŽ˜์ด์ง€ ํ˜•ํƒœ๋กœ ํ‘œ์‹œํ•ด์ค๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์ง์ ‘ ์›น๋ธŒ๋ผ์šฐ์ €๋ฅผ ํŒŒ์ด์ฌ์œผ๋กœ ์ œ์ž‘ํ•˜๊ณ  ์›น ์ฃผ์†Œ(URL)๋ฅผ ์ž…๋ ฅํ•˜๊ณ , ํŽ˜์ด์ง€๋ฅผ ํ™•๋Œ€ํ•˜๊ฑฐ๋‚˜ ์ถ•์†Œํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ์ฃผ์†Œ์ฐฝ๊ณผ ์คŒ ์ปจํŠธ๋กค ํŒจ๋„์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

PySide6์œผ๋กœ ๋‚˜๋งŒ์˜ ํŒŒ์ด์ฌ ์›น๋ธŒ๋ผ์šฐ์ € ๋งŒ๋“ค๊ธฐ

ํŒŒ์ด์ฌ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์›น ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

PyQt5 / PySide6 ํŒŒ์ด์ฌ GUI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

PyQt5 / PySide6 ๋‘˜ ๋‹ค Qt๋ผ๋Š” GUI ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํŒŒ์ด์ฌ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ํŒŒ์ด์ฌ์œผ๋กœ ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค๋‹ค ๋ณด๋ฉด ๋‹จ์ˆœํ•œ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์ฝ˜์†” ํ™”๋ฉด๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿด ๋•Œ "์œˆ๋„์šฐ ์ฐฝ์ด ์žˆ๋Š” ํ”„๋กœ๊ทธ๋žจ", ์ฆ‰ ๊ทธ๋ž˜ํ”ฝ ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(GUI)๊ฐ€ ํ•„์š”ํ•  ๋•Œ๊ฐ€ ์žˆ๋Š”๋ฐ, ๊ทธ๋Ÿด ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ํŒŒ์ด์ฌ ๋„๊ตฌ๊ฐ€ ๋ฐ”๋กœ PyQt5์™€ PySide6์ž…๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ PyQt5 PySide6
๊ฐœ๋ฐœ์‚ฌ Riverbank Qt Company (Qt ๊ณต์‹)
๋ผ์ด์„ ์Šค GPL ๋˜๋Š” ์ƒ์šฉ LGPL (์ƒ์—…์šฉ์—๋„ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
๋ฒ„์ „ Qt 5 ๊ธฐ๋ฐ˜ Qt 6 ๊ธฐ๋ฐ˜
์„ค์น˜ pip install pyqt5 pip install pyside6
๊ธฐ๋Šฅ ๊ฑฐ์˜ ๋™์ผ (๋ฒ„ํŠผ, ์ฐฝ, ์ž…๋ ฅ์ฐฝ, ๋ ˆ์ด์•„์›ƒ ๋“ฑ ๋ชจ๋‘ ๊ฐ€๋Šฅ) ๊ฑฐ์˜ ๋™์ผ (๋ฒ„ํŠผ, ์ฐฝ, ์ž…๋ ฅ์ฐฝ, ๋ ˆ์ด์•„์›ƒ ๋“ฑ ๋ชจ๋‘ ๊ฐ€๋Šฅ)

๊ฐœ์ธ ํ•™์Šต ๋ฐ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์— ์ ํ•ฉํ•œ ์„ ํƒ

  • ๊ฐœ์ธ ๊ณต๋ถ€์šฉ, ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ๋‘˜ ์ค‘ ์•„๋ฌด๊ฑฐ๋‚˜ ์จ๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค.
  • ์ƒ์—…์šฉ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๋งŒ๋“ค ์˜ˆ์ •์ด๋ผ๋ฉด PySide6์ด ๋” ์œ ๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ผ์ด์„ ์Šค ์ œํ•œ์ด ๋œํ•ฉ๋‹ˆ๋‹ค.
  • ์ตœ์‹  ๊ธฐ์ˆ ๊ณผ ์žฅ๊ธฐ์ ์ธ ์ง€์›์„ ๊ณ ๋ คํ•œ๋‹ค๋ฉด PySide6์ด ๋” ์ถ”์ฒœ๋ฉ๋‹ˆ๋‹ค. (Qt 6 ๊ธฐ๋ฐ˜)
๋ฐ˜์‘ํ˜•

Qt ํ”„๋ ˆ์ž„์›Œํฌ๋กœ GUI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœํ•˜๊ธฐ

  • Qt ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋ฐ˜ GUI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.
  • ๋‚ด๋ถ€์ ์œผ๋กœ QWebEngineView ๋ผ๋Š” ์›น ๋ทฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ์›นํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์‹ค์ œ๋กœ ํฌ๋กฌ ์—”์ง„(Chromium) ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๊ธฐ์— ๋งค์šฐ ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค.

๊ฐ€์žฅ ์ถ”์ฒœํ•˜๋Š” ๋ฐฉ๋ฒ•์€ PySide6 ๋˜๋Š” PyQt5๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ํฌ๋กฌ ๊ธฐ๋ฐ˜ ์›น ๋ทฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋ธŒ๋ผ์šฐ์ € ํ˜•ํƒœ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŒŒ์ด์ฌ ์›น๋ทฐ์ฐฝ ๋งŒ๋“ค๊ธฐ

  • URL ์ž…๋ ฅ์ฐฝ ๋งŒ๋“ค๊ธฐ
  • ์ด์ „/๋‹ค์Œ ์ด๋™, ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ ๊ตฌํ˜„
  • ํŽ˜์ด์ง€ ํ™•๋Œ€/์ถ•์†Œ (Zoom In/Out) ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • ์ฃผ์†Œ ์ž๋™ ์—…๋ฐ์ดํŠธ ๊ตฌํ˜„

[ ์ด๋ฏธ์ง€ ๋ณ€ํ™˜ ํˆด ์†Œ๊ฐœ ] ํฌํ† ์ƒต ์—†์ด ๋ธ”๋กœ๊ทธ์— ํ™œ์šฉํ•  ์ด๋ฏธ์ง€๋ฅผ ์†์‰ฝ๊ฒŒ ๋ณ€ํ™˜ํ•˜๋Š” ํŒ

์ฝ”๋”ฉํ•˜๊ธฐ

1. PySide6 ์„ค์น˜

pip install PySide6

2. ์›น ์—”์ง„ ๋ชจ๋“ˆ ํฌํ•จ ํ™•์ธ

PySide6์—๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” QWebEngineView๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

3. ์ „์ฒด ์ฝ”๋“œ ์˜ˆ์ œ (์ฃผ์†Œ์ฐฝ + ์คŒ ์ปจํŠธ๋กค)

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLineEdit
)
from PySide6.QtWebEngineWidgets import QWebEngineView
from PySide6.QtCore import QUrl

class BrowserWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PySide6 ์›น ๋ธŒ๋ผ์šฐ์ €")
        self.resize(1000, 800)

        self.webview = QWebEngineView()
        self.webview.setUrl(QUrl("https://www.google.com"))

        layout = QVBoxLayout(self)
        layout.addWidget(self._create_url_input_with_zoom(self.webview, "URL์„ ์ž…๋ ฅํ•˜์„ธ์š”..."))
        layout.addWidget(self.webview)

    def _load_url(self, webview, url_input):
        url = url_input.text()
        if not url.startswith("http"):
            url = "https://" + url
        webview.setUrl(QUrl(url))

    def _create_url_input_with_zoom(self, webview, placeholder):
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(5, 5, 5, 5)
        widget.setFixedHeight(90)

        # ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ ๋ฐ”
        nav_bar = QHBoxLayout()
        buttons = [
            ("←", webview.back),
            ("→", webview.forward),
            ("โ†ป", webview.reload),
            ("-", lambda: webview.setZoomFactor(webview.zoomFactor() - 0.1)),
            ("+", lambda: webview.setZoomFactor(webview.zoomFactor() + 0.1)),
        ]

        for i, (text, action) in enumerate(buttons):
            btn = QPushButton(text)
            btn.setFixedSize(40, 30)
            btn.clicked.connect(action)
            nav_bar.addWidget(btn)
            if i == 2:
                nav_bar.addStretch()

        # URL ์ž…๋ ฅ ํ•„๋“œ
        url_input = QLineEdit()
        url_input.setPlaceholderText(placeholder)
        url_input.setFixedHeight(50)
        url_input.returnPressed.connect(lambda: self._load_url(webview, url_input))

        layout.addLayout(nav_bar)
        layout.addWidget(url_input)

        def update_url():
            url_input.setText(webview.url().toString())
            nav_bar.itemAt(0).widget().setEnabled(webview.history().canGoBack())
            nav_bar.itemAt(1).widget().setEnabled(webview.history().canGoForward())

        webview.urlChanged.connect(update_url)
        webview.loadFinished.connect(update_url)
        update_url()

        return widget

ํŒŒ์ด์ฌ ์ฝ”๋“œ ์„ค๋ช…

QWebEngineView๋ž€?

QWebEngineView๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €์ฒ˜๋Ÿผ ์›น ํŽ˜์ด์ง€๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์œ„์ ฏ์ž…๋‹ˆ๋‹ค. ๊ตฌ๊ธ€, ๋„ค์ด๋ฒ„, ์œ ํŠœ๋ธŒ ๋“ฑ ์‹ค์ œ ์›น์‚ฌ์ดํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์š”. ๋‹จ ์™„๋ฒฝํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

URL ์ž…๋ ฅ์ฐฝ (QLineEdit)

  • ์‚ฌ์šฉ์ž๊ฐ€ ์›น ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ๊ณณ์ž…๋‹ˆ๋‹ค.
  • returnPressed.connect()๋ฅผ ์‚ฌ์šฉํ•ด ์—”ํ„ฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ž…๋ ฅ๋œ URL๋กœ ์ด๋™ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  • ์ฃผ์†Œ์ฐฝ์— https://๊ฐ€ ์—†์œผ๋ฉด ์ž๋™์œผ๋กœ ๋ถ™์—ฌ์ค๋‹ˆ๋‹ค.

๋’ค๋กœ, ์•ž์œผ๋กœ, ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ

  • ๊ฐ๊ฐ ์›น๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ๊ณผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
    • webview.back()
    • webview.forward()
    • webview.reload()

ํ™”๋ฉด ํ™•๋Œ€/์ถ•์†Œ (Zoom In/Out)

  • webview.zoomFactor()๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฐฐ์œจ์„ ์กฐ์ ˆํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ ๋ฐฐ์œจ์€ 1.0 (100%)์ด๊ณ , ์—ฌ๊ธฐ์— +0.1 ๋˜๋Š” -0.1์”ฉ ๊ฐ€๊ฐํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ: 1.2๋ฉด 120%, 0.8์ด๋ฉด 80%

์ฃผ์†Œ ์ž๋™ ๊ฐฑ์‹ 

์›น ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ์ฃผ์†Œ์ฐฝ์ด ์ž๋™์œผ๋กœ ๋ฐ”๋€Œ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ์‹œ๊ทธ๋„์„ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค:

webview.urlChanged.connect(update_url)
webview.loadFinished.connect(update_url)

์‹คํ–‰ ์ฝ”๋“œ

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

if __name__ == "__main__":
    import sys
    from PySide6.QtWidgets import QApplication

    app = QApplication(sys.argv)
    browser = BrowserWidget()
    browser.show()
    sys.exit(app.exec())
๋ฐ˜์‘ํ˜•

'IT' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

QWebEngineView ์›น ๊ธฐ๋Šฅ 100% ํ™œ์šฉ๋ฒ• PySide6 ๋ธŒ๋ผ์šฐ์ €  (4) 2025.04.18
ํŒŒ์ด์ฌ PySide6 ์›น๋ทฐ์—์„œ ๋งํฌ๊ฐ€ ์•ˆ ์—ด๋ฆด ๋•Œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•  (5) 2025.04.17
๋ธ”๋กœ๊ทธ ์ฝ˜ํ…์ธ  ๊ด€๋ฆฌ ํ”„๋กœ๊ทธ๋žจ v2 ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ! ์ž๋™ํ™” ๋„๊ตฌ์™€ IPTV ๊ธฐ๋Šฅ  (5) 2025.04.15
AI๋กœ ๋ธ”๋กœ๊ทธ ๊ธ€ ์ œ์ž‘์‹œ, ๋ชจ๋ฅด๋ฉด ๋ฌด์กฐ๊ฑด ์†ํ•ด ๋ณด๋Š” '๊ผญ' ์•Œ์•„์•ผ ํ•  ๊ฟ€ํŒ ๊ณต๊ฐœ!  (18) 2025.04.11
๋ธ”๋กœ๊ทธ์— SNS ๊ณต์œ  ๋ฒ„ํŠผ ๋„ฃ๋Š” ์‰ฌ์šด ๋ฐฉ๋ฒ• (์นด์นด์˜คํ†ก/ํŽ˜์ด์Šค๋ถ/ํŠธ์œ„ํ„ฐ)  (7) 2025.04.07
SAP ๋ฐฑ์—… ์‹œ์Šคํ…œ ๊ตฌ์ถ•์— ์ตœ์ ํ™” ๋œ NAS ์ œํ’ˆ์†Œ๊ฐœ ๋ฐ ๋ฐฉ๋ฒ• ์†Œ๊ฐœ  (5) 2025.04.07
์ค‘์†Œ๊ธฐ์—…์„ ์œ„ํ•œ ์™„๋ฒฝํ•œ ๋ฐฑ์—… ์‹œ์Šคํ…œ NAS ๊ตฌ์ถ• ๊ฐ€์ด๋“œ " ์•ˆ์ „ํ•œ ๋ฐฑ์—…์œผ๋กœ ์ž๋ฃŒ ๋ณด๊ด€ํ•˜๊ธฐ"  (4) 2025.04.07