4

I want my Tkinter (Tk 8.6) program to behave differently when Shift is held when the program is starting up.

I know how to check the state attribute in mouse and keyboard events, but my detection needs to work even when the user doesn't cause any events.

I tried <Configure> and <FocusIn> events, but their state attribute is always '??'. I tried generating a virtual event, but its state attribute is always 0.

Is there a way to query this information from Tk interpreter?

Aivar
  • 6,814
  • 5
  • 46
  • 78
  • Can you get key status as the first thing program does while opening? If shift is held, it should detect as the first job, if not well... rest of the code works. – Tuğberk Kaan Duman May 25 '18 at 19:40
  • The thing is I don't know how to get key status other than from keyboard and mouse events. – Aivar May 25 '18 at 19:41
  • 1
    @TuğberkKaanDuman I don't think so -- Tk reads key down and up *events*, and if the shift key is held down while the application is being launched, then the event is gone long before Tk has a chance to intercept it and read it. The only thing I can think of off-hand is to have the user *release* the shift key and listen for a keyup without a preceding keydown. – Adam Smith May 25 '18 at 19:42
  • 1
    Whoa, of course!! I forgot about key up. Thank you so much! :) – Aivar May 25 '18 at 19:44
  • 1
    It's not perfect solution, but it will do in my case. Thanks again for the idea! – Aivar May 25 '18 at 19:45
  • 1
    @Aivar I'm sure there's some more-natural way to do it, but I doubt you'll find anything that's cross-platform compatible. You'll have to ask the OS what the keystate is, rather than listening for the event in Tk. – Adam Smith May 25 '18 at 19:45
  • @AdamSmith I'm far better in C++ compared to python, but fun thing what I said works in C++. I thought it should be same in theory. I'll post an example code in C++ now, maybe it helps more. (with the output) – Tuğberk Kaan Duman May 25 '18 at 19:45
  • I'm ready to implement 3 different solutions to achieve Win+Linux+Mac compatibility – Aivar May 25 '18 at 19:46
  • https://hastebin.com/cahimilela.cpp this is my solution in C++. It should work like this in python too, in theory. – Tuğberk Kaan Duman May 25 '18 at 19:47
  • There might be a problem with focus, in Windows at least. – kabanus May 25 '18 at 19:48
  • @TuğberkKaanDuman that Windows.h include is what's pulling `GetKeyState` into scope, right? That looks like a binding to the OS layer, which of course can tell you what the current key state is. The problem is that doing this in Python through Tkinter you don't have a way to ask the OS what the key state is. Try to implement the same using a Tk event loop in C++ and you'll run into the same problem Aivar is having. – Adam Smith May 25 '18 at 20:14
  • 1
    Got it now, I'll try to find a way for it in Python. Thanks for explaining! @AdamSmith – Tuğberk Kaan Duman May 25 '18 at 20:16
  • @TuğberkKaanDuman I think a "proper" solution will do much the same as your C++ code -- grab the key state through a call to the OS layer requesting it outside of Tk. That's just not the "natural" way to handle keypresses in Tkinter. – Adam Smith May 25 '18 at 20:18
  • 1
    @AdamSmith it's been only 5 minutes but I'm already convinced that you can't do it inside Tk. Basically solutions are: check for key up instead in Tk or check the key down outisde of Tk and build Tk based on the result. That'll work! – Tuğberk Kaan Duman May 25 '18 at 20:24
  • @TuğberkKaanDuman I was testing the checking of the keyRelease for shift and checking if tkinter previously recorded a keyPress on shift by tracking the keyPress as a True/False result stored in a variable. The only problem I see with doing it this way is your program will have to rebuild itself after the fact because its very unlikely you will be able to release the button at the time where it checks for the down press. – Mike - SMT May 25 '18 at 21:40
  • NO DUPLICATE for me, hence the topic targets the question of detecting a held key BEFORE the startup, what is a fully different thing, then detecting the holding of a key as a gesture during runtime what is easily possible via events. The correct answer is therefore probably "technically this is not possible". Or any other news on this topic? – SiL3NC3 Mar 15 '23 at 09:07

1 Answers1

0

A window can only receive key events when it's focused:

KeyPress and KeyRelease events are sent to the window which currently has the keyboard focus.

Then:

  • Use <KeyPress-Shift_L> and/or <KeyPress-Shift_R> to detect presses of the Shift keys ("Shift" doesn't work, it can only be a modifier).
  • Set a flag to ignore repeating KeyPress events as per GUI Button hold down - tkinter of just unbind the keypress handler.

A proof-of-concept app:

import Tkinter as tkinter

class HoldKeyDetect(object):
    def __init__(self, widget, keys, handler=None):
        """Detect holding `keys` in `widget`"""
        self.widget = widget
        self.handler = handler        
        self.binds = {}
        for key in keys:
            evid = '<KeyPress-%s>'%key
            self.binds[evid] = widget.bind(evid,self.keypress)

    def __del__(self):
        try: self.unbind()
        except tkinter.TclError: pass   #app has been destroyed
    def unbind(self):
        while True:
            try: evid,fid = self.binds.popitem()
            except KeyError: break
            self.widget.unbind(evid, fid)

    def keypress(self,e):
        try:
            if self.handler:
                self.handler(e)
        finally:
            self.unbind()        

class App(object):
    def __init__(self,root):
        self.root = root
        root.focus_force()
        self.h = HoldKeyDetect(root,("Shift_L","Shift_R"),self.set_mode)
        root.after(1000,   # larger than keypress repeat interval + code overhead
                        self.continue_)
        self.mode = False
    def go(self):
        self.root.mainloop()
    def set_mode(self,_):
        print "Shift mode set"
        self.mode = True
    def continue_(self):
        del self.h
        print "Mode=", self.mode
        self.root.destroy()

if __name__ == '__main__':
    App(tkinter.Tk()).go()
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152