Popup Numpad (Numeric Keypad) for Multiple Entry Boxes

Viewed 534

Good Day everyone. I'm currently trying to implement a popup number pad with entry boxes for use on a pi 4 touchscreen. After searching for a while I found this forum with an effective solution provided by scotty101. Unfortunately, the code is only effective for a single entry widget. After fidgeting around for some time with my abysmal object-orientated knowledge, I cant seem to find a way to implement this code to work with multiple entry widgets.

A reduced working version of my code:

import tkinter as tk
from tkinter import simpledialog
from time import strftime

class Page(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
    def show(self):
        self.lift()  
    
class Page1(Page):                                                       
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)
        self.Create_Page1_Widgets()
       
    def Create_Page1_Widgets(self):
        self.BackCan=tk.Canvas(self,width=800,height=440,borderwidth=0,bg="white")
        self.BackCan.place(x=0,y=0) 
        #Entry Boxes#
        self.Page1Var1 = tk.StringVar()
        self.Page1Var2 = tk.StringVar()
        self.Page1e1=tk.Entry(self,width=12,justify="center",textvariable=self.Page1Var1)
        self.Page1e1.bind('<FocusIn>',self.numpadEntry); self.edited = False
        self.Page1e1.place(x=10,y=163,width=102,height=26)  
        self.Page1e2=tk.Entry(self,width=12,justify="center",textvariable=self.Page1Var2)
        self.Page1e2.bind('<FocusIn>',self.numpadEntry);
        self.Page1e2.place(x=129,y=163,width=102,height=26)
    
    def numpadEntry(self, event):
        if self.edited == False:
            self.edited = True
            new = numPad(self,self)
        else:
            self.edited = False

class Page2(Page):                                             
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)
        self.Create_Page2_Widgets()
    
    def Create_Page2_Widgets(self):      
        #Validation#
        #Page Backround#
        self.BackCan=tk.Canvas(self,width=800,height=440,borderwidth=0,bg="white")
        self.BackCan.place(x=0,y=0)
        ##Entry Boxes##
        self.Page2Var1 = tk.StringVar()
        self.Page2Var2 = tk.StringVar()
        self.PrefertHRe=tk.Entry(self,width=12,justify="center",textvariable=self.Page2Var1)
        self.PrefertHRe.bind('<FocusIn>',self.numpadEntry); self.edited = False #<-calls numpad
        self.PrefertHRe.place(x=10,y=200,width=102,height=26)
        self.PrefertMINe=tk.Entry(self,width=12,justify="center",textvariable=self.Page2Var2)
        self.PrefertMINe.place(x=129,y=200,width=102,height=26)
    
    def numpadEntry(self, event):
        if self.edited == False:
            self.edited = True
            new = numPad(self,self)
        else:
            self.edited = False

class numPad(simpledialog.Dialog):
    def __init__(self,master=None,parent=None):
        self.parent = parent
        self.top = tk.Toplevel(master=master)
        self.top.protocol("WM_DELETE_WINDOW",self.ok)
        self.createWidgets()
    def createWidgets(self):
        btn_list = ['7',  '8',  '9', '4',  '5',  '6', '1',  '2',  '3', '0',  'Close',  'Del']
        r = 1
        c = 0
        n = 0
        btn = []
        for label in btn_list:
            cmd = lambda x = label: self.click(x)
            cur = tk.Button(self.top, text=label, width=6, height=3, command=cmd)
            btn.append(cur)
            btn[-1].grid(row=r, column=c)
            n += 1
            c += 1
            if c == 3:
                c = 0
                r += 1
    def click(self,label):
        print(label)
        if label == 'Del':
            currentText = self.parent.Page1Var1.get() #<--Page1Var1 need to be dynamic?
            self.parent.Page1Var1.set(currentText[:-1])
        elif label == 'Close':
            self.ok()
        else:
            currentText = self.parent.Page1Var1.get()
            self.parent.Page1Var1.set(currentText+label)
        
    def ok(self):
        self.top.destroy()
        self.top.master.focus()

