Python+PySimpleGUI コンボボックスの日付入力

目次

日付をターゲットにしたコード演習など

今回のサンプルは誕生日を入力して年齢を表示するというだけの単純なもの。
コンボボックスでの選択入力も追加した。
しかし、相変わらず分からんことだらけでつまづきまくり、これだけでも丸一日以上も費やしてしまった。

初期画面
データ入力後
サンプルの実装機能
  • 日付時刻ライブラリの利用と日付演算の基本
  • コンボボックスで生年月日を選択入力
  • コンボボックスとテキストボックスの相互連動
  • 誕生日年月に応じてコンボボックスの日数を更新
  • 年齢計算用の独自関数作成
  • If 文による条件分岐
  • Frame による画面配置演習
  • enable_events によるエレメント単位でのイベント駆動

最初に念のため書いておくと、日付入力支援なんて CalendarButton関数 1行で済むことくらいは分かっているし、Calendar のフォントも気に入らないが、今は追求しないでおく。
今回のサンプルはコンボボックス等の演習目的なので、悪しからず。( ´ー`)

SG.CalendarButton("Calendar",target="日付")]

target(“キー名”) 引数で、Calendar の実行結果を表示するエレメントを指定する。
日付を選択すると”2017-01-18 22:24:20“のような日付時刻文字列が”日付”テキストボックスに表示される。
※PySimpleGUIライブラリの組込関数であり、次項の Calendarライブラリの関数とは別ものである

日付と時間を簡便に扱えるライブラリ

デスクトップアプリケーションを作成するには日付と時間の処理が欠かせない。
datetimecalendar ライブラリを使うと割りとラクになるので、GUI ライブラリと一緒に冒頭で import しておく。
現在の日付情報は何度も使用するのでグローバル変数に格納しておく。
ライブラリ名.date.today() で現在の日付が取得できる。

import PySimpleGUI as SG
import datetime as DT
import calendar
本日=DT.date.today()

ところで。
ライブラリ増加で実行ファイルサイズがどれくらい大きくなるのだろう・・・試すのが怖い気も (;`ー´)
機能を絞ってインポートするテもあるようだが。

変数の基本知識

Pytyon では、変数の宣言も型指定もない。
等号記号(=)で最初に代入されたオブジェクトによって型が決定される。
本日=DT.date.today() では、日付型の変数となる。
変数名に全角文字を使用するなど言語道断なのかもしれないが、命名規則にそのような制約はなくて問題なく使用できることが分かったので一部を除きビシバシ使いまくることにしている。

カスタム関数定義

アプリケーションプログラミングにとって独自の関数は必須。
入力された生年月日情報から年齢を算定するだけの機能。
def で定義し、行末のコロンは必須。ここでは、ちょっと気取って Age という半角英文字とした。
年と月を引数とすれば容易に算定できるが、たたこれでけのことにたどり着くのに半日要した。
演題がカンタン過ぎるためか、検索してもあまりに情報がなさすぎて時間がかかってしまった。
もちろん費やした時間とエネルギーは浪費でなく、今後の勉強のための情報収集なのだが。
先が思いやられるって・・・(;`ー´)

def Age(生年,月):
    Age=本日.year-int(生年)
    if 本日.month<int(月):
        Age=Age-1   
    return Age

たどり着いたのは、年数計算と月比較をするだけの単純機能。※不等号のみ != なので覚えとく
数値演算なので int 関数で引数を整数化しておく必要がある。
年齢算定方法なんてこれだけで済むのだが、日付の処理には datetime ライブラリの yearmonth という、そのままの機能のメソッド(?)が利用できる。もちろん day とか minute も完備。
return で呼び出し元に整数の演算結果を返すことが出来る。
見たままの単純機能なのでこれ以上の説明は省略 > 自分 (;`ー´)

画面構成

Frame(枠)によるエレメントのグループ化

前回までは layout という名のオブジェクト変数で画面レイアウトをしていたが、変数なので分かりやすい任意の名前でかまわない。今回は L0,L1,L2,L3 と名付けた4つの枠を使用している。
表題部分・生年月日のコンボボックス・テキストボックス・ボタングループである。

L0=[[SG.Text("PySimpleGUI 演習問題 - 日付とフレーム",font=18)],
    [SG.Text("本日は") ,SG.Text(本日.strftime('西暦 %Y年%m月%d日 です'))],]     # Frame 使用時は段落と枠括弧の二重くくりになる
L1 =[
     # テキスト中央寄せは justification="center"
    [SG.Text("生年月日:"),SG.Input(key="-Y-",size=(8,1),justification="center",enable_events=True),
     SG.Text("年"),SG.Input(key="-M-",size=6,justification="center"),
     SG.Text("月") ,SG.Input(key="-D-",size=6,justification="center"),SG.Text("日")] ,
     [SG.Text("年齢:"),SG.Text(key="-YMD-",font="Arial",size=4,justification="c"),SG.Text("歳")]
    
]
L2=[
    [SG.Text("生年月日:"),
     SG.Combo(key="-CY-",values=list(range(1900,本日.year-1)),size=(8,16),
              default_value=2000,enable_events=True),SG.Text("年"),
     SG.Combo(key="-CM-",values=list(range(1,13)),size=(4,13),enable_events=True),SG.Text("月"),
     SG.Combo(key="-CD-",values=list(range(1,32)),size=(4,16),enable_events=True),SG.Text("日"),
     ]
     
]
L3=[
    [SG.Button("実行",key="実行"),SG.Cancel()]]

ウィンドウ描画用に L と名付けたオブジェクト変数でひとまとめにしておいて window 関数を実行。
Frame オブジェクトは、並べた順序で枠として画面内に配置表示される。

L=[
    [SG.Frame("",L0,border_width=0)],
    [SG.Frame("生年月日を選択",L2)],[SG.Frame("テキスト入力も可",L1)],
    [SG.Frame("",L3,border_width=0)]
    
   ]
window=SG.Window("PySimpleGUI 演習",L,modal=True)

枠内には任意のエレメントをいくつでも配置出来るし混在しても問題ない。
window オブジェクトへの画面表示はウィンドウ左上からのレイアウト変数名を列挙すれば良い。
Frame を使うと全部のブロックが枠線付きになるので不要な場合は border_width=0 で非表示にできる。

コンボボックス

PySimpleGUI のコンボボックスは Access のような多機能・柔軟性はなく、単純なドロップダウンリストである。

上半分の部分がコンボボックスグループ。
コンボボックスの設置は、
ライブラリ名.Combo(キー名,Vlues=値リスト) が基本。
列幅とリスト行数は size、初期値設定は default_value
enable_events=True によって、更新後イベントを発生させることが出来る。

コンボボックスのリスト生成

基本的には values= 値リストである。SG.Combo(values=(“1″,”2″,”3”)) のような任意のカンマ区切り文字列列挙の他に、サンプルコードに用いた list 関数が利用出来る。※複数の要素を保つ一次元配列はタプルと呼ばれる。
range(始めの値,最後の値+1) 関数を組み合わせることで、リスト文字列を容易に生成できる。values=list(range(1,13)) なら 1から12までのリストとなる。最後の値に 1 を加算しないと目的のリスト数にならない。今のところ仕様になっている理由は不明・・・

L2=[
    [SG.Text("生年月日:"),
     SG.Combo(key="-CY-",values=list(range(1900,本日.year-1)),size=(8,16),
              default_value=2000,enable_events=True),SG.Text("年"),
     SG.Combo(key="-CM-",values=list(range(1,13)),size=(4,13),enable_events=True),SG.Text("月"),
     SG.Combo(key="-CD-",values=list(range(1,32)),size=(4,16),enable_events=True),SG.Text("日"),
     ]    
]

コンボボックスのリスト連動

コンボボックスは入力支援と誤入力防止に役立つ。
サンプルでは、”月”のコンボボックスを変更した場合に”日”のリストが更新される。
日付を扱うのだから当たり前の処理だが、calendar ライブラリの monthrange関数を利用する。
この答えを得るまでにかなり苦労した。
この関数は range(範囲) の名の通り複数の値を持つ「タプル」と呼ばれる配列型変数。
返り値が (月,日) の形式なので、日数だけを得るには 引数の最後で [1] 指定が必要となる。
実際に返す値は月に応じた月末の日なのだが、そのまま日数リストに利用している。

    elif event=="-CM-":
        window["-M-"].update(values["-CM-"])
        日数=calendar.monthrange(int(values["-CY-"]),int(values["-CM-"]))[1]  # 返り値の2番目が日数(コンボ用)
        window["-CD-"].update(values=list(range(1,日数+1)))     #生年日コンボボックスリストを更新
        window["-D-"].update("")
        window["-CD-"].set_focus()

今年はコンボボックスで2月を選択すると、2024年なのできちんと29日までのリストになっている。( ゚∀゚)

他のエレメントの値を参照してコンボボックスのリストを更新するには

window オブジェクト内のエレメントの値は values[“キー名”] で参照して取得できる。サンプルコードでキー名の前後にハイフンを付けているのは公式サイトでの推奨に従っている。もちろん付けなくても動作する。
コンボボックスのリストを更新する場合は list 関数の引数に代入してやれば良い。
年月日のエレメントはひと目で用途がわかるシンプルなものが多いので英大文字とした。
コンボボックスは “-CY-“,”-CM-“,”-CD-“、テキストボックスは“-Y-“,”-M-“,”-D-“ である。
月のコンボボックスのキー名は “-CM-” 。このコンボボックスを更新した場合にイベントを発生させ、まずはテキストボックスの”月”(キー名”-M-“)を window[“-M-“].update(values[“-CM-“]) によって更新。
年月日の”日”を扱うコンボボックスのリストを values=list(range(1,日数+1)) によって更新する。
プログラミングコード習得の常だが、答えをみるとどうということもない内容。
しかし、これにたどり着くための情報収集と応用思考にもかなりの時間とエネルギーが必要だった。
直接参考になる情報は探せなかったが、実現はできた。ネット情報がなければとっくに投げ出していただろう。

    # コンボボックス更新後処理
    if event=="-CY-":
        window["-Y-"].update(values["-CY-"])
        continue
    elif event=="-CM-":
        window["-M-"].update(values["-CM-"])
        日数=calendar.monthrange(int(values["-CY-"]),int(values["-CM-"]))[1]  # 返り値の2番目が日数(コンボ用)
        window["-CD-"].update(values=list(range(1,日数+1)))     #生年日コンボボックスリストを更新
        window["-D-"].update("")
        window["-CD-"].set_focus()
        continue
    elif event=="-CD-":
        window["-D-"].Update(values["-CD-"])
        window["-Y-"].Update(values["-CY-"])
        continue
    elif event=="-Y-":
        if int(values["-Y-"])>0:
            window["-CY-"] .Update(values["-Y-"])   
            continue
if values["-Y-"]=="" or values["-M-"]=="" or values["-D-"]=="":

条件分岐

if 文

イベント発生時の分岐処理は if 文で行っている。
基本は if 条件式 : で始まり elif 条件式 または else で終了。カンタン過ぎてさすがに説明不要とする。
注意点としては、ループ式も同様だが「endif」 のような終わりを示すコマンド(?)がなくて一連の処理がコードのインデントのみで判別される仕様。
慣れれば合理的とも思えるが、なかなか慣れずにエラー頻発の日々である。
イベントが多いとその数だけ同じ処理を書くことになるが、現段階では演習目的もあり、if 文のみである。
今のところこれしか知らないので。(;`ー´)

continue

イベント処理が終わっても if 文ブロックの終わりを示す方法がないので、次ブロックの入力チェック用であるコード if values[“-Y-“]==”” … 以降がそのまま実行される。そのため意図しない動作が起こる場合が多い。
サンプルでは起動直後などテキストボックスが未入力の状態で”実行”ボタンが押された場合に警告が出てしまう。
そのため強制的に continue でループ開始部(while True:)に戻して未然に防止している。

イベント処理の大半はコピペで済むとはいえ、自分で書いてても泥臭くて気持ち悪い。きっとスマートに処理できる手段があるのだろうが、今は目的の機能を実現することが最優先事項なので掘り下げないでおく。(;`ー´)

メッセージボックス

popup 関数はメッセージボックス表示用。popup_ok_cancel とすれば”OK”と”Cancel”の2ボタンとなり、結果を受け取って条件分岐処理が出来る。

    if values["-Y-"]=="" or values["-M-"]=="" or values["-D-"]=="":
        X=SG.popup_ok_cancel("生年月日が未入力")
        if X=="OK": # ok_cancel の返り値は OK or Cancel
            window["-Y-"].set_focus()   # フォーカスセット
1 2
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次