Clicking on menu item "open in new tab" on webview does not open webpage sometimes, other time it does open

100 Views Asked by At

I have a piece of code in JavaFX which creates a small web browser. The web page is shown in a web view. I have managed to get custom context menu popped up by right clicking on the links on the webpage. The context menu has a menu item labeled "open in new tab". However, I have a strange problem. If I click on the "open in new tab", sometimes related webpage opens in new tab, but other times it fails to do so. Please note that if I implement the functionality without any context menu and using right click alone directly on the link, the link opens every time in new tab. How do we describe the situation? Is it a threading issue? I have tested the code on Windows 7 64-bit and Windows 8.1 64-bit. I am using BlueJ on Java 17 and JavaFX 20.

Here is what I have tried.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.scene.web.WebEvent;
import javafx.stage.Stage;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.event.EventHandler;
import javafx.event.ActionEvent;

public class QuickTab extends Application
{
    TabPane tpane;
    TextField urlField;
    WebView webView;
    String url;
    public void start(Stage stage)
    {
        tpane = new TabPane();
        Tab urlTab = new Tab();
        HBox box = new HBox();
        urlField = new TextField();
        urlField.setPromptText("Enter URL");
        Button goBtn = new Button("Go");
        goBtn.setOnAction(e -> load_page());
        box.getChildren().addAll(urlField, goBtn);
        urlTab.setGraphic(box);
        tpane.getTabs().add(urlTab);
        
        stage.setScene(new Scene(tpane, 400, 300));
        stage.show();
    }
    private void load_page()
    {
        webView = new WebView();
        WebEngine engine = webView.getEngine();
        webView.setContextMenuEnabled(false);//***
        Tab tab = new Tab("home tab");
        tab.setContent(webView);
        tpane.getTabs().add(tab);      
        
        engine.setOnStatusChanged((WebEvent<String> event) -> {
            url = event.getData();
            
        });
        engine.load(urlField.getText());       
        webView.setOnMouseClicked(new TabOpener());
    }
    private class TabOpener implements EventHandler<MouseEvent>
    {
                 
        public void handle(MouseEvent e)
        {
            if(e.getButton() == MouseButton.SECONDARY)
            {
                ContextMenu cmenu = new ContextMenu();
                MenuItem item = new MenuItem("open in new tab"); 
                cmenu.getItems().addAll(item);
                cmenu.show(webView, e.getScreenX(), e.getScreenY());                    
                item.setOnAction(event -> {
                    Platform.runLater(() -> load_newPage());
                });
            }
            e.consume();
        }
        private void load_newPage()
        {
            WebView newView = new WebView();
            WebEngine newEngine = newView.getEngine();
            Tab newTab = new Tab(url);
            tpane.getTabs().add(newTab);
            newTab.setContent(newView);
            newEngine.load(url);
            
        }
    }    
}
1

There are 1 best solutions below

4
On

It's not clear how your program is going awry, but I see distinct code paths to add a new Tab: one gets a context listener, one doesn't.

One approach is to minimize unrelated class level attributes, while aggregating relevant fields with the corresponding Tab. In the variation below,

  • All Tab instances have access to the containing TabPane and a shared ContextMenu; the remaining attributes are local to each WebTab.

  • The initial Tab opens on the HOME page.

  • Each Tab gets an editable URL; edit and press Enter to load the page; press Tab to change tabs.

  • The context menu simply opens the current page in a new Tab, allowing continued browsing in the current Tab.

  • Also consider isolating individual links, illustrated here, or parsing the document, shown here.

Tab browser

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.scene.input.MouseButton;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.web.WebEngine;

//https://stackoverflow.com/q/77506981/230513
public class QuickTab extends Application {

    private static final String HOME = "https://example.edu";
    private final TabPane tabPane = new TabPane();
    private final MenuItem item = new MenuItem("Open this page in a new tab.");
    private final ContextMenu menu = new ContextMenu(item);

    @Override
    public void start(Stage stage) {
        tabPane.getTabs().add(new WebTab(HOME).tab());
        stage.setScene(new Scene(tabPane));
        stage.show();
    }

    private class WebTab {

        private final Tab tab = new Tab();
        private final TextField urlField = new TextField();
        private final WebView view = new WebView();
        private final WebEngine engine = view.getEngine();
        private String url;

        private WebTab(String s) {
            url = s;
            view.setContextMenuEnabled(false);
            tab.setGraphic(urlField);
            tab.setContent(view);
            urlField.focusedProperty().addListener((obs, ov, isFocused) -> {
                if (isFocused) {
                    tabPane.getSelectionModel().select(tab);
                }
            });
            urlField.setOnKeyPressed((KeyEvent e) -> {
                KeyCode code = e.getCode();
                if (KeyCode.ENTER == code) {
                    engine.load(urlField.getText());
                }
            });
            engine.locationProperty().addListener((obs, ov, nv) -> {
                url = nv;
                urlField.setText(nv);
            });
            view.setOnMouseClicked((e) -> {
                if (e.getButton() == MouseButton.SECONDARY) {
                    menu.show(view, e.getScreenX(), e.getScreenY());
                    item.setOnAction(event -> {
                        tabPane.getTabs().add(new WebTab(url).tab());
                    });
                }
                e.consume();
            });
            engine.load(url);
            urlField.setText(url);
        }

        public Tab tab() {
            return tab;
        }
    }

    public static void main(String[] args) {
        Application.launch();
    }
}