Home > Enterprise >  Open a numpad every time a Tkinter Entry is clicked with mouse
Open a numpad every time a Tkinter Entry is clicked with mouse

Time:11-05

I'm not a programmer by profession and currently try to program a small app to use on tablets using python's tkinter module. I want a numpad to pop up every time a tinkter Entry widget is clicked/touched. I found the following code on stackoverflow which creates a "NumpadEntry" class (inheriting from tk.Entry) that does almost exactly what I want. However, once the numpad window is closed, it can't be re-opened by another click into the NumpadEntry widget until I click into another window or use the second entry in between.

Example code:

import tkinter as tk

def enumerate_row_column(iterable, num_cols):
    for idx, item in enumerate(iterable):
        row = idx // num_cols
        col = idx % num_cols
        yield row,col,item

class NumpadEntry(tk.Entry):
    def __init__(self,parent=None,**kw):
        tk.Entry.__init__(self,parent,**kw)
        self.bind('<FocusIn>', self.numpadEntry)
        self.bind('<FocusOut>',self.numpadExit)
        self.edited = False
    def numpadEntry(self,event):
        if self.edited == False:
            print("You Clicked on me")
            self['bg']= '#ffffcc'
            self.edited = True
            new = numPad(self)
        else:
            self.edited = False
    def numpadExit(self,event):
        self['bg']= '#ffffff'

class numPad(tk.simpledialog.Dialog):
    def __init__(self,master=None,textVariable=None):
        self.top = tk.Toplevel(master=master)
        self.top.protocol("WM_DELETE_WINDOW",self.ok)
        self.createWidgets()
        self.master = master
        
    def createWidgets(self):
        btn_list = ['7',  '8',  '9', '4',  '5',  '6', '1',  '2',  '3', '0',  'Close',  'Del']
        # create and position all buttons with a for-loop
        btn = []
        # Use custom generator to give us row/column positions
        for r,c,label in enumerate_row_column(btn_list,3):
            # partial takes care of function and argument
            cmd = lambda x = label: self.click(x)
            # create the button
            cur = tk.Button(self.top, text=label, width=10, height=5, command=cmd)
            # position the button
            cur.grid(row=r, column=c)                                              
            btn.append(cur)
            
    def click(self,label):
        print(label)
        if label == 'Del':
            currentText = self.master.get()
            self.master.delete(0, tk.END)
            self.master.insert(0, currentText[:-1])
        elif label == 'Close':
            self.ok()
        else:
            currentText = self.master.get()
            self.master.delete(0, tk.END)
            self.master.insert(0, currentText label)
    def ok(self):
        self.top.destroy()
        self.top.master.focus()

class App(tk.Frame):
    def __init__(self,parent=None,**kw):
        tk.Frame.__init__(self,parent,**kw)
        self.textEntryVar1 = tk.StringVar()
        self.e1 = NumpadEntry(self,textvariable=self.textEntryVar1)
        self.e1.grid()

        self.textEntryVar2 = tk.StringVar()
        self.e2 = NumpadEntry(self,textvariable=self.textEntryVar2)
        self.e2.grid()
    

if __name__ == '__main__':
    root = tk.Tk()
    root.geometry("200x100")
    app = App(root)
    app.grid()
    root.mainloop()

I tried to somehow set the edited variable to False again, e.g. in the numpadExit func:

    def numpadExit(self,event):
        self['bg']= '#ffffff'
        self.edited == False

But this only leads to the numpad opening every time it is closed.

I also tried to use lambda or change the "FocusIn" to "Enter" here:

        self.bind('<FocusIn>', self.numpadEntry)

but this also didn't work.

I'm afraid this problem is somewhat above my current understanding of python :( Some help would therefore be highly appreciated. Many thanks in advance.

CodePudding user response:

The problem occurs because you bind to focus in and out, so you have to focus another entry before you can focus the first again. Instead, bind to <Button-1>, which is left click.

The program can be simplified a bit by having numPad subclass tk.Toplevel instead of Dialog. This also removes the need for the simpledialog import.
I've added grab_set to the numpad window. This directs all events to it so the user can't interact with the main window while the numpad is open. This means you don't have to worry about the user clicking the entry repeatedly and making lots of windows. Adding focus_force() gives the numpad window focus when it is created. I've removed the edited variable as it no longer needed and slightly changed the ok method of numPad so the entry colour is changed when the numpad is destroyed.

class NumpadEntry(tk.Entry):
    def __init__(self,parent=None,**kw):
        tk.Entry.__init__(self,parent,**kw)
        self.bind('<Button-1>', self.numpadEntry)
    def numpadEntry(self,event):
        print("You Clicked on me")
        self['bg']= '#ffffcc'
        new = numPad(self)
    def numpadExit(self):
        self['bg']= '#ffffff'

class numPad(tk.Toplevel):
    def __init__(self,master=None,textVariable=None):
        tk.Toplevel.__init__(self, master=master)
        self.protocol("WM_DELETE_WINDOW",self.ok)
        self.createWidgets()
        self.master = master
        self.grab_set()
        self.focus_force()
        
    def createWidgets(self):
        btn_list = ['7',  '8',  '9', '4',  '5',  '6', '1',  '2',  '3', '0',  'Close',  'Del']
        # create and position all buttons with a for-loop
        btn = []
        # Use custom generator to give us row/column positions
        for r,c,label in enumerate_row_column(btn_list,3):
            # partial takes care of function and argument
            cmd = lambda x = label: self.click(x)
            # create the button
            cur = tk.Button(self, text=label, width=10, height=5, command=cmd)
            # position the button
            cur.grid(row=r, column=c)                                              
            btn.append(cur)
            
    def click(self,label):
        print(label)
        if label == 'Del':
            currentText = self.master.get()
            self.master.delete(0, tk.END)
            self.master.insert(0, currentText[:-1])
        elif label == 'Close':
            self.ok()
        else:
            currentText = self.master.get()
            self.master.delete(0, tk.END)
            self.master.insert(0, currentText label)
    def ok(self):
        self.master.numpadExit()
        self.destroy()
  • Related