Home > OS >  tkinter keyboard shortcuts issue
tkinter keyboard shortcuts issue

Time:09-22

I have a drop-down menu with a keyboard shortcut that calls a function whenever I click it.

from tkinter import *


class app:
    def __init__(self):
        self.root = root
        self.root.geometry('500x380')
        self.root.title('Text Editor')
        self.menu = Menu(self.root)
        self.root.config(menu=self.menu)
        self.submenu = Menu(self.menu)
        self.menu.add_cascade(label='Tab', menu=self.submenu)
        self.submenu.add_command(label='Do Somthing', command=self.dosomthing)
        self.root.bind('<Control-r>', self.dosomthing)

    def dosomthing(self, event):
        print('Somthing Was Done!')

root = Tk()
app()
root.mainloop()

The shortcut is working perfectly. However, whenever I want to just click the label instead of using the shortcut, it says: TypeError: dosomthing() missing 1 required positional argument: 'event'

How do I fix it so I can click the label and also use the keyboard shortcut??

CodePudding user response:

I would change the event parameter I the dosomthing method to *args because Tkinter does not pass any parameter when calling the method in the menu.

it should look like this:

def dosomthing(self, *args):
    print('Somthing Was Done!')

if you never used *args it's a list with the parameters passed, for example, if you have this:

def method(*args):
    for i in args:
        print(i)

method("something","another thing")

it will return

something

another thing

you could also use lambda

    self.root.bind('<Control-r>', lambda e: self.dosomthing())

def dosomthing(self):
    print('Somthing Was Done!')

CodePudding user response:

dosomething method expects an argument. When you click on the menu item the dosomething is called without any argument.

One way to solve this is to set event parameter to None or give some default value.

    def dosomthing(self, event=None):
        print('Somthing Was Done!')

Full example:

from tkinter import *


class App:
    def __init__(self):
        self.root = Tk()
        self.root.geometry('500x380')
        self.root.title('Text Editor')
        
        self.menu = Menu(self.root)
        
        self.submenu = Menu(self.menu)
        self.menu.add_cascade(label='Tab', menu=self.submenu)
        self.submenu.add_command(label='Do Somthing', command=self.dosomthing)
        
        self.root.config(menu=self.menu)
        self.root.bind('<Control-r>', self.dosomthing) # bind_all is more appropriate here
        self.root.mainloop()

    def dosomthing(self, event=None):
        print('Somthing Was Done!')

App()

CodePudding user response:

The question has already been correctly answered by @Tartyto, But for someone else who may view this question in the future here are the reasons why this occurs and which of the methods mentioned by @Tartyto is better.

EDIT: Thanks to @Art for letting me know the mistakes with my answer, this edit is with the hope of resolving those mistakes.

Normally when you bind a function to a keybinding in tkinter you do it similar to how you did it like so -:

def __init__(self, ...) :
    self.root.bind('KEYBINDING', self.do_something)
    return

def do_something(self, event) :
    print('Did something....')
    return

So what happens here is that our callback function accepts an event argument which is passed by the bind function when it calls the callback.

But when the same function is called on your menu button being pressed, the callback function is called without any argument being passed, this results in python prompting you to pass the event argument for the callback function.

And to solve this we can either make the second argument optional, pass the second argument manually when its not being passed, or we can set a default value for the second argument. All are valid ways of doing it.

# OPTIONAL ARGUMENT WAY
def __init__(self, ....) :
    root.bind('KEYBINDING', self.do_something)
    ...
    return

def do_something(self, *args) :
    print('Did something....')
    return

# DEFAULT ARGUMENT WAY
def __init__(self, ....) :
    root.bind('KEYBINDING', self.do_something)
    ...
    return

def do_something(self, event = None) :
    print('Did something....')
    return

# LAMBDA WAY
def __init__(self, ....) :
    root.bind('KEYBINDING', lambda : self.do_something(None))
    ...
    return

def do_something(self, event) :
    print('Did something....')
    return
  • Related