많은 데이터를 한 눈에 보기 쉽게 표현하기 위해서는 반드시 표를 사용해야 하는데요, 이번 포스팅에서는 PyQt GUI에서 표와 관련된 위젯을 한 번 사용해 보도록 하겠습니다. 파이썬에서 표 형식의 데이터를 취급하는데 자주 사용되는 Pandas 라이브러리도 함께 사용해 보도록 하겠습니다.
이번 포스팅에서 만들 표가 포함된 QDialog입니다. 레이아웃Layout을 활용하여 위젯들을 원하는 위치에 배치하는 것에 대해서는 지난 포스팅에서 많이 했고, 이번 포스팅에서는 단순히 QTableWidget을 소개하는 것이 목적이기 때문에 위젯의 배치에는 별다른 신경을 쓰지 않았습니다.
Load PushButton을 클릭하면, csv형식으로 저장 해 두었던 데이터를 Panda DataFrame의 형태로 불러오고, 이 데이터 테이블을 표를 이용하여 GUI 화면에 출력하는 것 입니다. 또한 Save PushButton을 클릭하면 표의 내용이 저장되게 됩니다. 위 표에서 내용(숫자)는 더블 클릭을 하면 수정할 수 있기 때문에, 수정 된 사항을 저장하게 되는 것 입니다.
우선 테이블에 표시할 데이터가 필요한데, 위와 같이 매우 간단한 데이터를 csv형식으로 준비하였습니다. Pandas DataFrame을 자주 사용하시는 분이라면 익숙할 데이터의 형식입니다.
전체 코드를 보면서 각 부분을 설명하도록 하겠습니다.
import sys
import pandas as pd
from PyQt5.QtWidgets import *
class Main(QDialog):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
layout = QVBoxLayout()
'''Blank Table Widget'''
table_widget = QTableWidget()
'''PushButton for Load CSV file'''
button_load = QPushButton("Load")
button_load.clicked.connect(lambda state, widget = table_widget: self.slot_button_load(state, widget))
'''PushButton for Save modified CSV'''
button_save = QPushButton("Save")
button_save.clicked.connect(lambda state, widget = table_widget: self.slot_button_save(state, widget))
layout.addWidget(table_widget)
layout.addWidget(button_load)
layout.addWidget(button_save)
self.setLayout(layout)
self.resize(500, 500)
self.show()
def slot_button_load(self, state, widget):
filename = QFileDialog.getOpenFileName(self, 'Open file', './')
if filename[0]:
df = pd.read_csv(filename[0], index_col = 0)
self.create_table_widget(widget, df)
def create_table_widget(self, widget, df):
widget.setRowCount(len(df.index))
widget.setColumnCount(len(df.columns))
widget.setHorizontalHeaderLabels(df.columns)
widget.setVerticalHeaderLabels(df.index)
for row_index, row in enumerate(df.index):
for col_index, column in enumerate(df.columns):
value = df.loc[row][column]
item = QTableWidgetItem(str(value))
widget.setItem(row_index, col_index, item)
def slot_button_save(self, state, widget):
for row_index in range(widget.rowCount()):
for col_index in range(widget.columnCount()):
item = widget.item(row_index, col_index)
content = item.text()
print(content)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Main()
sys.exit(app.exec_())
코드가 약간 길기는 하지만, 실제로 이번 포스팅의 새로운 내용이라고 할 수 있는 QTableWidget과 관련된 부분은 그리 길지 않습니다.
'''Blank Table Widget'''
table_widget = QTableWidget()
QTableWidget()를 생성하는 부분은 위 부분으로 위와 같이 QTableWidget을 생성하면 빈 테이블이 table_widget 변수에 생성 됩니다.
Load 버튼을 클릭하면, 어떠한 csv 파일을 QTableWidget을 통해서 디스플레이를 할 것인지 파일을 선택하는 대화창이 뜹니다.
윈도우에서 쉽게 볼 수 있는 파일 선택 창으로, 운영체제에 따라서 디자인이 다릅니다. 파일 선택 대화창을 띄우고, 이로 부터 선택된 파일의 경로를 return 받는 부분의 코드는 아래와 같습니다.
filename = QFileDialog.getOpenFileName(self, 'Open file', './')
if filename[0]:
df = pd.read_csv(filename[0], index_col = 0)
self.create_table_widget(widget, df)
첫 줄은 QFileDialog의 getOpenFileName를 활용하여 파일 선택 대화창을 띄우고, 선택의 결과를 filename이라는 변수로 return 받는 부분입니다. QFileDialog는 PyQt에서 파일, 폴더를 선택하거나 생성하는 것과 관련된 대화창을 띄우고, 선택의 결과를 리턴 받는 것과 관련된 클래스로 이에 대해서는 다음 포스팅에서 조금 더 디테일 하게 알아 보도록 하겠습니다.
파일을 선택한다면 filename은 두 개의 text값을 리턴해 주는데, 첫 번째 인자는 선택된 파일의 경로를, 두 번째 인자는 "All Files (*)"를 리턴해 줍니다. 만일 파일 선택 대화창에서 파일을 선택하지 않고 취소 버튼을 클릭한다면, 빈 텍스트 ""를 반환하게 됩니다.
따라서, 파일을 선택한 경우와 그렇지 않은 경우에 대해서 다르게 그 다음 코딩을 해 주어야 하는데, 파일을 선택한 경우에만 다음을 진행할 수 있도록 if filename[0]을 이용하였습니다. 파일이 있는 경우에, 이 파일을 Panda DataFrame형태로 읽어서 df 변수에 저장을 하고, 이 데이터 테이블의 내용을 이용하여 코드의 윗부분에 초기화 해 두었던 QTableWidget인 widget에 반영하게 됩니다.
def create_table_widget(self, widget, df):
"""
df의 행과 열의 숫자에 맞게 QTableWidget의 행과 열의 갯수를 지정
df의 행과 열의 이름이 QTableWidget에 표시 될 수 있도록
HorizontalHeaderLabels와 VerticalHeaderLabels를 지정
"""
widget.setRowCount(len(df.index))
widget.setColumnCount(len(df.columns))
widget.setHorizontalHeaderLabels(df.columns)
widget.setVerticalHeaderLabels(df.index)
for row_index, row in enumerate(df.index):
for col_index, column in enumerate(df.columns):
value = df.loc[row][column]
"""
QTableWidget의 row_index 열, col_index 행에 들어갈 아이템을 생성
"""
item = QTableWidgetItem(str(value))
"""
생성된 아이템을 위젯의 row_index, col_index (행, 열)에 배치
"""
widget.setItem(row_index, col_index, item)
이 부분이 이번 포스팅에 핵심적인 내용이라고 할 수 있는 데이터를 표로 반영하는 부분입니다. QTableWidget은 widget변수이고, df는 앞에서 리턴 받은 Panda DataFame입니다.
위 코드의 앞 부분에는 QTableWidget의 행과 열의 갯수, 그리고 각 행과 열의 라벨을 지정해 줍니다. https://doc.qt.io/qt-5/qtablewidget.html 의 내용을 보면 각 함수가 어떠한 역할을 하는지 확인할 수 있습니다.
for 로 시작되는 뒷 부분에서는 QTableWidget의 각 (행, 열)에 값을 입력합니다. 각 (행, 렬)에 해당하는 내용을 담은 QTableWidgetItem을 생성하고, 이를 (행, 열)에 지정하는 방식입니다. Pandas DataFrame의 각 (행,열)의 값을 참조하는 df.loc[row][column]을 활용하여 값을 얻어내고, 이를 string의 형태로 바꾼 후 화면에 디스플레이 하는 방식이 입니다.
def slot_button_save(self, state, widget):
for row_index in range(widget.rowCount()):
for col_index in range(widget.columnCount()):
item = widget.item(row_index, col_index)
content = item.text()
print(content)
코드의 마지막 부분은 Save 버튼을 클릭했을 때, 연결되는 슬롯을 정의합니다. QTableWidget의 각 (행, 열)의 데이터를 받아와서, 이를 터미널의 화면에 출력하는 내용입니다. 실제 사용에서는 터미널에 출력하기 보다는 새로운 데이터 테이블을 만들어서 저장하면 됩니다.
코드를 보면 쉽게 이해가 될 텐데, 핵심적인 부분은
item = widget.item(row_index, col_index)
content = item.text()
입니다. widget(QTableWidget)의 (row_index, col_index)에 정의 돼 있는 QTableWidgetItem을 item으로 리턴 받고, 이 item에 있는 값을 텍스트 형태로 받아 오는 것 입니다.
이번 포스팅에서는 매우 QTableWidget의 매우 간단한 기능만을 알아 봤는데요, QTableWidget에 설정해 줄 수 있는 기능들이 매우 많기 때문에, 코딩을 잘 하기만 한다면 엑셀과 같은 기능을 구현 할 수 도 있습니다.
'PyQt GUI' 카테고리의 다른 글
PyQt GUI (18) 괜찮은 테마 소개 : qt_material (4) | 2022.03.07 |
---|---|
PyQt GUI (17) QTreeView를 이용하여 폴더 트리 만들기 + 드래그-앤-드롭 drag-and-drop을 이용하여 파일 이름을 옮기기 (0) | 2022.02.19 |
PyQt GUI (15) 리스트 아이템(list item) 드래그-앤-드롭(drag and drop) 이용하기 (1) | 2021.10.08 |
PyQt GUI (14) 그래프 라이브러리 Matplotlib을 GUI에 포함하기(embeding) (0) | 2021.10.04 |
PyQt GUI (13) Pyinstaller를 이용하여 PyQt5파이썬 코드를 exe윈도우 실행 파일로 만들기 (0) | 2021.08.19 |