プログラムでおかえしできるかな

Python、フェイジョア、日常のあれこれでお返し、元SEの隠居生活。

連続動作するタイマーの作り方【Python】

このエントリーをはてなブックマークに追加

連続動作するタイマーを Python アプリとして作成しました。
Tkinter の after() メソッドでタイマーを実現しています。
JSON ファイルの読み方やコンボボックスのスタイルなどちょっとした工夫もあります。
ソースコードと併せて紹介します。

▽アプリの画面

アプリはお使いいただけます 📖 連続動作するタイマー【フリー】 🔗
アプリの画面や機能などはこちらの記事で確認できます。

本アプリのコードについて説明します。
使用しているプログラミング言語は Python です。

目次

◆タイマー機能の考え方と使用する関数

Python でタイマー機能を実現するには、Tkinter の after() メソッドや threading.Timer クラスを使用する方法があります。
今回はTkinter の after() メソッドを使用しました。
after() メソッドは、指定した時間が経過した後に関数を起動するメソッドで、これを繰り返し呼び出すことで一定時間間隔でカウントダウンを行います。

graph LR a1[func
after実行]--待ち時間-->a2[func
after実行]--待ち時間-->a3[func
after実行]-->a4((繰り返し))

待ち時間を1秒にすることで1秒ごとにカウントする処理を実現できます。

◇after()メソッドを使った簡単なタイマーの例

5から始めて1秒ごとにカウントダウンするタイマーの例です。
Tkinter で画面を作って残り時間を表示します。

import tkinter as tk

app = tk.Label(None, font=("", 20))
app.pack()

def countdown(count):
    if count > 0:app.after(1000, countdown, count - 1)
    app.config(text=str(count))

countdown(5)
app.mainloop()

◇after()メソッド

Tkinter が提供しているメソッドです。
指定した時間経過後に関数を実行します。つまり関数の実行スケジュールを行えます。
すべてのウィジェットで使用可能なメソッドです。

  • 【構文】after(delay_ms, callback=None, *args)
  • 例:after(1000, self.countdown, count - 1)
  • 主な引数
    • delay_ms:遅延時間(ms)
    • callback :関数
    • *args  :関数に渡す引数
  • 返り値
    after_cancel() メソッドに渡す ID

◇afterのキャンセル:after_cancel()メソッド

after() メソッドで待機している関数の実行をキャンセルします。

  • 【構文】after_cancel(id)
  • 主な引数
    • idafter() メソッドの返り値

◆タイマー機能の実装

本アプリでどのようにタイマー機能を実装したか説明します。

はじめに、いくつか勝手に用語を定義しているので説明します。

  • ジョブ  :カウントダウンする時間とその説明を定義したもの
          シーケンスの中に複数のジョブを定義できます
  • シーケンス:複数のジョブを定義したもの
          複数のジョブはその定義順に処理します
          複数のシーケンスを定義できます

シーケンス ジョブ
朝の体操 (屈伸 , 10秒)、(腕回し  , 10秒)、(首回し, 10秒)
昼の体操 (前屈 , 10秒)、(腕立て伏せ, 20秒)、(腹筋, 30秒)
夜の体操 (足上げ, 10秒)、(背筋伸ばし, 10秒)

本アプリでは、複数あるシーケンスから一つを選択します。
選択されたシーケンスのジョブを一つずつ処理します。
ジョブの処理では、ジョブに設定された時間をカウントダウンします。
これを繰り返して連続して動作するタイマーを実現します。

また、これらの設定を JSON ファイルに定義します。
アプリでは、JSON ファイルを読み込んで、これらの設定を辞書として保持します。

◇カウントダウン

「◆タイマー機能の考え方と使用する関数」で説明したように Tkinter の after() メソッドを使用して1秒ごとのカウントダウンを実装します。
カウントダウンするということは1秒ごとに表示する数値を減らしていきます。
表示する数値を引数にした関数を用意し、1秒ごとに呼ばれるようにします。
関数の中で自分自身を次に呼ばれる時間でスケジューリングすれば繰り返し実行されます。
厳密には遅延が生じているはずですが、それでも遅延を最小限にするために、次のスケジューリングを関数のはじめに行います。

