Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Window pops up in background, how to make active? #1077

Closed
timkostka opened this issue Jan 14, 2019 · 21 comments
Closed

Window pops up in background, how to make active? #1077

timkostka opened this issue Jan 14, 2019 · 21 comments

Comments

@timkostka
Copy link

Under certain circumstances, when I create a window, it gets created in the background and doesn't have focus. Is there an analogue to Window's SetActiveWindow function that would make the window active?

I am using autohotkey to run the python script using a command similar to a run pythonw script.py whenever the user presses a hotkey. It's not entirely clear why the window sometimes has focus and sometimes doesn't. If there was a way to programmatically determine if the window has focus, and if not, set it, that would solve the issue for me.

@MikeTheWatchGuy
Copy link
Collaborator

I assume you mean the tkinter version of PySimpleGUI.

You may want to try calling:
window.BringToFront()

@MikeTheWatchGuy
Copy link
Collaborator

If that doesn't help, you can also try:
window.TKroot.focus_set()

or perhaps
window.TKroot.focus_force()

I haven't used these calls and found examples in places like here:
https://stackoverflow.com/questions/16083491/make-a-tkinter-toplevel-active#25346702

You can access the tkinter "root" window using this variable:
window.TKroot

I recommend experimenting with this variable and the different types of calls you can make that modifies focus. Let me know if there's one that works so that I can add it to the SDK, making a "force focus" call.

@bdowling
Copy link

bdowling commented Jan 15, 2019

I found this worked for me, it appears that TKroot.lift() (e.g. current BringToFront()) only raises it if within its own application group if you have multiple windows open. The topmost attributes below raises it above all other active windows, at least it my Windows environment.

                root = window.TKroot
                root.attributes('-topmost', True)
                root.attributes('-topmost', False)
                window.BringToFront()

not sure how you would like to see this implemented, perhaps adding an all attribute as in BringToFront(all=True) -- if something like that I can submit a PR if you want.

https://stackoverflow.com/questions/1892339/how-to-make-a-tkinter-window-jump-to-the-front

@timkostka
Copy link
Author

timkostka commented Jan 15, 2019

I tried all of the proposed solutions, to varying results.

These had no effect for me:

window.BringToFront()
window.TKroot.focus_set()
window.TKroot.focus_force() # <-- edit: this works somewhat, see next comment

This brought the window to the front, but didn't actually give it focus. Keyboard events still went to the previously active window.

window.TKroot.attributes('-topmost', True)
window.TKroot.attributes('-topmost', False)

Adding window.BringToFront() to the end of this had no effect.

I will search online a bit to see if there's something else in TK that allows this. Stay tuned.

@timkostka
Copy link
Author

Previously, I was trying these after the first call to window.Read(). In other words, something like this:

window.Layout(layout)
bring_to_front = True
while True:
    # read current state
    button, value = window.ReadNonBlocking()
    # on the first call, bring window to front and give it focus
    if bring_to_front:
        window.TKroot.focus_force()
        bring_to_front = False
    ...

This has the effect of first showing the window, then trying to give it focus on the first loop by reading the bring_to_front flag. This worked, however, it took away focus from from the default InputText element so I missed that the window actually had focus. In other words, the window had focus, but the default text element did not, so any keyboard events were lost. In order to give the text element, one can add something like window.FindElement('input').TKEntry.focus_set() to give focus to that particular element within the form.

I then switched to the following:

window.Layout(layout)
window.Show(layout)
window.TKroot.focus_force()
while True:
    # read current state
    button, value = window.ReadNonBlocking()
    ...

And this is now working as expected.

Examples used on the internet say using the focus_force function is rude, and the source itself says Use with caution!. That makes sense, since grabbing focus randomly is typically not what the user wants. However, in this case, I think it is perfectly reasonable to grab focus when the application starts. That is the typical behavior in Windows, even though it occasionally is not happening in my use case for some reason.

To implement, perhaps you can add a flag to the Window initializer something along the lines of

class Window:
    ...

    def __init__(self, ..., force_focus=False):
        ...

And if force_focus is set to True, then call window.TKroot.focus_force() immediately after showing. Or just expose the focus_force() elsewhere. But then the user has to do call window.Show() before calling, or make a nonblocking loop and call it on the first go, or something else.

