컴퓨터/Python

python pyside6 exceltoexcel modify same file same sheet

풍경소리^^ 2025. 1. 10. 09:09

엑셀 파일 qtablewidget 이용해서

원본파일의 시트를 더블클릭해서 선택

대상파일의 시트를 더블클릭해서 선택

저장버튼을 누르면

원본파일의 시트내용을 대상파일의 시트 마직막 행 다음에 이어 붙여넣기해서 저장

 

pyside6_exceltoexcelmodifysamefilesamesheet.py

import os
import pandas as pd
from PySide6.QtWidgets import (
    QApplication, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QListWidget, QLabel,
    QTableWidget, QTableWidgetItem, QFileDialog, QMessageBox
)
import openpyxl
import datetime

class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.window_width, self.window_height = 1000, 500
        self.resize(self.window_width, self.window_height)

        ##### layout_main ###### 
        layout_main = QHBoxLayout()
        self.setLayout(layout_main)

        ##### layout_1 (좌측 레이아웃) ###### 
        layout_1 = QVBoxLayout()

        # 원본 엑셀 파일 열기 버튼
        s_open_btn = QPushButton('원본 엑셀 파일 열기', self)
        s_open_btn.setMaximumWidth(300)

        self.s_listwidget = QListWidget(self)
        self.s_listwidget.setMaximumWidth(300)

        # 원본 파일 경로를 표시할 라벨 추가
        self.label_s = QLabel()
        self.label_s.setMaximumWidth(300)
        self.label_s.setText("원본파일 경로")
        self.label_s.setStyleSheet("color: #000000; border-style: solid; border-width: 2px; border-color: green;")

        layout_1.addWidget(s_open_btn)
        layout_1.addWidget(self.label_s)  # 원본 파일 경로 라벨 추가
        layout_1.addWidget(self.s_listwidget)

        # 대상 엑셀 파일 선택 버튼
        t_open_btn = QPushButton('대상 엑셀 파일 선택', self)
        t_open_btn.setMaximumWidth(300)
        t_open_btn.clicked.connect(self.fn_t_clickOpenBtn)

        self.t_listwidget = QListWidget(self)
        self.t_listwidget.setMaximumWidth(300)

        self.label_t = QLabel()
        self.label_t.setMaximumWidth(300)
        self.label_t.setText("대상파일 경로")
        self.label_t.setStyleSheet("color: #000000; border-style: solid; border-width: 2px; border-color: red;")

        layout_1.addWidget(t_open_btn)
        layout_1.addWidget(self.label_t)
        layout_1.addWidget(self.t_listwidget)

        # 저장 버튼 추가
        save_btn = QPushButton('저장', self)
        save_btn.clicked.connect(self.fn_df_save)
        layout_1.addWidget(save_btn)  # layout_1에 저장 버튼 추가

        layout_main.addLayout(layout_1)

        ##### layout_2 (우측 레이아웃) ###### 
        layout_2 = QVBoxLayout()

        # 원본/대상 구분 라벨 추가
        self.label_table_type = QLabel("원본", self)
        self.label_table_type.setStyleSheet("background-color: green; color: white; font-size: 16px; font-weight: bold;")
        layout_2.addWidget(self.label_table_type)

        self.table = QTableWidget()
        layout_2.addWidget(self.table)

        layout_main.addLayout(layout_2)

        s_open_btn.clicked.connect(self.fn_s_clickOpenBtn)
        self.s_listwidget.itemDoubleClicked.connect(self.getItem_s)
        self.t_listwidget.itemDoubleClicked.connect(self.getItem_t)

    def getItem_s(self):
        self.s_row = self.s_listwidget.currentRow()
        self.loadData(self.s_row, "원본")

    def getItem_t(self):
        self.t_row = self.t_listwidget.currentRow()
        self.loadData(self.t_row, "대상")

    def fn_s_clickOpenBtn(self):
        self.table.clear()
        self.s_listwidget.clear()
        file_filter = 'Excel File (*.xlsx *.xlsm)'
        self.s_file_path, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption='파일 열기',
            filter=file_filter
        )
        if self.s_file_path:
            # 시트 목록을 가져와서 리스트에 추가
            self.list_sheet_s = self.fn_listsheetData(self.s_file_path)
            for sheet in self.list_sheet_s:
                self.s_listwidget.addItem(sheet)

            # 첫 번째 시트를 자동 선택하고 배경을 노란색으로 변경
            if self.list_sheet_s:
                self.s_listwidget.setCurrentRow(0)  # 첫 번째 시트 선택
                self.loadData(0, "원본")  # 첫 번째 시트 로드

            # 선택된 원본 파일 경로 표시
            self.label_s.setText(self.s_file_path)

    def fn_t_clickOpenBtn(self):
        self.label_t.clear()
        self.t_listwidget.clear()
        file_filter = 'Excel File (*.xlsx *.xlsm)'
        self.t_file_path, _ = QFileDialog.getOpenFileName(
            parent=self,
            caption='파일 열기',
            filter=file_filter
        )
        if self.t_file_path:
            # 시트 목록을 가져와서 리스트에 추가
            self.df_list_t = self.fn_listsheetData(self.t_file_path)
            for sheet in self.df_list_t:
                self.t_listwidget.addItem(sheet)

            # 첫 번째 시트를 자동 선택
            if self.df_list_t:
                self.t_listwidget.setCurrentRow(0)  # 첫 번째 시트 선택
                self.label_t.setText(self.t_file_path)
                self.loadData(0, "대상")  # 첫 번째 시트 로드

    def fn_listsheetData(self, file_path):
        self.wb_s = openpyxl.load_workbook(file_path)
        list_sheet_s = []
        with pd.ExcelFile(file_path) as wb:
            for sh in wb.sheet_names:
                list_sheet_s.append(sh)
        return list_sheet_s

    def loadData(self, row, table_type):
        try:
            self.table.clear()
            if table_type == "원본":
                select_sheet_name = self.list_sheet_s[row]
                self.df_s = pd.read_excel(self.s_file_path, sheet_name=select_sheet_name)

                # NaN 값을 빈 문자열로 변경하기 전에 문자열로 변환하여 dtype 문제를 해결
                self.df_s = self.df_s.astype(str).replace('nan', '')

                self.label_table_type.setText("원본")  # 원본 표시
                self.label_table_type.setStyleSheet("background-color: green; color: white; font-size: 16px; font-weight: bold;")  # 녹색 배경
            else:
                select_sheet_name = self.df_list_t[row]
                self.df_s = pd.read_excel(self.t_file_path, sheet_name=select_sheet_name)

                # NaN 값을 빈 문자열로 변경하기 전에 문자열로 변환하여 dtype 문제를 해결
                self.df_s = self.df_s.astype(str).replace('nan', '')

                self.label_table_type.setText("대상")  # 대상 표시
                self.label_table_type.setStyleSheet("background-color: red; color: white; font-size: 16px; font-weight: bold;")  # 빨간색 배경
            
            self.table.setRowCount(self.df_s.shape[0])
            self.table.setColumnCount(self.df_s.shape[1])
            self.table.setHorizontalHeaderLabels(self.df_s.columns)

            for row_index, row_data in self.df_s.iterrows():
                for col_index, value in enumerate(row_data):
                    tableItem = QTableWidgetItem(str(value))
                    self.table.setItem(row_index, col_index, tableItem)
        except Exception as e:
            print(e)
            return

    def fn_df_save(self):
        try:
            # 원본 파일 경로가 없을 때 경고 메시지
            if not self.label_s.text() or self.label_s.text() == "원본파일 경로":
                QMessageBox.warning(self, "경고", "원본 파일을 선택하세요.")
                return

            # 대상 파일 경로가 없을 때 경고 메시지
            if not self.label_t.text() or self.label_t.text() == "대상파일 경로":
                QMessageBox.warning(self, "경고", "대상 파일을 선택하세요.")
                return

            t_row = self.t_listwidget.currentRow()  # 현재 선택된 대상 시트 행
            if t_row == -1:
                QMessageBox.warning(self, "경고", "대상 시트를 선택하세요.")
                return

            # 원본과 대상 파일, 시트 정보 가져오기
            self.list_sheet_t = self.fn_listsheetData(self.t_file_path)
            
            # 선택된 원본 시트 이름과 대상 시트 이름
            selected_sheet_s = self.s_listwidget.item(self.s_listwidget.currentRow()).text()
            selected_sheet_t = self.t_listwidget.item(t_row).text()

            # 원본과 대상 시트가 동일한 경우 경고 메시지
            if self.s_file_path == self.t_file_path and selected_sheet_s == selected_sheet_t:
                QMessageBox.warning(self, "경고", "같은 파일의 같은 시트는 데이터를 보낼 수 없습니다.")
                return

            wb_s = openpyxl.load_workbook(self.s_file_path)
            ws_s = wb_s[selected_sheet_s]

            ext_t = os.path.splitext(self.t_file_path)[1]
            if ext_t == ".xlsx":
                wb_t = openpyxl.load_workbook(self.t_file_path)
            else:
                wb_t = openpyxl.load_workbook(self.t_file_path, read_only=False, keep_vba=True)

            ws_t = wb_t[selected_sheet_t]

            # 원본 엑셀 파일의 제목 줄을 제외한 데이터 복사
            mr = ws_s.max_row
            mc = ws_s.max_column

            # ws_t의 다음 빈 행 찾기
            next_row = ws_t.max_row + 1

            for i in range(2, mr + 1):  # 제목 줄을 제외하기 위해 2부터 시작
                for j in range(1, mc + 1):
                    c = ws_s.cell(row=i, column=j)
                    if isinstance(c.value, (datetime.date, datetime.datetime)):
                        # 날짜 형식만 유지해서 저장
                        ws_t.cell(row=next_row, column=j).value = c.value.strftime('%Y-%m-%d')
                    else:
                        ws_t.cell(row=next_row, column=j).value = c.value
                next_row += 1    

            # 대상 파일 저장
            wb_t.save(self.t_file_path)
            wb_t.close()
            wb_s.close()

            # 대상 파일의 선택된 시트를 다시 로드하여 테이블 갱신
            self.loadData(t_row, "대상")
            QMessageBox.information(self, "성공", "저장이 완료되었습니다.")

        except Exception as e:
            print(e)
            return


if __name__ == '__main__':
    app = QApplication([])
    window = MyApp()
    window.show()
    app.exec()

 

pyside6_exceltoexcelmodifysamefilesamesheet.py
0.01MB