▽手順

【カウントダウン(メソッド)】

  1. 引数の残り時間が0より大きい場合
    • 次の countdown() メソッドの実行を予約:after()
      引数で残り時間から1引いたものを指定
  2. 引数の残り時間が0の場合:現在のジョブのカウントダウン終了
    1. 次のジョブの時間をカウントダウンするよう設定
      1. キューからジョブを取得:popleft()
      2. 次の countdown() メソッドの実行を予約:after()
        引数でキューから取得した時間を指定
      3. キューの説明内容をラベルに設定
      4. リストボックスの選択位置を次のジョブに変更
        1. 現在選択されている要素番号を取得:curselection()
        2. 選択解除:clear()
        3. 次の要素を選択:set()
    2. キューが空だったら:例外で判断
      • リストボックスの選択を解除
      • 説明内容を空文に設定
  3. 画面表示を更新:update_count()
  4. 音を鳴らす処理:beep()

【画面表示の更新】

  1. 引数の残り時間を分と秒に分ける:divmod()
  2. 分が0の場合
    • 秒数を残数用ラベルに設定
    • 「●」を分数分カウンター用ラベルに設定
      同じ文字を続けた文字列を作る場合 "文字"*文字数 で作れます
    • 秒数をタイトルに設定
  3. 分が0でない場合
    • 「分:秒」を残数用ラベルに設定
    • 「◎」を分数分カウンター用ラベルに設定
    • 「分:秒」をタイトルに設定
  4. 表示を更新:update_idletasks()

※画面の更新はどこか一か所でまとめてやると動きがぎくしゃくしません
当初、ウィジェット変数を使用して更新していました。
そうすると複数のウィジェット変数のそれぞれの更新時に画面を更新するので画面更新がぎくしゃくしてしまいました。
なので更新すべきデータの設定完了後、update_idletasks() メソッドで更新しています。

▽コード:カウントダウン

    def countdown(self, count:int, event=None):
        """
        カウントダウン(1秒ごとに実行)次の実行をafter()メソッドで予約
        Args:
            int:    残り時間
        """
        # afterは速やかに処理して遅れを最小限にする
        if count > 0:
            # call countdown again after 1000ms (1s)
            self.after_id = self.view.after(1000, self.countdown, count - 1)
        else:
            # 次のジョブを設定
            try:
                que = self.que.popleft()                        # 次のジョブを取得
                # カウントダウン予約
                self.after_id = self.view.after(1000, self.countdown, que[1])
                self.view.lbl_explain["text"] = que[0]          # 説明を画面にセット
                # リストボックスの選択されている要素の選択を解除して次の要素を選択
                i = self.view.lbx_jobs.curselection()[0]        # 選択されているリストid取得
                self.view.lbx_jobs.select_clear(i)              # 選択解除
                self.view.lbx_jobs.select_set(i + 1)            # 選択設定
            except (IndexError, AttributeError) as e:
                # キューの残りがなくなった時の処理
                # リストボックスの選択状態を解除
                for i in self.view.lbx_jobs.curselection():
                    self.view.lbx_jobs.select_clear(i)
                self.view.lbl_explain["text"] = ''

        # カウントダウン時に実行する処理
        logger.debug(f"カウントダウン開始 {count}")
        self.view.update_count(count)   # 画面表示を更新
        self.beep(count)               

▽コード:残量の画面表示更新

    def update_count(self, count:int):
        """
        残り時間の表示更新
        ウィジェット変数を使って更新するとぎくしゃくするので
        使わずに複数のウィジェットに値を設定して最後にupdate_idletasks()する
        Args:
            int:    残り時間(秒)
        """
        m, s = divmod(count, 60)                    # 秒を分と秒に分ける
        if m == 0:  # 1分未満の場合
            self.lbl_rest['text'] = s
            self.lbl_counter.config(text='●'*s)
            self.master.title(f"{s:02} カウントダウンタイマー") # タイトルの残り時間を更新
        else:
            self.lbl_rest['text'] = f"{m}:{s:02}"
            self.lbl_counter.config(text='◎'*m)
            self.master.title(f"{m}:{s:02} カウントダウンタイマー") # タイトルの残り時間を更新
        self.update_idletasks()

