Pysimplegui: [Possible Bug] Tooltip behaves funny when using timeout

Created on 29 Oct 2019  路  14Comments  路  Source: PySimpleGUI/PySimpleGUI

Type of Issues (Enhancement, Error, Bug, Question)

If I use for example window.Read(timeout=500) and want to use set_tooltip in the main loop, the tooltip stays permanently:
tooltip_screenshot

I have also tried this proposed solution: https://github.com/PySimpleGUI/PySimpleGUI/issues/1402

I intend to check a status every few seconds and display the result in the tooltip.
If there is no workaround for this particular phenomenon, does anyone have any idea how to trigger/call a function if the window was inactive in background and becomes active again? I would really appreciate a solution or tip...

Operating System

Windows 10

Python version

3.7.5

PySimpleGUI Port and Version

PySimpleGUI TK 4.5.0.26

Your Experience Levels In Months or Years

8 MONTHS - Python programming experience
2 YEARS (JavaScript / PHP) - Programming experience overall
NO - Have used another Python GUI Framework (tkiner, Qt, etc) previously (yes/no is fine)?

You have completed these steps:

  • [x] Read instructions on how to file an Issue
  • [x] Searched through main docs http://www.PySimpleGUI.org for your problem
  • [x] Searched through the readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
  • [x] Looked for Demo Programs that are similar to your goal http://www.PySimpleGUI.com
  • [x] Note that there are also Demo Programs under each port on GitHub
  • [x] Run your program outside of your debugger (from a command line)
  • [x] Searched through Issues (open and closed) to see if already reported

Code or partial code causing the problem

import PySimpleGUI as sg
import random

layout = [[sg.Text('This is a sample text.', key='_text_', tooltip='')],
          [sg.Button('Exit')]]

window = sg.Window('Window Title', layout, finalize=True)

while True:
    event, values = window.Read(timeout=500)

    if event in (None, 'Exit'):
        break

    window['_text_'].set_tooltip(random.randint(500, 1000))

Most helpful comment

I am happy to see that PySimpleGUI is developing so fast and versatile.

Yea, me too! When it works. Trying to keep up with advanced capabilities while also finishing up the ports. Thankfully PySimpleGUI users like yourself really help by researching stuff like this. It enables me to figure out a PySimpleGUI-way of doing things and not be bogged down the with details that you researched and posted.

All 14 comments

Yea, it's not a behavior that's been previously tested. I don't recall every trying it.

Does it work correctly if you don't keep setting the tooltip prior to the previous one disappearing?

For your other question...

Detecting when a specific window gets focus isn't an event currently supported. I assume it will be OS dependent if not supported by the GUI framework.

Can you describe the steps the windows go through that would cause the kind of event you're looking for?

Thank you for your quick answer and questions! =)

I plan to give the path input field another function with a kind of clipboard button (in addition to the browse button). The user should have the possibility to change during the running program briefly into the Windows Explorer, copy a path from the address bar and paste it back into the path input field in the program. The tooltip above the clipboard button should show the user which path is currently in the clipboard.

The clipboard functionality I could already realize with the great module 'win32clipboard' (--> pywin32 has to be installed for this). I wanted to give the user an additional visual hint with the tooltip before copying the path into the field.

This copy and paste process can be repeated. So I thought it would be handy if the clipboard data query was only done when the window comes to the foreground / becomes active again. In the meantime, I found a module that might be helpful in this matter: 'PyGetWindow'. I can imagine that otherwise the constant query within a loop becomes unnecessarily processor-heavy.

Thanks for your tips. If I find out more about this, I'll report it.

I too used win32clipboard back when first working with PySimpleGUI.

I was recently introduced to pyperclip, a cross-platform solution. Maybe it'll be what you need?
https://github.com/asweigart/pyperclip

Some things you're looking for may be beyond the current scope of PySimpleGUI. In particular I'm thinking of the window focus functionality. New features are a matter of priority, scope, and resources. Hopefully if PySimpleGUI doesn't directly support what you're after, you'll be able to find workarounds.

That's what I think. The tooltip display of clipboard data is actually not a vital function, just an additional idea.

This script snippet might answer your first question:

import PySimpleGUI as sg
import random

layout = [[sg.Text('This is a sample text.', key='_text_', tooltip=None)],
          [sg.Button('Set Tooltip', key='_button_tooltip_')]]

window = sg.Window('Window Title', layout, finalize=True)

while True:
    event, values = window.Read(timeout=1000, timeout_key='_timeout_key_tooltip_')

    # tooltip is permanently displayed after triggering:
    # if event == '_timeout_key_tooltip_':
    #     window['_text_'].set_tooltip(random.randint(500, 1000))

    # works great:
    if event == '_button_tooltip_':
        window['_text_'].set_tooltip(random.randint(500, 1000))

    if event in (None, 'Exit'):
        break

You were absolutely right: If I set timeout=5000 and move the mouse away from the text before triggering set_tooltip again, it works as expected. But it must be avoided a hover over the text at the same time when set_tooltip is triggered. Otherwise the tooltip remains permanently.

import PySimpleGUI as sg
import random

layout = [[sg.Text('This is a sample text.', key='_text_', tooltip=None)],
          [sg.Button('Set Tooltip', key='_button_tooltip_')]]

window = sg.Window('Window Title', layout, finalize=True)

while True:
    new_number = random.randint(500, 1000)

    event, values = window.Read(timeout=5000, timeout_key='_timeout_key_tooltip_')

    # tooltips remain permanently in place:
    if event == '_timeout_key_tooltip_':
        window['_text_'].set_tooltip(new_number)
        print(new_number)

    # works great:
    # if event == '_button_tooltip_':
    #     window['_text_'].set_tooltip(new_number)
    #     print(new_number)

    if event in (None, 'Exit'):
        break

set_tooltip_demo

I solved my problem after doing some research. Finally I can trigger a function outside the loop as soon as the window has the focus again and of course the other way around. It is realized with :

window.TKroot.bind("<FocusIn>", on_focus_in)
window.TKroot.bind("<FocusOut>", on_focus_out)

Very useful! :-) This Stackoverflow post helped me:
https://stackoverflow.com/questions/46567324/tkinter-window-focus-loss-event

Many thanks for the hint regarding 'pyperclip', it's less complicated! Now my little demo looks like this:

import PySimpleGUI as sg
import pyperclip as cb
import os

sg.SetOptions(tooltip_time=20)

layout = [[sg.Text('Please open Windows Explorer and copy a path to clipboard...', size=(50, 1))],
          [sg.Input('', size=(50, 1), disabled=True, key='_input_'), sg.Button('Get Clipboard data', tooltip='', key='_btn_cb_')],
          [sg.Text('', size=(50, 1), text_color='darkred', key='_output_')],
          [sg.Button('Exit')]]

window = sg.Window('Clipboard & Tooltip', layout, finalize=True)

clipboard_data = ''

def on_focus_in(event):
    if event.widget == window.TKroot:
        global clipboard_data
        try:
            clipboard_data = cb.paste()
        except:
            print('Something went wrong!')
        else:
            if os.path.isdir(clipboard_data):
                window['_btn_cb_'].set_tooltip('Clipboard data:\n'+clipboard_data)
                window['_output_']('Clipboard data: '+clipboard_data, text_color='darkgreen')
            else:
                window['_btn_cb_'].set_tooltip('Clipboard data:\nNo valid path in clipboard!')
                window['_output_']('Clipboard data: No valid path in clipboard!', text_color='darkred')
        print('window has focus!')

def on_focus_out(event):
    if event.widget == window.TKroot:
        print('window lost focus!')

window.TKroot.bind("<FocusIn>", on_focus_in)
window.TKroot.bind("<FocusOut>", on_focus_out)

try:
    clipboard_data = cb.paste()
except:
    print('Something went wrong!')
else:
    if os.path.isdir(clipboard_data):
        window['_btn_cb_'].set_tooltip('Clipboard data:\n'+clipboard_data)
        window['_output_']('Clipboard data: '+clipboard_data, text_color='darkgreen')
    else:
        window['_btn_cb_'].set_tooltip('Clipboard data:\nNo valid path in clipboard!')
        window['_output_']('Clipboard data: No valid path in clipboard!', text_color='darkred')

while True:

    event, values = window()

    if event == '_btn_cb_':
        try:
            clipboard_data = cb.paste()
        except:
            print('Something went wrong!')
        else:
            if os.path.isdir(clipboard_data):
                window['_input_'](clipboard_data)
                window['_btn_cb_'].set_tooltip('Clipboard data:\n'+clipboard_data)
                window['_output_']('Clipboard data: '+clipboard_data, text_color='darkgreen')
            else:
                window['_btn_cb_'].set_tooltip('Clipboard data:\nNo valid path in clipboard!')
                window['_output_']('Clipboard data: No valid path in clipboard!', text_color='darkred')

    if event in (None, 'Exit'):
        break

Nice find.

There has been a recent addition to PySimpleGUI in an effort to help with these kinds of extensions. It was written for Elements, not the entire window, so I'll have to work more on this.

Elements have a new method that you can bind tkinter events to and it will route them through to your read call like all other events.

Using it would look something like this if you want to bind a double click:

elem.Widget.bind('<Double-Button-1>', elem.ButtonReboundCallback)

It's not fully functional yet and the Window object needs a similar method that can be bound to.

I was thinking one sneaky kind of way through this is to create an invisible button for each event, then bind the window focus events to those hidden buttons. This should, maybe, I hope, but don't know for sure, will cause the event to come through the read.

window.TKroot.bind("<FocusIn>", on_focus_in)

It's experimental at this point for sure.

Wanted to give you a heads-up on where PySimpleGUI is going with these kinds of extensions.

Are you seeing multiple events being generated for a single focus event?

This code demonstrates what I was describing in the last post. When the window gets focus, the window.read() returns with the key describing if it's focus in or out. I see 3 calls for some reason.

import PySimpleGUI as sg

layout = [  [sg.Text('My Window')],
            [sg.Input(key='-IN-'), sg.Text('', key='-OUT-')],
            [sg.Button('Do Something'), sg.Button('Exit'),
             sg.Button('-FOCUS-IN-', visible=False), sg.Button('-FOCUS-OUT-', visible=False)]  ]

window = sg.Window('Window Title', layout, finalize=True)

window.TKroot.bind("<FocusIn>", window['-FOCUS-IN-'].ButtonReboundCallback)
window.TKroot.bind("<FocusOut>", window['-FOCUS-OUT-'].ButtonReboundCallback)

while True:             # Event Loop
    event, values = window.read()
    print(event, values)
    if event in (None, 'Exit'):
        break
    if event == 'Do Something':
        window['-OUT-'].Update(values['-IN-'])
window.close()

You can watch it run on Trinket here:
https://trinket.io/library/trinkets/49c5d264d8

Trinket is running Linux so these multiple events seem to be happening on both Windows and Linux.

This is all new stuff so who knows where bugs may be at the moment.

I am happy to see that PySimpleGUI is developing so fast and versatile.

Yes, I noticed the multiple calls also on a Windows system. I could reduce it to one call with the following line: if event.widget == window.TKroot:

That's good to hear. It's a tkinter thing then it would appear.

What I'm trying to do in providing these 'extensions' is to allow advanced users to extend the capabilities, but can do it in a way that retains the PySimpleGUI user interface architecture.

I didn't check to see what tkinter is reporting when the event happens. I haven't debugged that far yet.

I am happy to see that PySimpleGUI is developing so fast and versatile.

Yea, me too! When it works. Trying to keep up with advanced capabilities while also finishing up the ports. Thankfully PySimpleGUI users like yourself really help by researching stuff like this. It enables me to figure out a PySimpleGUI-way of doing things and not be bogged down the with details that you researched and posted.

Here's a version that ignores duplicate focus events. It will report focus changes only 1 time instead of 3.

More work needed to determine how to handle reporting specific elements getting focused when the entire window is mapped like this. I didn't expect there to be multiple widgets reporting focus at the same time.

If it's important to determine the element with the focus, then I think calling find_element_with_focus after getting one of these focus event would be a good design pattern to follow if it mattered exactly which element got the focus.

Of course, all of this is not portable, but doesn't seem to be a problem in your case.

import PySimpleGUI as sg

layout = [  [sg.Text('My Window')],
            [sg.Input(key='-IN-'), sg.Text('', key='-OUT-')],
            [sg.Button('Do Something'), sg.Button('Exit'),
             sg.Button('-FOCUS-IN-', visible=False), sg.Button('-FOCUS-OUT-', visible=False)]  ]

window = sg.Window('Window Title', layout, finalize=True)

window.TKroot.bind("<FocusIn>", window['-FOCUS-IN-'].ButtonReboundCallback)
window.TKroot.bind("<FocusOut>", window['-FOCUS-OUT-'].ButtonReboundCallback)

has_focus = True
while True:             # Event Loop
    event, values = window.read()
    if event in (None, 'Exit'):
        break
    if event == '-FOCUS-IN-' and not has_focus:
        print('Got focus!')
        has_focus = True
    elif event == '-FOCUS-OUT-' and has_focus:
        print('Leaving focus!')
        has_focus = False
window.close()
Was this page helpful?
0 / 5 - 0 ratings