enaml
enaml copied to clipboard
keypress event
I just discovered enaml and am really enjoying it. But one thing I really need is to be able to accept keypresses in my form. I have seen a stackexchange post on this but I can't get that to work. Is there any way to do this?
Thanks!
I believe this is not possible at the moment. You could define your own widget taking care of it, but that means writing some Qt specific code. However this is an interesting request and I will try to come up with at least a plan for implementing this. But I can make no promise at to when this feature may land.
There's a KeyEvent
you can try by installing enamlx. It was updated recently to work with enaml 0.10.2.
See https://github.com/frmdstryr/enamlx/blob/master/enamlx/widgets/key_event.py
Thinking a bit about it and discussing with @sccolbert, we came to wonder what is your use case for capturing key press and if it cannot be solved in a different way.
I generate forms that act as sort of dashboards, meaning that they mostly display information to the user. But the user must be able to quickly enter bits of information without much hassle, and without having to pay too much attention since they have other tasks to do at the same time. Over the years, I have found that accepting a keypress by the form is by far the best way to do this because the user doesn't also have to hit enter, or make sure that the correct text box is selected, or that the mouse cursor is exactly where it needs to be. They have a number keypad and just hit the right number at the right time, and it works great.
@frmdstryr, thanks. Is there an example of how to use keyevent? I see that the most recent commits to occ_viewer.py and plot_area.py include comments about adding key events, but I don't see where in the code.
I tried this with a form and I'm not sure it's going to do what you intend as the currently focused field captures the key press so it must be added to each form element and special keys filtered out.
But heres the modified person tutorial example.
Import enamlx and call install before importing enaml.
#------------------------------------------------------------------------------
# Copyright (c) 2013, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
from __future__ import unicode_literals, print_function
from atom.api import Atom, Unicode, Range, Bool, observe
import enamlx
enamlx.install()
import enaml
from enaml.qt.qt_application import QtApplication
class Person(Atom):
""" A simple class representing a person object.
"""
last_name = Unicode()
first_name = Unicode()
age = Range(low=0)
debug = Bool(False)
@observe('age')
def debug_print(self, change):
""" Prints out a debug message whenever the person's age changes.
"""
if self.debug:
templ = "{first} {last} is {age} years old."
s = templ.format(
first=self.first_name, last=self.last_name, age=self.age,
)
print(s)
if __name__ == '__main__':
with enaml.imports():
from person_view import PersonView
john = Person(first_name='John', last_name='Doe', age=42)
john.debug = True
app = QtApplication()
view = PersonView(person=john)
view.show()
app.start()
Then add KeyEvents to the Window and to each Field you want to listen to (as the Field captures the key presses before it gets to the parent).
#------------------------------------------------------------------------------
# Copyright (c) 2013, Nucleic Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#------------------------------------------------------------------------------
from enaml.widgets.api import Window, Label, Field, Form
from enaml.stdlib.fields import IntField
from enamlx.widgets.api import KeyEvent
enamldef PersonForm(Form):
attr person
attr key_manager
Label:
text = 'First Name'
Field:
text := person.first_name
KeyEvent:
# Captures all presses from the field and send to the parent
pressed :: key_manager.pressed(change)
Label:
text = 'Last Name'
Field:
text := person.last_name
Label:
text = 'Age'
IntField:
minimum = 0
value := person.age
enamldef PersonView(Window):
attr person
KeyEvent: keys:
# Captures all key presses not handled by the focused widget
pressed :: print(change)
released :: print(change)
PersonForm:
person := parent.person
key_manager = keys
Also note, nesting a KeyEvent as a direct child in the form will mess up the automatic layout as the KeyEvent uses the parent widget.
I'm sure there's a better way to implement this but it worked for what I needed at the time (capture arrow presses to move a CNC machine head).
The approach we’ve taken in the past was a container subclass that captures key events that bubble up and sends them to enaml.
The main use case was for shortcuts that were applied to a particular area of the screen (hence the container). We limited the implementation to only send them to enaml when there was modifier present also (plus a few exceptions).
e.g. Ctrl-S to save a view, Esc to close it.
Thanks for the comments. @frmdstryr, that seems like it will work. But for my particular use case, it would require edits to each form item, which are not always known until runtime, and by a non-programmer. For now I will have to wait and hope something shows up in enaml.
@cbrown1 I am kind of confused because in usual GUI system, if the widget that has focus handle the key event it will never bubble up to its parent (http://doc.qt.io/qt-5/qkeyevent.html). So in your use case, what would you expect to see if one field has focus before the user start typing ? and how is the form routing the keypress if the focused widget does not handle it ?
There aren't any text input fields in my forms, only labels, images, etc and maybe a button or two. My understanding is that when any of these widgets have focus, they pass the key events on to the form. Of course, if a textbox had focus it would process the key event normally.
In this case I believe that @frmdstryr solution would work with a single KeyEvent on the container since the children do not capture the key press (he needs multiple because the field captures it). After discussing it with @sccolbert, it appears that the clean way to implement this would be through features (like focus event and drag and drop support). This solution is preferred first because it encourages the user to not abuse the feature and second because it avoids possible issues with re-parenting. I woon't have time to work on this any time soon, but volunteers are welcome.
This doesn't solve the general case, but might solve the specific case the @cbrown1 is having.
I am writing a similar app (mainly to learn Enaml). It shows an image and the user must classify it by pressing one character - e.g. 'd' for 'dog', 'c' for 'cat', 'n' for 'neither' or '0' for 'unclear. There are only a small and finite number of keys I need to capture.
I have two similar approaches that are working for me: Menu bars with keyboard shortcuts and toolbars with keyboard shortcuts. I haven't decided which looks better yet. One possibility is to create a toolbar and style it as invisible, so I can be even more flexible.
Shortcuts are added to the menuitem/action title - "Cat\tc" means "c" is the keyboard shortcut. (Does "&Cat" work too? I haven't tested.)
It doesn't work if the cursor is on a field element, but do you need those?