Release mnemonic when application loses focus - javafx

Background
Due to a known bug, JavaFX applications that use a MenuBar will keep the mnemonic selected ("latched") when the user presses Alt+Tab to switch to another program. When the user returns to the JavaFX application, the framework retains the latch.
Problem
If the user then presses a letter that corresponds to the mnemonic, then the letter is consumed and that menu is opened.
This behaviour is not what users have come to expect from an application: it interrupts workflow. Rather, Alt+Tab should not put the application in a state whereby the menu can open. This is conflating Alt by itself to trigger the menu with Alt+Tab, a conceptually different operation.
Other questions seek to disable the mnemonic, but we want to clear the latch so that when the user returns to the application, pressing a letter will not trigger opening the menu.
Question
How do you instruct a JavaFX application to clear the latched mnemonics when Alt+Tab is pressed (i.e., the application focus is lost)?

There are a couple of parts to this solution: releasing the mnemonics and consuming the Alt key press. Be sure to implement both.
Release mnemonics
One way to work around the bug is to add a focus listener to the application's Stage that fires a key released event for all known mnemonics. Given a Stage instance, we can iterate over all the main menu mnemonics as follows:
stage.focusedProperty().addListener( ( c, lost, show ) -> {
if( lost ) {
for( final var mnemonics : stage.getScene().getMnemonics().values() ) {
for( final var mnemonic : mnemonics ) {
mnemonic.getNode().fireEvent( keyUp( ALT, false ) );
}
}
}
else if( show ) {
// Make sure the menu does not capture focus.
stage.getScene().focusOwnerProperty().get().requestFocus();
}
} );
We'll need a few helper methods to create the key release event:
public static Event keyDown( final KeyCode code, final boolean shift ) {
return keyEvent( KEY_PRESSED, code, shift );
}
public static Event keyUp( final KeyCode code, final boolean shift ) {
return keyEvent( KEY_RELEASED, code, shift );
}
private static Event keyEvent(
final EventType<KeyEvent> type, final KeyCode code, final boolean shift ) {
return new KeyEvent(
type, "", "", code, shift, false, false, false
);
}
With those methods in place, cycling windows by pressing Alt+Tab no longer opens the menu upon returning to the JavaFX application followed by pressing a mnemonic key (such as "f" for the "File" menu).
Consume event
Additionally, make the scene consume the event:
scene.addEventHandler( KEY_PRESSED, event -> {
final var code = event.getCode();
if( event.isAltDown() && (code == ALT_GRAPH || code == ALT) ) {
event.consume();
}
} );

Related

Intercepting Tab key press to manage focus switching manually

I want to intercept Tab key press in my main window to prevent Qt from switching focus. Here's what I've tried so far:
bool CMainWindow::event(QEvent * e)
{
if (e && e->type() == QEvent::KeyPress)
{
QKeyEvent * keyEvent = dynamic_cast<QKeyEvent*>(e);
if (keyEvent && keyEvent->key() == Qt::Key_Tab)
return true;
}
return QMainWindow::event(e);
}
This doesn't work, event isn't called when I press Tab. How to achieve what I want?
The most elegant way I found to avoid focus change is to reimplement in your class derived from QWidget the method bool focusNextPrevChild(bool next) and simply return FALSE. In case you want to allow it, return TRUE.
Like other keys you get now also the key Qt::Key_Tab in keyPressEvent(QKeyEvent* event)
Reimplementing virtual bool QApplication::notify(QObject * receiver, QEvent * e) and pasting the code from my question there works.
You can achieve by using setFocusPolicy( Qt::NoFocus) property of QWidget. You can set Focus policy on widget which doesn't require tab focus. I think the reason why event handler is not calling, because Tab is managed by Qt framework internally. Please see QWidget::setTabOrder API, which is static.
You'll need to install an event filter on your main window in order to receive the events. You can use installEventFilter method for this.
Another option is to override the keyPressEvent method to handle the key presses.

How to implement the same story text to different page objects in Jbehave

