728x90

QTableWidget에 1,000 단위로 쉼표(,)를 추가하고 숫자 정렬 기능 추가하기

들어가며

  • PyQt5에서 @QTableWidget@에 값을 넣을 때, 기본적으로 @string@ 형식의 값이 들어가게 된다.(@int@형의 값은 넣을 수 없다.)
  • 이 때, @QTableWidget@에 (@string@ 형의) 숫자를 넣을 때 1,000 단위로 쉼표(@,@)를 추가하고, 정렬(오름차순/내림차순)을 할 수 있도록 구현해보자. 

 

실습을 위한 QTableWidget 객체 생성 하기

  • 다음과 같이 실습을 위해 필요한 @QTableWidget@ 객체를 생성한다.
self.table_widget = QTableWidget(self)
self.table_widget.setGeometry(50, 50, 400, 200)

self.table_widget.setColumnCount(2)
self.table_widget.setHorizontalHeaderLabels(["숫자", "텍스트"])
self.table_widget.setRowCount(6)
data = [(3000, "apple"), (1500, "banana"), (5000, "orange"), (2000, "grape"), (1000, "melon"), (40000, "peach")]
for row, (number, text) in enumerate(data):
    item_number = QTableWidgetItem(number)
    item_number.setData(Qt.EditRole, number)
    self.table_widget.setItem(row, 0, item_number)

    item_text = QTableWidgetItem(text)
    self.table_widget.setItem(row, 1, item_text)

 

 

컬럼별 정렬 기능 구현하기

  • 우선 다음과 같은 정렬 함수를 구현해준다.
  • @sortItems@ 함수와 @Qt.AscendingOrder@와 @Qt.DescendingOrder@ 인자를 사용하여 컬럼에 있는 요소들을 정렬해주는 방식이다.
def sort_column(self, column_index):
    self.table_widget.sortItems(column_index, Qt.AscendingOrder if self.is_column_sorted_ascending(column_index) else Qt.DescendingOrder)

def is_column_sorted_ascending(self, column_index):
    return self.table_widget.horizontalHeader().sortIndicatorSection() == column_index and self.table_widget.horizontalHeader().sortIndicatorOrder() == Qt.AscendingOrder

 

  • 다음과 같이 컬럼 헤더를 더블 클릭하면 정렬이 되는 함수를 연결해준다.
# 컬럼 헤더 더블 클릭 이벤트를 처리하는 함수 연결
header = self.table_widget.horizontalHeader()
header.sectionDoubleClicked.connect(self.sort_column)

 

  • 이제 해당 컬럼명을 더블 클릭할 때마다 해당 컬럼에 있는 요소들은 오름차순내림차순이 번갈아가며 적용되게 된다.

 

정렬이 제대로 수행되지 않는 이슈 해결하기

  • 만약, @{40,000, 5,000, 3,000, 15,000}@와 같이 쉼표(@,@)가 포함된 형태의 숫자들을 표에 넣고 내림차순으로 정렬하면 @{5,000, 40,000, 3,000, 15,000}@ 형태로 잘못된 정렬이 수행 되게 된다. 
  • 왜냐하면 정렬을 수행할 때, 문자열(@string@)을 기준으로 정렬을 수행하기 때문이다.
    • 맨 앞에 있는 숫자들(@5@, @4@, @3@, @1@)을 기준으로 정렬 작업이 수행된다.
  • 따라서 이러한 이슈를 해결하기 위해서는 표에 값을 넣을 때 쉼표를 제거해서 넣고, 요소(Item) 별로 @setData@ 함수에 @Qt.EditRole@ 인자를 넣어 표에서 값이 숫자형으로 표현될 수 있도록 바꿔주어야 한다.
data = [(3000, "apple"), (1500, "banana"), (5000, "orange"), (2000, "grape"), (1000, "melon"), (40000, "peach")]

for row, (number, text) in enumerate(data):
    item_number = QTableWidgetItem(number)
    item_number.setData(Qt.EditRole, number)          # Qt.EditRole 인자를 넣어준다.
    self.table_widget.setItem(row, 0, item_number)

    item_text = QTableWidgetItem(text)
    self.table_widget.setItem(row, 1, item_text)
  • 하지만, @Qt.EditRole@ 인자를 넣어 요소를 표에 넣을 경우, 1,000 단위로 쉼표(@,@)가 넣어지지 않아 가독성이 떨어진게 된다는 단점이 생기게 된다.

 

@replace@ 함수를 이용하여 문자열 값에 있는 쉼표(,) 제거하기
  • 참고로, 가져온 값들에 쉼표가 있을 경우, 다음과 같이 @replace(',', '')@ 함수를 이용하여 쉼표를 제거해줄 수 있다.
