I am showing a Keyboard if the user clicks into a my extended TextField with the code shown below. When scrolling with a mouse you don't loose the focus to the TextField, but when scrolling by touch the focus is lost - and keyboard dispears of course. Is there a way to get the same behavior on touchScroll as on mouseScroll? I don't want the keyboard to disapear if the user is scrolling with touch!
focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(final ObservableValue<? extends Boolean> observable, final Boolean oldValue,
final Boolean newValue) {
KeyboardUtils.INSTANCE.setVisible(newValue);
}
});
This is pretty basic sir, when you are scrolling with a Touchscroll you definitely Touch a scrollable Pane area, and that Pane requestFocus()by the touch, so your TextField will loose its focus.
so to solve it you send focus back to your TextField if you detect a touch either by using the Scrolling listener of that Pane or Node or go for setOnTouchStationary() or setOnTouchReleased(), to help tweak the visibility of your keyboard instead of lying on focus of your TextField.
EDIT
Try this
Node lastFocusedNode =null; // lastly known node to have focus
//now every node or child in your ScrollPane or Scrollable parent
//that you care about will have a focusable listener-including
// your textfield
textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
if(!newValue){//if they loose focus
lastFocusedNode = textField;
//if they loose focus attach them to lastFocusedNode
}
}
});
//the above saves you iterations
then when your ScrollPane/scrollable Node Receives focus you set them to the lastFocusedNode since its just going to allow scrolling.
sp.focusedProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
if (lastFocusedNode != null) {
lastFocusedNode.requestFocus();
}
}
});
the above assumes your ScrollPane will just not do anything consuming aside from scrolling..
if you ScrollPane/scrollable parent is not going with that assumption then
you go with this approach-detect when the user scrolls after touching your content area of your Scrollable Node-this works only if user attempts to scroll after touching.
//approach loaded
final InvalidationListener lis = new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
//here it is changing
if(sp.isFocused())
lastFocusedNode.requestFocus();//take the focus away
}
};
using the above invalidation listener you set it on the hvalueProperty() and vvalueProperty() or your ScrollPane - which Scrollable parent are you using?
sp.hvalueProperty().addListener(lis);
sp.vvalueProperty().addListener(lis);
then you are done. any of the above solution will cause No problemo
EDIT 2
from what i know TouchEvent is for Touch enabled computers, so maybe go with MouseEvent and you can detect Pane.setOnMousePressed(); etc etcc
Hope it helps
Related
I have a TextField, and I would like to do something if the user clicks anywhere that is not the TextField itself.
Apparently, the onMouseClicked event won't trigger if you don't click the node itself, so that wouldn't work.
Listening the focusedProperty may have been a good idea, but the problem is that almost the entirety of my application is not focus traversable, so in many cases clicking outside the textfield won't unfocus it, so the listener won't be notified.
The only thing left in my mind is to put an event filter on the scene itself, intercept mouse clicks, get the click coordinates and determine if they fall within the bounds of the textfield. I find this a little bit overkill and I may be missing something more obvious.
Is there another way to determine if the user clicked anywhere outside my TextField node?
The only thing left in my mind is to put an event filter on the scene itself, intercept mouse clicks, get the click coordinates and determine if they fall within the bounds of the textfield. I find this a little bit overkill and I may be missing something more obvious.
In fact I consider this your best option, but with a variation:
Use the PickResult provided by the MouseEvent to get the target Node and check, if it's in the hierarchy of the Node.
(This can be necessary, if the Node has children; e.g. a TextField also contains a Text element.)
In fact this seems to be the only option that cannot be broken by consuming the event somewhere in the scene graph.
Example
This moves the focus to the root pane in case the user clicks somewhere except the TextField.
public static boolean inHierarchy(Node node, Node potentialHierarchyElement) {
if (potentialHierarchyElement == null) {
return true;
}
while (node != null) {
if (node == potentialHierarchyElement) {
return true;
}
node = node.getParent();
}
return false;
}
#Override
public void start(Stage primaryStage) {
TextField textField = new TextField();
textField.setMinSize(400, Region.USE_PREF_SIZE);
textField.setMaxWidth(400);
textField.setEditable(false);
textField.textProperty().bind(Bindings.when(textField.focusedProperty()).then("Got the Focus!").otherwise("Please give me the focus!"));
StackPane root = new StackPane();
root.getChildren().add(textField);
Scene scene = new Scene(root, 500, 200);
scene.addEventFilter(MouseEvent.MOUSE_CLICKED, evt -> {
if (!inHierarchy(evt.getPickResult().getIntersectedNode(), textField)) {
root.requestFocus();
}
});
primaryStage.setScene(scene);
primaryStage.show();
}
Assume you top level container of your app is Pane. You can apply mouse click event to that pane and inside it handle the mouse click event for the textfield. So this way, if the user clicks on the textfield or clicks somewhere else both events will be notified. Here's is the sample code.
Pane.setOnMouseClicked((MouseEvent evt) -> {
System.out.println("Click outside textfield");
textField.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println("textfield clicked");
}
});
});
If you have any other controls in that Pane. This mouse click wont interfere with them, it will be only called when you click directly on the pane.
I have a piece of code much like this:
package blah;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TextInputControl;
import javafx.stage.Stage;
public class SimpleExample {
TextInputControl textFieldForWork;
LocalTextChangeListener localTextChangeListener;
public SimpleExample(TextInputControl textFieldForWork, Stage s) {
this.textFieldForWork = textFieldForWork;
localTextChangeListener = new LocalTextChangeListener();
System.out.println("Creating new focus listener for TextField component");
LocalFocusListener localFocusListener = new LocalFocusListener();
s.focusedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (observable.getValue().toString().equals("false")) {
System.out.println("Removing TextField focus listener");
textFieldForWork.focusedProperty().removeListener(localFocusListener);
} else {
System.out.println("Adding TextField focus listener");
textFieldForWork.focusedProperty().addListener(localFocusListener);
}
}
});
}
private class LocalFocusListener implements ChangeListener {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
if (observable.getValue().toString().equals("true")) {
System.out.println("Adding text change listener");
textFieldForWork.textProperty().addListener(localTextChangeListener);
} else {
System.out.println("Removing text change listener");
textFieldForWork.textProperty().removeListener(localTextChangeListener);
}
}
}
private class LocalTextChangeListener implements ChangeListener {
#Override
public void changed(ObservableValue observable, Object oldValue, Object newValue) {
System.out.println("Textfield changed - do processing");
}
}}
The purpose of this code is to fire a listener each time the user types something in the textfield. The other necessary function of this code, is that upon dialog defocus, the textfield listeners should be removed.
Any help appreciated!
I'm not sure I really understand why you need to observe the focused property of the stage. Can't you just register the listener with the text field once and leave it there? It won't be invoked unless the text changes.
If you really need the functionality you describe, you can do it. Here's a description of what's going on:
The focusedProperty of the text field tracks whether or not the Node with focus within the current scene graph is the text field. It is "local to the scene graph", which means it is independent of whether or not the window is the active or focused window.
The focusedProperty of the window tracks whether or not the window has focus. This changes if you move the application to the background, etc.
Obviously, at the point where you create the text field, is hasn't been added to a scene or window, so just doing
textFieldForWork.getScene().getWindow().focusedProperty().addListener(...)
won't work, because getScene() will return null at this point. Even if the scene is non-null, it might not yet belong to a window, so you may have getScene() non-null but getScene().getWindow() null at some point.
So what you actually want to do is observe the sequence of properties. Start with textFieldForWork.sceneProperty() and observe it; if it changes and is non-null, then observe textFieldForInput.getScene().windowProperty(); when that changes and is non-null, observe textFieldForInput.getScene().getWindow().focusedProperty().
You could handle this yourself, creating listeners for each step in the chain and adding and removing them as necessary, but the EasyBind framework has API that manages exactly this use case. Using EasyBind you can do
MonadicObservableValue<Boolean> stageFocused =
EasyBind.monadic(textFieldForWork.sceneProperty())
.flatMap(Scene::windowProperty)
.flatMap(Window::focusedProperty)
.orElse(false);
stageFocused.addListener((obs, wasFocused, isNowFocused) -> {
if (isNowFocused) {
// stage now has focus...
} else {
// stage has lost focus...
}
});
If you want to check the condition that the text field has focus and the window containing it has focus, you can do
BooleanBinding stageAndTextFieldFocused = Bindings.createBooleanBinding(() ->
stageFocused.get() && tf.isFocused(),
stageFocused, tf.focusedProperty());
with stageFocused as above. Then just do
stageAndTextFieldFocused.addListener((obs, wasFocused, isNowFocused) ->
{ /* etc ... */ });
I have a transparent image on a Button (no text), which is placed on a Composite. Since the Composite is white (created with FormToolkit#createComposite(parent, SWT.NONE)), I'd like the Button background to be the same color. How do I do it?
The Label does the trick, but doesn't have the shadows like Button does when I'm clicking on it..
The background color of a Button is determined by the OS. In fact, the documentation for Control.setBackground() states that:
Note: This operation is a hint and may be overridden by the platform. For example, on Windows the background of a Button cannot be changed.
That said, one possible way to circumvent this is to override the paint event as shown here: Changing org.eclipse.swt.widgets background color in Windows. When I tried this out the results were a bit wonky.
The safest and most consistent approach would be to use a label like in your second image, but have different images to display on various mouse events to emulate how a button behaves.
Those images can emulate the shadow just by adding whatever shape of shadow you want to the image itself. That shadow can also change for each image to give the impression that the button is being pressed or not.
For example, I'm thinking something along the lines of:
public class MyButton {
private final Label buttonLabel;
public MyButton(final Composite parent, final Theme theme) {
buttonLabel = new Label(parent, SWT.NONE);
buttonLabel.setImage(theme.getUpImage());
buttonLabel.addMouseListener(new MouseAdapter() {
#Override
public void mouseDown(final MouseEvent mouseEvent) {
buttonLabel.setImage(theme.getButtonPressedImage());
}
#Override
public void mouseUp(final MouseEvent mouseEvent) {
buttonLabel.setImage(theme.getButtonUpImage());
}
});
buttonLabel.addMouseTrackListener(new MouseTrackAdapter() {
#Override
public void mouseEnter(final MouseEvent mouseEvent) {
buttonLabel.setImage(theme.getButtonHoverImage());
}
#Override
public void mouseExit(final MouseEvent mouseEvent) {
buttonLabel.setImage(theme.getButtonUpImage());
}
});
}
}
Where the Theme just has all of the images already conveniently loaded.
You'll also need to make sure that the parent Composite has the background mode set to force its background color:
parent.setBackgroundMode(SWT.INHERIT_FORCE);
Obviously the drawback to this approach is that you have to handle the mouse click logic yourself (ie. mouseDown isn't really clicked until the mouse is released, so you'll have to handle the state of the button in each listener method).
I added a JPanel to a JRadioButton - so that I may display whatever I want in the radio button.
This all worked as expected. But to allow for text wrapping, I used a JTextArea and added it to the JPanel contained within the radio button.
Now I have an issue where, if the user clicks on the JTextArea, then the JTextArea consumes the mouseEvent and as a result there is no response from the radio button (it doesn't 'select').
Is there a way get the JTextArea to ignore the mouse click, so that the parent may handle it instead?
I tried add the JTextArea's listeners to the radioButton instead.
I also tried to remove its listeners completely, but both these attempts failed.
Anyone have any suggestions?
Strong beware
Most JSomething are not meant to be used as containers even though it's possible - the outcome of doing it anyway is more or less visually and behaviourally undetermined!
That said, did it recently, to implement something similar to a Windows task dialog. If the requirement includes keeping the button clickable (and why else would you mis-use it as a container :-) the main problem (layout apart) is to make all added components completely mouse-transparent. Which is more difficult than can be expected. The minimum is to not allow adding of mouseListeners and disable the acceptance of mouseEvents:
final JTextArea area = new JTextArea("replacement ..") {
#Override
public synchronized void addMouseListener(MouseListener l) {
LOG.info("adding here ...?");
}
#Override
public synchronized void addMouseMotionListener(
MouseMotionListener l) {
}
#Override
public synchronized void addMouseWheelListener(
MouseWheelListener l) {
}
#Override
public void addNotify() {
disableEvents(AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK |
AWTEvent.MOUSE_WHEEL_EVENT_MASK);
super.addNotify();
}
};
Plus make sure it's not focusable
area.setEditable(false);
area.setFocusable(false);
area.setRequestFocusEnabled(false);
Plus unregister dragging and tooltips
ToolTipManager.sharedInstance().unregisterComponent(area);
area.setDragEnabled(false);
Nevertheless, there might still be surprises ahead, f.i. call the following twice (that is disable and enable again), which will internally re-enable mouseEvent:
area.setAutoscrolls(!area.getAutoscrolls());
So at the end of the day, we might get away with it - but never be entirely certain that we succeeded.
What about this? Create and add your own MouseListener to TextArea
JPanel p = new JPanel();
JTextArea t = new JTextArea("line \n line");
t.addMouseListener(new MyMouseListener());
p.add(t);
jRadioButton1.add(p);
jRadioButton1.addMouseListener(new MyRadioButtonMouseListener());
And in the MyMouseListener Dispatch event
private class MyMouseListener implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
Component source = (Component) e.getSource();
source.getParent().getParent().dispatchEvent(e); // 2x getParent() because JTextArea->JPanel->JRadio
}
.
.
.
}
And finally RadioButtonMouseListener
private class MyRadioButtonMouseListener implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
System.out.println("CLICK ON RADIOBUTTON !!");
}
.
.
.
}
I need to hide the stage window when the window's minimize button is clicked. I'm doing everything is java code not in the scripting language. How can I attach a window event handler to a JavaFX stage window minimize button?
Attach a listener to your Stage's iconified property.
stage.iconifiedProperty().addListener(new ChangeListener<Boolean>() {
#Override public void changed(ObservableValue<? extends Boolean> prop, Boolean oldValue, Boolean newValue) {
System.out.println("Iconified? " + newValue);
}
});