본문 바로가기
Python/PySide PyQt

[PySide] Ex: Img Viewer. QListWidget and Matplotlib

by ds31x 2024. 6. 4.

PySide를 사용하여 QListWidget과 Matplotlib 연동하기

이 글에서는 PySide6를 사용하여 QListWidgetMatplotlib를 이용하여 

Image viewer를 만드는 방법을 설명함.

 

사용자는 디렉토리에서 PNG 파일을 선택하고

선택한 이미지를 Matplotlib를 사용하여 표시할 수 있음.

 

예제 코드를 통해 이를 구현하는 방법을 단계별로 살펴보겠음.


프로젝트 설정

먼저 PySide6와 Matplotlib를 설치해야 함. 이를 위해 아래의 명령어를 실행하기 바람.

pip install PySide6 matplotlib

주요 클래스 및 메서드 소개

ImageCanvas 클래스

ImageCanvas 클래스는 Matplotlib의 FigureCanvasQTAgg를 상속하여 이미지를 표시하는 기능을 제공함.

이 클래스는 PNG 파일을 읽어 Matplotlib 축에 표시함.

class ImageCanvas(FigureCanvas):
    def __init__(self, parent=None):
        # Matplotlib Figure 객체 생성
        self.fig = Figure()
        # Figure에 축(axes)을 추가
        self.ax = self.fig.add_subplot(111)
        super().__init__(self.fig)
        self.setParent(parent)
        self.setStyleSheet("background-color: #2f2f2f;")
        # 축을 숨김
        self.ax.axis('off')
        # Figure의 여백을 조정하여 이미지가 전체 영역을 차지하도록 설정
        self.fig.subplots_adjust(
            left=0, right=1,
            top=1, bottom=0
        )

    def display_image(self, image_path):
        # 축을 지움
        self.ax.clear()
        # 이미지 파일을 읽어서 축에 표시
        img = mpimg.imread(image_path)
        self.ax.imshow(img)
        # 축을 숨김
        self.ax.axis('off')
        # Figure의 여백을 조정하여 이미지가 전체 영역을 차지하도록 설정
        self.fig.subplots_adjust(
            left=0, right=1,
            top=1, bottom=0
        )
        # 그림을 다시 그림
        self.draw()

 

FigureCanvas 통해 Matplotlib 을 PySide에서 이용하는 보다 자세한 방법 설명은 다음 URL을 참고할 것.

2024.04.29 - [Python/PySide PyQt] - [PySide6] matplotlib 이용하기

 

[PySide6] matplotlib 이용하기

matplotlib 이용하기PyQt, PySide에서는 PyQtGraph를 통해서도 graph등을 그릴 수 있으나,대중적으로 사용되는 matplotlib를 이용할 수도 있다.PyQtGraph는 Qt vector 기반의 QGraphicsScene를 통해 상호작용이 가능한

ds31x.tistory.com

 


MainWindow 클래스

MainWindow 클래스는 응용 프로그램의 main window를 구성함.

 

여기에는 QListWidget과 Matplotlib의 ImageCanvas, 그리고 기타 UI 요소들이 포함됨.

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("PNG Viewer")

        # 메인 레이아웃 설정
        main_layout = QHBoxLayout()
        central_widget = QWidget()
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

        right_layout = QVBoxLayout()
        right_widget = QWidget()
        right_widget.setLayout(right_layout)

        # QListWidget 생성 및 설정
        self.list_widget = QListWidget()
        self.list_widget.itemClicked.connect(self.on_item_clicked)
        main_layout.addWidget(self.list_widget)

        # Matplotlib FigureCanvas 생성 및 설정
        self.canvas = ImageCanvas(self)

        # NavigationToolbar 생성 및 설정
        self.nav_toolbar = NavigationToolbar(self.canvas, self)

        right_layout.addWidget(self.nav_toolbar)
        right_layout.addWidget(self.canvas)

        main_layout.addWidget(right_widget)

        # StatusBar 생성
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # 메뉴바 설정
        menubar = self.menuBar()
        file_menu = menubar.addMenu("Select Img Dir")
        menubar.setNativeMenuBar(False)

        # 디렉토리 선택 액션 추가
        open_action = QAction("Open Directory", self)
        open_action.triggered.connect(self.open_directory)
        file_menu.addAction(open_action)

        self.show()

    def open_directory(self):
        # 디렉토리 선택 다이얼로그 열기
        directory = QFileDialog.getExistingDirectory(self, "Select Directory")
        if directory:
            self.list_widget.clear()
            # 디렉토리의 png 파일 목록 가져오기
            png_files = [f for f in os.listdir(directory) if f.endswith('.png')]
            for png_file in png_files:
                # QListWidgetItem 생성
                item = QListWidgetItem(png_file)
                # 파일 경로를 Qt.UserRole에 저장
                item.setData(Qt.UserRole, os.path.join(directory, png_file))
                # QListWidget에 아이템 추가
                self.list_widget.addItem(item)

    def on_item_clicked(self, item):
        # 선택된 아이템의 파일 경로를 가져와서 이미지 표시
        file_path = item.data(Qt.UserRole)
        self.canvas.display_image(file_path)
        self.status_bar.showMessage(file_path)

