🧠 概要:
概要
この記事では、退勤時刻までのカウントダウンタイマーアプリを作成する過程が紹介されています。Tkinterを用いたプログラムが中心となっており、ユーザーが勤務時間を設定すると、タイマーが稼働し、残りの時間を表示します。アプリは2つの画面を利用し、入出力のデータ管理やユーザーインターフェースの工夫がなされている点が特徴です。
要約
- アプリの目的: 退勤時刻までのカウントダウンタイマーを作成。
- 使用技術: Tkinterを用いてプログラム作成。
- 機能:
- 就業開始時刻、終了時刻、昼休憩時間を入力するためのテキストボックス。
- 設定した時間をJSON形式で保存。
- カウントダウンタイマーが終了時間までの残り時間を表示。
- 昼休憩中はタイマーが停止。
- 画面構成: 2画面のアプリケーションで、入力画面とタイマー表示画面を持つ。
- プログラムの規模: 複雑なロジックが必要で、行数も増加(127行)。
- 開発者の学び: 複雑なプログラムロジックの必要性や、画面数が増えることでのコードの複雑化を実感。
- チャレンジの背景: 100個アプリを開発するプロジェクトの一部として、成長を目指す。
これらは、退勤時刻カウントダウンタイマーの実装やその過程での技術的な挑戦と成果を反映しています。
前回の応用として退勤時刻をカウントダウンするタイマーを作りました。
ChatGPTに命令した内容は以下になります。
tkinterを使って以下の条件を満たすプログラムを作って・目的は定時まで何時間か計測するプログラムの作成・前提条件として、最初の画面でテキストボックスに就業開始時間と就業終了時間、昼休憩時間の開始時間と昼休憩の終了時間(すべて24時間記載方式)を入力し、登録ボタンを押すとこの4つのデータがJSON形式でプログラムと同じフォルダに出力される・JSON形式でフォルダに出力されたと同時に、先に作った付箋風デジタル時計が表示され、最初の画面は消える。・この付箋風デジタル時計に表示されるのは、時間ではなく就業終了時間まで何時間何分何秒かを表示する。(これを就業終了タイマーと定義)・そのため就業終了時間に現在の時刻を差し引いて、就業終了時間までの時間を算出する。・ただし、昼休憩時間の開始と終了があるので、この時間帯は時計の進行をストップさせる必要がある。・就業終了タイマーに画面が遷移したら、右クリックメニューにある「終了」、もしくは「メニューに戻る」のいずれかをクリックしないと、テキストボックスの時間設定(つまり最初の設定画面)に戻れないものとする。
・「終了」はそのままアプリを終了。「メニューに戻る」は就業終了タイマーの画面から、最初の画面に遷移するという意味でプログラムを作成したい。
ChatGPTへの命令
2画面使ったアプリケーションになるので、必然的に命令文(プロンプト)は長くなります。
自分の中で仕様に漏れはないか、まず頭の中でロジックを組んで、その後でChatGPTに命令しないと、バグだらけのプログラムになるからです。
そうしてChatGPTが作ったプログラムが以下になります。
import tkinter as tkfrom tkinter import messageboxfrom datetime import datetime, timedeltaimport jsonimport osimport threading CONFIG_FILE = "work_config.json" class SetupWindow: def __init__(self, root): self.root = root self.root.title("就業時間設定") self.root.geometry("300x300") tk.Label(root, text="出勤時刻 (HH:MM)").pack() self.start_entry = tk.Entry(root) self.start_entry.pack() tk.Label(root, text="退勤時刻 (HH:MM)").pack() self.end_entry = tk.Entry(root) self.end_entry.pack() tk.Label(root, text="昼休憩開始 (HH:MM)").pack() self.break_start_entry = tk.Entry(root) self.break_start_entry.pack() tk.Label(root, text="昼休憩終了 (HH:MM)").pack() self.break_end_entry = tk.Entry(root) self.break_end_entry.pack() tk.Button(root, text="登録", command=self.save_config).pack(pady=10) def save_config(self): try: config = { "start": self._validate_time(self.start_entry.get()), "end": self._validate_time(self.end_entry.get()), "break_start": self._validate_time(self.break_start_entry.get()), "break_end": self._validate_time(self.break_end_entry.get()) } with open(CONFIG_FILE, "w") as f: json.dump(config, f) self.root.destroy() show_timer_window(config) except ValueError as e: messagebox.showerror("エラー", str(e)) def _validate_time(self, time_str): datetime.strptime(time_str, "%H:%M") return time_str class TimerWindow: def __init__(self, config): self.root = tk.Tk() self.root.overrideredirect(True) self.root.wm_attributes("-topmost", True) self.root.geometry("250x100+100+100") self.label = tk.Label(self.root, font=("Arial", 18), bg="lightyellow", fg="black") self.label.pack(expand=True, fill="both") self.label.bind("<Button-3>", self.show_menu) self.label.bind("<Button-1>", self.start_move) self.label.bind("<B1-Motion>", self.do_move) self.menu = tk.Menu(self.root, tearoff=0) self.menu.add_command(label="メニューに戻る", command=self.return_to_menu) self.menu.add_command(label="終了", command=self.root.quit) self.config = config self.update_clock() self.root.mainloop() def get_remaining_time(self): now = datetime.now() end = datetime.combine(now.date(), datetime.strptime(self.config["end"], "%H:%M").time()) break_start = datetime.combine(now.date(), datetime.strptime(self.config["break_start"], "%H:%M").time()) break_end = datetime.combine(now.date(), datetime.strptime(self.config["break_end"], "%H:%M").time()) remaining = end - now if break_start <= now < break_end: remaining += break_end - now return remaining def update_clock(self): remaining = self.get_remaining_time() if remaining.total_seconds() < 0: self.label.config(text="勤務時間終了") else: hours, remainder = divmod(int(remaining.total_seconds()), 3600) minutes, seconds = divmod(remainder, 60) time_str = f"{hours:,}:{minutes:02}:{seconds:02}" self.label.config(text=time_str) self.root.after(1000, self.update_clock) def show_menu(self, event): self.menu.post(event.x_root, event.y_root) def start_move(self, event): self.offset_x = event.x self.offset_y = event.y def do_move(self, event): x = self.root.winfo_pointerx() - self.offset_x y = self.root.winfo_pointery() - self.offset_y self.root.geometry(f"+{x}+{y}") def return_to_menu(self): self.root.destroy() main() def show_timer_window(config): TimerWindow(config) def main(): root = tk.Tk() app = SetupWindow(root) root.mainloop() if __name__ == "__main__": main()
なんと行数が一気に増えて、127行のプログラムになりました。
今までのプログラムは、1画面で完結するものでしたが、今回は2画面使うアプリケーションのため、行数が一気に増えることになります。
というのは1画面だと、入力と出力(今までのプログラムは入力も固定のものがほとんど)が1つだけでしたが、画面が2つになると相互の画面のデータの受け渡し、1画面目のデータの入力もロジックとして組むので、プログラムの規模はそれなりに大きくなります。
「#100日チャレンジ 毎日連続100本アプリを作ったら人生が変わった」の著者大塚あみ氏は、400行のプログラムをChatGPTで作ったと言っていますが、当然画面数やプログラムの規模が大きくなると、そうならざるを得ません。
例えば3画面を遷移させると、相互でどのようなデータの入出力があるのか、画面の設計はどのくらい複雑なのかという課題を自分の頭の中でしっかりロジックを組まないと、バグだらけのプログラムができてしまいます。
この2画面でこれだけの複雑さなのですから、ソースコードの行数も指数関数的に伸びていくのは必然かと。
そうした中100個アプリを開発する(バージョンアップ含め)場合、規模も含めて後半に作るのか、簡単なものだけで100個作るのかによっても成長曲線は変わっていくという気がします。
今回2画面プログラムを作って、学習したことは以上になります。
動画は以下になります。
Views: 0