PyQt개발강좌, 데이터를 위한 커스텀 필터 및 정렬 기능 구현

PyQt는 파이썬을 위한 Qt 애플리케이션 개발 프레임워크로, GUI를 신속하게 개발할 수 있게 해줍니다. 본 강좌에서는 PyQt를 사용하여 데이터를 관리하고 표시하기 위한 커스텀 필터와 정렬 기능을 어떻게 구현할 수 있는지에 대해 자세히 다룰 것입니다. 특히, QTableViewQAbstractTableModel을 활용하여 데이터의 표시, 필터링 및 정렬을 구현하는 방법에 대해 알아볼 것입니다.

1. 기본 개념

PyQt를 활용한 GUI 애플리케이션에서 데이터 관리는 매우 중요합니다. 사용자가 데이터를 쉽게 볼 수 있도록 구현하는 것뿐만 아니라, 데이터를 필터링하고 정렬할 수 있는 강력한 도구를 제공해야 합니다. 이를 위해 다음과 같은 주요 구성 요소를 사용합니다:

  • QTableView: 데이터를 테이블 형태로 표시하기 위한 위젯입니다.
  • QAbstractTableModel: 데이터의 내부 구조를 정의하며, 데이터를 표시하는 방법과 필터링 및 정렬을 관리합니다.
  • 커스텀 필터 및 정렬 기능: 사용자가 원하는 대로 데이터를 필터링하고 정렬할 수 있게 해주는 기능입니다.

2. QTableView와 QAbstractTableModel 소개

QTableView는 데이터를 테이블 형식으로 표시하는 데 사용됩니다. 데이터에 대한 다양한 조작과 표시 설정이 가능합니다. QAbstractTableModel은 데이터를 모델로 관리하는 클래스로, QTableView와 함께 사용되어야 합니다. 모델은 데이터가 어떻게 저장되고 쿼리되는지를 정의합니다.

2.1 QTableView 설정하기

먼저 간단한 PyQt 애플리케이션을 설정하고 QTableView를 사용하는 방법을 살펴보겠습니다. 아래의 코드를 확인하십시오:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTableView
from PyQt5.QtCore import Qt, QAbstractTableModel, QVariant

class MyModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, parent=None):
        return len(self._data)

    def columnCount(self, parent=None):
        return len(self._data[0]) if self._data else 0

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return QVariant(self._data[index.row()][index.column()])

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return QVariant(f'Column {section + 1}')
            else:
                return QVariant(f'Row {section + 1}')
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = QMainWindow()
    window.setWindowTitle('QTableView 예제')

    data = [
        ['Alice', 30],
        ['Bob', 25],
        ['Charlie', 35]
    ]

    model = MyModel(data)

    table_view = QTableView()
    table_view.setModel(model)

    layout = QVBoxLayout()
    layout.addWidget(table_view)

    central_widget = QWidget()
    central_widget.setLayout(layout)
    window.setCentralWidget(central_widget)

    window.resize(400, 300)
    window.show()
    sys.exit(app.exec_())

이 코드에서는 가장 간단한 형태의 QTableViewQAbstractTableModel을 설정했습니다. MyModel 클래스를 생성하여 데이터를 보유하고, 데이터의 행과 열 개수를 정의하며, 특정 셀의 데이터를 반환하는 간단한 모델을 구현했습니다.

3. 커스텀 필터 만들기

이제 데이터를 필터링할 수 있는 기능을 추가해보겠습니다. 이를 위해 모델에 필터링 기능을 추가하고, QLineEdit를 사용하여 사용자 입력을 받을 수 있도록 할 것입니다.

3.1 필터 기능 구현

필터 기능을 구현하기 위해 모델 클래스에 필터링 로직을 추가하겠습니다. 사용자가 입력한 필터 문자열 기반으로 데이터를 필터링할 수 있도록 합니다.

class MyModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        self._filtered_data = data

    def filterData(self, filter_string):
        self.beginResetModel()
        if filter_string:
            self._filtered_data = [row for row in self._data if filter_string.lower() in row[0].lower()]
        else:
            self._filtered_data = self._data
        self.endResetModel()

    def rowCount(self, parent=None):
        return len(self._filtered_data)

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return QVariant(self._filtered_data[index.row()][index.column()])

여기에서 filterData 메서드는 입력된 필터 문자열을 기반으로 데이터를 필터링합니다. beginResetModelendResetModel 메서드를 사용하여 모델의 데이터가 변경되었음을 알립니다.

3.2 QLineEdit 추가하기

이제 필터를 입력할 QLineEdit를 사용자 인터페이스에 추가하고, 사용자 입력에 따라 필터를 적용할 것입니다.

from PyQt5.QtWidgets import QLineEdit

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

        self.setWindowTitle('QTableView 필터링 예제')

        self.data = [
            ['Alice', 30],
            ['Bob', 25],
            ['Charlie', 35]
        ]

        self.model = MyModel(self.data)

        self.table_view = QTableView()
        self.table_view.setModel(self.model)

        self.filter_line_edit = QLineEdit()
        self.filter_line_edit.setPlaceholderText('이름으로 필터링...')
        self.filter_line_edit.textChanged.connect(self.onFilterChanged)

        layout = QVBoxLayout()
        layout.addWidget(self.filter_line_edit)
        layout.addWidget(self.table_view)

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

    def onFilterChanged(self, text):
        self.model.filterData(text)