I am just starting out with Jbehave Web with WebDriver and wondered whether it was possible to have the same textual step apply to different step methods.
Say for example you have the following two scenarios
Scenario 1
Given I am on the properties to buy page
When I click Search
Then I should see the results page containing all properties to buy
Scenario 2
Given I am on the properties to rent page
When I click Search
Then I should see the results page containing all properties to rent
If I implemented this using the page object pattern I would have a page object called for example buyProperties and likewise for rental properties a page object called something along the lines of rentProperties (as well as result page objects).
In both scenarios a search button/link is clicked so the step text is the same. However, in reality they are on different pages (and page objects).
How could I implement Jbehave so that for the rental scenario it knows to call the step method implementing clicking the search button on the rentProperties page and for the buy scenario it knows to call the step method implementing clicking the search button on the buyProperties page?
Your steps class will have two methods - one annotated with #Given("...rent") and one annotated #Given("...buy"). Each method does it's own thing. If "rent" and "buy" is a variable passed in then do different things based on the value of that variable. I'm not sure I get the question...sorry.
Try
#Given ("I am on the properties to $action page")
public void given_i_am_on_the_properties_action_page(#Named("action") String action) {
if (action.equalsIgnoreCase("Buy") {
do something;
}
if (action.equalsIgnoreCase("Rent") {
do something;
}
}
The 'do something' could be setting up the page object for the next steps.
Similarly you can use the same method and a variable for your #Then step.
I have used something similar to select menu items and to wait for the page to load before going on to the next step
#When ("I select menu item $menuItem")
public static void when_i_select_menu_item(#Named("menuItem") String menuItem) {
String item = "";
String waitFor = "";
if (menuItem.equalsIgnoreCase("admin")) {
item = "Admin";
waitFor = "admin_page";
}
if (menuItem.equalsIgnoreCase("home")) {
item = "Home";
waitFor = "home_page";
}
if (menuItem.equalsIgnoreCase("search")) {
item = "Search";
waitFor = "search_page";
}
driver.findElement(By.id(item)).click();
(new WebDriverWait(driver, timeout)).until(new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver d) {
return element.findElement(By.id(waitFor)).isDisplayed() || d.findElement(By.id(waitFor)).isEnabled();
}
});
}

silently transfer keyPressEvent to one child , and make it focus?

When user types in a QWidget based window, I wanted a QLineEdit to process
all input keys,
so I tried the following two solution in keyPressEvent() of that QWidget:
A.
void Window::keyPressEvent (QKeyEvent *e)
{
switch (e->key())
{
// handle other short cuts
default:
QApplication::sendEvent (lineEdit , e);
break;
}
}
Well, this sometimes crashes the whole interface, especially when I resize window.
B.
void Window::keyPressEvent (QKeyEvent *e)
{
switch (e->key())
{
// handle other short cuts
default:
if ( ! lineEdit.hasFocus () )
{
lineEdit.setFocus ();
lineEdit.setText (e->key());
// i wanted to push the first key input to that QLineEdit , but how ?
// or i'll miss it
}
break;
}
}
Also I'm thinking about giving lineEdit focus all the time, but I can't do that as other events needed to be handled by the main UI.
Update
It won't crash when I filter key inputs, but why ?
default:
if ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ||
(e->key() >= Qt::Key_A && e->key() <= Qt::Key_Z )
)
QApplication::sendEvent(filter , e);
break;
}
I believe you are running into a crash because you are using sendEvent to send an event object that you don't have control over.
I don't think the Qt event system expects you to grab its events and throw them in other directions, and it's likely that the event object is getting destroyed before the line edit expects. In the case where you're filtering out input keys, it's probably not crashing because the line edit doesn't care about those kinds of key strokes and isn't using the event object as much as it would otherwise.
If you really want to use the sendEvent() functionality, then I would suggest you create your own QKeyEvent on the stack and pass it to the sendEvent() function (as demonstrated here), or you can just do something like this:
lineEdit.setText( lineEdit.text() + event->text() );
When a widget does not handle an event, it forwards it to its parent. So using sendEvent() to forward to a child is dangerous, as it can make a recursion.
The easiest way of doing it would be to use QKeyEvent::text instead of QKeyEvent::key and you should be OK. You might also try to create a copy of QKeyEvent and pass it to your QLineEdit. Thos are rather hacks than solutions though. If you need shortcuts in main window while QLineEdit has focus (assuming it is in this window) you can use QShortcut with Qt::WidgetWithChildrenShortcut context - this way you can keep your LineEdit active at all times.

JTextField keyevent handling issue

Doesn't .getKeyCode( ) return the key's int value? Because I have set my JTextField to listen to a keylistener, and in the keytyped method, I check what key has been pressed. Here's a snippet of my code:
JTextField jtf = new JTextField( );
jtf.addKeyListener( this );
.
.
.
public void keyTyped( KeyEvent e )
{
if( e.getKeyCode( ) == KeyEvent.VK_ENTER ) System.out.println( "pressed enter" );
}
but everytime I type enter in the JTextField, nothing happens, ie nothing prints.
maybe you should check first if you event handler is actually get called when you pressed anything... or if your event handler is able to receive KeyEvents objects, i believe the problem lies there... for it to work, the component must have focus... Java Tutorial
I think you are comparing the wrong values. e.getKeyCode() returns the key, then i guess KeyEvent.VK_ENTER is something really different.
I just figured out a better way. you should put all your key methods inside an inner class just under your main class, Put your GUI construction code in a constructor method again out side of your main method. now. have you're inner method implement the KeyListener interface, and instantiate the three abstract methods associated with said interface, you're only using the keyTyped(KeyEvent e) method. inside that method, instantiate an int variable called keyHit and assign it to e.getKeyChar(); then put an if statement that states if(keyHit == '\n') the \n will corresopnd to the ENTER key,
I tried to post a template of the code Iused to get it to work, but It was a little long to go through and indent so if you're really need it i'll send it to you via some e-mail address or something
VK_ENTER corresponds to the int value of the enter key, seeing as how it is a constant. You should be using input and action maps from the swing class to listen for the key values when the JTextField is in focus as opposed to applying a key listener to the text field and listening for one key. Because an event is created (although may or may not be handled) every time a key is pressed, you should prevent the method from running when keys that aren't the enter key are pressed. Therefore preventing runtime errors and unnecessary method calls.
You have to implement KeyListener Inteface and override the keyTyped method in your class definition to use
jtf.addKeyListener(this).
Replace the above line with
jtf.addKeyListener(new KeyAdapter(){
#Override
public void keyTyped(KeyEvent e) {
if( e.getKeyCode( ) == KeyEvent.VK_ENTER )
System.out.println( "pressed enter" );
}
});