@MikeTheWatchGuy
Copy link
Collaborator

Some of these calls, like Show are not meant to be called by user code. I know now I should have named it _show.

Have you tried simply calling input_element.SetFocus() ? This is the mechanism normally used to control focus. I'm wondering if setting the focus will bring the window into focus too.

@timkostka
Copy link
Author

timkostka commented Jan 15, 2019

I did try that. There are two things of interest here.

  1. Set the active element within the form to the InputText element.
  2. Make the window active and in the foreground so that it receives keyboard events.

Using SetFocus() does number 1, but doesn't change set the active window at all. Using force_focus does number 2.

You don't have to change anything in the code if you don't want. The solution feels like a bit of a hack, and it's unclear why I'm seeing this behavior in the first place.

@elibroftw
Copy link

elibroftw commented Jul 21, 2019

For me, the window is only focused for the first time. This example below demonstrates the issue I have with BringToFront. Hell I don't even need to include BringToFront because the result is the same (On first loop, window is focused, on every loop thereafter, the window is unfocused and I need to alt + shift + tab to refocus).

import PySimpleGUI as Sg
import time

fg = '#aaaaaa'
bg = '#121212'
font_family = 'SourceSans', 11
button_color = ('black', '#4285f4')
window_icon = b'iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAAAXNSR0IArs4c6QAABV9JREFUeAHt\nWUtsVUUYvhQsG0qMDaQQTUPwEVkQN7gsTXGhG+JO9zRhwZaN1o1xISEkJmIISWVDQoILG1iXtNfY\nUBe6MRrAR1OjK+MjqSaK2ML3lU47d/J/M+fce8+B9s6ffDlzvvlf83fOPG4bjSy5ArkCuQK5ArkC\nuQK5ArkCuQIlKrANuuPAPPAXcH+LYmltjBwrx1xIhqA1DWzVoqhxccwce1RYxV4sjisaxx6dSZxq\nTrlXn6yBlC/Q06uFcePmursu4XTigrxrvbc3G39j2ANu6GGBWEVLQj1LZzNyyfH2bcZR1ZlzLlCi\n2rlAuUCJCiS68wxKFGhHoj/VrXaBlF3d/W3vwnkGJf5UuUCJAoWfWNtTMRFn03Z3OoOWN+3ICybe\naYGeQZwJ4MeC8XpWjZ/mGDAFuFvx4/RUfxiVo9LvCv8yvMwCKvij4NXAVC5Kv6v8q/D2NaCSqJNX\nA1M5rOuHu9Zd9PA3IeIP4HvgDnALmAN+AcpIP5TfA04Dna53ZeKGuuE4XT8LZInST/61v4O3i8Ax\noMyAR6C/CKi/WNU8Qpui4prKJJWBxf8M/fcB7mRFZDeUrgGWr6o5lZ+Kq/TbSp6f5SRwUHrd6OCs\nuwCoxKriNzJobal4rVremzIowt+Dn3NAkd+034ZeEZ/d0vGG2NJU/luU/BdlUIbnQv6671S0x8GX\n8duJrkhBxlf6De46g8ABYBQ4CXwIcLteAcok+RH0dwIxqWsmqRzUeJR+lN+D3hNAEyharK+gm1rE\n61iTkIYpXS2QH+E5vHwCFCkUd7tDvnHQ3o73qne3IOT6a2UFchGOoDEDqECO/x06vIIo4RFgEXD6\n3X6quCqO0m+bfw2WC4AKSJ5Fis2kEfQvJ3zE/Mf64NYUZWMqd0pycW8CKih5fm6xNelMwj7mO9YH\nt6YoG1OZJO9gvHvNAleAd4DDQFF5Aoq8iqjA5Llwq92Nu2gVF1y4NUXlaSqTVAb8fD4AihbrFHT/\nj/jjEUAJfwVQebTLq1jKn9JPJsY14jIwLD1sdLBIKgHyscPkbMI25tfq28iqtWXpkpOiDEL+X3g4\nC/CTiEnsc+OJW11LuOOFMTt5Vzkqn0q/dFJz8LRXems0uCY1AZUI725KptCh7MryKobyo/TbSugn\neHtJenx4dVFHAF5wnxW2Y+DVAMryIoT0r/RXp/zz6B0F+Cvg50CRswl3v1iReE5Sg/oYfZbwV70f\nAGVXhrf8k1M+lL7J8xPievMPoByS50yKfW4zwv4/8Ops9JawieVh9cGNKZYuubaEg/gUUE7Jc01S\nCzevJSvCngdES/aBjB0XYrn4fZZvcr6O31b6hfgJaKmBMghnm5Kr6PATcW2esPuUUYW8ix8+Ow75\nJjyoIvEIMCwi8FcAZfeKsKmSDgvj3rsSkzPJOQyfPEwqaaIj1Oc7z0x1i5UHOSncjW4Dk0CRv6ha\nk7jzqWvJCfRZifFfSnWLlUe0QKHBZ8iY274SLtxqd+PdzRL+Mqk+s6ctgwq5cLzuXYZ0Cv7zT2gf\nlRYPF2Vf37UXIjbqxs61rU5xuYZPmUOo6N5ZJDWTePbhJ+V0/af6zM4L/XfBK/H9Vtle8hMourU+\nCSOuS5b8CvKm1QHuuOC/EfwLgq+T/tYPVrRAtBkB1MJ93XfqtV/02n6TP8pZwmPAo5ZLfgJlCkS7\nN3xjr/2l1/ab+/0Xr81riSVPWWSN3A3EailQGDv1bfMIYAnXJ8tWzZRBof+b5XyNs/x3k5tGnKFI\n/NWuVECekywZAGnZKv1+oX/Xcr7GWf475ZjfPDAObFuLkx+5ArkCuQK5ArkCuQK5ArkCuQLpCjwA\nMCQ8Gt1a8k0AAAAASUVORK5CYII=\n'