이 코드에서는 QLineEdit를 추가하고, 사용자가 입력하는 텍스트가 변경될 때마다 onFilterChanged 메서드를 호출하여 모델에 필터를 적용합니다.

4. 데이터 정렬 기능 구현하기

다음으로 데이터를 정렬할 수 있는 기능을 추가해보겠습니다. 사용자가 특정 열을 클릭하면 해당 열의 데이터를 정렬할 수 있도록 합니다.

4.1 정렬 기능을 위한 모델 수정

모델에 정렬 기능을 추가하기 위해 별도의 정렬 메서드를 작성하겠습니다. 이 메서드는 지정된 열을 기준으로 데이터를 오름차순 또는 내림차순으로 정렬합니다.

class MyModel(QAbstractTableModel):
    # 기존 코드...

    def sort(self, column, order):
        self.beginResetModel()
        self._filtered_data.sort(key=lambda x: x[column], reverse=(order == Qt.DescendingOrder))
        self.endResetModel()

4.2 QTableView에서 정렬 처리하기

이제 QTableView에서 열 헤더 클릭 시 정렬을 처리할 수 있도록 설정합니다.

self.table_view.setSortingEnabled(True)
self.table_view.horizontalHeader().sectionClicked.connect(self.onHeaderClicked)

def onHeaderClicked(self, index):
    current_order = self.table_view.horizontalHeader().sortIndicatorOrder()
    new_order = Qt.AscendingOrder if current_order == Qt.DescendingOrder else Qt.DescendingOrder
    self.model.sort(index, new_order)

이 코드는 사용자가 열 헤더를 클릭할 때마다 onHeaderClicked 메서드를 호출하여 정렬을 수행합니다.

5. 전체 코드

이제 모든 구성 요소를 통합한 전체 코드를 볼 수 있습니다.

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTableView, QLineEdit
from PyQt5.QtCore import Qt, QAbstractTableModel, QVariant

class MyModel(QAbstractTableModel):
    def __init__(self, data):
        super().__init__()
        self._data = data
        self._filtered_data = data

    def filterData(self, filter_string):
        self.beginResetModel()
        if filter_string:
            self._filtered_data = [row for row in self._data if filter_string.lower() in row[0].lower()]
        else:
            self._filtered_data = self._data
        self.endResetModel()

    def sort(self, column, order):
        self.beginResetModel()
        self._filtered_data.sort(key=lambda x: x[column], reverse=(order == Qt.DescendingOrder))
        self.endResetModel()

    def rowCount(self, parent=None):
        return len(self._filtered_data)

    def columnCount(self, parent=None):
        return len(self._filtered_data[0]) if self._filtered_data else 0

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return QVariant(self._filtered_data[index.row()][index.column()])

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole:
            if orientation == Qt.Horizontal:
                return QVariant(f'Column {section + 1}')
            else:
                return QVariant(f'Row {section + 1}')

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('QTableView 필터링 및 정렬 예제')

        self.data = [
            ['Alice', 30],
            ['Bob', 25],
            ['Charlie', 35]
        ]
        self.model = MyModel(self.data)

        self.table_view = QTableView()
        self.table_view.setModel(self.model)
        self.table_view.setSortingEnabled(True)
        self.table_view.horizontalHeader().sectionClicked.connect(self.onHeaderClicked)

        self.filter_line_edit = QLineEdit()
        self.filter_line_edit.setPlaceholderText('이름으로 필터링...')
        self.filter_line_edit.textChanged.connect(self.onFilterChanged)

        layout = QVBoxLayout()
        layout.addWidget(self.filter_line_edit)
        layout.addWidget(self.table_view)

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

    def onFilterChanged(self, text):
        self.model.filterData(text)

    def onHeaderClicked(self, index):
        current_order = self.table_view.horizontalHeader().sortIndicatorOrder()
        new_order = Qt.AscendingOrder if current_order == Qt.DescendingOrder else Qt.DescendingOrder
        self.model.sort(index, new_order)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.resize(400, 300)
    window.show()
    sys.exit(app.exec_())

6. 마무리

본 강좌에서는 PyQt를 사용하여 커스텀 필터와 정렬 기능을 포함한 데이터 테이블을 구현하는 방법에 대해 설명하였습니다. QTableViewQAbstractTableModel을 활용하여 유연하고 강력한 데이터 관리를 할 수 있습니다. 이 예제를 기반으로 다양한 기능을 추가하고 지속적으로 발전시켜 나가길 바랍니다.

앞으로도 PyQt에 대한 다양한 강좌를 연구하고, 기술을 확장해 나가기를 바랍니다. 질문이나 코멘트가 있다면 언제든지 남겨주세요!