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

[Question] Multiline keyboard navigation (tab specifically) #5131

Closed
4 of 5 tasks
schinkelg opened this issue Jan 26, 2022 · 10 comments
Closed
4 of 5 tasks

[Question] Multiline keyboard navigation (tab specifically) #5131

schinkelg opened this issue Jan 26, 2022 · 10 comments
Labels
Done - Install Dev Build (see docs for how) See https://docs.pysimplegui.com/en/latest/documentation/installing_licensing/upgrading/ enhancement New feature or request Port - TK PySimpleGUI question Further information is requested

Comments

@schinkelg
Copy link

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

Question


Operating System

Linux

PySimpleGUI Port (tkinter, Qt, Wx, Web)

tkinter


Versions

Python version: 3.10.2 (main, Jan 15 2022, 19:56:27) [GCC 11.1.0]
port: tkinter
tkinter version: 8.6.12
PySimpleGUI version: 4.56.0
PySimpleGUI filename: [...]/lib/python3.10/site-packages/PySimpleGUI/PySimpleGUI.py


Your Experience In Months or Years (optional)

Years Python programming experience
10

Years Programming experience overall
30

Have used another Python GUI Framework? (tkinter, Qt, etc) (yes/no is fine)
Yes

Anything else you think would be helpful?


Troubleshooting

  • Searched main docs for your problem www.PySimpleGUI.org
  • Looked for Demo Programs that are similar to your goal Demos.PySimpleGUI.org
  • [na] If not tkinter - looked for Demo Programs for specific port
  • [na] For non tkinter - Looked at readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
  • Run your program outside of your debugger (from a command line)
  • Searched through Issues (open and closed) to see if already reported Issues.PySimpleGUI.org
  • Tried using the PySimpleGUI.py file on GitHub. Your problem may have already been fixed but not released

Detailed Description

When I add a Multiline input, tab (the key, not the UI element) can no longer be used to navigate the window. CTRL+Tab works, but that's counterintuitive.

Code To Duplicate

[sg.Text("Name", size=(15, 1)), sg.ML(key="Name")],

Watcha Makin?

Playing around, discovering PySimpleGui. The main purpose of what I'm making is a fast data entry tool. Hence the need to quickly navigate the window using tab. Multiline currently breaks this flow. I can do without tab characters in my user input. Maybe I can override the default behavior somehow? I couldn't find anything in the docs or issues for this, but searching for this problem is difficult with 'tab' also being a UI element.

@jason990420
Copy link
Collaborator

jason990420 commented Jan 26, 2022

According tkinter reference

  • By focus traversal, we mean the sequence of elements/widgets that will be visited as the user moves from element/widget to element/widget with the tab key.
  • You can traverse backwards using shift-tab.
  • The sg.Input and sg.Multiline elements (tk.Entry and tk.Text widgets) are intended to accept keyboard input, and if a sg.Input or sg.Multiline element (tk.Entry or tk.Text widget) currently have the focus, any characters you type into it will be added to its text. The usual editing characters such as ← and → will have their usual effects.
  • Because sg.Multiline elements (tk.Text widgets) can contain tab characters, you must use the special key sequence control-tab to move the focus past a text widget.

Note: It looks like only for sg.Multiline/tk.Text now, not for sg.Input/tk.Entry.

@jason990420 jason990420 added question Further information is requested Port - TK PySimpleGUI labels Jan 26, 2022
@PySimpleGUI
Copy link
Owner

Perhaps it's possible to bind to the tab key for the Multiline and get an event that you'll use to move to the next focus?

I normally have not seen this behavior in GUIs, but I do notice for example there on this very Issue as I type the comment if I enter a tab, it being to traverse focus areas of this window. It's honestly the first time I've seen this in a user interface.

@DeusAres
Copy link

Perhaps it's possible to bind to the tab key for the Multiline and get an event that you'll use to move to the next focus?

I'm interested in this, is it actually possible?

@PySimpleGUI
Copy link
Owner

Here's a bind example that manually moves from the Multiline to the Input element.

import PySimpleGUI as sg

layout = [  [sg.Text('My Window')],
            [sg.Multiline(key='-MLINE-')],
            [sg.Input(size=(12,1), key='-IN-')],
            [sg.Button('Go'), sg.Button('Exit')]  ]

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

window['-MLINE-'].bind('<Tab>', '+TAB')

while True:
    event, values = window.read()
    print(event, values)
    if event == sg.WIN_CLOSED or event == 'Exit':
        break
    if event.endswith('+TAB'):
        window['-IN-'].set_focus()

window.close()

@DeusAres
Copy link

DeusAres commented Mar 19, 2022

Wouldn't it be easier to link the TAB key to a TAB+CTRL keystroke in order to avoid the binding to every element and not create switching methods for every input and multiline?

@PySimpleGUI
Copy link
Owner

I've not looked at chaining a binding before to see if that's possible. Nor, as I said, had I dug into getting the focus order from tkinter to figure out what would normally be next. I was just responding to your request for the method I suggested which was to intercept the Tab and then take the appropriate action within your event loop. I've not done much with focus order manipulation before is all.

@jason990420
Copy link
Collaborator

Following code show the way to bind class to all sg.Multiline/tk.Text, then you can use Tab key to traverse focus to next.

import PySimpleGUI as sg

def callback1(event):
    event.widget.tk_focusNext().focus()
    return 'break'

def callback2(event):
    event.widget.tk_focusNext().focus()

layout = [
    [sg.Input(key=(0, 1))],
    [sg.Input()],
    [sg.Multiline(size=(10, 5), expand_x=True, key='Multiline')],
    [sg.Listbox(['1', '2', '3'], size=(5, 3)),
     sg.Combo(['a', 'b', 'c'],   size=(5, 3)),
     sg.Button('Send')],
]
window = sg.Window('Title', layout, finalize=True)  # Window finalized is required when binding
window['Multiline'].Widget.bind("<Tab>", callback1)
#window.TKroot.bind_class('Text', "<Tab>", callback2) # Binding on all tk.Text/sg.Multiline

