예전에 python ui 만들때는 간단하게 tkinter로 구현했었는데 이번에 만들 프로그램은 pyqt를 사용해서 구현해봤다.
pyqt를 사용한 이유는 프로그램 ui 구상 단계에서 tkinter로 구현할 수 없는 기능이 포함되어 있었던 것 같기도 한데... 만들다 보니까 그게 뭐였는지 까먹었단 말이지...?
그래서 지금와서 생각해보면 tkinter로 만들었으면 훨씬 빨리 만들었을 것 같기도 하고...
뭐, pyqt라는걸 사용해봤다는 것에 의의를 두자.
아무튼 pyqt를 사용해서 ui를 구현하면서 가장 어려웠던 게 스크롤 영역이다.
어차피 나중에 쓸 일 있으면 복붙하면 되니까 적당히 코드만 올려놔도 상관없겠지.
먼저 기본적인 스크롤 영역 생성하는 예제다.
'''
[99] scroll test 스크롤 예제 ok
'''
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QFrame, QVBoxLayout, QLabel, QWidget, QPushButton, QScrollArea, QGridLayout
from PyQt5.QtGui import QColor
import random
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# main window 설정
self.setGeometry(100, 100, 1000, 800)
self.setStyleSheet("background-color: #f0e4e9;")
# frame_a 생성 및 스타일 설정
frame_a = QFrame(self)
frame_a.setGeometry(10, 10, 1000, 800)
frame_a.setStyleSheet("background-color: #d0e6ff; border-radius: 15px;")
# frame_b 생성 및 스타일 설정 (frame_a의 하위 프레임)
frame_b = QFrame(frame_a)
frame_b.setGeometry(100, 100, 800, 300)
frame_b.setStyleSheet("background-color: #c4fff9; border-radius: 15px;")
# QScrollArea 생성
scroll_area = QScrollArea(frame_b)
scroll_area.setGeometry(0, 0, 800, 300)
# 스크롤 가능한 컨테이너 위젯 생성
scroll_content = QWidget()
scroll_area.setWidget(scroll_content)
# 컨테이너 위젯에 격자 레이아웃 추가
layout = QGridLayout(scroll_content)
# 스크롤을 테스트하기 위한 내용 추가 (50개의 frame)
for i in range(50):
# 무작위 색상 생성
color = QColor(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
# frame 생성 및 스타일 설정
frame = QFrame()
frame.setFixedSize(50, 50)
frame.setStyleSheet(f"background-color: {color.name()};")
layout.addWidget(frame, i // 5, i % 5) # 격자 위치 설정
# 스크롤 영역의 크기를 컨텐츠에 맞게 조정
scroll_content.resize(scroll_content.sizeHint())
# 수직 스크롤바 숨김
scroll_area.setVerticalScrollBarPolicy(1) # 1은 숨김을 의미
def main():
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
코드를 실행하면 50개의 위젯이 배치되며 위젯의 배치는 가로 5열씩 n줄로 설정했다.
내가 하고싶었던 것은 저 배치된 위젯의 내용이 변경되면 업데이트해서 보여주는 건데 그 과정이 꽤 힘들었단 말이지.
코드에서는 scroll_area라는 영역에 scroll_content를 보여주고 있다.
scroll_content에는 layout이라는 그리드 레이아웃을 사용해서 frame 위젯들을 격자 모양으로 배치허고 있다.
여기까지 봤을 때, 그냥 일반적으로 scroll_area의 내용을 변경하려면 그냥 scroll_content를 초기화 하면 되잖아?
scroll_content를 초기화해서 다시 scroll_area에 집어넣어주면 될 거라고 생각했다.
scroll_content = QWidget()
...
결론부터 말하면 안된다.
pyqt에서는 위젯에 포함된 자식 위젯이 있으면 부모 위젯이 삭제가 안된다나?
그럼 scroll_content의 자식 위젯들은 어떻게 삭제하는지 찾아보니 아래처럼 위젯 수만큼 하나씩 삭제해줘야 한다...네?
for i in reversed(range(self.layout.count())):
self.layout.itemAt(i).widget().setParent(None)
어쩄든 우여곡절 끝에 자식 위젯을 삭제했다.
그럼 이제 다시 처음 하려고 했던 scroll_content를 삭제하고 scroll_area에 새로운 content를 붙이려고 했다.
처음에 높이가 100이고 5개의 위젯이 4열로 총 20개 위젯이 배치되어 있었고,
위젯의 개수가 늘어나서 5개의 위젯을 6열로 30개 위젯을 배치하라고 시키면 자동으로 높이를 늘려서 자연스러운 스크롤 영역을 구현해 줄 것이라고 기대했는데...
여전히 안된다.
역시 정확하게 왜 그런건지는 모르겠지만... scroll_area의 size가 자동으로 변경되지 않는 문제가 있다.
아래의 sizeHint() 가 변경된 레이아웃의 크기를 반영하는 게 아니라 이전에 계산된 크기를 그대로 반영한다고 하는데.
# 스크롤 영역의 크기를 컨텐츠에 맞게 조정
scroll_content.resize(scroll_content.sizeHint())
음 그러니까... 설명이 어렵네...
이렇게 된다.
이 문제를 해결하려면 scroll_area의 resizeable 속성을 활성화해주면 된다.
그리고 resize 속성을 활성화했으면 sizeHint() 메서드는 필요없으니 지워주자.
# 자동으로 자식 위젯의 크기에 맞게 조절
scroll_area.setWidgetResizable(True)
먼저 구현이 완료된 코드는 이렇다.
'''
[99] scroll update 로직 ok
'''
from PyQt5.QtWidgets import QMainWindow, QApplication, QFrame, QPushButton, QScrollArea, QWidget, QGridLayout
from PyQt5.QtGui import QColor
import sys
import random
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(100, 100, 1000, 800)
self.setStyleSheet("background-color: #f0e4e9;")
frame_a = QFrame(self)
frame_a.setGeometry(10, 10, 1000, 800)
frame_a.setStyleSheet("background-color: #d0e6ff; border-radius: 15px;")
self.button = QPushButton('Change Content', frame_a)
self.button.setGeometry(100, 400, 200, 50)
self.button.clicked.connect(self.change_content)
frame_b = QFrame(frame_a)
frame_b.setGeometry(100, 100, 800, 300)
frame_b.setStyleSheet("background-color: #c4fff9; border-radius: 15px;")
self.scroll_area = QScrollArea(frame_b)
self.scroll_area.setGeometry(0, 0, 500, 300)
self.scroll_area.setWidgetResizable(True)
self.scroll_content = QWidget()
self.scroll_area.setWidget(self.scroll_content)
self.layout = QGridLayout(self.scroll_content)
self.cnt = 10
def add_frames(self):
for i in range(20):
color = QColor(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
frame = QFrame()
frame.setFixedSize(50, 50)
frame.setStyleSheet(f"background-color: {color.name()};")
self.layout.addWidget(frame, i // 5, i % 5)
def change_content(self):
for i in reversed(range(self.layout.count())):
self.layout.itemAt(i).widget().setParent(None)
for i in range(self.cnt):
color = QColor(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
frame = QFrame()
frame.setFixedSize(50, 50)
frame.setStyleSheet(f"background-color: {color.name()};")
self.layout.addWidget(frame, i // 5, i % 5)
self.cnt += 5
widget_count = self.layout.count()
rows = (widget_count + 4) // 5
spacing = 10
self.layout.setVerticalSpacing(spacing)
self.layout.setHorizontalSpacing(spacing)
self.layout.setRowStretch(rows, 1)
self.layout.activate()
self.scroll_area.ensureWidgetVisible(self.scroll_content)
self.updateGeometry()
def main():
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
테스트를 위해 change content 버튼을 클릭하면 5n개의 위젯이 스크롤 영역에 업데이트 되도록 했다.
5개 -> 10개 -> 15개 -> ... 이런식이다.
사진으로만 보면 5개를 추가하고 그 다음 5개를 추가하는 식으로 보일텐데,
실제로는 5개 추가 -> 5개 제거 후 10개 추가 -> 10개 제거 후 15개 추가 -> ... 이런식이다.
마지막으로 resize option만 추가했을 때 위젯이 업데이트 될 때 공간이 scroll_area의 size에 맞춰서 자동으로 정렬되는 문제가 있다. 자동으로 정렬이라고 하면 듣기 좋지만 문제는 내가 원하는 정렬이 아니라는 점이지.
아래 사진과 같이 이상하게 정렬된다.
위에서부터 일렬로 차근차근 정렬되는 게 아니라 가운데 정렬이 되기 때문에 수직 간격을 조절하고 마지막 행이 빈 공간을 채우지 않도록 아래 코드를 추가해주면 된다.
widget_count = layout.count()
rows = (widget_count + 4) // 5
spacing = 10
layout.setVerticalSpacing(spacing)
layout.setHorizontalSpacing(spacing)
layout.setRowStretch(rows, 1)
layout.activate()
scroll_area.ensureWidgetVisible(self.scroll_content)
updateGeometry()
끝!
'공부 > python' 카테고리의 다른 글
[django] 장고 예제 프로젝트 만들기 (1) | 2024.06.09 |
---|---|
[json 조작] 2개의 json에서 같은 key가 있으면 value합치기 (0) | 2023.12.25 |
[pip install error] ERROR: To modify pip, please run the following command: ... (1) | 2022.11.27 |
[Pytesseract를 사용한 메이플스토리 길드 스코어 분석] 파이썬 스크립트 exe 파일로 변환 및 프로그램 작동 방법 (0) | 2022.03.30 |
[Pytesseract를 사용한 메이플스토리 길드 스코어 분석] 저장된 엑셀 파일 분석하기 (0) | 2022.03.30 |
댓글