class MainView(tk.Frame):
    def __init__(self,  *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        super().__init__()
        #Navigation Frame#
        p2 = Page2(self);p1 = Page1(self)
        Navigation_frame = tk.Frame(self, width=800, height=55, background="bisque")
        container = tk.Frame(self)
        Navigation_frame.pack(side="bottom");Navigation_frame.pack_propagate(0)
        container.pack(side="top", fill="both", expand=True)
        NavCan=tk.Canvas(Navigation_frame,width=800,height=55,borderwidth=0,bg="white")
        NavCan.place(x=0,y=0)
        p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
        p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
        b1 = tk.Button(Navigation_frame, height=2, width=10, text="1", command=p1.lift)
        b2 = tk.Button(Navigation_frame, height=2, width=10, text="2", command=p2.lift)
        b1.place(x=144, y=6);b2.place(x=253, y=6)       
        #Clock#
        def clock(): 
            string = strftime('%H:%M:%S')
            lbl.config(text = string); lbl.after(1000, clock)
        #Clock Label#
        lbl = tk.Label(Navigation_frame,font=("Arial",20,'bold'),background= 'grey',foreground 
        = 'black'); lbl.place(x=20, y=12)  
        p1.show()
        clock()
   
if __name__ == "__main__":
    root = tk.Tk()
    main = MainView(root)
    main.pack(side="top", fill="both", expand=True)
    root.wm_geometry("800x440")
    root.attributes('-fullscreen', False)
    root.mainloop()

So the Question is, Is there a way to change the numpad focus to that of another entry widget?

2 Answers

The idea is to have a current_entry attribute in MainView that contains the currently selected entry. Then you define the numpadEntry() function as a method of MainView to update the value of current_entry:

def numpadEntry(self, event):
    # change current entry
    self.current_entry = event.widget

    if self.numpad is None:  # numpad does not exists
        self.numpad = NumPad(self)
    else:
        self.numpad.lift()   # make numpad visible

In this function I also assumed that MainView has a numpad attribute which is the NumPad window.

Now you can bind <FocusIn> on all your entries to numpadEntry to edit the current one. Then in the numpad, instead of modifying a StringVar, you directly modify the entry content with entry.delete(-1) and entry.insert('end', <char>).

Full code:

import tkinter as tk

class Page(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        self.create_widgets()

    def create_widgets(self):
        pass

    def show(self):
        self.lift()

class Page1(Page):
    def create_widgets(self):
        self.BackCan = tk.Canvas(self, width=800, height=440, borderwidth=0, bg="white")
        self.BackCan.place(x=0, y=0)
        #Entry Boxes#
        self.Page1e1 = tk.Entry(self, width=12, justify="center")
        self.Page1e1.bind('<FocusIn>', self.master.numpadEntry)
        self.edited = False
        self.Page1e1.place(x=10, y=163, width=102, height=26)
        self.Page1e2 = tk.Entry(self, width=12, justify="center")
        self.Page1e2.bind('<FocusIn>', self.master.numpadEntry)
        self.Page1e2.place(x=129, y=163, width=102, height=26)

class Page2(Page):
    def create_widgets(self):
        #Validation#
        #Page Backround#
        self.BackCan = tk.Canvas(self, width=800, height=440, borderwidth=0, bg="white")
        self.BackCan.place(x=0, y=0)
        ##Entry Boxes##
        self.PrefertHRe = tk.Entry(self, width=12, justify="center")
        self.PrefertHRe.bind('<FocusIn>', self.master.numpadEntry)
        self.edited = False #<-calls numpad
        self.PrefertHRe.place(x=10, y=200, width=102, height=26)
        self.PrefertMINe = tk.Entry(self, width=12, justify="center")
        self.PrefertMINe.place(x=129, y=200, width=102, height=26)
        self.PrefertMINe.bind('<FocusIn>', self.master.numpadEntry)

class NumPad(tk.Toplevel):
    def __init__(self, master=None):
        tk.Toplevel.__init__(self, master)
        self.protocol("WM_DELETE_WINDOW", self.ok)
        self.createWidgets()

    def createWidgets(self):
        btn_list = ['7', '8', '9', '4', '5', '6', '1', '2', '3', '0', 'Close', 'Del']
        r = 1
        c = 0
        n = 0
        
        for label in btn_list:
            cur = tk.Button(self, text=label, width=6, height=3,
                            command=lambda x=label: self.click(x))
            
            cur.grid(row=r, column=c)
            n += 1
            c += 1
            if c == 3:
                c = 0
                r += 1

    def click(self, label):
        if label == 'Del':
            self.master.current_entry.delete(-1)
        elif label == 'Close':
            self.ok()
        else:
            self.master.current_entry.insert('end', label)

    def ok(self):
        self.destroy()
        self.master.focus()
        self.master.numpad = None

class MainView(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

        self.numpad = None  # NumPad
        self.current_entry = None  # currently selected entry

        p2 = Page2(self)
        p1 = Page1(self)
        Navigation_frame = tk.Frame(self, width=800, height=55, background="bisque")
        container = tk.Frame(self)
        Navigation_frame.pack(side="bottom")
        Navigation_frame.pack_propagate(0)
        container.pack(side="top", fill="both", expand=True)
        NavCan = tk.Canvas(Navigation_frame, width=800, height=55, borderwidth=0, bg="white")
        NavCan.place(x=0, y=0)
        p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
        p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
        b1 = tk.Button(Navigation_frame, height=2, width=10, text="1", command=p1.show)
        b2 = tk.Button(Navigation_frame, height=2, width=10, text="2", command=p2.show)
        b1.place(x=144, y=6)
        b2.place(x=253, y=6)

        p1.show()

    def numpadEntry(self, event):
        # change current entry
        self.current_entry = event.widget
        # create numpad if does not exist yet
        if self.numpad is None:
            self.numpad = NumPad(self)
        else:
            self.numpad.lift()


if __name__ == "__main__":
    root = tk.Tk()
    main = MainView(root)
    main.pack(side="top", fill="both", expand=True)
    root.wm_geometry("800x440")
    root.attributes('-fullscreen', False)
    root.mainloop()    

I changed the events to pass the stringVar object to the number pad. There is some issue with number pad coming up, but that is related to your edit flags.

import tkinter as tk
from tkinter import simpledialog
from time import strftime


class Page(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)

    def show(self):
        self.lift()


class Page1(Page):
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)
        self.Create_Page1_Widgets()

    def Create_Page1_Widgets(self):
        self.BackCan = tk.Canvas(self, width=800, height=440, borderwidth=0, bg="white")
        self.BackCan.place(x=0, y=0)
        # Entry Boxes#
        self.Page1Var1 = tk.StringVar()
        self.Page1Var2 = tk.StringVar()
        self.Page1e1 = tk.Entry(self, width=12, justify="center", textvariable=self.Page1Var1)
        self.Page1e1.bind('<FocusIn>', lambda event: self.numpadEntry(event, self.Page1Var1))
        self.edited = False
        self.Page1e1.place(x=10, y=163, width=102, height=26)
        self.Page1e2 = tk.Entry(self, width=12, justify="center", textvariable=self.Page1Var2)
        self.Page1e2.bind('<FocusIn>', lambda event: self.numpadEntry(event, self.Page1Var2))
        self.Page1e2.place(x=129, y=163, width=102, height=26)

    def numpadEntry(self, event, string_var):
        if self.edited == False:
            self.edited = True
            new = numPad(string_var)
        else:
            self.edited = False


class Page2(Page):
    def __init__(self, *args, **kwargs):
        Page.__init__(self, *args, **kwargs)
        self.Create_Page2_Widgets()

    def Create_Page2_Widgets(self):
        # Validation#
        # Page Backround#
        self.BackCan = tk.Canvas(self, width=800, height=440, borderwidth=0, bg="white")
        self.BackCan.place(x=0, y=0)
        ##Entry Boxes##
        self.Page2Var1 = tk.StringVar()
        self.Page2Var2 = tk.StringVar()
        self.PrefertHRe = tk.Entry(self, width=12, justify="center", textvariable=self.Page2Var1)
        self.PrefertHRe.bind('<FocusIn>', lambda event: self.numpadEntry(event, self.Page2Var1))
        self.edited = False  # <-calls numpad
        self.PrefertHRe.place(x=10, y=200, width=102, height=26)
        self.PrefertMINe = tk.Entry(self, width=12, justify="center", textvariable=self.Page2Var2)
        self.PrefertMINe.place(x=129, y=200, width=102, height=26)

    def numpadEntry(self, event, string_var):
        if self.edited == False:
            self.edited = True
            new = numPad(string_var)
        else:
            self.edited = False


class numPad(simpledialog.Dialog):
    def __init__(self, the_entry):
        self.top = tk.Toplevel(master=None)
        self.top.protocol("WM_DELETE_WINDOW", self.ok)
        self.the_entry_widget = the_entry
        self.createWidgets()

    def createWidgets(self):
        btn_list = ['7', '8', '9', '4', '5', '6', '1', '2', '3', '0', 'Close', 'Del']
        r = 1
        c = 0
        n = 0
        btn = []
        for label in btn_list:
            cmd = lambda x=label: self.click(x)
            cur = tk.Button(self.top, text=label, width=6, height=3, command=cmd)
            btn.append(cur)
            btn[-1].grid(row=r, column=c)
            n += 1
            c += 1
            if c == 3:
                c = 0
                r += 1

    def click(self, label):
        print(label)
        if label == 'Del':
            currentText = self.the_entry_widget.get()  # <--Page1Var1 need to be dynamic?
            self.the_entry_widget.set(currentText[:-1])
        elif label == 'Close':
            self.ok()
        else:
            currentText = self.the_entry_widget.get()
            self.the_entry_widget.set(currentText + label)

    def ok(self):
        self.top.destroy()
        self.top.master.focus()


class MainView(tk.Frame):
    def __init__(self, *args, **kwargs):
        tk.Frame.__init__(self, *args, **kwargs)
        super().__init__()
        # Navigation Frame#
        p2 = Page2(self)
        p1 = Page1(self)
        Navigation_frame = tk.Frame(self, width=800, height=55, background="bisque")
        container = tk.Frame(self)
        Navigation_frame.pack(side="bottom")
        Navigation_frame.pack_propagate(0)
        container.pack(side="top", fill="both", expand=True)
        NavCan = tk.Canvas(Navigation_frame, width=800, height=55, borderwidth=0, bg="white")
        NavCan.place(x=0, y=0)
        p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
        p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
        b1 = tk.Button(Navigation_frame, height=2, width=10, text="1", command=p1.lift)
        b2 = tk.Button(Navigation_frame, height=2, width=10, text="2", command=p2.lift)
        b1.place(x=144, y=6)
        b2.place(x=253, y=6)

        # Clock#
        def clock():
            string = strftime('%H:%M:%S')
            lbl.config(text=string)
            lbl.after(1000, clock)

        # Clock Label#
        lbl = tk.Label(Navigation_frame, font=("Arial", 20, 'bold'), background='grey', foreground
        ='black')
        lbl.place(x=20, y=12)
        p1.show()
        clock()


if __name__ == "__main__":
    root = tk.Tk()
    main = MainView(root)
    main.pack(side="top", fill="both", expand=True)
    root.wm_geometry("800x440")
    root.attributes('-fullscreen', False)
    root.mainloop()
Related