※ラベルウィジェットの表示文字更新に2種類の方法を使っていますが、特に意味はありません。
widget["text"]=widget.config(text=)
 どちらが使い勝手がいいか試しています。

▽リストボックスの操作

▽使用したメソッド

  • curselection():選択要素の取得(タプル)
  • select_set(first, last=None):要素の選択。first から last まで。first だけなら一つ
  • select_clear(first, last=None):要素の選択解除

◇シーケンスのカウントダウン

シーケンスのカウントダウンとは、シーケンスに定義されているジョブの時間を連続してカウントダウンしていくことです。
そのためには、カウントダウンを連続して行う仕組みが必要になります。

今回、ベースとなる一つのカウントダウンの処理ができた後、続けて別のカウントダウンの処理をどうやって始めるかを検討しました。
スレッド化なども検討しました。
最終的に、作ったカウントダウンの処理を新しい時間で再度開始することで実現することにしました。
そのために使用したのがキューです。キューは一時的なデータを先入先出で扱えるものです。
シーケンスの開始時にシーケンス内のジョブをキューに蓄えます。
キューから始めのカウントダウンデータを取り出します。
一つのカウントダウンが終わったら、次のカウントダウンデータをキューから取り出します。
これらを繰り返し、キューのデータが無くなったら終了します。
こうすることでシーケンス内のジョブを順次カウントダウンできるようにしました。

以下で説明している手順は、キューの作成と1件目のデータの取出しです。
一つのジョブのカウントダウンが終わって、次のジョブへ移る時のキュー操作は、「◇カウントダウン」で説明しています。

▽手順

【シーケンスのカウントダウン】

  1. シーケンスの内容(ジョブ(内容と時間のタプルのリスト))をキューに設定
    画面で選択されたシーケンスの内容で deque インスタンスを作成
  2. シーケンスの1件目の設定
    • シーケンス表示リストボックスの1件目を選択
    • キューからデータ取得:popleft()
    • カウントダウン開始:beep_and_countdown_start()countdown()
    • 説明内容をラベルにセット

▽コード:シーケンスのカウントダウン

    def seq_countdown(self, event=None):
        """
        シーケンスのカウントダウン開始(先頭のジョブからカウントダウン開始)
        """
        self.view.set_wraplength()  # 説明用ラベルの折り返し幅をリストボックスの幅に合わせる
        # シーケンスの内容(ジョブ(内容と時間のタプルのリスト))をキューに設定
        self.que = deque(self.model.mjc_json[self.view.var_seqs.get()])
        # シーケンスの1件目の設定
        self.view.lbx_jobs.select_set(0)        # リストボックスの選択
        que = self.que.popleft()                # キューからデータ取得
        self.beep_and_countdown_start(que[1])   # カウントダウン開始
        # self.countdown(que[1])                # カウントダウン開始
        self.view.lbl_explain["text"] = que[0]  # 説明を画面にセット
        logger.debug(f"シーケンス開始 {que[1]}")

▽collections.dequeクラス

キューを提供するクラスです。
インポートして使用します。

  • インポート:from collections import deque

▽コンストラクタ

  • 【構文】deque([iterable[, maxlen]])
  • 主な引数
    • iterable:このデータから、新しい deque オブジェクトを初期化
      append() を使って左から順に追加
      指定されない場合、新しい deque オブジェクトは空になります

▽メソッド

  • append(x)     :x を右に追加
  • appendleft(x)   :x を左に追加
  • clear()      :全要素を削除
  • count(x)      :x に等しい要素数を返す
  • extend(iterable)  :イテラブルを右に追加
  • extendleft(iterable) :イテラブルを右に追加
  • pop()       :右から要素を一つ削除して返す。空の場合は IndexError
  • popleft()     :左から要素を一つ削除して返す。空の場合は IndexError

◇カウントダウンの中断

カウントダウンの中断は、after() メソッドでスケジューリングしたものを after_cancel() メソッドでキャンセルします。

▽手順