number.setData(Qt.EditRole, int(number.replace(',', '')))

 

1,000 단위로 쉼표를 추가해서 숫자가 보이도록 해주기

  • 이제 @Qt.EditRole@ 형태의 요소(Item)들을 1,000 단위로 쉼표(,)를 추가해서 숫자가 보이도록 해주자.
  • 우선 다음과 같이 커스텀 델리게이션 클래스를 만들어준다. 이 클래스는 인자로 들어온 값의 자료형(@int@, @float@, @string@)에 따라 다르게 포맷이 설정될 수 있도록 해준다. (@{:,.0f}@, @{:,}@)일 경우 @@
class NumberFormatDelegate(QStyledItemDelegate):
    def displayText(self, value, locale):
        if isinstance(value, (int, float)):
            formatted_value = "{:,.0f}".format(value) if isinstance(value, float) else "{:,}".format(value)
            return formatted_value
        return super().displayText(value, locale)
델리게이션(위임, Delegation)이란, 특정 기능을 다른 객체에게 위임하고, 그에 따라 필요한 시점에서 메소드의 호출만 받는 디자인 패턴이다.
예를 들어, 자동차가 연료 관리를 엔진에게 '위임(Delegation)'하는 경우, 연료가 부족한 경우(또는 연료가 가득 찬 경우)에 엔진이 자동차에게게 알려주도록 할 수 있다. 자동차는 엔진을 잘 사용하기만 하면 되고, 엔진은 연료의 상태를 자동차에게 알려주기만 하면 된다. 자동차는 연료를 관리하는 기능을 엔진에게 위임(Delegation)하고, 연료 보충이 필요할 경우 호출을 받으면 되는 형태이다.

 

  • 그리고 다음과 같이 커스텀 델리게이션을 적용해준다.
column_index = 0    # 1,000 단위로 쉼표를 추가해 줄 컬럼 지정
delegate = NumberFormatDelegate(self.table_widget)
self.table_widget.setItemDelegateForColumn(column_index, delegate)

 

  • 이제 @0@번째 컬럼(@숫자@)의 요소들은 1,000자리 단위로 쉼표가 추가되게 된다.

 

전체 코드

from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidget, QTableWidgetItem, QStyledItemDelegate
from PyQt5.QtCore import Qt

class NumberFormatDelegate(QStyledItemDelegate):
    def displayText(self, value, locale):
        if isinstance(value, (int, float)):
            formatted_value = "{:,.0f}".format(value) if isinstance(value, float) else "{:,}".format(value)
            return formatted_value
        return super().displayText(value, locale)

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

        self.setGeometry(100, 100, 500, 300)
        self.initUI()

    def initUI(self):
        self.table_widget = QTableWidget(self)
        self.table_widget.setGeometry(50, 50, 400, 200)

        self.table_widget.setColumnCount(2)
        self.table_widget.setHorizontalHeaderLabels(["숫자", "텍스트"])
        self.table_widget.setRowCount(6)
        data = [(3000, "apple"), (1500, "banana"), (5000, "orange"), (2000, "grape"), (1000, "melon"), (40000, "peach")]
        for row, (number, text) in enumerate(data):
            item_number = QTableWidgetItem(number)
            item_number.setData(Qt.EditRole, number)
            self.table_widget.setItem(row, 0, item_number)

            item_text = QTableWidgetItem(text)
            self.table_widget.setItem(row, 1, item_text)

        # 커스텀 델리게이트를 열에 설정
        column_index = 0
        delegate = NumberFormatDelegate(self.table_widget)
        self.table_widget.setItemDelegateForColumn(column_index, delegate)

        # 컬럼 헤더 더블클릭 이벤트를 처리하는 함수 연결
        header = self.table_widget.horizontalHeader()
        header.sectionDoubleClicked.connect(self.sort_column)

        self.setWindowTitle('정렬 가능한 QTableWidget')
        self.show()

    def sort_column(self, column_index):
        self.table_widget.sortItems(column_index, Qt.AscendingOrder if self.is_column_sorted_ascending(column_index) else Qt.DescendingOrder)

    def is_column_sorted_ascending(self, column_index):
        return self.table_widget.horizontalHeader().sortIndicatorSection() == column_index and self.table_widget.horizontalHeader().sortIndicatorOrder() == Qt.AscendingOrder

if __name__ == '__main__':
    app = QApplication([])
    window = MyWindow()
    app.exec_()

 

프로그램 실행 결과

오름차순 정렬 후의 모습 내림차순 정렬 후의 모습

 

참고 사이트

 

QTableWidget Class | Qt Widgets 6.5.1

 

doc.qt.io

728x90