0

I am using cmd.exe to run a program that opens a tkinter window. I'd like to keep focus on the console when the root.mainloop() function is called. Example code below. This is running on a Window system with Python 3.9.

# gui thread
def guiTask():
  print("doing tasks here")
  root.after(50, guiTask)

# main GUI
root = tk.Tk()
root.geometry('%dx%d' % (800, 800))
root.after(50, guiTask)
root.mainloop()

So this bit of code will just print something every 50 ms after opening a tkinter window. The focus goes to this window upon execution. I'd like the focus to be kept on the console. Is this possible?

jliu83
  • 1,553
  • 5
  • 18
  • 23
  • 1
    You can get the window handle of current window using `hwnd = ctypes.windll.user32.GetForegroundWindow()` before creating tkinter window, then use `ctypes.windll.user32.SetForegroundWindow(hwnd)` after tkinter window is shown. – acw1668 Oct 11 '21 at 06:34

1 Answers1

1

Windows only (to my knowledge):
Well, I found a way (partially (by what is meant the majority of the function) taken from Get HWND of each Window? (the question itself)):

import tkinter as tk


def win_focus_set(win_name):
    import ctypes
    enum_windows = ctypes.windll.user32.EnumWindows
    enum_windows_proc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
    get_window_text = ctypes.windll.user32.GetWindowTextW
    get_window_text_length = ctypes.windll.user32.GetWindowTextLengthW
    is_window_visible = ctypes.windll.user32.IsWindowVisible
    windows = []

    def foreach_window(hwnd, _):
        if is_window_visible(hwnd):
            length = get_window_text_length(hwnd)
            buff = ctypes.create_unicode_buffer(length + 1)
            get_window_text(hwnd, buff, length + 1)
            windows.append((hwnd, buff.value))
        return True

    enum_windows(enum_windows_proc(foreach_window), 0)
    for hwnd, name in windows:
        if win_name in name:
            ctypes.windll.user32.BringWindowToTop(hwnd)


def gui_task():
    print("doing tasks here")
    root.after(50, gui_task)


# main GUI
root = tk.Tk()
root.geometry('%dx%d' % (800, 800))
root.after(50, gui_task)
root.after(1, win_focus_set, 'cmd.exe')
root.mainloop()

Basically it is just that function and then call it as soon as possible using .after so that it gets executed right after the main window shows up (also "cmd.exe" just has to be in the title of the window (so if you had two cmds open it may switch to the other (although that could happen even if you provided the full title if they both had the same title)) and .after takes the rest of the arguments as arguments to pass to the scheduled function)

The other option would be to get the window that is currently in focus and bring that to top after tkinter starts:

import tkinter as tk
import ctypes

hwnd = ctypes.windll.user32.GetForegroundWindow()


def gui_task():
    print("doing tasks here")
    root.after(50, gui_task)


# main GUI
root = tk.Tk()
root.geometry('%dx%d' % (800, 800))
root.after(50, gui_task)
root.after(1, ctypes.windll.user32.BringWindowToTop, hwnd)
root.mainloop()
Matiiss
  • 5,970
  • 2
  • 12
  • 29
  • Hmm.. the first thing that came to mind was the multiple cmd.exe problem. I wonder if there is a more portable and bulletproof way to do this, with functions from tkinter instead? – jliu83 Oct 11 '21 at 03:39
  • @jliu83 unless you create the window with `tkinter` there is nothing you can really do with it from `tkinter`, you can however use the suggestion in comments which I will try to include in the answer too – Matiiss Oct 11 '21 at 08:36
  • @jliu83 I haved add the other much shorter way of doing this, btw, `.after` "loops" are not threads, they don't run concurrently but asynchronously – Matiiss Oct 11 '21 at 09:07
  • Thank you for clarifying the thread vs concurrency distinction. Your second solution worked great. – jliu83 Oct 11 '21 at 19:31
  • @jliu83 great,btw, thread and concurrency is the same thing in the sense that threads run concurrently, meaning you can have multiple of those that execute whatever you tell them to at the same time, asynchronous means that the program has some event loop that processes events, if an event has to be processed for too long it will block the whole program (you can test it by replacing that `print` with some loop like `for i in range(10000): print(i)`) which wouldn't be the case with threads if one thread took a while to execute then the other one could still run, it is just a technicality but... – Matiiss Oct 11 '21 at 19:36