FLEX: dialog not display immediately

In an AIR application I have the following code:
theDialog = PopUpManager.createPopUp( this, TheDialogClass, true ) as TheDialogClass;
theDialog.addEventListener(FlexEvent.CREATION_COMPLETE, cpuIntensiveCalc);
At the end of cpuIntensiveCalc the dialog is removed. The dialog informs the user that "something is going on, please stand by."
The problem is that cpuIntensiveCalc starts before the dialog draws. So the user experience is that the application freezes for 10 seconds with no indicator, then the modal dialog flashes quickly (less than a second) on screen.
The Adobe docs say this about creation_complete
Dispatched when the component has finished its construction,
property processing, measuring, layout, and drawing.
So this feels like the correct event.
In the name of completeness, I also tried
theDialog = PopUpManager.createPopUp( this, TheDialogClass, true ) as TheDialogClass;
cpuIntensiveCalc();
But had the same results.
TIA
The reason for this is that the Flash Player is single threaded, and so you are blocking the UI from reacting to the Dialog Popup until the maths chunk is finished.
Hacky fix time...
You have two options.
(This one should work, but is untested) Wrap the cpuIntensiveCalc() call in a callLater, so that the UI can finish rendering before you block the rendering.
Or
Use "Green Threads" to break up your processing so that you don't completely block the UI processing. Take a look.
(I just had the same issue => even if this thread is old, I just wanted to contribute my solution)
(disclaimer: this is a bit ugly, but they say that's ok in the UI layer... ;-) )
Flex is single threaded (at least from our developer's perspective, I think behind the scene threads are used by the VM)
=> you typically execute your code in the UI thread, after the user did some action on a widget. Any call to update a UI component (like setProgress or setLabel) will only be rendered on screen at the end of the render cycle (see again UiComponent life cycle).
=> In therory calling "cpuIntensiveCalc" in a callLater will let the framework display your popup before executing the method.
In practice though, I noticed you typically have to have for a couple of UI cylces before the popup be displayed, like this:
new MuchLaterRunner(popup, 7, cpuIntensiveCalc).runLater();
MuchLaterRunner being defined like this:
public class MuchLaterRunner
{
var uiComp:UIComponent;
var currentCounter = 0;
var cyclesToWaitBeforeExecution=0;
var command:Function;
public function MuchLaterRunner(uiComp:UIComponent, cyclesToWaitBeforeExecution:uint, command:Function)
{
this.uiComp = uiComp;
this.command = command;
this.cyclesToWaitBeforeExecution =cyclesToWaitBeforeExecution;
}
public function runLater() {
currentCounter ++;
if (currentCounter >= cyclesToWaitBeforeExecution) {
uiComp.callLater(command);
} else {
// wait one more cycle...
uiComp.callLater(runLater);
}
}
}
The issue is the same when calling setProgress afterward: we must divide cpuIntensiveCalc into small callable methods that can be ran at each UI cycle, otherwise the progressbar won't, err, progress.
Use enterFrame event on popup. Don't forget to remove the listener in the enterFrame event handler - otherwise the cpu intensive method will be called in each frame, crashing your app. If this doesn't work at first, use a private number as a counter and keep incrementing it in the enter frame handler - call cpu heavy method only when the counter reaches the appropriate value. Find the 'appropriate' value by trail and error.
theDialog = PopUpManager.createPopUp(this, TheDialogClass, true) as TheDialogClass;
theDialog.addEventListener(Event.ENTER_FRAME, onEnterFrame);
private function onEnterFrame(e:Event):void
{
//can use theDialog instead of e.currentTarget here.
(e.currentTarget).removeEventListener(Event.ENTER_FRAME, onEnterFrame);
cpuIntensiveCalc();
}
//in case the above method doesn't work, do it the following way:
theDialog.addEventListener(Event.ENTER_FRAME, onEnterFrame);
private var _frameCounter:Number = 0;
private function onEnterFrame(e:Event):void
{
_frameCounter++;
var desiredCount:Number = 1;//start with one - increment until it works.
if(_frameCounter < desiredCount)
return;
//can use theDialog instead of e.currentTarget here.
(e.currentTarget).removeEventListener(Event.ENTER_FRAME, onEnterFrame);
cpuIntensiveCalc();
}

Resources