예전에 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 |
댓글