2
1

【TkEasyGUI】Excelシートの並べ替えをつくったった(6/13追記)

Last updated at Posted at 2024-05-30

ChatGPTがなあ!

やりたかったこと

Excelをこねこねする処理があり、シート名部分一致で処理を実行するため、処理させたいシートの順番を前に持っていきたかった。
……部分一致にさせるな案件と言えばそうなのだけども。
処理終わってからいちいち開いて直すより、GUI上で調整してから処理を開始すればよくね? になり、初のGUI画面作成へ。

ツール選定の経緯

初心者なので、pythonでGUI作るにはどんなフレームワークがいいのか分かりません。
天下のChatGPTに尋ねたところ、「Tkinter」や「PySimpleGUI」が軽くて良いよと教えてもらいました。

そしてPySimpleGUIを調べてみると、なるほど見やすい分かりやすいこれはいい!
https://qiita.com/dario_okazaki/items/656de21cab5c81cabe59

是非とも使いたかったのですが……なんと商用利用は有料(無償でもアカウント登録必須)になってしまったようで、類似のTkEasyGUIを使うことにしました。
https://daisuke20240310.hatenablog.com/entry/tkeasygui
2024年4月うまれのピチピチちゃん。

成果物

GUIさぱらんなのでサンプルコピペしながら動作を把握した後にChatGPTに任せました。
8割くらいChatGPT製。
エラーが出たとこだけ修正しました。

PySimpleGUIで作ってくださいと言って出してくれたコードをTkEasyGUIで使うようにしました。
とはいえ完全な互換性はないようなので、複雑な処理を書きたいときは要注意。

import TkEasyGUI as sg
# import PySimpleGUI as sg
import xlwings as xw
import pandas as pd

# Excelファイルを開く関数
def open_excel_file(file_path):
    try:
        read_book = pd.ExcelFile(file_path)
        sh_name = read_book.sheet_names     # sheet_names メソッドで Excel ブック内の各シートの名前をリストで取得
        app = xw.App(visible=False, add_book=False)
        wb = app.books.open(file_path)
        return wb, sh_name
    except Exception as e:
        sg.popup_error(f"Excelファイルを開く際にエラーが発生しました:{str(e)}")
        return None, []

# シートを並べ替える関数
def reorder_sheets(wb, sheet_names, from_index, to_index):
    try:
        
        # シートを移動する
        sheet_to_move = wb.sheets[from_index]
        new_name = wb.sheets[from_index].name
        # sheet_to_move.api.Copy(Before=sheet_to_move.api)
        if from_index > to_index:
            # 上に移動
            sheet_to_move.api.Copy(Before=wb.sheets[to_index].api)
            # sg.popup("moto"+str(wb.sheets[from_index+1].name))
            wb.sheets[from_index+1].delete()      
        else:
            # 下に移動
            sheet_to_move.api.Copy(After=wb.sheets[to_index].api)
            # sg.popup("moto"+str(wb.sheets[from_index].name))
            wb.sheets[from_index].delete()
        
        
        # sg.popup("moto"+str(wb.sheets[from_index].name)+"  ato"+str(wb.sheets[to_index].name))
        wb.sheets[to_index].name = new_name
        
        # ブックを保存して閉じる
        wb.save()
        wb.close()
        
        sg.popup("シートの並べ替えが完了しました。")
    except Exception as e:
        sg.popup_error(f"シートの並べ替え中にエラーが発生しました:{str(e)}")

# レイアウト
layout = [
    [sg.Text("Excelファイルを選択してください:")],
    [sg.Input(key="-FILE-", enable_events=True), sg.FileBrowse()],
    [sg.Text("シートの並べ替え:")],
    [sg.Listbox(values=[], size=(30, 6), key="-SHEETS-", enable_events=True)],
    [sg.Button("上に移動", key="-MOVE_UP-", disabled=True), sg.Button("下に移動", key="-MOVE_DOWN-", disabled=True)],
    [sg.Button("終了")]
]

# ウィンドウを作成
window = sg.Window("Excelシート並べ替え", layout)

