일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- esp32
- 공모주
- 국내주식
- ConnectedHomeIP
- cluster
- 매터
- matter
- 오블완
- 애플
- 월패드
- MQTT
- homebridge
- 배당
- Home Assistant
- SK텔레콤
- raspberry pi
- Python
- Apple
- RS-485
- Bestin
- Espressif
- 미국주식
- 해외주식
- 나스닥
- 홈네트워크
- 현대통신
- 코스피
- 파이썬
- 힐스테이트 광교산
- 티스토리챌린지
- Today
- Total
YOGYUI
PyQt5 - QtWebEngine::웹브라우저 만들기 (3) 본문
4. 네비게이션 툴바 만들기
북마크바를 만들려고 하다보니, 탭 안의 위젯이 url 에디트 등의 컨트롤들을 각각 모두 가지고 있는 것이 불합리해보여서 별도로 네비게이션 툴바를 만들었다 (QToolBar 상속)
[NavigationWidget.py]
from PyQt5.QtCore import pyqtSignal, QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QToolBar, QToolButton, QLineEdit
class NavigationToolBar(QToolBar):
_is_loading: bool = False
sig_navigate_url = pyqtSignal(str)
sig_go_backward = pyqtSignal()
sig_go_forward = pyqtSignal()
sig_reload = pyqtSignal()
sig_stop = pyqtSignal()
sig_go_home = pyqtSignal()
def __init__(self, parent=None):
super().__init__('Navigation', parent=parent)
self.editUrl = QLineEdit()
self.btnBackward = QToolButton()
self.btnForward = QToolButton()
self.btnReload = QToolButton()
self.btnHome = QToolButton()
self._iconRefresh = QIcon('./Resource/reload.png')
self._iconStop = QIcon('./Resource/cancel.png')
self.initControl()
self.initLayout()
stylesheet = "QToolBar {border: 0x;}"
self.setStyleSheet(stylesheet)
def initLayout(self):
self.setIconSize(QSize(22, 22))
self.addWidget(self.btnBackward)
self.addWidget(self.btnForward)
self.addWidget(self.btnReload)
self.addWidget(self.btnHome)
self.addWidget(self.editUrl)
self.editUrl.setFixedHeight(24)
font = self.editUrl.font()
font.setPointSize(11)
self.editUrl.setFont(font)
def initControl(self):
self.editUrl.returnPressed.connect(self.onEditUrlReturnPressed)
self.btnBackward.setEnabled(False)
self.btnBackward.clicked.connect(self.sig_go_backward.emit)
self.btnBackward.setIcon(QIcon('./Resource/previous.png'))
self.btnBackward.setToolTip('Previous')
self.btnForward.setEnabled(False)
self.btnForward.clicked.connect(self.sig_go_forward.emit)
self.btnForward.setIcon(QIcon('./Resource/forward.png'))
self.btnForward.setToolTip('Forward')
self.btnReload.clicked.connect(self.onClickBtnStopRefresh)
self.btnReload.setIcon(self._iconRefresh)
self.btnReload.setToolTip('Reload')
self.btnHome.clicked.connect(self.sig_go_home.emit)
self.btnHome.setIcon(QIcon('./Resource/home.png'))
self.btnHome.setToolTip('Home')
def onEditUrlReturnPressed(self):
self.sig_navigate_url.emit(self.editUrl.text())
def onClickBtnStopRefresh(self):
if self._is_loading:
self.sig_stop.emit()
else:
self.sig_reload.emit()
def setEditUrlFocused(self):
self.editUrl.setFocus()
self.editUrl.selectAll()
def setIsLoading(self, loading: bool):
self._is_loading = loading
if loading:
self.btnReload.setIcon(self._iconStop)
else:
self.btnReload.setIcon(self._iconRefresh)
기존 위젯 (WebPageWidget)의 네비게이션 관련 컨트롤들은 모두 제거한 후, 윈도우(WebBrowserWindow)에 네비게이션 툴바 관련 객체 추가 및 시그널들을 연결해줬다 (메뉴바도 추가)
[WebBrowserWindow.py]
# ...
from NavigationWidget import NavigationToolBar
from PyQt5.QtWidgets import QMenuBar, QMenu, QAction
class WebBrowserWindow(QMainWindow):
_mb_show_navbar: QAction
def __init__(self, parent=None, init_url: Union[str, QUrl, None] = 'about:blank'):
# ...
self._navBar = NavigationToolBar(self)
self._menuBar = QMenuBar(self)
self.initMenuBar()
def initControl(self):
self.addToolBar(Qt.TopToolBarArea, self._navBar)
self._navBar.sig_navigate_url.connect(self.onNavBarNavitageUrl)
self._navBar.sig_go_backward.connect(self.onNavBarGoBackward)
self._navBar.sig_go_forward.connect(self.onNavBarGoForward)
self._navBar.sig_reload.connect(self.onNavBarReload)
self._navBar.sig_stop.connect(self.onNavBarStop)
self._navBar.sig_go_home.connect(self.onNavBarGoHome)
def initMenuBar(self):
self.setMenuBar(self._menuBar)
menuFile = QMenu('File', self._menuBar)
self._menuBar.addAction(menuFile.menuAction())
mb_close = makeQAction(parent=self, text='Close', triggered=self.close)
menuFile.addAction(mb_close)
menuView = QMenu('View', self._menuBar)
self._menuBar.addAction(menuView.menuAction())
self._mb_show_navbar = makeQAction(parent=self, text='Navigation Bar', checkable=True, triggered=self.toggleNavigationBar)
menuView.addAction(self._mb_show_navbar)
menuView.aboutToShow.connect(self.onMenuViewAboutToShow)
def onMenuViewAboutToShow(self):
self._mb_show_navbar.setChecked(self._navBar.isVisible())
def onNavBarNavitageUrl(self, url: str):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
curwgt.load(url)
def onNavBarGoBackward(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
curwgt.view().back()
def onNavBarGoForward(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
curwgt.view().forward()
def onNavBarReload(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
curwgt.view().reload()
def onNavBarStop(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
curwgt.view().stop()
def onNavBarGoHome(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
curwgt.load(self._config.url_home)
def refreshNavBarState(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
history = curwgt.view().history()
self._navBar.btnBackward.setEnabled(history.canGoBack())
self._navBar.btnForward.setEnabled(history.canGoForward())
def onPageLoadStarted(self, view: WebPageWidget):
curwgt = self._tabWidget.currentWidget()
if curwgt == view:
self._navBar.setIsLoading(True)
self.refreshNavBarState()
def onPageLoadFinished(self, view: WebPageWidget):
curwgt = self._tabWidget.currentWidget()
if curwgt == view:
self._navBar.setIsLoading(False)
self.refreshNavBarState()
def toggleNavigationBar(self):
if self._navBar.isVisible():
self._navBar.hide()
else:
self._navBar.show()
5. 북마크 툴바 만들기
북마크 바도 네비게이션 바와 똑같이 QToolBar를 상속해서 만들어보자
(테스트를 위해 네이버와 구글 아이템을 수동으로 추가)
[BookMarkWidget.py]
import urllib.request
from functools import partial
from PyQt5.QtCore import Qt, pyqtSignal, QSize
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QToolBar, QToolButton
class BookMarkToolBar(QToolBar):
sig_navitage = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__('Bookmark', parent=parent)
stylesheet = "QToolBar {border: 0px; spacing: 0px;}"
self.setStyleSheet(stylesheet)
self._bookmarks = list()
self._bookmarks.append({
'url': 'https://www.naver.com/',
'title': 'NAVER',
'icon_url': 'https://www.naver.com/favicon.ico?1',
})
self._bookmarks.append({
'url': 'https://www.google.com/',
'title': 'Google',
'icon_url': 'https://www.google.com/favicon.ico',
})
self.setIconSize(QSize(18, 18))
self.drawItems()
def drawItems(self):
self.clear()
for item in self._bookmarks:
data = urllib.request.urlopen(item.get('icon_url')).read()
pixmap = QPixmap()
pixmap.loadFromData(data)
icon = QIcon(pixmap)
btn = QToolButton()
btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
btn.setIcon(icon)
btn.setText(item.get('title'))
btn.setToolTip(item.get('title'))
btn.clicked.connect(partial(self.sig_navitage.emit, item.get('url')))
self.addWidget(btn)
브라우저 윈도우에 툴바를 추가해주자
from BookMarkWidget import BookMarkToolBar
class WebBrowserWindow(QMainWindow):
_mb_show_bookmark: QAction
def __init__(self, parent=None, init_url: Union[str, QUrl, None] = 'about:blank'):
# ...
self._bookmarkBar = BookMarkToolBar(self)
def initControl(self):
self.addToolBar(Qt.TopToolBarArea, self._navBar)
# ...
self.addToolBarBreak(Qt.TopToolBarArea)
self.addToolBar(Qt.TopToolBarArea, self._bookmarkBar)
self._bookmarkBar.sig_navitage.connect(self.onNavBarNavitageUrl)
def initMenuBar(self):
# ...
self._mb_show_bookmark = makeQAction(parent=self, text='Bookmark Bar', checkable=True, triggered=self.toggleBookMarkBar)
menuView.addAction(self._mb_show_bookmark)
def onMenuViewAboutToShow(self):
self._mb_show_navbar.setChecked(self._navBar.isVisible())
self._mb_show_bookmark.setChecked(self._bookmarkBar.isVisible())
def toggleBookMarkBar(self):
if self._bookmarkBar.isVisible():
self._bookmarkBar.hide()
else:
self._bookmarkBar.show()
이제 북마크 관리 기능을 만들어보자
5.1. 북마크 매니저 만들기
BookMarkItem, BookMarkManager 클래스 구현
[BookMarkWidget.py]
import urllib.request
from typing import List
from functools import partial
from PyQt5.QtCore import Qt, pyqtSignal, QSize, QObject
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QToolBar, QToolButton
class BookMarkItem:
def __init__(self, url: str, title: str, icon_url: str):
self.url = url
self.title = title
self.icon_url = icon_url
class BookMarkManager(QObject):
sig_changed = pyqtSignal()
def __init__(self):
super().__init__()
self.bookmarks: List[BookMarkItem] = list()
def urlList(self) -> List[str]:
return [x.url for x in self.bookmarks]
def isExist(self, url: str):
return url in self.urlList()
def add(self, url: str, title: str, icon_url: str):
self.bookmarks.append(BookMarkItem(url, title, icon_url))
self.sig_changed.emit()
def remove(self, url: str):
find = list(filter(lambda x: x.url == url, self.bookmarks))
if len(find) >= 1:
self.bookmarks.remove(find[0])
self.sig_changed.emit()
class BookMarkToolBar(QToolBar):
sig_navitage = pyqtSignal(str)
def __init__(self, manager: BookMarkManager, parent=None):
super().__init__('Bookmark', parent=parent)
stylesheet = "QToolBar {border: 0px; spacing: 0px;}"
self.setStyleSheet(stylesheet)
self._manager = manager
self._manager.sig_changed.connect(self.drawItems)
self.setIconSize(QSize(18, 18))
self.drawItems()
def drawItems(self):
self.clear()
for item in self._manager.bookmarks:
try:
data = urllib.request.urlopen(item.icon_url).read()
pixmap = QPixmap()
pixmap.loadFromData(data)
icon = QIcon(pixmap)
btn = QToolButton()
btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
btn.setIcon(icon)
btn.setText(item.title)
btn.setToolTip(item.title)
btn.clicked.connect(partial(self.sig_navitage.emit, item.url))
self.addWidget(btn)
except Exception:
pass
- 툴바 아이템은 버튼에 표시할 타이틀(title), 실제 네비게이션할 주소(url), 표시할 아이콘 주소(icon_url) 멤버변수를 갖도록 구현했다
- 툴바에 아이템(QToolButton)을 추가할 때, 지정된 아이콘 URL을 urllib 패키지의 request를 사용해서 데이터를 읽어온 뒤 QPixmap으로 변환하여 아이콘을 만드는 방법을 사용했다
- 북마크 매니저의 아이템 리스트가 변경될 때마다 콜백 시그널을 호출해 툴바가 바로바로 새로 그리도록 구현했다 (합리적인 UX는 아니다...)
5.2. 브라우저 설정 파일 관리
북마크 아이템을 관리하기 위해 로컬에 xml 파일로 저장/불러오기할 수 있도록 WebBrowserConfig 클래스를 구현하자
[ConfigUtil.py]
import os
import xml.etree.ElementTree as ET
from BookMarkWidget import BookMarkManager
from Common import writeXmlFile
class WebBrowserConfig:
def __init__(self, bookmarkManager: BookMarkManager):
curpath = os.path.dirname(os.path.abspath(__file__))
configpath = os.path.join(os.path.dirname(curpath), 'Config')
self.xml_path = os.path.join(configpath, 'config.xml')
self.url_home = 'about:blank'
self.bookmarkManager = bookmarkManager
self.load_from_xml()
def load_from_xml(self):
if not os.path.isfile(self.xml_path):
self.save_to_xml()
xmldata = ET.parse(self.xml_path)
root = xmldata.getroot()
node = root.find('home')
if node is not None:
self.url_home = node.text
node = root.find('bookmarks')
if node is not None:
for child in list(node):
title = child.tag
url = child.attrib.get('url')
icon_url = child.attrib.get('icon_url')
self.bookmarkManager.add(url, title, icon_url)
def save_to_xml(self):
if os.path.isfile(self.xml_path):
xmldata = ET.parse(self.xml_path)
else:
xmldata = ET.ElementTree(ET.Element('BrowserConfig'))
root = xmldata.getroot()
node = root.find('home')
if node is None:
node = ET.Element('home')
root.append(node)
node.text = self.url_home
node = root.find('bookmarks')
if node is None:
node = ET.Element('bookmarks')
root.append(node)
for item in self.bookmarkManager.bookmarks:
child = node.find(item.title)
if child is None:
child = ET.Element(item.title)
node.append(child)
child.attrib['url'] = item.url
child.attrib['icon_url'] = item.icon_url
writeXmlFile(root, self.xml_path, backup=False)
* writeXmlFile 함수 구현 내역은 GitHub 참고
5.3. 네비게이션 바, 브라우저 윈도우 수정
북마크 관련 버튼을 추가해주자
[NavigationWidget.py]
class NavigationToolBar(QToolBar):
# ...
sig_toggle_bookmark = pyqtSignal()
def __init__(self, parent=None):
# ...
self.btnBookmark = QToolButton()
self._iconBookmarkOff = QIcon('./Resource/bookmark_off.png')
self._iconBookmarkOn = QIcon('./Resource/bookmark_on.png')
def initLayout(self):
# ...
self.addWidget(self.btnBookmark)
def initControl(self):
# ...
self.btnBookmark.clicked.connect(self.sig_toggle_bookmark.emit)
self.btnBookmark.setIcon(self._iconBookmarkOff)
self.btnBookmark.setToolTip('BookMark')
def setBookMarkStatus(self, exist: bool):
if exist:
self.btnBookmark.setIcon(self._iconBookmarkOn)
else:
self.btnBookmark.setIcon(self._iconBookmarkOff)
북마크매니저, 북마크바, 네비게이션바의 이벤트를 서로 연결해주자
[WebBrowserWindow.py]
from BookMarkWidget import BookMarkToolBar, BookMarkManager
class WebBrowserWindow(QMainWindow):
def __init__(self, parent=None, init_url: Union[str, QUrl, None] = 'about:blank'):
# ...
self._bookMarkManager = BookMarkManager()
self._config = WebBrowserConfig(self._bookMarkManager)
self._bookmarkBar = BookMarkToolBar(self._bookMarkManager, self)
def release(self):
self.closeWebPageAll()
self._config.save_to_xml()
def onNavBarToggleBookmark(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
url = curwgt.view().url().toString()
if self._bookMarkManager.isExist(url):
self._bookMarkManager.remove(url)
else:
url_icon = curwgt.view().iconUrl().toString()
title = curwgt.view().title()
self._bookMarkManager.add(url, title, url_icon)
self.refreshNavBarState()
def refreshNavBarState(self):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
history = curwgt.view().history()
self._navBar.btnBackward.setEnabled(history.canGoBack())
self._navBar.btnForward.setEnabled(history.canGoForward())
bookmark_url_list = self._bookMarkManager.urlList()
self._navBar.setBookMarkStatus(curwgt.view().url().toString() in bookmark_url_list)
- refreshNavBarState 호출 시, 현재 텝 페이지의 URL이 북마크 매니저 아이템 리스트에 존재하는지 여부에 따라 네비게이션 툴바 북마크 아이템의 아이콘을 변경하도록 구현했다
/Config/config.xml 파일이 생성되고 북마크 정보가 저장되었으며, 다음 실행부터 해당 북마크가 적용된다
[config.xml]
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<BrowserConfig>
<home>naver.com</home>
<bookmarks>
<NAVER url="https://www.naver.com/" icon_url="https://www.naver.com/favicon.ico?1"/>
<Google url="https://www.google.com/" icon_url="https://www.google.com/favicon.ico"/>
<Daum url="https://www.daum.net/" icon_url="https://www.daum.net/favicon.ico"/>
</bookmarks>
</BrowserConfig>
일반 브라우저처럼 북마크를 사용하기 위해서는 다음 기능들이 추가로 구현되어야 한다
TODO: 북마크 아이템 옮기기, 북마크 폴더 기능, 북마크바 컨텍스트 메뉴
6. 자바스크립트 실행
QWebEnginePage는 자바스크립트 실행을 위한 runJavaScript 메서드를 제공한다
콜백함수를 등록하면 실행결과를 QVariant 객체로 리턴받을 수 있다고 한다
가이드를 따라 간단하게 구현해보자
개발자도구로 활용할 위젯을 하나 만들었다
[DeveloperWidget.py]
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QTextEdit, QPushButton, QLineEdit
from PyQt5.QtWidgets import QVBoxLayout, QGroupBox, QSizePolicy
class DeveloperWidget(QWidget):
sig_run_js = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent=parent)
self._editJavaScript = QTextEdit()
self._btnRunJavaScript = QPushButton('RUN')
self._editJsResult = QLineEdit()
self.initControl()
self.initLayout()
def initLayout(self):
vbox = QVBoxLayout(self)
vbox.setContentsMargins(4, 4, 4, 4)
vbox.setSpacing(4)
grbox = QGroupBox('JavaScript')
grbox.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
vbox_gr = QVBoxLayout(grbox)
vbox_gr.setContentsMargins(4, 4, 4, 4)
vbox_gr.setSpacing(4)
vbox_gr.addWidget(self._editJavaScript)
vbox_gr.addWidget(self._btnRunJavaScript)
vbox_gr.addWidget(self._editJsResult)
vbox.addWidget(grbox)
vbox.addWidget(QWidget())
def initControl(self):
self._editJavaScript.setLineWrapColumnOrWidth(-1)
self._editJavaScript.setLineWrapMode(QTextEdit.FixedPixelWidth)
self._btnRunJavaScript.clicked.connect(self.onClickBtnRunJavaScript)
self._editJsResult.setReadOnly(True)
def onClickBtnRunJavaScript(self):
script = self._editJavaScript.toPlainText()
self.sig_run_js.emit(script)
def setJsResult(self, obj: object):
self._editJsResult.setText(str(obj))
WebPageWidget에 자바스크립트 관련 구문을 추가해주자
[WebPageWidget.py]
class WebPageWidget(QWidget):
sig_js_result = pyqtSignal(object)
# ...
def runJavaScript(self, script: str):
self.view().page().runJavaScript(script, self.jsCallback)
def jsCallback(self, v: QVariant):
self.sig_js_result.emit(v)
브라우저 윈도우의 central widget을 QSplitter로 바꾼 뒤, Splitter에 탭 위젯과 개발자 위젯을 배치한 뒤 시그널들을 연결해주자
[WebBrowserWindow.py]
from PyQt5.QtWidgets import QSplitter
from DeveloperWidget import DeveloperWidget
class WebBrowserWindow(QMainWindow):
_mb_show_devtool: QAction
def __init__(self, parent=None, init_url: Union[str, QUrl, None] = 'about:blank'):
# ...
self._splitter = QSplitter(Qt.Horizontal, self)
self._tabWidget = CustomTabWidget()
self._devWidget = DeveloperWidget()
def initLayout(self):
self.setCentralWidget(self._splitter)
self._splitter.addWidget(self._tabWidget)
self._splitter.addWidget(self._devWidget)
self._devWidget.hide()
def initControl(self):
self._splitter.setStyleSheet("QSplitter:handle:horizontal {background:rgb(204,206,219); margin:1px 1px}")
# ...
self._devWidget.sig_run_js.connect(self.runJavaScript)
def initMenuBar(self):
self._mb_show_devtool = makeQAction(parent=self, text='Dev Tool', checkable=True, triggered=self.toggleDevTool)
menuView.addAction(self._mb_show_devtool)
def onMenuViewAboutToShow(self):
self._mb_show_devtool.setChecked(self._devWidget.isVisible())
def toggleDevTool(self):
if self._devWidget.isVisible():
self._devWidget.hide()
else:
self._devWidget.show()
def runJavaScript(self, script: str):
curwgt = self._tabWidget.currentWidget()
if isinstance(curwgt, WebPageWidget):
curwgt.runJavaScript(script)
def onJavaScriptResult(self, obj: object):
self._devWidget.setJsResult(obj)
현재 로드된 페이지의 타이틀을 자바스크립트로 가져와보자 (document.title 호출)
안타깝게도 QWebEnginePage의 자바스크립트 엔진으로 리턴받을 수 있는 객체 타입은 한정적이다
네이버 메인 페이지에서 특정 element의 영역 정보를 가져와보자
document.getElementsByClassName("nav_item")[0].getBoundingClientRect();
예시 코드는 네이버 메인 페이지 네비게이션 아이템 중 '메일' element의 geometry를 가져오는 코드
크롬 개발자도구에서 실행해보자
DOMRect 객체가 반환된다
하지만 QWebEnginePage의 반환결과는
class WebPageWidget(QWidget):
def jsCallback(self, v: QVariant):
print(v, type(v))
self.sig_js_result.emit(v)
{} <class 'dict'>
dict형으로 반환되는데, 내용물은 없다
결과물을 JSON으로 변환하면 일단 dict형으로 제대로 받아볼 수는 있다
document.getElementsByClassName("nav_item")[0].getBoundingClientRect().toJSON();
{'bottom': 251, 'height': 30, 'left': 30, 'right': 89.40625, 'top': 221, 'width': 59.40625, 'x': 30, 'y': 221} <class 'dict'>
한계가 있는만큼 활용도가 높지는 않다
JS를 효과적으로 구동할 수 있는 다른 방법이 있는지 찾아봐야겠다
지금까지의 구현 내용은 다음 깃허브 링크에서 확인할 수 있다
https://github.com/YOGYUI/Projects/tree/rev.3/WebBrowser/python
일단 코어 기능은 그럭저럭 완성했으니 이제 쉬엄쉬엄 업데이트해나갈 예정이다
(블로그 포스트는 안할지도...)
'Software > Python' 카테고리의 다른 글
PyQt5 - QMenuBar location in macOS (0) | 2021.09.17 |
---|---|
PyQt5 - QtWebEngine Chromium Version 확인하기 (0) | 2021.09.10 |
PyQt5 - QtWebEngine::웹브라우저 만들기 (2) (0) | 2021.09.05 |
PyQt5 - QtWebEngine::웹브라우저 만들기 (1) (8) | 2021.09.03 |
Python - str format 중괄호 (brace) 출력하기 (0) | 2021.08.25 |