주요 기능 설명

  • QListWidget 설정:
    • QListWidget는 사용자가 PNG 파일을 선택할 수 있는 리스트를 제공함
  • 디렉토리 열기:
    • 메뉴에서 "Open Directory" 옵션을 선택하면 파일 다이얼로그가 열리고,
    • 사용자가 선택한 디렉토리의 PNG 파일 목록이 QListWidget에 표시됨
  • 이미지 표시:
    • 사용자가 리스트에서 파일을 선택하면,
    • 해당 파일의 경로가 ImageCanvas로 전달되어 이미지가 표시됨
  • 상태바:
    • 선택한 파일의 경로가 상태바에 표시됨

QListWidgetItem 사용법과 Qt.UserRole의 의미

  • QListWidgetItem:
    • QListWidgetItemQListWidget에 표시되는 각 항목을 나타내는 클래스임.
    • 각 항목은 텍스트와 데이터를 가질 수 있음. 텍스트는 항목의 표시 이름을 나타내고, 데이터는 항목에 관련된 추가 정보를 저장함.
  • item.setData(Qt.UserRole, value):
    • setData 메서드는 항목에 데이터를 설정함.
    • Qt.UserRole은 사용자가 정의한 데이터를 저장할 수 있는 역할(role)을 나타냄.
    • Qt.UserRole을 사용하면 항목과 관련된 추가 정보를 저장할 수 있음.
    • 예를 들어, 파일 경로를 저장하여 항목이 클릭될 때 해당 경로를 참조할 수 있음.

전체 소스 코드는 다음과 같음.

import sys
import os
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QListWidget,
    QListWidgetItem, 
    QHBoxLayout, QVBoxLayout, QWidget, QFileDialog, QMenuBar, 
    QStatusBar)
from PySide6.QtCore import Qt
from PySide6.QtGui import QAction

from matplotlib.figure import Figure
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.image as mpimg

class ImageCanvas(FigureCanvas):
    def __init__(self, parent=None):
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)
        super().__init__(self.fig)
        self.setParent(parent)
        self.setStyleSheet("background-color: #2f2f2f;")
        self.ax.axis('off')  # Hide the axis
        self.fig.subplots_adjust(
            left=0, right=1,
            top=1, bottom=0
        )

    def display_image(self, image_path):
        self.ax.clear()
        img = mpimg.imread(image_path)
        self.ax.imshow(img)
        self.ax.axis('off')  # Hide the axis
        self.fig.subplots_adjust(
            left=0, right=1,
            top=1, bottom=0
        )
        self.draw()

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("PNG Viewer")

        # 메인 레이아웃 설정
        main_layout = QHBoxLayout()
        central_widget = QWidget()
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)
        
        right_layout = QVBoxLayout()
        right_widget = QWidget()
        right_widget.setLayout(right_layout)

        # QListWidget 생성 및 설정
        self.list_widget = QListWidget()
        self.list_widget.itemClicked.connect(self.on_item_clicked)
        main_layout.addWidget(self.list_widget)
        
        
        # Matplotlib FigureCanvas 생성 및 설정
        self.canvas = ImageCanvas(self)
        
        # NavigationToolbar 생성 및 설정.
        self.nav_toolbar = NavigationToolbar(self.canvas, self)
        
        right_layout.addWidget(self.nav_toolbar)
        right_layout.addWidget(self.canvas)
        
        
        main_layout.addWidget(right_widget)

        # StatusBar 생성
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # 메뉴바 설정
        menubar = self.menuBar()
        file_menu = menubar.addMenu("Select Img Dir")
        menubar.setNativeMenuBar(False)

        # 디렉토리 선택 액션 추가
        open_action = QAction("Open Directory", self)
        open_action.triggered.connect(self.open_directory)
        file_menu.addAction(open_action)
    
        self.show()

    def open_directory(self):
        # 디렉토리 선택 다이얼로그 열기
        directory = QFileDialog.getExistingDirectory(self, "Select Directory")
        if directory:
            self.list_widget.clear()
            # 디렉토리의 png 파일 목록 가져오기
            png_files = [f for f in os.listdir(directory) if f.endswith('.png')]
            for png_file in png_files:
                item = QListWidgetItem(png_file)
                item.setData(Qt.UserRole, os.path.join(directory, png_file))
                self.list_widget.addItem(item)

    def on_item_clicked(self, item):
        # 선택된 아이템의 파일 경로를 가져와서 이미지 표시
        file_path = item.data(Qt.UserRole)
        self.canvas.display_image(file_path)
        self.status_bar.showMessage(file_path)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