【カウントダウンの中断】

  1. after をキャンセル:after_cancel()
    after() メソッドを実行した時の返り値を使用
    取得できない時は例外が発生する
  2. キューをクリア:clear()

▽コード:カウントダウンの中断

    def abort_cd(self, event=None):
        """
        カウントダウンの中断
        """
        try:
            self.view.after_cancel(self.after_id)   # afterをキャンセル
            self.que.clear()                        # キューをクリア
        except (NameError, AttributeError):
            pass

◇音を鳴らす

本アプリでは、一つのジョブのカウントダウンが終わる時に時報のような音を鳴らしてお知らせしています。
3秒前から「プ、プ、プ、ピー」と鳴らします。

音を鳴らすには、winsound モジュールを使用します。
インポートして使用します。

  • インポート:import winsound as ws

モジュールの Beep() メソッドでビープ音を鳴らします。
winsoundモジュールは標準モジュールで使用にはインポートが必要です。
基本は、音の周波数を指定してどのくらいの長さ鳴らすのかを指定します。
今回は、時報と同じ音にしました。自分でよい音を見つけられなかったので(笑)
時報の音は、プが 440Hz でピーが 880Hz です。音階でいうと「ラ」です。
長さはそれぞれ 500ms と 1000ms にしました。

▽手順

【音を鳴らす】

  1. 引数が3より大きければ何もせず戻る
  2. ビープ音の周波数設定
  3. 引数が0の時は「ピー」の音、それ以外は「プ」のビープ音を鳴らす

▽コード:音を鳴らす

    def beep(self, count:int):
        """
        残り3秒になったら音を鳴らす
        Args:
            int:    残り時間
        """
        if count > 3: return
        f_d = {False:(440, 500), True:(880, 1000)}  # 時報のような音(ラ、ラ、高いラ)
        ws.Beep(f_d[count == 0][0], f_d[count == 0][1])

▽Beep()メソッド

  • 【構文】winsound.Beep(frequency, duration)
  • 主な引数
    • frequency:周波数(Hz)37 から 32767 の範囲
    • duration:音を出している長さ(ms)

◇開始音を鳴らす

当初、音を鳴らすのはカウントダウン終了時だけにしていました。
そうすると音が出るまでに遅れが出ることがあったので開始音を鳴らすようにしました。
936Hz の音を 250ms 鳴らします。
この周波数は活性の周波数として紹介されていたので採用しました。
開始ボタンが押された時に開始音を鳴らすメソッドを呼ぶようにし、そのメソッド内から、カウントダウン開始のメソッドを呼び出しています。

▽コード:開始音を鳴らす

    def beep_and_countdown_start(self, count:int):
        """
        開始時に音を鳴らすしてからカウントダウンを開始

        Args:
            count(int):    残り時間
        """
        ws.Beep(936, 250)           # 活性の周波数
        self.countdown(count)       # カウントダウン開始

追加:2024-07-01

◇シーケンス内容の表示

連続してタイマーを動作させる場合、どのジョブをカウントダウンしているのか分かる方が親切です。
そのため、選択したシーケンスの内容(ジョブ)を表示するようにしました。

表示にはリストボックスウィジェットを使用します。
また、カウントダウンしているジョブが分かるように、該当ジョブを選択状態にします。

リストボックスの内容はウィジェット変数を割り当てます。
そうすることでウィジェット変数(var_jobs)に表示させたい内容をセットするだけで表示内容を更新できます。

▽手順

【表示内容設定】

  1. シーケンス用コンボボックスで選択されているシーケンスの取得:get()
  2. シーケンス用辞書の選択された要素から内容と時間を取りだし表示用文字列に変換したリストを作成:内包表記
  3. 作成したリストをウィジェット変数にセット:set()

▽コード:シーケンス内容の表示

▽リストボックスの定義

        # ジョブリストボックス
        self.var_jobs = tk.StringVar(value=[])
        self.lbx_jobs = tk.Listbox(master, listvariable=self.var_jobs, height=0)
        self.lbx_jobs.pack(fill=tk.X)

