Is there a way to keep listening to a property change, for a few seconds, then fire an event (call a method)?
For example, when the user enter data in a text field:
textField.textProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2) {
//before calling a method to do something.. wait for a few seconds ...
}
});
A scenario would be firing an action based on the string value. For example, hitting "M" for move, or "MA" for mask. I would like to "keep listening" for 2 seconds before making an action.
As Jeffrey pointed out, you can use ReactFX:
EventStreams.valuesOf(textField.textProperty())
.successionEnds(Duration.ofSeconds(2))
.subscribe(s -> doSomething(s));
There are a few ways to solve this.
Usually I would recommend the Java Timer API, but if by "making an action" you imply updating stuff on the FX thread, you would have to synchronize the threads, which is bothersome.
Depending on your use case, you could instead use Transitions or the Timeline in FX.
Here is an example with a transition:
package application;
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TransitionedInputExample extends Application {
#Override
public void start(final Stage stage) {
final ImageView spinner =
new ImageView("https://cdn1.iconfinder.com/data/icons/duesseldorf/16/process.png");
spinner.setVisible(false);
final Label title = new Label("Timed Action Commander Example", spinner);
title.setContentDisplay(ContentDisplay.BOTTOM);
title.setFont(Font.font("Helvetica", FontWeight.BOLD, FontPosture.REGULAR, 16));
final TextField textInput = new TextField();
textInput.setPromptText("Enter command");
final TextArea textOutput = new TextArea();
textOutput.setPromptText("Command results will show up here");
final VBox layout = new VBox(title, textInput, textOutput);
layout.setSpacing(24);
// setup some transition that rotates an icon for 2 seconds
final RotateTransition rotateTransition = new RotateTransition(Duration.seconds(1), spinner);
rotateTransition.setByAngle(90);
// delay rotation so that user can type without being distracted at once
rotateTransition.setDelay(Duration.seconds(1));
// restart transition on user input
textInput.textProperty().addListener((observable, oldText, newText) -> {
spinner.setVisible(true);
rotateTransition.playFromStart();
});
rotateTransition.setOnFinished((finishHim) -> {
// execute command
textOutput.setText("Executing " + textInput.getText());
spinner.setVisible(false);
});
final Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
public static void main(final String[] args) {
launch(args);
}
}
For a solutition using a Timeline see this post .
You might consider using Inhibeans. It allows you to block the handling of an event until you want the change events to fire. The whole ReactFX project might also be useful because what essentially what you need to do is build a state machine of events. You are looking for patterns in the events like regex.
For example, let's say you use 'M' for move and 'MA' for mask. You'll have two paths through your state machine.
M
M -> A
Then you can use a timer to determine how long you'll wait for the events to pile up before you process it.
Checkout this sample copied from the ReactFX site:
reduceSuccessions
Accumulates events emitted in close temporal succession into one.
EventSource<Integer> source = new EventSource<>();
EventStream<Integer> accum = source.reduceSuccessions((a, b) -> a + b, Duration.ofMillis(200));
source.push(1);
source.push(2);
// wait 150ms
source.push(3);
// wait 150ms
source.push(4);
// wait 250ms
source.push(5);
// wait 250ms
In the above example, an event that is emitted no later than 200ms after the previous one is accumulated (added) to the previous one. accum emits these values: 10, 5.
Related
How to set jxBrowser to open links that would pop-up in a new window to open on the calling page (or, at least, in a new tab)?
this is the call I think I have to override (it's the example):
//....
Engine engine = Engine.newInstance(
EngineOptions.newBuilder(
enderingMode.OFF_SCREEN ).enableIncognito().build()
Browser _browser = engine.newBrowser();
//this won't even compile
_browser.set(
OpenPopupCallback.class,
(params) -> {
// Access the created popup.
Browser popup = params.popupBrowser();
_browser.navigation().loadUrl(params.targetUrl());
return Response.proceed();
});
_browser.navigation().loadUrl("http://www.stackoverflow.com");
This is how I call it in my jfx but won't even compile, the code without this call works (opens a browser).
Update, given the nature of the popup I tried to rewrite javascript function (window.open) itself to force name to _parent.
This by running on every navigation the code
String the Javascript = "window.open = function (open) {return function (url, name, features{ console.log("open wrapper");return open.call(window, url, '_parent', features);};}(window.open);"
I thaught I couldn achieve this by
_browser.frames().get(0).executeJavaScript(theJavascript);
But in the remote console, I can't even see the log message ("open wrapper").
I double-checked the same code and it works if copy-pasted in the remote consolle.
What am I missing?
Here's complete JavaFX example that demonstrates how to open popup's URL in the main Browser instance and suppress popup:
import static com.teamdev.jxbrowser.engine.RenderingMode.OFF_SCREEN;
import com.teamdev.jxbrowser.browser.Browser;
import com.teamdev.jxbrowser.browser.callback.CreatePopupCallback;
import com.teamdev.jxbrowser.browser.callback.CreatePopupCallback.Response;
import com.teamdev.jxbrowser.engine.Engine;
import com.teamdev.jxbrowser.view.javafx.BrowserView;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public final class SmokeTest extends Application {
#Override
public void start(Stage primaryStage) {
// Creating and running Chromium engine.
Engine engine = Engine.newInstance(OFF_SCREEN);
Browser browser = engine.newBrowser();
browser.set(CreatePopupCallback.class, params -> {
browser.navigation().loadUrl(params.targetUrl());
return Response.suppress();
});
// Creating UI component for rendering web content
// loaded in the given Browser instance.
BrowserView view = BrowserView.newInstance(browser);
BorderPane root = new BorderPane(view);
Scene scene = new Scene(root, 1280, 900);
primaryStage.setTitle("Hello World");
primaryStage.setScene(scene);
primaryStage.show();
browser.navigation().loadUrl(
"https://www.encodedna.com/javascript/demo/open-new-window-using-javascript-method.htm");
// Close the engine when stage is about to close.
primaryStage.setOnCloseRequest(event -> engine.close());
}
}
Run this program and click a button that displays popup. You will see that popup is not displayed and its URL is loaded in the main Browser.
I have a main class (Main.java), two FXML files (FXML1.fxml, FXML2.fxml) and the corresponding controller (FXML1Controller.java, FXML2Controller.java).
In the FXML1.fxml I have two text fields and two buttons.
In the associated controller (FXML1Controller.java) I have declared the text fields and the button as follows:
public TextField textField1;
public TextField textField2;
public Button buttonchange;
public Button buttonOpenWindow;
public void open ()
{
...
stage.show ();
}
public void change ()
{
textField2.setText (textField1.getText ());
}
I type text into textField1. When I click on the buttonChange, then the text should be set in textfield2. Works. It's simple.
When I click the buttonOpen, then a new window opens.
There, I have only one button, but want to do the same.
So something in the way like this:
public void changeFromHere ()
{
FXMLController1 c1 = new FXMLController1 ();
c1.change ();
}
I also know that similar questions have been asked here.
But somehow it does not work the way I want it.
I always get a NullPointerException. For sure. I know.
So I have done the following:
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
...
public class FXML2Controller implements Initializable
{
...
public Button buttonChangeFromHere;
URL location;
FXMLLoader fxmlLoader;
Pane root;
FXMLController1 fc1;
public voidChangeFromHere ()
{
fc1.change ();
}
#Override
public void initialize (URL url, ResourceBundle rb)
{
try
{
location = getClass ().getResource ("FXML1.fxml");
fxmlLoader = new FXMLLoader (location);
root = (Pane) fxmlLoader.load ();
fc1 = (FXMLController1) fxmlLoader.getController ();
} catch (IOException ex)
{
Logger.getLogger (FXML2Controller.class.getName ()) .log (Level.SEVERE, null, ex);
}
}
...
}
Now I can not get a NullPointerException and it should work this way. But nothing happened !?
If I use for testing System.out.println (fc1.textField2.getText ());, then I get the text "Hello", which I have defined in JavaFXSceneBuilder.
If I remove the text in SceneBuilder, then I get "null". Sure. Likewise, I get "null" when I type in the term "Hello" (in textField1) while program is running.
I have also tried to initialize the textfields first in the initialize method. Then I also get always the text "Hello", although I typed "Byebye".
The solutions to similar questions in this forum can not help me.
That's why I put so a similar question again.
This code is also just an example. I can not paste my whole code here. What I really want to do is:
In my program I have a list (listView).
In the list are paths to files that I save as a file. Works fine (In the Main Controller, Main.fxml). There, I have a method public void save (). Now I want to call from another controller class's the save () method. But almost all variables are "null".
But they can't be "null", because the listView shows me the entrys.
I do not think that getter and setter methods are appropriate, because it would be very much redundant code. And I don't want that.
If someone could answer the question how to do it with textfields from example, then I would be grateful.
If it does not work with textfields, it will not work with other components, too:-(.
Thanks in advance.
I have this utility method which allows easily to change what is shown in specific location of my application.
The problem is it looks more like that the new Part is on top of the old Part (the old Part is not removed and it is still visible under the new Part).
package cz.vutbr.fit.xhriba01.bc.ui;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainer;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.modeling.EPartService;
public class UI {
public static final String PART_INSPECTOR_ID = "bc.part.inspector";
public static void changeInspectorView(String partDescriptorId, EPartService partService, EModelService modelService) {
MPart part = partService.createPart(partDescriptorId);
MPart oldPart = partService.findPart(UI.PART_INSPECTOR_ID);
MPartSashContainer parent = (MPartSashContainer) modelService.getContainer(oldPart);
parent.getChildren().remove(oldPart);
part.setElementId(UI.PART_INSPECTOR_ID);
parent.getChildren().add(0, part);
}
}
You should use:
partService.hidePart(oldPart);
to hide the old part (also removes it from the children).
You might also just be able to do:
oldPart.setToBeRendered(false);
but I am not sure that does enough to update the Eclipse internal state.
I am trying a sample with icePDF . Everything is working fine but i need to disable the toolbar which appears at the top. i tried few things but its not working. Can some body please help me out with it. Below is my code.
//package XML.test;
package applet;
import java.util.ResourceBundle;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.icepdf.ri.common.ComponentKeyBinding;
import org.icepdf.ri.common.SwingController;
import org.icepdf.ri.common.SwingViewBuilder;
import org.icepdf.ri.util.PropertiesManager;
import org.icepdf.core.pobjects.fonts.*;
import org.icepdf.core.views.DocumentViewController;
import org.icepdf.core.*;
public class ViewerComponentExample
{
static void buildFrame(String filepath)
{
System.getProperties().put("org.icepdf.core.scaleImages", "false");
System.getProperties().put("org.icepdf.core.imageReference","smoothScaled");
System.getProperties().put("org.icepdf.core.target.dither", "VALUE_DITHER_DISABLE");
System.getProperties().put("org.icepdf.core.target.fractionalmetrics", "VALUE_FRACTIONALMETRICS_OFF");
System.getProperties().put("org.icepdf.core.target.interpolation", "VALUE_INTERPOLATION_NEAREST_ NEIGHBOR");
System.getProperties().put("org.icepdf.core.screen.interpolation", "VALUE_INTERPOLATION_NEAREST_NEIGHBOR");
System.getProperties().put("org.icepdf.core.awtFontLoading","true");
SwingController controller = new SwingController();
PropertiesManager properties = new PropertiesManager(System.getProperties(), ResourceBundle.getBundle(PropertiesManager.DEFAULT_MESSAGE_BUNDLE));
properties.setBoolean(PropertiesManager.PROPERTY_SHOW_TOOLBAR_ANNOTATION, Boolean.FALSE);
properties.setBoolean(PropertiesManager.PROPERTY_SHOW_TOOLBAR_FIT, Boolean.FALSE);
// Build a SwingViewFactory configured with the controller
SwingViewBuilder factory = new SwingViewBuilder(controller);
JPanel viewerComponentPanel = factory.buildViewerPanel();
// add copy keyboard command
ComponentKeyBinding.install(controller, viewerComponentPanel);
// add interactive mouse link annotation support via callback
controller.getDocumentViewController().setAnnotationCallback(
new org.icepdf.ri.common.MyAnnotationCallback(
controller.getDocumentViewController()));
// Use the factory to build a JPanel that is pre-configured
//with a complete, active Viewer UI.
// Create a JFrame to display the panel in
JFrame window = new JFrame("Metrics Wizard Help");
window.getContentPane().add(viewerComponentPanel);
window.pack();
window.setVisible(true);
controller.setPageFitMode(DocumentViewController.PAGE_FIT_WINDOW_WIDTH, false);
controller.openDocument(filepath);
}
public static void main(String args[])
{
String filepath = "C:/Users/vishalt/Workspaces/Eclipse 4.2 Java/htmltopdf/src/XML/output/SCB_TEST.pdf";
buildFrame(filepath);
}
}
private SwingController controller;
controller = new SwingController();
SwingViewBuilder viewBuilder = new SwingViewBuilder(controller, properties);
JPanel panel = viewBuilder.buildViewerPanel();
controller.setToolBarVisible(false);
You have to set the toolbar invisible because icePdf looks in the PDF-document for the property and overwrites your setting with default when there is no document opened!
There are two ways to this.
1) Follow this example to set all the toolbars to false.
http://www.icesoft.org/JForum/posts/list/17673.page#sthash.48ICrL2A.dpbs
2) You can modify or remove the toolbar by editing the source code for SwingViewBuilder.
Here is a link to the code: http://sventon.icesoft.org/svn/repos/repo/show//icepdf/trunk/icepdf/viewer/src/org/icepdf/ri/common/SwingViewBuilder.java?revision=34004
You probably want to comment out lines 481 - 483.
481 JToolBar toolBar = buildCompleteToolBar(embeddableComponent);
482 if (toolBar != null)
483 cp.add(toolBar, BorderLayout.NORTH)
Remove your import for SwingViewBuilder and create your own class with those lines commented out.
Here's a minimal example to illustrate the problem. When the button is clicked, 500 TextView objects should get added, each containing some text. What actually happens is that there is a short delay, 500 empty TextViews get added, there is a much longer delay and then they all get populated with text at once and the layout sizes itself properly. Code below:
import gtk.Button;
import gtk.Main;
import gtk.MainWindow;
import gtk.Notebook;
import gtk.ScrolledWindow;
import gtk.Statusbar;
import gtk.TextView;
import gtk.TextBuffer;
import gtk.UIManager;
import gtk.VBox;
import gtk.Window;
import std.stdio;
class UI : MainWindow
{
Notebook notebook;
this() {
super("Test");
setDefaultSize(200, 100);
VBox box = new VBox(false, 2);
notebook = new Notebook();
Button button = new Button("add lines");
button.addOnClicked(&addLines);
box.packStart(notebook, true, true, 0);
box.packStart(button, false, false, 2);
add(box);
showAll();
}
void addLines(Button b) {
VBox box = new VBox(false, 2);
for (int i = 0; i < 500; i++) {
auto tv = new TextView();
tv.getBuffer().setText("line");
box.packStart(tv, false, false, 1);
}
ScrolledWindow swin = new ScrolledWindow(box);
notebook.add(swin);
showAll();
}
}
void main(string[] args)
{
Main.init(args);
auto ui = new UI();
Main.run();
}
Edit: this thread suggests that creating a bunch of text views is intrinsically expensive, and that I should be rewriting using a treeview.
GTK is event-driven, and uses a message pump. If in a callback you do a lengthy operation, you never give a chance to the message pump to process the pending messages. You could replace the code in your callback by a sleep of 2 seconds, the effect would be the same: the UI would be frozen during that time slice.
If you can't split up your actions, use the d equivalent of what is described in gtk_events_pending documentation:
/* computation going on */
...
while (gtk_events_pending ())
gtk_main_iteration ();
...
/* computation continued */
Called between each of your loop iterations, it will give some time to GTK to process the events you generated by adding your widgets.
After some more googling and experimenting, it turns out that GtkTextViews are intrinsically expensive to instantiate, and I should not have been trying to create so many of them. As per the advice in this thread I will be reworking my code to use a GtkTreeView instead.