참고: Qt.UserRole 외의 argument 사용 예시

QListWidgetItem의 데이터는 setData 메서드를 통해 설정하고, data 메서드를 통해 가져올 수 있음.

이 때 설정 가능한 Data의 종류는 다음과 같음.


Qt.DisplayRole

기본적으로 항목에 표시되는 텍스트를 설정함.

item.setData(Qt.DisplayRole, "파일 이름")

Qt.ToolTipRole

항목에 마우스를 올렸을 때 표시되는 툴팁 텍스트를 설정함.

item.setData(Qt.ToolTipRole, "툴팁 텍스트")

Qt.StatusTipRole

상태바에 표시되는 텍스트를 설정함.

signal 처리가 필요함. 아래 예제 참고

item.setData(Qt.StatusTipRole, "상태바 텍스트")
...
# QListWidget에 항목 위에 마우스를 올렸을 때 StatusTipRole 데이터를 상태바에 표시
self.list_widget.itemEntered.connect(self.show_status_tip)
...
def show_status_tip(self, item):
    # 항목의 StatusTipRole 데이터를 상태바에 표시
    status_tip = item.data(Qt.StatusTipRole)
    self.status_bar.showMessage(status_tip)

Qt.WhatsThisRole

항목에 대해 "What's This?" 도움말 텍스트를 설정함.

별도로 "What's This?" 모드로 들어가는 처리 필요 (아래 예제 코드 확인)

from PySide6.QtWidgets import QWhatsThis

item.setData(Qt.WhatsThisRole, "이 항목에 대한 설명")
...
def toggle_whats_this_mode(self):
    # "What's This?" 모드를 토글함. 버튼 등에 연결됨.
    if QWhatsThis.inWhatsThisMode():
        QWhatsThis.leaveWhatsThisMode()
    else:
        QWhatsThis.enterWhatsThisMode()

Qt.FontRole

항목의 텍스트 폰트를 설정함.

from PySide6.QtGui import QFont
font = QFont("Arial", 12, QFont.Bold)
item.setData(Qt.FontRole, font)

Qt.TextAlignmentRole

항목의 텍스트 정렬을 설정함.

from PySide6.QtCore import Qt
item.setData(Qt.TextAlignmentRole, Qt.AlignCenter)

Qt.BackgroundRole

항목의 배경색을 설정함.

from PySide6.QtGui import QColor
item.setData(Qt.BackgroundRole, QColor(Qt.yellow))

Qt.ForegroundRole

항목의 텍스트 색상을 설정함.

from PySide6.QtGui import QColor
item.setData(Qt.ForegroundRole, QColor(Qt.red))

Qt.CheckStateRole

항목의 체크 상태를 설정함 (체크박스가 있는 경우).

from PySide6.QtCore import Qt
item.setData(Qt.CheckStateRole, Qt.Checked)

Qt.DecorationRole

항목의 아이콘 또는 이미지를 설정함.

from PySide6.QtGui import QIcon
icon = QIcon("path/to/icon.png")
item.setData(Qt.DecorationRole, icon)

전체 예제 코드

아래는 다양한 역할들을 사용하는 전체 예제 코드임.

상태바를 추가하여 각 역할의 효과를 확인할 수 있음.

import sys
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QListWidget, 
    QListWidgetItem, QVBoxLayout, QWidget, 
    QStatusBar, QPushButton, QWhatsThis,
)
from PySide6.QtGui import QFont, QColor, QIcon
from PySide6.QtCore import Qt

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("QListWidget Roles Example")

        layout = QVBoxLayout()
        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

        self.list_widget = QListWidget()
        layout.addWidget(self.list_widget)

        # StatusBar 생성
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # "What's This?" 버튼 생성
        self.whats_this_button = QPushButton("What's This?")
        self.whats_this_button.clicked.connect(self.toggle_whats_this_mode)
        layout.addWidget(self.whats_this_button)

        # "Edit Item" 버튼 생성
        self.edit_button = QPushButton("Edit Item")
        self.edit_button.clicked.connect(self.edit_selected_item)
        layout.addWidget(self.edit_button)

        # QListWidgetItem 생성
        item1 = QListWidgetItem("Display Role Text 1")
        item1.setData(Qt.DisplayRole, "Updated Display Role Text 1")
        # item1.setData(Qt.EditRole, "Edit Role Text 1") #not working
        item1.setData(Qt.ToolTipRole, "ToolTip Role Text 1")
        item1.setData(Qt.StatusTipRole, "StatusTip Role Text 1")
        item1.setData(Qt.WhatsThisRole, "What's This Role Text 1")

        font1 = QFont("Arial", 12, QFont.Bold)
        item1.setData(Qt.FontRole, font1)

        item1.setData(Qt.TextAlignmentRole, Qt.AlignCenter)

        background_color1 = QColor(Qt.yellow)
        item1.setData(Qt.BackgroundRole, background_color1)

        foreground_color1 = QColor(Qt.red)
        item1.setData(Qt.ForegroundRole, foreground_color1)

        item1.setData(Qt.CheckStateRole, Qt.Checked)

        icon1 = QIcon("path/to/icon1.png")  # 아이콘 파일 경로
        item1.setData(Qt.DecorationRole, icon1)

        # QListWidget에 아이템 추가
        self.list_widget.addItem(item1)

        # 두번째 QListWidgetItem 생성
        item2 = QListWidgetItem("Display Role Text 2")
        item2.setData(Qt.DisplayRole, "Updated Display Role Text 2")
        # item2.setData(Qt.EditRole, "Edit Role Text 2") # not working
        item2.setData(Qt.ToolTipRole, "ToolTip Role Text 2")
        item2.setData(Qt.StatusTipRole, "StatusTip Role Text 2")
        item2.setData(Qt.WhatsThisRole, "What's This Role Text 2")

        font2 = QFont("Times New Roman", 10)
        font2.setItalic(True)
        item2.setData(Qt.FontRole, font2)

        item2.setData(Qt.TextAlignmentRole, Qt.AlignRight)

        background_color2 = QColor(Qt.green)
        item2.setData(Qt.BackgroundRole, background_color2)

        foreground_color2 = QColor(Qt.blue)
        item2.setData(Qt.ForegroundRole, foreground_color2)

        item2.setData(Qt.CheckStateRole, Qt.Unchecked)

        icon2 = QIcon("path/to/icon2.png")  # 아이콘 파일 경로
        item2.setData(Qt.DecorationRole, icon2)

        # QListWidget에 아이템 추가
        self.list_widget.addItem(item2)

        # QListWidget에 항목 위에 마우스를 올렸을 때 StatusTipRole 데이터를 상태바에 표시
        self.list_widget.itemEntered.connect(self.show_status_tip)

        # QListWidget의 항목에 마우스를 올리기 위해 설정
        self.list_widget.setMouseTracking(True)

    def toggle_whats_this_mode(self):
        # "What's This?" 모드를 토글함
        if QWhatsThis.inWhatsThisMode():
            QWhatsThis.leaveWhatsThisMode()
        else:
            QWhatsThis.enterWhatsThisMode()

    def show_status_tip(self, item):
        # 항목의 StatusTipRole 데이터를 상태바에 표시
        status_tip = item.data(Qt.StatusTipRole)
        self.status_bar.showMessage(status_tip)

    def edit_selected_item(self):
        # 선택된 항목을 편집 모드로 바꿈
        current_item = self.list_widget.currentItem()
        current_item.setFlags(current_item.flags() | Qt.ItemIsEditable)  # 편집 가능하도록 설정

        if current_item:
            self.list_widget.editItem(current_item)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

이 예제는 다양한 역할들을 사용하여 QListWidgetItem의 속성을 설정하는 방법을 보여줌.

각 역할은 항목의 다양한 속성들을 제어할 수 있도록 함.


각 역할은 특정 기능을 가지고 있으며, 이를 적절히 활용하면 더욱 풍부한 UI를 만들 수 있음.