# イベントループ
while True:
    event, values = window.read()
    if event == sg.WINDOW_CLOSED or event == "終了":
        break
    elif event == "-FILE-":
        file_path = values["-FILE-"]
        workbook, sheet_names = open_excel_file(file_path)
        window["-SHEETS-"].update(values=sheet_names)
        window["-MOVE_UP-"].update(disabled=True)
        window["-MOVE_DOWN-"].update(disabled=True)
    elif event == "-SHEETS-":
        selected_index = sheet_names.index(values["-SHEETS-"][0])
        if selected_index > 0:
            window["-MOVE_UP-"].update(disabled=False)
        else:
            window["-MOVE_UP-"].update(disabled=True)
        if selected_index < len(sheet_names) - 1:
            window["-MOVE_DOWN-"].update(disabled=False)
        else:
            window["-MOVE_DOWN-"].update(disabled=True)
    elif event == "-MOVE_UP-":
        selected_index = sheet_names.index(values["-SHEETS-"][0])
        reorder_sheets(workbook, sheet_names, selected_index, selected_index - 1)
        workbook, sheet_names = open_excel_file(file_path)
        window["-SHEETS-"].update(values=sheet_names)
    elif event == "-MOVE_DOWN-":
        selected_index = sheet_names.index(values["-SHEETS-"][0])
        reorder_sheets(workbook, sheet_names, selected_index, selected_index + 1)
        workbook, sheet_names = open_excel_file(file_path)
        window["-SHEETS-"].update(values=sheet_names)

window.close()

修正したのは大きく二か所。
-SHEETS-のイベント処理とシートを並べ替える関数(def reorder_sheets)です。

一つ目のところは、文字列を渡さなきゃいけない引数に永遠に数値を渡そうとするGPTがいたので手で修正。
こういうエラーが出るから直してクレメンス、と伝えたつもりでも期待通りの回答(エラーが出ないコード)が返ってこなかったのでぜんぜん使いこなせてない。

二つ目のところは、大本の処理でExcelを操作する際、xlwingsを使っていたのでなんとかしてxlwingsを使う処理にさせた次第です。
xlwingsではシート移動できないようなので、コピーを挿入してリネームするという力業で実装しました。
ひな形は作ってもらいましたが、やはりというかエラー吐くコードが出力されてしまったので最終的に自力で修正しました。

xlwingsに拘らない人はopenpyxlを使えば下のように短くかけるのでオススメ。

from openpyxl import load_workbook

# Excelファイルを開く関数
def open_excel_file(file_path):
    try:
        workbook = load_workbook(file_path)
        sheet_names = workbook.sheetnames
        return workbook, sheet_names
    except Exception as e:
        sg.popup_error(f"Excelファイルを開く際にエラーが発生しました:\n{str(e)}")
        return None, []

# シートを並べ替える関数
def reorder_sheets(workbook, sheet_names, from_index, to_index):
    try:
        sheet_name = sheet_names[from_index]
        workbook.move_sheet(sheet_name, offset=to_index - from_index)
        workbook.save(file_path)
        sg.popup("シートの並べ替えが完了しました。")
    except Exception as e:
        sg.popup_error(f"シートの並べ替え中にエラーが発生しました:\n{str(e)}")

おしまい

PySimpleGUIではOutputのところにprintの結果を出力できるらしいですが、今のところTkEasyGUIではできなさそうです。
そこだけが地味にぐぬぬとなりましたが、あとは大した不満はありません。
強いて言うならテーマでダークにできると嬉しいかなくらい。
ほぼほぼ同一のコード使えて便利でした。

6/13追記

なんか勘違いしていたようなのですが、printでログ出力できますね!
というメモ。
MultilineじゃなくてOutputでもできる。

import TkEasyGUI as eg
from datetime import datetime

logg =eg.Multiline(size=(70, 20), font=('Courier', 10))
layout = [
    [eg.Text("printテスト")],
    [logg],
    [eg.Submit(button_text='ログ入力',key="-input-")]
    ]
print = logg.print

# Create the window
window = eg.Window("Demo", layout, finalize=True)

while True:
    event, values = window.read()

    # とじる
    if event in [eg.WINDOW_CLOSED, "-close-"]:
        # wb.close()
        break

    if event == '-input-':
        now = datetime.now()
        ymd = now.strftime("%Y/%m/%d %H:%M:%S")   
        print('現在の時刻は {0} です'.format(ymd))

ただ、下記サイトの例のように問答無用でprintがoutputに出力することは出来ない模様。
https://shinshin86.hateblo.jp/entry/2021/09/25/092419

問答無用でprintが出力される仕様はクセつよそうだけども。
GUI上とパーツ分けしている部品単体で動かしたときのどちらもいいかんじにprintだせたらなあと思って悩んどります。
のでもうすこし悩みます。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1