Trying to do a requestFocus() on the WebView does not work until the user has first clicked on the control.
I know this must be possible as htmlEditor can be focused this way (and I suspect it is based on a contenteditable WebView).
I am coding my own specialized htmlEditor using a webview with "contenteditable" and I would really like to be able to focus it like I can do with the standard htmlEditor.
I believe this must be an issue with Javafx and I have already submitted it to Jira, but I wonder if anyone can think of a work-around for this.
UPDATE: Issue number in jira: RT-21695
Short demostration code:
/* Demo webview */
public class WebViewConteneditableDemo extends Application {
String initialEditview = "<html><head>"
+ "</head><body contenteditable='true'>"
+"</body></html>";
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Webview focus demo");
final WebView editor = new WebView();
Button btn = new Button();
btn.setText("Test Webview focus");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
editor.requestFocus();
}
});
BorderPane root = new BorderPane();
root.setTop(btn);
root.setCenter(editor);
editor.getEngine().loadContent(initialEditview);
primaryStage.setScene(new Scene(root, 500, 450));
primaryStage.show();
}
}
The requestFocus api is just a request for focus, it does not guarantee focus.
Sometimes the internal implementation of other JavaFX controls request focus before or after you have requested focus which ends up in your requestFocus call not having any effect.
Often you can make the requestFocus call take effect by either wrapping it in a Platform.runLater or using a Timeline with a KeyFrame which invokes requestFocus after a delay.
If neither of those work, then there is likely a bug in the requestFocus processing for WebView which the JavaFX team can address in the context of the jira you filed.
Update
The specific issue in the sample code in the question was that, although the WebView was focused, the editable content element in the WebView was not focused.
I tried loading just the html from the sample code <html><head></head><body contenteditable='true'></body></html> into firefox and it behaved exactly the same as the JavaFX WebView (i.e. the editable content element was not focused until it was clicked on). So I don't believe this is an issue with WebView.
To get the editable element focused, you need to execute some script when you want it focused, for example in the javascript onload hook <body onLoad='document.body.focus();' contenteditable='true'/>
Here is an executable sample application which demonstrates programmatic control of focus behaviour of contenteditable elements in a JavaFX WebView:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class WebViewEditable extends Application {
String content = "<body bgcolor='cornsilk' onLoad='document.body.focus();' contenteditable='true'/>";
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
final WebView editor = new WebView();
editor.getEngine().loadContent(content);
Button webviewFocusButton = new Button("Focus on WebView");
webviewFocusButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent event) {
editor.getEngine().executeScript("document.body.focus()");
editor.requestFocus();
}
});
Button selfFocusButton = new Button("Focus on this Button");
Label focusLabel = new Label();
focusLabel.textProperty().bind(Bindings
.when(editor.focusedProperty())
.then("WebView has the focus.")
.otherwise("WebView does not have the focus.")
);
focusLabel.setMaxWidth(Double.MAX_VALUE);
focusLabel.setStyle("-fx-background-color: coral; -fx-padding: 5;");
BorderPane layout = new BorderPane();
layout.setTop(HBoxBuilder.create().spacing(10).children(webviewFocusButton, selfFocusButton).style("-fx-padding: 10; -fx-background-color: palegreen").build());
layout.setCenter(editor);
layout.setBottom(focusLabel);
stage.setScene(new Scene(layout));
stage.show();
}
}
you can focus the webview of the HTMLEditor this way:
import com.sun.javafx.scene.web.skin.HTMLEditorSkin;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.web.HTMLEditor;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class FocusTest extends Application {
public static void main(final String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
final HTMLEditor editor = new HTMLEditor();
final WebView editorView = (WebView) editor.lookup(".web-view");
primaryStage.setScene(new Scene(editor));
primaryStage.sizeToScene();
primaryStage.show();
Platform.runLater(() -> {
view.fireEvent(new MouseEvent(MouseEvent.MOUSE_PRESSED, 100, 100, 200, 200, MouseButton.PRIMARY, 1, false, false, false, false, false, false, false, false, false, false, null));
editor.requestFocus();
view.fireEvent(new MouseEvent(MouseEvent.MOUSE_RELEASED, 100, 100, 200, 200, MouseButton.PRIMARY, 1, false, false, false, false, false, false, false, false, false, false, null));
});
}
}
Related
Vaadin 7.6.2
Take the following example:
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
public class MyClass extends Panel {
TextField myField = new TextField();
HorizontalLayout hLayout = new HorizontalLayout( myField );
VerticalLayout vLayout = new VerticalLayout( myField );
Button button = new Button( "Press Me" );
public MyClass() {
super();
applySettings();
}
private void applySettings() {
button.addClickListener(new ClickListener() {
#Override
public void buttonClick(Button.ClickEvent event) {
setContent( hLayout );
}
});
vLayout.addComponent( button );
this.setContent( vLayout );
}
}
When I click the button the vLayout disappears but the hLayout (with myField) doesn't appear. What step am I'm missing? Or, is there a different way to do this?
If I add a secondary text field, like so:
TextField myField = new TextField();
TextField myField2 = new TextField(); // tf2
HorizontalLayout hLayout = new HorizontalLayout( myField );
VerticalLayout vLayout = new VerticalLayout( myField2 ); // tf2
It appears to work, however what I'm trying to achieve is the ability to dynamically switch my layouts using the fields (and their data) from the switched-out layout.
One component can not have 2 parents at the same time (hLayout & vLayout in your case), thus if it already has one, Vaadin will remove it from the previous parent and add it as a child to the current one. This is the addComponent method inherited from AbstractComponentContainer:
/**
* This only implements the events and component parent calls. The extending
* classes must implement component list maintenance and call this method
* after component list maintenance.
*
* #see com.vaadin.ui.ComponentContainer#addComponent(Component)
*/
#Override
public void addComponent(Component c) {
// Make sure we're not adding the component inside it's own content
if (isOrHasAncestor(c)) {
throw new IllegalArgumentException(
"Component cannot be added inside it's own content");
}
if (c.getParent() != null) {
// If the component already has a parent, try to remove it
AbstractSingleComponentContainer.removeFromParent(c);
}
c.setParent(this);
fireComponentAttachEvent(c);
markAsDirty();
}
If you're in debug mode, you can somewhat see an image of the composition tree in your browser by adding ?debug to your URL, something like http://localhost:8080/?debug
Well, it looks like I may have solved it.
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
public class MyClass extends Panel {
TextField myField = new TextField();
HorizontalLayout hLayout = new HorizontalLayout();
VerticalLayout vLayout = new VerticalLayout();
Button button = new Button( "Press Me" );
public MyClass() {
super();
applySettings();
}
private void applySettings() {
button.addClickListener(new ClickListener() {
#Override
public void buttonClick(Button.ClickEvent event) {
vLayout.removeAllComponents(); // this is optional
hLayout.addComponent( myField );
setContent( hLayout );
}
});
vLayout.addComponents( myField, button );
this.setContent( vLayout );
}
}
If I don't add myField in the layout constructors, but add it later in the code, it seems to work.
UPDATE
After more testing it seems whichever layout LAST called addComponent() for a given field, THAT layout gets the handle to that field. This behavior seems odd and if someone could explain why this is? That would be enlightening.
Vaadin 7.6.2
When I mouseover a field that has failed validation I see the error message as expected, however the delay before this message appears is too slow and should be immediate, as the user can click into the field BEFORE the message pops open.
Is there a way to adjust the time it takes to display a validation message?
Add the following css to your custom theme and compile. The default animate in/out time is 120ms, below I have changed the animate in time to 1ms to get rid of the validators tooltip delay.
.v-contextmenu[class*="animate-in"] {
-webkit-animation: valo-overlay-animate-in 1ms;
-moz-animation: valo-overlay-animate-in 1ms;
animation: valo-overlay-animate-in 1ms;
}
.v-contextmenu[class*="animate-out"] {
-webkit-animation: valo-animate-out-fade 120ms;
-moz-animation: valo-animate-out-fade 120ms;
animation: valo-animate-out-fade 120ms;
}
UI Code...
#Theme("mytheme")
#Widgetset("com.kevin.vaadin.sample.MyAppWidgetset")
public class MyUI extends UI {
#Override
protected void init(VaadinRequest vaadinRequest) {
final VerticalLayout layout = new VerticalLayout();
final TextField name = new TextField();
name.setCaption("Type your name here:");
Button button = new Button("Click Me");
button.addClickListener( e -> {
layout.addComponent(new Label("Thanks " + name.getValue()
+ ", it works!"));
});
name.addValidator(new StringLengthValidator("Name is too short.", 5, 10000000, false));
layout.addComponents(name, button);
layout.setMargin(true);
layout.setSpacing(true);
setContent(layout);
}
#WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
#VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
}
}
I'm new of vaadin and now I'm running in the following trouble. I have a TabSheet that contains a few tabs.
Now my Tax Code component setted in immediate mode and others it has a the following ValueChangeLister
TextField taxCode = new TextField();
taxCode.setImmediate(true);
taxCode.addValueChangeListener(new ValueChangeListener() {
#Override
public void valueChange(ValueChangeEvent event) {
if(taxCode.isModified()){
searchByTaxCode(taxCode.getValue());
}
}
});
then each time that the user changes the value to taxCode component, the method searchByTaxCode is invoked. This happens also when the user switch from Tab2 to Tab1 and I don't want this?
How can I fix this problem?
This simple test app does not fire the ValueChangeEvent when switching tabs, only when actually changing the value in the TextField. You have something more that could influence the behaviour?
public class TabSheetEventsUI extends UI {
#WebServlet(value = "/tabsheetevents/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = TabSheetEventsUI.class)
public static class Servlet extends VaadinServlet {
}
#Override
protected void init(VaadinRequest request) {
TabSheet tabSheet = new TabSheet();
Panel tab1 = new Panel("Tab 1");
Panel tab2 = new Panel("Tab 2");
tabSheet.addTab(tab1);
tabSheet.addTab(tab2);
final TextField tab1tf = new TextField("tab1tf");
tab1tf.setImmediate(true);
tab1tf.addValueChangeListener(new ValueChangeListener() {
#Override
public void valueChange(ValueChangeEvent event) {
System.out.println("Value change event catched.");
}
});
tab1.setContent(tab1tf);
setContent(tabSheet);
}
}
I'm trying to popup a global dialog from a background thread that I started from an alternate entry point.
public static void main(String[] args) {
MyApp theApp = new MyApp();
if (args != null && args.length > 0 && args[0].equals("test")) {
new Thread(new Runnable() {
public void run() {
try {
synchronized (UiApplication.getEventLock()) {
UiEngine ui = Ui.getUiEngine();
Screen screen = new Dialog(Dialog.D_OK, "Test", Dialog.OK,
Bitmap.getPredefinedBitmap(Bitmap.EXCLAMATION),
Manager.VERTICAL_SCROLL);
ui.pushGlobalScreen(screen, 1, UiEngine.GLOBAL_MODAL);
}
} catch (Exception e) {
System.out.println(e.toString());
}
}
}).start();
} else {
theApp.enterEventDispatcher();
}
}
I tried so many variations to make it work but it's still not showing up. I tried
synchronizing Application.getEventLock(), I also tried
UiApplication.getUiApplication().invokeLater,
UiApplication.getUiApplication().invokeAndWait. I even tried synchronizing the eventlock first before calling the invokeLater (which I think is redundant, but I still tried...). I'm not sure what I'm doing wrong.
okk i am giving you a sample demo ....
First of all edit the BlackBerry_App_Descriptor.xml click on Application Tab
In ApplicationArgument write alternate and check Auto Run on start up
Click on Alternate Entry Points click on add and write in the title BackgroundApp
Make a class which will extend Application class rather than UiApplication class like this way
import net.rim.device.api.system.Alert;
import net.rim.device.api.system.Application;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.Screen;
import net.rim.device.api.ui.Ui;
import net.rim.device.api.ui.UiEngine;
import net.rim.device.api.ui.component.Dialog;
public class BackGroundApp extends Application {
// this class is used for the background processing .....
public void startBackgroundThread()
{
new Thread(){
public void run() {
while (true) {
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (getEventLock()) {
//with this UiEngine pushGlobal dialogs
//whenever with the app in background
UiEngine ui = Ui.getUiEngine();
Screen screen = new Dialog(Dialog.D_OK, "You have updates!",
Dialog.OK, Bitmap
.getPredefinedBitmap(Bitmap.EXCLAMATION),
Manager.VERTICAL_SCROLL);
ui.pushGlobalScreen(screen, 1, UiEngine.GLOBAL_QUEUE);
}
}
}
}.start();
}
}
Make a class which will extend UiApplication class like this way
public class GuiTest extends UiApplication {
static Timer t;
public static void main(String[] args) {
if(args.length>0&&"alternate".equals(args[0])){
BackGroundApp app = new BackGroundApp();
app.startBackgroundThread();
app.enterEventDispatcher();
}
else{
GuiTest test = new GuiTest();
test.enterEventDispatcher();
}
}
public GuiTest(){
Myscreen screeMyscreen = new Myscreen();
pushScreen(screeMyscreen);
}
}
Now make a class MyScreen and add all your Ui in it .... and push the screen
public class Myscreen extends MainScreen {
public Myscreen(){
CreateGui();
}
public void CreateGui(){
// Your Ui goes here .......
}
}
run the sample you will see after one minute a dialog will appear on your screen no matter if you are in the application or out side of it. Thanks may be this might be help full.
I've just started with programming for the Blackberry device. I'm using version 5 of the API.
I'm building a very simple application which is just a browserfield. So far it's all working great. I can display my browserfield with the content I need.
The problem I'm having now is if the device doesn't have an active internet connection I get the ugly "Error requesting content for" message.
I would need to someone display my own message if the device doesn't have an active connection.
Something like "You need to have an active internet connection to use this application" with an Exit button which closes the app.
I've tried to find this for hours but no luck.
Hopefully it's something relatively easy so I can get help here.
Here's my code so far:
package com.mycompany.webview;
import net.rim.device.api.browser.field2.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.container.*;
public class webview extends UiApplication
{
public static void main(String[] args)
{
webview app = new webview();
app.enterEventDispatcher();
}
public webview()
{
pushScreen(new webviewScreen());
}
}
class webviewScreen extends MainScreen
{
public webviewScreen()
{
BrowserField myBrowserField = new BrowserField();
add(myBrowserField);
myBrowserField.requestContent("http://www.google.com");
}
}
Would really appreciate some help please.
Thanks
I got it working.
If anyone else is wondering how it's done, this is how I did it:
package com.mycompany.webview;
import net.rim.device.api.browser.field2.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.system.CoverageInfo;
public class webview extends UiApplication
{
public static void main(String[] args)
{
webview app = new webview();
app.enterEventDispatcher();
}
public webview()
{
pushScreen(new webviewScreen());
}
}
class webviewScreen extends MainScreen
{
public webviewScreen()
{
if (CoverageInfo.isOutOfCoverage())
{
UiApplication.getUiApplication().invokeLater(new Runnable()
{
public void run()
{
Dialog.alert("You need an active internet connection to use this application");
System.exit(0);
}
});
}
else
{
BrowserField myBrowserField = new BrowserField();
add(myBrowserField);
myBrowserField.requestContent("http://www.google.com");
}
}
}