while True:
    settings_layout = [
            [Sg.Text(f'SAMPLE TEXT', text_color=fg, background_color=bg, font=font_family)]
            ]
    settings_window = Sg.Window('Music Caster Settings', settings_layout, background_color=bg, icon=window_icon, return_keyboard_events=True, use_default_focus=False)
    settings_window.BringToFront()
    while True:
        settings_event, settings_values = settings_window.Read(timeout=0)
        if settings_event is None:
            time.sleep(2)
            break

@timkostka
Copy link
Author

I switched over to using wxWidgets natively and I still experience similar behavior, where the window pops up but isn't the foreground window. I think there's something happening within Windows causing this behavior that isn't specific to PySimpleGUI (or TK or wxWidgets).

@MikeTheWatchGuy
Copy link
Collaborator

You need to Add Finalize to your Window call. I just noticed you hadn't done that.

You cannot operation on windows and elements until the window is either Finalized or Read. Please add a .Finalize() to the end of your call to Window.

@MikeTheWatchGuy
Copy link
Collaborator

Normally people run into these problems when doing Updates.

Never call ReadNonBlocking directly. If you want a truly nonblocking read, call window.Read(timeout=0).

I never got to see your call to Window. This is why it's really important to post an entire working / non-working program like @elibroftw did. That's what got me to thinking you likely are not calling Finalize.

I'll add something about Window calls as well.

@timkostka
Copy link
Author

Closing as I no longer use PySimpleGUI. Someone else can reopen if this is still an issue for them.

@MikeTheWatchGuy
Copy link
Collaborator

I'm sorry PySimpleGUI wasn't able to do all the stuff you needed. Sorry that I didn't realize you hadn't called Read or Finalize first sooner. Good luck on your project.

@elibroftw
Copy link

Okay I found the solution. @timkostka was right and Finalize helps with this.
This works.

settings_window = sg.Window('TITLE', ['Layout'])
settings_window.Finalize()
settings_window.TKroot.focus_force()

@MikeTheWatchGuy
Copy link
Collaborator

Uhm, tim didn't bring up Finalize. I did. And I've updated the readme / readthedocs already to explain this.

https://pysimplegui.readthedocs.io/en/latest/#window-object-beginning-a-window

@elibroftw
Copy link

elibroftw commented Jul 23, 2019 via email

@MikeTheWatchGuy
Copy link
Collaborator

MikeTheWatchGuy commented Jul 23, 2019

I don't recommend running calling Window.Read with timeout=0. It can cause problems of starving the GUI and eating all your CPU needlessly. Even 10ms is better than 0.

I mention it here because it could have a material impact if one window/thread takes all of the CPU and you never give any cycles to the other windows so that they can react to your method calls.

@elibroftw
Copy link

elibroftw commented Jul 23, 2019 via email

@MikeTheWatchGuy
Copy link
Collaborator

No problem. Just want you to be aware, educated, about the calls you're making. It's a completely different code path taken for timeout=0 windows. It uses different tkinter calls that may have a real impact on this problem.

I've debated removing the capability entirely but there are some realtime applications where timeout=0 is critical to use so I can't in good faith remove it. It's worth the risk to give those projects a chance to use PySimpleGUI

@pachinmiki
Copy link

pachinmiki commented Oct 14, 2021

I couldn't solve this problem with pysimplegui alone, but used win32gui to solve it.

import win32gui          
window = sg.Window('Check Screen', layout, return_keyboard_events=True, finalize=True, use_default_focus=True)
main_app = win32gui.FindWindow(None, 'Check Screen')
win32gui.SetForegroundWindow(main_app)

@PySimpleGUI
Copy link
Owner

PySimpleGUI commented Oct 14, 2021

I've been working with the win32gui package quite a bit more lately.

There are a couple of older demos that use it:

image

Don't tell Jason, but I was playing with it yesterday morning early. I was exploring how to move my windows that get moved by windows when a monitor is turned off automatically. It annoyingly moves all of my windows to another of my monitors, not my main monitor.

This code is a complete mess, but it's a start as it moved my Notepad++ window to (0,0) on my main monitor. It's not something I'm spending a lot of time on, but it's a good package to know more about.

import ctypes
import PySimpleGUI as sg
import win32gui
def move_window(title, location=(0, 0)):
    user32 = ctypes.windll.user32

    # get screen resolution of primary monitor
    res = (user32.GetSystemMetrics(0), user32.GetSystemMetrics(1))
    # res is (2293, 960) for 3440x1440 display at 150% scaling
    user32.SetProcessDPIAware()
    res = (user32.GetSystemMetrics(0), user32.GetSystemMetrics(1))
    # res is now (3440, 1440) for 3440x1440 display at 150% scaling

    # get handle for Notepad window
    # non-zero value for handle should mean it found a window that matches
    handle = user32.FindWindowW(title, None)

    # meaning of 2nd parameter defined here
    # https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
    # minimize window using handle
    # print('Handle = ', title, handle)
    user32.ShowWindow(handle, 6)
    # maximize window using handle
    # user32.ShowWindow(handle, 9)

    # move window using handle
    # MoveWindow(handle, x, y, height, width, repaint(bool))
    # user32.move_window(handle, location[0], location[1])
    # user32.MoveWindow(handle, location[0], location[1], True)


EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
IsWindowVisible = ctypes.windll.user32.IsWindowVisible
user32 = ctypes.windll.user32
titles = []


def move_window2(handle, location):
    print('moving handle=', handle)
    rect = win32gui.GetWindowRect(handle)
    x = rect[0]
    y = rect[1]
    w = rect[2] - x
    h = rect[3] - y
    print(f'rect = {rect}')
    win32gui.MoveWindow(handle, location[0], location[1], w, h, True)

def windowEnumerationHandler(hwnd, top_windows):
    top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))

def find_windows():
    results = []
    top_windows = []
    win32gui.EnumWindows(windowEnumerationHandler, top_windows)
    for window in top_windows:
        print(window)
        if 'Notepad' in window[1]:
            move_window2(window[0], (0,0))
        # if "notepad" in i[1].lower():
        #     print i
        #     win32gui.ShowWindow(i[0],5)
        #     win32gui.SetForegroundWindow(i[0])
        #     break


def foreach_window(hwnd, lParam):
    if IsWindowVisible(hwnd):
        length = GetWindowTextLength(hwnd)
        buff = ctypes.create_unicode_buffer(length + 1)
        GetWindowText(hwnd, buff, length + 1)
        title = str(buff.value)
        titles.append(buff.value)
        if 'Notepad' in title:
            move_window(title, (0, 0))
    return True

find_windows()
exit()

EnumWindows(EnumWindowsProc(foreach_window), 0)

print(titles)

I ran into some problems on 3.10 with a few of these windows related packages that are not yet available. I don't think this was the one though.

Anyway, it's a package on my radar for sure!

Thank you for posting the code snippet!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants