1

Ok so I have multiple problems with the code under:

  1. when the key chosen in the Combo Box is held down, it keeps printing "You Pressed It", is there a way to avoid this?
  2. When I press the set hotkey, the label changes but the while loop in the process() doesnt, its suppose to do a process of tasks but I simplified it to print for this question.
run = False

def press():
    global run
    while True:
        if keyboard.read_key(hotCombo.get()):
            print("You Pressed It")
            run = not run
            keyboard.wait(hotCombo.get())
            if run == True:
                status["text"]="Working"
            else:
                status["text"]="Not Working"
            
def process():
    while run == True:
        print("runnning")

Been tinkering with it and found more problems

I ended up with this but while its printing run I cant seem to stop it

def process():
    global run
    while True:
        if keyboard.read_key(hotCombo.get()):
            print("kijanbdsjokn")
            run = not run
            keyboard.wait(hotCombo.get())
            if run == True:
                status["text"]="Working"
            else:
                status["text"]="Not Working"
            while run == True:
                print("run")
                time.sleep(1)
            
That Guy
  • 55
  • 1
  • 6
  • I assume you are calling `autofisher` somewhere. Think about what happens when you do. Since `run` is False, the loop will immediately exit, and the function will return. You would have to have this in a separate thread to have it be live, and you would have to have it be `while True:` to run forever. Which, of course, is a terrible idea, because it will chew up 100% of a CPU. No other Python code will run. – Tim Roberts Mar 19 '22 at 06:45
  • This doesn't look like a true `tkinter` program. Adding your own event loop within (what looks like) a callback is generally not a good idea. – Roland Smith Mar 19 '22 at 06:53
  • @RolandSmith Yeah I had a few people tell me that in the python discord tell me that but didnt really direct me to the correct path, Can I ask why I cant just integrate tkinter into a working python script using threading? – That Guy Mar 19 '22 at 07:00
  • @TimRoberts No im actually not calling autofisher anywhere, I just assumed whats inside will run because the function ```press``` runs without having to call it. Is there a way to have it run only when the hotkey is pressed AND NOT inside a ```while True``` – That Guy Mar 19 '22 at 07:03
  • @OGXirvin The answer to that question is so long that I made it into an answer below. :-) – Roland Smith Mar 19 '22 at 08:03
  • `press` will not run if you don't call it. I guarantee it. Either you are calling it, or you are passing it as a parameter to some other function that calls it. – Tim Roberts Mar 19 '22 at 23:29
  • @TimRoberts Im not sure but I am using threads, does that call the function? – That Guy Mar 20 '22 at 02:23
  • If you do `x = threading.Thread(target=press)` and `x.start()`, then yes, of course, it will be called. – Tim Roberts Mar 20 '22 at 02:41
  • @TimRoberts yes I have that setup, you can see my whole code in https://stackoverflow.com/questions/71543616/this-somehow-freezes-my-whole-program-and-im-not-sure-why – That Guy Mar 20 '22 at 02:44

2 Answers2

1

Can I ask why I cant just integrate tkinter into a working python script using threading?

A Python script is generally linear. You do things in sequence and then you exit.

In a tkinter program, your code consists of three things.

  • Code to set up the window and widgets.
  • Initialization of global variables (doesn't really matter if you hide them in a class instance; they're still globals).
  • Most of it will be functions/methods that are called as callbacks from tkinter when it is in the mainloop.

So in atkinter program most of your code is a guest in the mainloop. Where it is executed in small pieces in reaction to events. This is a completely different kind of program. It was called event-driven or message based programming long before that became cool in web servers and frameworks.

So, can you integrate a script in a tkinter program? Yes, it is possible. There are basically three ways you can do it;

  • Split the code up into small pieces that can be called via after timeouts. This involves the most reorganization of your code. To keep the GUI responsive, event handlers (like timeouts) should not take too long; 50 ms seems to be a reasonable upper limit.
  • Run it in a different thread. We will cover that in more detail below.
  • Run it in a different process. Broadly similar to running in a thread (the API's of threading.Thread and multiprocessing.Process are almost the same by design). The largest difference is that communication between processes has to be done explicitly via e.g. Queue or Pipe.

There are some things that you have to take into account when using extra threads, especially in a tkinter program.

1) Python version

You need to use Python 3. This will not work well in Python 2 for reasons that are beyond the scope of this answer. Better preemption of threads in Python 3 is a big part of it.

2) Multithreaded tkinter build

The tkinter (or rather the underlying tcl interpreter) needs to be built with threading enabled. I gather that the official python.org builds for ms-windows are, but apart from that YMMV. On some UNIX-like systems such as Linux or *BSD the packages/ports systems gives you a choice in this.

3) Make your code into a function

You need to wrap up the core of your original script in a function so you can start it in a thread.

4) Make the function thread-friendly

You probably want to be able to interrupt that thread if it takes too long. So you have to adapt it to check regularly if it should continue. Checking if a global named run is True is one method. Note that the threading API does not allow you to just terminate a thread.

5 The normal perils of multithreading

You have to be careful with modifying widgets or globals from both threads at the same time.

At the time of writing, the Python GIL helps you here. Since it assures that only one thread at a time is executing Python bytecode, any change that can be done in a single bytecode is multithreading safe as a side effect.

For example, look at the modification of a global in the modify function:

In [1]: import dis

In [2]: data = []
Out[2]: []

In [3]: def modify():
   ...:     global data
   ...:     newdata = [1,2,3]
   ...:     data = newdata
   ...:     

In [4]: dis.dis(modify)
  3           0 BUILD_LIST               0
              2 LOAD_CONST               1 ((1, 2, 3))
              4 LIST_EXTEND              1
              6 STORE_FAST               0 (newdata)

  4           8 LOAD_FAST                0 (newdata)
             10 STORE_GLOBAL             0 (data)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE

See how the new list is built separately, and only when it is comple is it assigned to the global. (This was not by accident.)

It takes only a single bytecode instruction (STORE_GLOBAL) to set a global variable to a newly created list. So at no point can the value of data be ambiguous.

But a lot of things take more then one bytecode. So there is a chance that one thread is preempted in favor of the other while it was modifying a variable or widget. How big of a chance that is depends on how often these situations happen and how long they take.

IIRC, currently a thread is preempted every 15 ms. So an change that takes longer than that is guaranteed to be preempted. As is any task that drops the GIL for I/O.

So if you see weird things happening, make sure to use Locks to regulate access to shared resources.

It helps if e.g. a widget or variable is only modified from one thread, and only read from all the other threads.

Roland Smith
  • 42,427
  • 3
  • 64
  • 94
0

One way to handle your key is to turn it into a two-phase loop:

def press():
    global run
    while True:
        while not keyboard.read_key(hotCombo.get()):
            time.sleep(0.2)
        run = True
        status["text"]="Working"
        while keyboard.read_key(hotCombo.get()):
            print("running")
            time.sleep(0.2)
        run == False
        status["text"]="Not Working"
Tim Roberts
  • 48,973
  • 4
  • 21
  • 30
  • Does not work at all lol, also Im not sure if its something with my tkinter but any key that I press ends up printing ```runnning``` Also it doesnt work because I only want run to be toggled by the hotkey and the part that you wrote prints ```runnning``` everytime I press the hotkey – That Guy Mar 19 '22 at 07:10