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

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

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

実行イメージ

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

DateEntryをつくりました

スクリプト

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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()
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()
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)で取得できました。

まとめ

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

コメントを残す

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


reCaptcha の認証期間が終了しました。ページを再読み込みしてください。