Javafx 8 using KeyEvents with ImageView - event-handling

I am trying to make a game on Javafx 8 using NetBeans 8.1. The super class of all my game objects is ImageView and I would like to handle key events within a game object instead of in my Scene. The problem is that KeyEvents seem to only work on the Scene and when I add a KeyEvent Handler to my game object nothing happens when I press the keys. Is there anyway to add KeyEvents to any Node, such as an ImageView, and have it work? Below is an example of what I am aiming for (VisibleGameObject extends ImageView):
package hangmanElements;
import javafx.event.EventHandler;
import javafxgame2D.gameObjects.VisibleGameObject;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class HangmanPost extends VisibleGameObject
{
public HangmanPost()
{
super(new Image("/resources/hangmanPost.png"));
setOriginAtCenter();
setPosition(100, 250);
setOnKeyPressed(new EventHandler<KeyEvent>(){
#Override
public void handle(KeyEvent event)
{
if(event.getCode() == KeyCode.UP)
{
System.out.println("Up pressed MAN!");
}
}
});
}
#Override
public void update()
{
}
}

Key events are only generated by scene graph nodes when they have keyboard focus. By default, an ImageView does not gain keyboard focus. You need to call
setFocusTraversable(true);
on the ImageView.

Related

JavaFX WebView usage, transparent background, jdk8 vs jdk9

I have some code snipped out of a much bigger app, which renders some white text on a black background in a JavaFX WebView. The background colour of the page is set to transparent, using some code from Transparent background in the WebView in JavaFX
import java.lang.reflect.Field;
import org.w3c.dom.Document;
import com.sun.webkit.WebPage;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class TestWebView extends Application {
public void start(Stage stage) throws Exception {
StackPane stackpane = new StackPane();
Scene scene = new Scene(stackpane, stage.getWidth(), stage.getHeight(), Color.BLACK);
stage.setScene(scene);
scene.setFill(Color.BLACK);
stackpane.setStyle("-fx-background-color: BLACK");
WebView webview = new WebView();
stackpane.getChildren().add(webview);
WebEngine webengine = webview.getEngine();
webengine.documentProperty().addListener(new WebDocumentListener(webengine));
webengine.loadContent("<p style='color:white'>Hello World</p>");
stage.show();
}
public static void main(String[] args) {
launch(args);
}
protected class WebDocumentListener implements ChangeListener<Document> {
private final WebEngine wdl_webEngine;
public WebDocumentListener(WebEngine webEngine) {
wdl_webEngine = webEngine;
}
#Override
public void changed(ObservableValue<? extends Document> arg0, Document arg1, Document arg2) {
try {
Field f = wdl_webEngine.getClass().getDeclaredField("page");
f.setAccessible(true);
com.sun.webkit.WebPage page = (WebPage) f.get(wdl_webEngine);
page.setBackgroundColor((new java.awt.Color(0, 0, 0, 0)).getRGB());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Testing on MacOS 10.11.6, with Oracle's JDK:
With JDK 1.8.0_152, this code works nicely - I get white text on black. (And the transparency works too when I layer things underneath it in the stackpane)
With JDK 9 (9+181), com.sun.webkit.WebPage is no longer accessible, so I have to compile and run it with --add-exports javafx.web/com.sun.webkit=ALL-UNNAMED - but having done that, I get black text on a black screen. I can tell the text is there by selecting the text and dragging it, which makes the text appear white while being dragged.
Ideally, I'd like to keep a single codebase that works for both JDK 8 and 9. (Java's usually been good to me with backward compatibility). Or as a second best, how do I get the white text I'm expecting in JDK 9?
Can anyone point me in the right direction? Many thanks in advance.
I had the same issue, I solved it by going further in the reflective process :
Field f = webEngine.getClass().getDeclaredField("page");
f.setAccessible(true);
Object page = f.get(webEngine);
Method m = page.getClass().getMethod("setBackgroundColor", int.class);
m.setAccessible(true);
m.invoke(page, (new java.awt.Color(0, 0, 0, 0)).getRGB());

Custom Button in MessageDialog

I want to add my custom button with AjaxEventBehavior to MessageDialog , i can add simple custom button without AjaxEventBehavior by extending DialogButton class, but this will be useless button with out listener, anyone no how to do it?
here is my code:
import com.googlecode.wicket.jquery.ui.widget.dialog.MessageDialog;
import org.apache.wicket.ajax.AjaxRequestTarget;
import
org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.model.Model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class TipOfTheDayDialog extends MessageDialog {
private static final Logger log =
LoggerFactory.getLogger(TipOfTheDayDialog.class);
Model<String> model = null;
List<DialogButton> dialogButtons =null;
public TipOfTheDayDialog(String id, Model<String> model,List<DialogButton> dialogButtons) {
super(id, Model.of("Совет дня"), model, dialogButtons);
this.model = model;
}
#Override
public void onClose(IPartialPageRequestHandler handler, DialogButton button) {}
#Override
public void onClick(AjaxRequestTarget target, DialogButton button)
{
if(!button.getName().equals("next")){
super.close(target,button);
this.close(target, button);
}else {
model.setObject("another message");
target.add(this);
}
}
}
I have decided to override method onclick instead of adding button with its own listener, but now i have another problem, i can't change message of dialog without it's closing , when i change message by this line of the code: model.setObject("another message"); and then add it to the target by this code : target.add(this); dialog window closes, how to fix it?

EventFilter consume() does not prevent SpaceChars in TextField

I have a JavaFX GUI where I wish to intercept the pressing of the SpaceBar and use it to call a method. I wrote an EventFilter that seems to do the trick. It includes the command event.consume() which I believe is supposed to keep the KeyEvent from propagating to the various controls.
My issue is that when I added a TextField, and this field has the focus, the Spacebar presses are not being consumed as I thought they would. The " " are captured by the TextField. I would like to intercept and prevent the " " from being added to the TextField.
What am I leaving out in the code below in order to keep " " from reaching the TextField? The api, if I am reading it correctly, says that filters registered with a parent control can intercept an event before it reaches the children nodes. But even when putting the filter directly on the TextField, I am still having " " chars appear in the TextField.
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class SpaceIntercept extends Application implements EventHandler <KeyEvent>
{
public static void main(String[] args)
{
Application.launch(args);
}
#Override
public void start(Stage primaryStage)
{
TextField textField = new TextField("asdf");
Group root = new Group();
Scene scene = new Scene(root, 200, 100);
scene.addEventFilter(KeyEvent.ANY, event -> handle(event));
// root.addEventFilter(KeyEvent.ANY, event -> handle(event));
// textField.addEventFilter(KeyEvent.ANY, event -> handle(event));
root.getChildren().add(textField);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void handle(KeyEvent event)
{
if (event.getCode() == KeyCode.SPACE)
{
if (event.getEventType() == KeyEvent.KEY_PRESSED)
{
System.out.println("Code that responds to SpaceBar");
}
event.consume();
}
}
}
The text field is probably listening for KEY_TYPED events. As is well-documented, getCode() returns KeyCode.UNDEFINED for a KEY_TYPED event. Thus you do not catch this case.
You can check for the character variable as well as the code variable to handle all cases:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
public class SpaceIntercept extends Application implements EventHandler <KeyEvent>
{
public static void main(String[] args)
{
Application.launch(args);
}
#Override
public void start(Stage primaryStage)
{
TextField textField = new TextField("asdf");
Group root = new Group();
Scene scene = new Scene(root, 200, 100);
scene.addEventFilter(KeyEvent.ANY, event -> handle(event));
// root.addEventFilter(KeyEvent.ANY, event -> handle(event));
// textField.addEventFilter(KeyEvent.ANY, event -> handle(event));
root.getChildren().add(textField);
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void handle(KeyEvent event)
{
if (event.getCode() == KeyCode.SPACE || " ".equals(event.getCharacter()))
{
if (event.getEventType() == KeyEvent.KEY_PRESSED)
{
System.out.println("Code that responds to SpaceBar");
}
event.consume();
}
}
}
A simple solution i can think,which although doesn't blocks the space from being added to the TextField,but it replaces it after it has been added almost instantly is adding a changeListener to the TextProperty of the TextField:
textField.textProperty().addListener((observable,oldValue,newValue)->{
textField.setText(textField.getText().replace(" ", ""));
});
This may also be helpfull http://fxexperience.com/2012/02/restricting-input-on-a-textfield/

How to create a javafx 2.0 application MDI

How to implement something kinda internal frame in JavaFx 2.0 specifically?
My attempt is as so..
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
ConnectDb connection;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) throws Exception {
final Stage stage1 = new Stage();
StackPane pane = new StackPane();
Button btn = new Button("Click Me");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
connection = new ConnectDb();
try {
connection.start(stage1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Fire some thing..");
}
});
pane.getChildren().add(btn);
stage.setScene(new Scene(pane ,200, 300));
stage.show();
}
}
ConnectDb.java
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ConnectDb extends Application {
#Override
public void start(Stage stage) throws Exception {
StackPane pane = new StackPane();
Button btn = new Button("Click On Button which is me");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Something here..");
}
});
pane.getChildren().add(btn);
stage.setScene(new Scene(pane ,200, 300));
stage.show();
}
}
First of all, for your approach, you don't really need to (and therefore should not) extend ConnectDb from Application as you just use the start method to create new stages. You just need one Application class (in your case Main). You could just as well create the new stage/scene in your first event handler.
Secondly, there is no real MDI support in JavaFX 2.1. Right now, you can just have multiple Stages (which is the equivalent to having multiple windows/frames). But you cannot have something like an internal frame in a desktop pane.
I guess you could take the following actions:
Just use multiple Stages (windows) with the drawback that they will float quite uninspiredly on your desktop
Use Swing as a container (with JDesktopPane and JInternalFrame) and integrate JavaFX (here's a nice How-To)
Implement your own framework that emulates MDI behavior
Find a framework that provides MDI behavior
Wait for a future release of JavaFX that hopefully provides MDI support (as far as I know, there's a change request pending...)
Create parent AncorPane.
Add several children AnchorPanes to it. They will serve as internal frames. Add different content to these.
Set children AnchorPanes invisible.
Add buttons to hide, resize or close children AnchorPanes. When needed, call function to set all children AnchorPanes invisible, except for one.

Prevent an accordion in JavaFX from collapsing

Is there an easy way of preventing an accordion in JavaFX 2.1 from fully collapsing? I have an accordion with a few entries but if the user clicks the active accordion entry it collapses the accordion.
I could probably use a mouse click listener to check do the check and act accordingly but this feels like it should be even simpler than that to accomplish.
Add a listener to the currently expanded accordion pane and prevent it from being collapsed by the user by modifying it's collapsible property.
Here is a sample app:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class AccordionSample extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(Stage primaryStage) {
// create some titled panes to go in an accordion.
TitledPane adminPane = new TitledPane("Animals",
VBoxBuilder.create().style("-fx-padding: 10").spacing(10).children(
ButtonBuilder.create().text("Zebra").maxWidth(Double.MAX_VALUE).build(),
ButtonBuilder.create().text("Shrew").maxWidth(Double.MAX_VALUE).build()
).build()
);
TitledPane viewPane = new TitledPane("Vegetables",
VBoxBuilder.create().style("-fx-padding: 10").spacing(10).children(
ButtonBuilder.create().text("Eggplant").maxWidth(Double.MAX_VALUE).build(),
ButtonBuilder.create().text("Carrot").maxWidth(Double.MAX_VALUE).build()
).build()
);
// create an accordion, ensuring the currently expanded pane can not be clicked on to collapse.
Accordion accordion = new Accordion();
accordion.getPanes().addAll(adminPane, viewPane);
accordion.expandedPaneProperty().addListener(new ChangeListener<TitledPane>() {
#Override public void changed(ObservableValue<? extends TitledPane> property, final TitledPane oldPane, final TitledPane newPane) {
if (oldPane != null) oldPane.setCollapsible(true);
if (newPane != null) Platform.runLater(new Runnable() { #Override public void run() {
newPane.setCollapsible(false);
}});
}
});
for (TitledPane pane: accordion.getPanes()) pane.setAnimated(false);
accordion.setExpandedPane(accordion.getPanes().get(0));
// layout the scene.
StackPane layout = new StackPane();
layout.setStyle("-fx-padding: 10; -fx-background-color: cornsilk;");
layout.getChildren().add(accordion);
primaryStage.setScene(new Scene(layout));
primaryStage.show();
}
}
Here is another solution for making sure the accordion will never completely collapse. The difference from the great original answer by #jewelsea is little - I didn't like the fact that the default down facing arrow was disappearing from the open accordion TitledPane face, because its "collapsible" property is being set to false. I played with it a bit more to achieve a more "natural" feel for my interface.
/* Make sure the accordion can never be completely collapsed */
accordeon.expandedPaneProperty().addListener((ObservableValue<? extends TitledPane> observable, TitledPane oldPane, TitledPane newPane) -> {
Boolean expand = true; // This value will change to false if there's (at least) one pane that is in "expanded" state, so we don't have to expand anything manually
for(TitledPane pane: accordeon.getPanes()) {
if(pane.isExpanded()) {
expand = false;
}
}
/* Here we already know whether we need to expand the old pane again */
if((expand == true) && (oldPane != null)) {
Platform.runLater(() -> {
accordeon.setExpandedPane(oldPane);
});
}
});