▽リストボックスへ表示内容設定

    def set_jobs(self, event=None):
        """
        選択されたシーケンスのジョブをリストボックスに設定
        """
        # モデルのデータをビューにセット
        d = self.model.mjc_json
        job = self.view.cmb_seqs.get()  # 選択されたシーケンスを取得
        # 選択されたシーケンスのタプルのリストを取得(「選択されたシーケンス」キーの値)
        # タプル:(内容, 時間)から「内容(分:秒)」に変換
        v = [f"{veiw} (%d:%02d)"%divmod(time, 60) for veiw, time in d[job]]
        self.view.var_jobs.set(v)       # リストボックスに設定

▽シーケンス用コンボボックスで選択変更した時のイベント登録

    def set_my_ctr(self, my_ctr):
        """
        MyControlクラスの参照を設定
        Args:
            MyControl:  MyControlオブジェクト
        """
        self.my_ctr = my_ctr
        self.cmb_seqs.bind('<<ComboboxSelected>>', self.my_ctr.set_jobs)

▽アプリ起動時のリストボックスの初期化

        # マルチジョブカウントダウンタイマーのデータをモデルからビューへ
        # シーケンスの取得(一層目がシーケンスの辞書)
        d = self.model.mjc_json
        self.view.cmb_seqs.config(values=list(d.keys()))
        job = list(d.keys())[0]     # 先頭を取得
        self.view.cmb_seqs.set(job) # 先頭を選択状態に
        self.set_jobs()             # ジョブをリストボックスに設定(setではイベントが発生しないため)

◆JSONファイルを変換して取得する

設定ファイルは JSON 型式にしました。
次の2つのファイルを読んで使用します。

  • timer.json   :タイマー用 JSON ファイル
  • mjc_timer.json:シーケンス用 JSON ファイル

詳しい仕様についてはこちらの記事を参照してください。
 📖 ◆設定ファイル - 連続動作するタイマー【フリー】 🔗

JSON ファイルの構造は次のようになっています。
構造は、mjc_timer.jsontimer.json とも同じです。

   {
     "シーケンス名":[[ジョブの説明, 時間], [ジョブの説明, 時間], [ジョブの説明, 時間]...],
     "シーケンス名":[[ジョブの説明, 時間], [ジョブの説明, 時間], [ジョブの説明, 時間]...]
    }

更に時間については、人が判断しやすいように例えば、3"3""1:23" と書かれており、これをプログラムで扱いやすいように秒数(整数)に変換します。
"1:23" の変換には datetime.strptime() メソッドを使用します。
これは日時の書式にあった文字列を解釈して datetime オブジェクトを返します。
更に分と秒を指定して timedelta オブジェクトを作成し、total_seconds() メソッドで秒に変換します。
total_seconds() メソッドは結果を float で返すので int に変換します。

◇JSONファイルの読み込み

この JSON ファイルを読み込んで時間の変換を施し辞書を作成します。

▽手順

【設定ファイルの読み込み:モデルクラスのインストラクタ】

  1. mjc_timer.json ファイルの読み込み(シーケンス用):load()
    読み込み時にデータの変換を行う:str_to_sec
  2. timer.json ファイルの読み込み(タイマー用):load()
    読み込み時にデータの変換を行う:str_to_sec
  3. 例外処理
    • ファイルがない場合にダイアログ表示して終了
    • JSON のデコードエラーの場合、エラー内容を表示して終了
    • その他の例外の場合、エラー内容を表示して終了

【辞書の編集】

  1. 辞書を読み込んで内包表記でタプルの時間側のデータを整数の秒に置換
    • 時間側のデータ置換:to_sec()
      1. データが整数の場合はそのまま返す
      2. 時間表示の文字列の場合は datetime 型に変換:strptime()
      3. datetime 型から秒を取得:total_seconds()
      4. 文字列が時間表示でなかったらそのまま整数に変換

▽コード:設定ファイルの読み込み