while True:

    event, values = window.read()
    if event == sg.WIN_CLOSED:
        break

window.close()

Following code won't work well, Tab key won't "break" and will still add spaces into Multiline element.

import PySimpleGUI as sg

layout = [
    [sg.Input(key=(0, 1))],
    [sg.Input()],
    [sg.Multiline(size=(10, 5), expand_x=True, key='Multiline')],
    [sg.Listbox(['1', '2', '3'], size=(5, 3)),
     sg.Combo(['a', 'b', 'c'],   size=(5, 3)),
     sg.Button('Send')],
]
window = sg.Window('Title', layout, finalize=True)  # Window finalized is required when binding
window['Multiline'].bind('<Tab>', ' Tab')

while True:

    event, values = window.read()
    if event == sg.WIN_CLOSED:
        break
    elif event == "Multiline Tab":
        window['Multiline'].Widget.tk_focusNext().focus()

window.close()

@PySimpleGUI
Copy link
Owner

PySimpleGUI commented Mar 25, 2022

I knew when I wrote a response that I've not yet looked at the ordering of focus that it would be minutes before Jason would post an answer where he had figured out focus ordering in tkinter. Incredible support!

This is one feature where I'm unsure exactly how I would go about adding an enhancement to handle the focus order. Maybe a call at the window level to move to the next element. window.get_next_focus_element() may be a good way to go. This would allow the return value to be passed to Element.set_focus()... or... Element.next_focus_element() that returned the element that should get focus after the element.

In the second example, where the tab is added to the Multiline, it's possible to simply strip that tab off before moving to the next element.

import PySimpleGUI as sg

layout = [
    [sg.Input(key=(0, 1))],
    [sg.Input()],
    [sg.Multiline(size=(10, 5), expand_x=True, key='Multiline')],
    [sg.Listbox(['1', '2', '3'], size=(5, 3)),
     sg.Combo(['a', 'b', 'c'],   size=(5, 3)),
     sg.Button('Send')],
]
window = sg.Window('Title', layout, finalize=True)  # Window finalized is required when binding
window['Multiline'].bind('<Tab>', ' Tab')

while True:

    event, values = window.read()
    if event == sg.WIN_CLOSED:
        break
    elif event == "Multiline Tab":
        window['Multiline'].update(values['Multiline'].replace('\t', ''))
        window['Multiline'].Widget.tk_focusNext().focus()

window.close()

It won't traverse backwards however as the Shift+Tab needs to be bound as well, but does remove the tab and moves to the next element.

@PySimpleGUI PySimpleGUI added enhancement New feature or request Done - Install Dev Build (see docs for how) See https://docs.pysimplegui.com/en/latest/documentation/installing_licensing/upgrading/ labels Mar 26, 2022
@PySimpleGUI
Copy link
Owner

Added to 4.57.0.18....

I've added 2 new methods to all Elements that will help with this in the future. It's bugged me for a while that the focus ordering isn't addressed at all in the APIs.

I think that these changes and this sample code will do what is being requested in this issue. Both tab and Shift+Tab now traverse the window in the way one would expect.

import PySimpleGUI as sg

layout = [
    [sg.Input(key=(0, 1))],
    [sg.Input()],
    [sg.Multiline(size=(10, 5), expand_x=True, key='Multiline', focus=True)],
    [sg.Listbox(['1', '2', '3'], size=(5, 3)),
     sg.Combo(['a', 'b', 'c'],   size=(5, 3)),
     sg.Button('Send')]]

window = sg.Window('Title', layout, finalize=True)  # Window finalized is required when binding
window['Multiline'].bind('<Tab>', ' Tab')
window['Multiline'].bind('<Shift-Tab>', ' ShiftTab')

while True:

    event, values = window.read()
    if event == sg.WIN_CLOSED:
        break
    elif event == "Multiline Tab":
        window['Multiline'].update(values['Multiline'].replace('\t', ''))
        window['Multiline'].get_next_focus().set_focus()
    elif event == "Multiline ShiftTab":
        window['Multiline'].get_previous_focus().set_focus()

window.close()

@PySimpleGUI
Copy link
Owner

Here's a new version of the code that uses the new propagate parameter for the bind method.

Setting propagate to False keeps the tab from being entered into the Multiline element and so we no longer need to strip it out of the Multiline now. It's a nice feature change!

import PySimpleGUI as sg

layout = [
    [sg.Input(key=(0, 1))],
    [sg.Input()],
    [sg.Multiline(size=(10, 5), expand_x=True, key='Multiline', focus=True)],
    [sg.Listbox(['1', '2', '3'], size=(5, 3)),
     sg.Combo(['a', 'b', 'c'],   size=(5, 3)),
     sg.Button('Send')]]

window = sg.Window('Title', layout, finalize=True)  # Window finalized is required when binding

window['Multiline'].bind('<Tab>', ' Tab', propagate=False)
window['Multiline'].bind('<Shift-Tab>', ' ShiftTab')

while True:

    event, values = window.read()
    if event == sg.WIN_CLOSED:
        break
    elif event == "Multiline Tab":
        window['Multiline'].get_next_focus().set_focus()
    elif event == "Multiline ShiftTab":
        window['Multiline'].get_previous_focus().set_focus()

window.close()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Done - Install Dev Build (see docs for how) See https://docs.pysimplegui.com/en/latest/documentation/installing_licensing/upgrading/ enhancement New feature or request Port - TK PySimpleGUI question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants