JavaFX wait for enter - javafx

Essentially, what I'm trying to do is something like to a text-based RPG using JavaFX. Right now, to display some text, I've got this:
final IntegerProperty i = new SimpleIntegerProperty(0);
Timeline timeline = new Timeline();
KeyFrame keyFrame = new KeyFrame(
Duration.millis(70),
event -> {
if (i.get() > info.getText().length()) {
timeline.stop();
} else {
text.setText(info.getText().substring(0, i.get()));
i.set(i.get() + 1);
}
});
timeline.getKeyFrames().add(keyFrame);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
timeline.setOnFinished(a -> {
hb_start.getChildren().clear();
hb_start.getChildren().addAll(start_left,start_right);
hb_start.setAlignment(Pos.CENTER);
});
Because the length of the animation depends on the size of the text, the cyclecount is set to indefinite. Unless there's some other way I'm missing to make the animation play once and then stop, I'd like it so that when you press enter (or some other key that I decide on later) for it to call timeline.stop(); but I can't figure out how to add any sort of listener. Trying to implement keyListenerseems to come with all sorts of stuff that I don't need, and it also doesn't work with a TextField, and instead wants a JTextField, which might be fine, except that I don't have a clue how to do anything with Swing.
Currently, the text is being displayed in aTextFlow from the text of Text. I'm assuming the listener would be added to the TextFlow, or even the scene itself, honestly, I'm at a loss for what to do. It sounds simple, but I can't seem to figure it out.

KeyListener is a AWT class, not a JavaFX class. Unless you're embedding a Swing component in your JavaFX application or a JavaFX node in a Swing application, you should use JavaFX's equivalent EventHandler<KeyEvent> instead. Furthermore there is no need to include a TextField (or Swing's JTextField) in your application just for the sake of receiving key events. You could add the listener directly to the Scene:
final KeyCode stopKey = KeyCode.ENTER;
EventHandler<KeyEvent> handler = event -> {
if (event.getCode() == stopKey) {
timeline.stop();
}
};
scene.setOnKeyPressed(handler);
Note that events can be consumed by nodes before they reach the scene, e.g. by a TextField that has the focus. In this case you could make sure you get the event by registering a the listener as a event filter instead:
// scene.setOnKeyPressed(handler);
scene.addEventFilter(KeyEvent.KEY_PRESSED, handler);

Related

JavaFX width/height properties fire only once

I'm new to JavaFX and am trying to monitor the resize events on a window so that I can trigger a recalculation of the layout.
If I create a stage and set the scene like in the example below I only get each resize event to be fired once. No matter how many times I resize the window.
Stage stage = new Stage();
stage.setScene(someScene);
stage.setTitle("Some Title");
stage.initModality(Modality.APPLICATION_MODAL);
stage.show();
stage.widthProperty().addListener(observable -> {
System.out.println("Width changed");
});
stage.heightProperty().addListener(observable -> {
System.out.println("Height changed");
});
First note:
I'm trying to monitor the resize events on a window so that I can
trigger a recalculation of the layout.
This is almost certainly the wrong approach. Layout recalculations will be triggered automatically when the stage and scene change size. If you use standard layout panes, there is no need to register listeners. If you really need a custom layout (which is highly unlikely), you should subclass Pane and override layoutChildren() (and other appropriate methods) to hook into the same layout system.
However, in the interests of explaining what you're observing:
You're registering an InvalidationListener with each property, which gets notified when the property goes from a valid state to an invalid state.
The invalid state only becomes valid again if you actually request the value of the property (e.g. stage.getWidth()). Since you never do that, the property never becomes valid, and hence never goes from valid to invalid again.
Instead, register a ChangeListener with each property:
stage.widthProperty().addListener((observable, oldWidth, newWidth) -> {
System.out.println("Width changed");
});
stage.heightProperty().addListener((observable, oldHeight, newHeight) -> {
System.out.println("Height changed");
});
Alternatively, you can force validation by requesting the value (though I think the change listener above actually gets to the point of what you are trying to do):
stage.widthProperty().addListener(observable -> {
System.out.println("Width changed: "+stage.getWidth());
});
etc.

Interaction with parent control triggers RippleDrawable in Xamarin.Forns custom renderer

I have implemented a custom clickable label class in Xamarin.Forms along with a custom renderer, that adds a RippleDrawable as the controls Foreground. I am creating the RippleDrawable with the following code:
public static Drawable CreateRippleDrawable(Context context)
{
var typedValue = new TypedValue();
context.Theme.ResolveAttribute(Resource.Attribute.SelectableItemBackground, typedValue, true);
var rippleDrawable = context.Resources.GetDrawable(typedValue.ResourceId, context.Theme);
return rippleDrawable;
}
In my custom renderer I assign the drawable
this.Control.Foreground = DrawableHelper.CreateRippleDrawable(this.Context);
and update the ripple when the user touches the control
private void LinkLabelRenderer_Touch(object sender, TouchEventArgs e)
{
if (e.Event.Action == MotionEventActions.Down)
{
this.Pressed = true;
}
if (e.Event.Action == MotionEventActions.Cancel)
{
this.Pressed = false;
}
if (e.Event.Action == MotionEventActions.Up)
{
this.Ripple.SetHotspot(e.Event.GetX(), e.Event.GetY());
this.Pressed = false;
// raise the event of the Xamarin.Forms control
}
}
Now, whenever I click the control, the ripple will be shown, which is the expected behavior, but if I touch (tap or long-press) the parents of the control (e.g. the StackLayout, Grid or whatever layout contains the label, including their parent Layout, Page or View) the ripple animation will be triggered. Anyway, the event handler LinkLabelRenderer_Touch in not called in this case, only when the actual control is touched.
I can work around this behavior by adding an empty GestureRecognizer to the respective parent(s), but I really dislike this solution, because this is but a hack. And to make things worse it is a hack I'll always have to remember whenever I use the control.
How can I prevent the RippleDrawable being shown when the parent is touched?
Turned out I got things fundamentally wrong. Subscribing the Touch event is not the way to go. I had to make the control clickable and subscribe the Click event
this.Control.Clickable = true;
this.Click += LinkLabelRenderer_OnClick;
There is no need to handle all that RippleTouch stuff the way I did (via the Touch event) but could let android handle things for me.

How to migrate a clickable Frame with ripple touch to Xamarin.Forms 2.5?

I have implemented a clickable Frame in Xamarin.Forms 2.3.4 with a custom FrameRenderer that set Clickable (and LongPressable FWIW) to true, subscribed the respective events and set the FrameRenderers foreground
TypedValue typedValue = new TypedValue();
this.Context.Theme.ResolveAttribute(Android.Resource.Attribute.SelectableItemBackground, typedValue, true);
this.Foreground = this.Resources.GetDrawable(typedValue.ResourceId, this.Context.Theme);
to achieve Material motion (ripple touch).
After updating to XF 2.5 (most likely as of 2.3.5, since fast renderers have been introduced with that release) my touch events have ceased to work. My custom renderer is assigned correctly, so are the Clickable and LongPressable properties, but nothing happens. Partially I have been able to work around the issue - at least for the moment - by subscribing to FrameRenderer.Touch and call OnClick from that event handler. This renders the app usable, but unfortunately lacks the visual feedback in form of the ripple touch effect.
Is there any way I can restore the original behavior? Is there any way to implement a clickable frame with ripple touch in XF 2.5?
With the help of this answer I have figured out a solution. Here's a draft of it:
First of all I store a local reference to my RippleDrawable (see documentation)
private void SetMaterialMotion()
{
TypedValue typedValue = new TypedValue();
this.Context.Theme.ResolveAttribute(Android.Resource.Attribute.SelectableItemBackground, typedValue, true);
this.Foreground = this.Resources.GetDrawable(typedValue.ResourceId, this.Context.Theme);
this._layerDrawable = this.Foreground as RippleDrawable;
}
then I have to subscribe to the Touch event
private void SubscribeClickEvent()
{
if (this.ClickableFrame.IsClickable)
{
this.Touch += ClickableFrameRenderer_Touch;
}
}
In my ClickableFrameRenderer_Touch I set the Pressed property and the hotspot of the RippleDrawable
private void ClickableFrameRenderer_Touch(object sender, TouchEventArgs e)
{
if(e.Event.Action == Android.Views.MotionEventActions.Down)
{
this.Pressed = true;
}
if(e.Event.Action == Android.Views.MotionEventActions.Up)
{
this._layerDrawable.SetHotspot(e.Event.GetX(), e.Event.GetY());
this.Pressed = false;
}
}
This will show the ripple touch motion (more or less) as expected. Of course this does not handle long presses, yet, but I'm on a way.
Anyway, this is not a solution I like very much. I still think that there has to be a supported way on making FastRenderers.FrameRenderer clickable. If anyone knows it: Please share your knowledge.

JavaFX textarea listeners

Is it possible to turn off arrow key listeners in TextArea when it has focus. I am reffering mostly to moving scrollpane with them when there is more text than space in area. I'm trying to avoid this.
Thanks for suggestion.
You could use an event filter. Filters receive events during the event capturing phase (1st, down the scene graph) of event processing, whereas handlers are triggered during the event bubbling phase (2nd, up the scene graph). Have a look at this snippet:
TextArea textArea = new TextArea();
textArea.addEventFilter(KeyEvent.ANY, event -> {
KeyCode code = event.getCode();
boolean isArrowKey = code == KeyCode.UP || code == KeyCode.RIGHT
|| code == KeyCode.DOWN || code == KeyCode.LEFT;
if (isArrowKey) {
event.consume();
}
});
The consume() method stops further propagation of the event. As a result, no (key event) listener will be triggered. But this also disables the ability to navigate through the arrow keys—is that really what you want?
EDIT: in regards to your comment, this answer states:
The behavior for controlling the scroll bars is internally coded in the TextAreaSkin (which in Java 8 is not part of the public JavaFX API). You could copy or subclass the TextAreaSkin to customize its behavior and then attach your customized skin to your node. This is really the "proper" way to customize internal control behavior in the way in which you wish.

JavaFX TreeView: remove expand / collapse button (disclosure node) & functionality

I want to have a TreeView that has all of its children permanently expanded, and I don't want the user to be able to expand or collapse any of the children.
To do this I've found that I need to do the following:
Remove icon with CSS (Done)
Change expand and collapse image TreeView JavaFX 2.2
[edit] Above link should be used to change image; to remove completely, use this solution: https://stackoverflow.com/a/27831191/4430591
Remove double click functionality (Done)
Disable TreeItem's default expand/collapse on double click JavaFX 2.2
[edit] Remove ability to collapse / expand using keyboard arrrow keys (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
[edit] Remove ability to right click for a ContextMenu (Done)
Given in José Pereda's solution below ( https://stackoverflow.com/a/27831085/4430591 )
Remove icon's clickablity (How do I do this?)
[edit] solution: https://stackoverflow.com/a/27831191/4430591
Even though the icon is no longer visible, it's still clickable. I don't see any way of filtering this; I only see ways to be able to respond to it after the fact.
Also, if I'm missing anything else that I need to do to ensure this functionality, please let me know.
I feel quite silly. I think this was mostly just a matter of not knowing what that darn arrow was called. Apparently it's a disclosureNode? Maybe that's common knowledge.
In the custom defined TreeCell, all I did was add this line in the updateItem method:
setDisclosureNode(null);
The solution to avoid modifying the skin or the default behavior is more simple if we trap the clicks before they are dispatched, and consume the right ones.
For that we can use an EventDispatcher, to filter both the mouse pressed and the right click over the arrows, which are StackPane nodes:
class CellEventDispatcher implements EventDispatcher {
private final EventDispatcher original;
public CellEventDispatcher(EventDispatcher original) {
this.original = original;
}
#Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
if (event.getEventType().equals(MouseEvent.MOUSE_PRESSED) ||
event.getEventType().equals(ContextMenuEvent.ANY)){
event.consume();
}
if(event instanceof KeyEvent && event.getEventType().equals(KeyEvent.KEY_PRESSED)){
if((((KeyEvent)event).getCode().equals(KeyCode.LEFT) ||
((KeyEvent)event).getCode().equals(KeyCode.RIGHT))){
event.consume();
}
}
return original.dispatchEvent(event, tail);
}
}
Now we apply our custom dispatcher to the tree view:
#Override
public void start(Stage primaryStage) {
TreeView<String> tree = new TreeView<>();
...
EventDispatcher treeOriginal = tree.getEventDispatcher();
tree.setEventDispatcher(new CellEventDispatcher(treeOriginal));
Scene scene = new Scene(tree);
primaryStage.setScene(scene);
primaryStage.show();
}
This will consume any click (left or right) over the arrows on the tree.
EDIT
Added to the event dispatcher class the case where the user uses the keyboard to traverse the tree view, consuming the collapse/expand events with arrow LEFT or RIGHT.

Resources