▽設定ファイルの読み込み

    def __init__(self) -> None:
        """
        コンストラクタ:JSONファイルの読み込み
        """
        try:
            # マルチジョブカウントダウンタイマーデータの読み込み
            path = "mjc_timer.json"
            with open(path, 'r') as f:
                self.mjc_json = json.load(f, object_hook=self.str_to_sec)
            # シンプルタイマーデータの読み込み
            path = "timer.json"
            with open(path, 'r') as f:
                self.timer_json = json.load(f, object_hook=self.str_to_sec)
        except FileNotFoundError:
            messagebox.showerror("エラー", f"ファイルが存在しません - {path}")
            sys.exit(1)
        except json.decoder.JSONDecodeError as e:
            messagebox.showerror("JSON記述エラー", f"{e.msg}")
            sys.exit(1)
        except Exception as e2:
            messagebox.showerror("例外", f"{e2.msg}")
            sys.exit(1)

▽辞書の編集

    def str_to_sec(self, d:dict) -> dict:
        """
        辞書を編集して辞書で返す(json.load()のpbject_hook用)
        時間の値にある文字を秒に変更
        辞書の構成:{ジョブキー:[[表示, 時間],[]...]
        Args:
            dict:   辞書
        Returns:
            dict:   変換後辞書
        """
        for k ,v in d.items():
            d[k] = [[kk, self.to_sec(vv)] for kk, vv in v]
        return d

▽時間情報の変換

    def to_sec(self, x) -> int:
        """
        文字で書かれた時間を秒に変更
        Args:
            any:    時間(秒を示すint、秒、分秒を示す文字列)
        Returns:
            int:    秒
        """
        # 整数の場合はそのまま返す。このケースが多いので先に判断する
        if isinstance(x, int): return x
        
        try:
            t = datetime.strptime(x, "%M:%S")
            t = int(timedelta(minutes=t.minute, seconds=t.second).total_seconds())
        except ValueError:
            t = int(x)
        return t

◇load()メソッド

  • 【構文】json.load(fp, *, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
  • 主な引数
    • fp:ファイルなど
    • object_hook:オプションの関数で結果(dict)に対して呼び出されobject_hook の返り値は dict の代わりに使われます。この機能は独自のデコーダを実装するのに使えます。
    • parse_float:指定されるとJSON の浮動小数点数文字列に対して実施されます。例えば、JSON 浮動小数点数に対してパーサ (たとえば decimal.Decimal) を使うのに使えます。
    • parse_int:指定されるとJSON の整数文字列に対して実施されます。例えば、JSON 整数に対してパーサ (たとえば float) を使うのに使えます。

◆Tkinterによる画面構築

Tkinter(ティーキンター)は Python が標準で提供している GUI パッケージです。
他のプログラムの開発ツールで提供されているようなルック&フィールな GUI ツールではありません。
従って、画面を構成するには、すべてコードを書く必要があります。

Tkinter を使用するにはインポートが必要です。
また、今回は ttk(Tkinter の拡張) にしかないコンボボックスを使用しています。そのためのインポートも必要になります。

  • インポート:import tkinter as tk
  • インポート:from tkinter import ttk

慣例的に tk と別名を付けるようです。

◇Tkinterの使用ウィジェット

Tkinter がサポートする GUI の部品をウィジェット (widget) と呼びます。

ここでは次のウィジェットを使用しています。

◇使用ウィジェット

ウィジェットは基本的にインスタンスを作成し、表示テキストや表示方法などを指定して使用します。

今回使用しているウィジェットです。

パッケージ ウィジェット名 用途 見た目
tkinter Frame
フレーム
ウィジェットの受け皿
tkinter.ttk Combobox
コンボボックス
選択可能なテキストを表示するプルダウンボックス t09
tkinter Button
ボタン
押すと処理が動くボタン
tkinter Listbox
リストボックス
選択可能なテキストを表示するボックス t08
tkinter Label
ラベル
文字、画像の表示

◇コンボボックスのリストボックス部分のスタイル

コンボボックスで使い方が他のウィジェットと違う部分があるので説明します。

▽リストボックス部分のオプション

コンボボックスでポップダウンして現れるリストボックスは、オプションデータベースを使用してオプションを設定します。
そのために option_add() メソッドを使用します。

設定例:

  • option_add('*TCombobox*Listbox.Justify', 'center')   中央揃い
  • option_add('*TCombobox*Listbox.background', 'yellow')  背景を黄色に

※たぶん ttk のスタイルを使用しているのだと思います。
 複数のコンボボックスを作成した場合、両方のスタイルが変わります。

本アプリでは、コンボボックスに表示される文字を中央揃いにしています。

▽コード:時間選択コンボボックスの作成

        # 時間選択コンボボックス
        self.times = {'5秒':5, '10秒':10, '15秒':15, '20秒':20} # 仮の初期値
        self.var_time = tk.StringVar()
        self.cmb_time = ttk.Combobox(master, justify=tk.CENTER              # エントリー部分を中央揃いに
                                    , textvariable=self.var_time
                                    , values=list(self.times.keys()))
        self.cmb_time.option_add('*TCombobox*Listbox.Justify', 'center')  # リストボックス部分を中央揃いに
        self.cmb_time.pack()
        self.cmb_time.set('5秒')

◇画面構成 - フレーム構造

プログラムを作るにあたって、どのような画面にするか決めなくてはなりません。画面構成を考えるということですね。
画面を構成するというのは、GUI 部品(ウィジェット)を画面に配置することです。
ウィジェットは設定したテキストに合わせてサイズが決まるので、そのまま使用しても画面構成上問題ないものと、広げないと見栄えが良くないものが出てきます。
今回は基本的に横に広げて上から順番に配置しています。

今回の画面構成です。

  • cmb_time(ComboBox):単純なタイマーの時間選択し
  • btn_start(Button):タイマーカウントダウン開始ボタン
  • cmb_time(ComboBox):シーケンスの選択肢
  • btn_seq_start(Button):シーケンスカウントダウン開始ボタン
  • lbx_jobs(ListBox):シーケンスのジョブを表示
  • lbl_rest(Label):残り時間を表示
  • lbl_counter(Label):残り時間を図形表示
  • lbl_explain(Label):シーケンスのカウントダウン中ジョブの説明を表示
  • btn_break(Button):カウントダウン中止ボタン

※「cmb_time」などはプログラムで使用した変数名です。()内はクラス名です。

◇ウィジェットの配置 - pack

▶説明は別記事を参照してください
 📖 ウィジェットの配置 - pack - SQLクライアントアプリの作り方(Tkinterで表)【Python】 🔗

◇動作をウィジェットに割り当てる - バインド

▶説明は別記事を参照してください
 📖 動作をウィジェットに割り当てる - Excel viewerアプリの作り方(Tkinterでタブと表)【Python】 🔗

本アプリでは、ボタンウィジェットの command オプションで割り当てています。

        self.btn_start = tk.Button(master, text='開始'
            , command=lambda:self.my_ctr.beep_and_countdown_start(self.times[self.var_time.get()]))
        self.btn_seq_start = tk.Button(master, text='シーケンス開始'
                            , command=lambda:self.my_ctr.seq_countdown())

更新:2024-07-01

◆ソースの取得

全体のソースはこちらから取得できます。

  • ソース   :mjc_timer.py
  • 設定ファイル:timer.json   :時間用 JSON ファイル
  • 設定ファイル:mjc_timer.json:シーケンス用 JSON ファイル
  • 取得先   :GitHub juu7g/Python-mjc-timer

◇更新情報

  • 1.0.1:2024-07-01
    • 残り時間をタイトルに表示
    • カウント開始時にブザーを鳴らす
  • 1.0.0:2024-02-20:初期リリース

更新:2024-07-01

◆さいごに

連続動作するタイマーの作り方を紹介しました。

時間経過のアニメーションは、円周上の丸を消していくように作ろうと検討していました。
プログレスバーを円にするのは難しそうでした。
アナログ時計のコードを参考にすれば可能に感じました。
でも、今回はそこが目玉ではないと思い方針変更して単に丸の数で示すようにしました。
使っていただくには見た目も大事なんですが・・・


あわせて読みたい 📖 音声入力で文章作成するアプリの作り方【Python】 🔗

◇ご注意

本記事は次のバージョンの下で動作した内容を基に記述しています。

  • Python 3.8.5

◇免責事項

ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。

◆参考

投稿: 、更新: