Python tkinter GUI プログラミング Entryその3

 今日も見に来てくださって、ありがとうございます。石川さんです。

 自分でもしつこいなぁ、とは思いますが、Entryで日付を入力するためのウィジェットをつくる、パート3です。前回つくったのは、ちょっと納得できなかったのですよねぇ。ま、今回も納得したか、というと、微妙なのですけど、まあ、現状としてはこんなものでしょう。

実行イメージ

 出来上がりイメージはこんな感じになります。

DateEntryをつくりました

スクリプト

 スクリプトは以下のようになりました。日付を入力するだけなのに、ずいぶんとかかりました。ひょっとしたら車輪の再発明をしたのかも知れませんねぇ。

import tkinter as tk
import tkinter.messagebox as mb
from datetime import datetime

class DateEntry(tk.Frame):
    def __init__(self,master=None,frame_look={},**look):
        args = dict(relief=tk.SUNKEN,bg="white")
        args.update(frame_look)
        tk.Frame.__init__(self, master, **args)

        yearvc = (self.register(self.year_check), "%V", "%d", "%i", "%S", "%P")
        monthvc = (self.register(self.month_check), "%V", "%d", "%i", "%S", "%P")
        dayvc = (self.register(self.day_check), "%V", "%d", "%i", "%S", "%P")
        self.year = tk.Entry(self,width=4,relief=tk.FLAT,validate="all",validatecommand=yearvc)
        self.sep1 = tk.Label(self,text="/",relief=tk.FLAT,bg="white")
        self.month = tk.Entry(self,width=2,relief=tk.FLAT,validate="all",validatecommand=monthvc)
        self.sep2 = tk.Label(self,text="/",relief=tk.FLAT,bg="white")
        self.day = tk.Entry(self,width=2,relief=tk.FLAT,validate="all",validatecommand=dayvc)
        
        self.year.pack(side=tk.LEFT)
        self.sep1.pack(side=tk.LEFT)
        self.month.pack(side=tk.LEFT)
        self.sep2.pack(side=tk.LEFT)
        self.day.pack(side=tk.LEFT)
        
        self.year.bind("<KeyPress>",lambda e:self.key('year', e))
        self.month.bind("<KeyPress>",lambda e:self.key('month', e))
        self.day.bind("<KeyPress>",lambda e:self.key('day', e))

    def setPrev(self, prev=None):
        self.prev = prev

    def setNext(self, next=None,):
        self.next = next
    
    def year_check(self, event, command, index, char, proposed):
        if event == 'key':
            if command == '0': # Delete command
                return True
            else:
                if not char.isdigit():
                    return False
        if event == 'key' and len(proposed) == 4:
            self.month.focus()
        return True
    
    def month_check(self, event, command, index, char, proposed):
        if event == 'key':
            if command == '0': # Delete command
                return True
            else:
                if not char.isdigit():
                    return False
            if int(proposed) < 0 or 12 < int(proposed):
                return False
            if len(proposed) == 2:
                self.day.focus()
        return True
    
    def day_check(self, event, command, index, char, proposed):
        if event == 'key':
            if command == '0': # Delete command
                return True
            else:
                if not char.isdigit():
                    return False
            if int(proposed) < 0 or 31 < int(proposed):
                return False
        if event == 'key' and len(proposed) == 2:
            if self.next:
                self.next.focus()
        return True

    def key(self, w, event):
        if event.keysym == 'Left':
            if w == 'year':
                if self.prev:
                    if self.year.index(tk.INSERT) == 0:
                        self.prev.focus()
            elif w == 'month':
                if self.month.index(tk.INSERT) == 0:
                    self.year.focus()
            elif w == 'day':
                if self.day.index(tk.INSERT) == 0:
                    self.month.focus()
        elif event.keysym == 'Right':
            if w == 'year':
                if len(self.year.get()) == self.year.index(tk.INSERT):
                    self.month.focus()
            elif w == 'month':
                if len(self.month.get()) == self.month.index(tk.INSERT):
                    self.day.focus()
            elif w == 'day':
                if self.next:
                    if len(self.day.get()) == self.day.index(tk.INSERT):
                        self.next.focus()

    def get(self):
        try:
            d = datetime(year=int(self.year.get()),
                        month=int(self.month.get()),
                        day=int(self.day.get())) 
        except:
            return None    
        return d

    def clear(self):
        self.year.delete(0,tk.END)
        self.month.delete(0,tk.END)
        self.day.delete(0,tk.END)

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("DateEntry test")
        self.label = tk.Label(text="日付を入力してください(YYYY/MM/DD):")
        self.label.grid(row=0,column=0)
        self.date_entry = DateEntry()
        self.clear_button = tk.Button(text="Clear", command=self.clear)
        self.close_button = tk.Button(text="Close", command=self.destroy)
        self.show = tk.Button(text="show", command=self.show)
        
        self.date_entry.setPrev(self.show)
        self.date_entry.setNext(self.clear_button)

        self.date_entry.grid(row=0,column=1)
        self.clear_button.grid(row=2,column=0)
        self.close_button.grid(row=2,column=1)
        self.show.grid(row=2,column=2)

    def clear(self):
        self.date_entry.clear()
        
    def show(self):
        d = self.date_entry.get()
        if d:
            message = "入力されたのは、「"+d.strftime("%Y/%m/%d")+"」です。"
        else:
            message = "正しい日付がセットされていません。"
        mb.showinfo(title="info",message=message)

if __name__ == "__main__":
    app = App()
    app.mainloop()

解説

 日付を入力するのに、途中のハイフンやスラッシュを入力するのはイケてないよねぇ、と思ったので、なんとかならないかとちょっと調べてみました。ここにありました。みんな同じようなこと考えるのですね。EntryとLabelを組み合わせたFrameをつくることで実現してありましたので、これを参考にしてつくったのが上記のスクリプトです。

 年、月、日をそれぞれEntryでつくります。それぞれvalidatecommandを設定しています。入力は数字のみ、年は4桁入力されたとき、月と日は2桁入力されたとき、それぞれ次の項目に移動するようにしています。月は1~12、日は1~31のみ入力できるようにしました。

 あと、左右のキーで年月日を移動できるようにしてみました。ポイントは現在のカーソル位置が途中の時は項目間の移動ではなくて、項目内の移動にしなくてはならない、というところでしょうか。現在のカーソル位置は、tk.Entry.index(tk.INSERT)で取得できました。

まとめ

 日付項目を実装するだけなのに、けっこうなコーディング量になってしまいました。これって、誰かがもう作っているんじゃないかなぁ、と、思いながら作りましたが、楽しかったです♪

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


The reCAPTCHA verification period has expired. Please reload the page.