JavaFX WebView: как добавить слушателя HyperLink в карту / область использования? - PullRequest
2 голосов
/ 03 апреля 2020

При использовании WebView есть способ добавить псевдо-HyperlinkListener для тегов <img>, используя атрибут usemap для ссылки клика на карту?

Например, в следующем HTML из w3schools ,

<h1>The map and area elements</h1>

<p>Click on the sun or on one of the planets to watch it closer:</p>

<img src="planets.gif" width="145" height="126" alt="Planets" usemap="#planetmap">

<map name="planetmap">
  <area shape="rect" coords="0,0,82,126" alt="Sun" href="sun.htm">
  <area shape="circle" coords="90,58,3" alt="Mercury" href="mercur.htm">
  <area shape="circle" coords="124,58,8" alt="Venus" href="venus.htm">
</map>

есть ли способ получить прослушиватель событий, когда пользователь нажимает на элемент "Sun"?

слушатели гиперссылок с тегами <a>, HyperlinkListener в JavaFX WebEngine .

1 Ответ

1 голос
/ 03 апреля 2020

Я смог сделать это с помощью следующих шагов:

  • Подождите, пока загрузится веб-страница.
  • Используйте XPath, чтобы найти элемент <img>.
  • Используйте usemap значение <img>, чтобы найти соответствующий элемент <map>.
  • Получите список всех <area> дочерних элементов <map>.
  • Добавить щелчок слушатель элемента <map>, который:
    • использует вызов JavaScript getBoundingClientRect() для получения границ элемента <img> относительно области просмотра.
    • Получает координаты щелчка мыши относительно в область просмотра.
    • Перебирает дочерние элементы <area> и для каждого анализирует атрибут coords в списке значений, затем создает Shape, соответствующий атрибуту shape.
    • Корректирует координаты щелчка мыши, чтобы они относились к началу координат <img>.
    • Проверяет, находятся ли эти координаты щелчка мыши внутри фигуры.

Вот полная демонстрация:

import java.net.URI;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.MouseEvent;

import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;

import javafx.scene.shape.Shape;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Polygon;

import javafx.scene.web.WebView;
import javafx.scene.web.WebEngine;

import netscape.javascript.JSObject;

public class WebViewImageMapTest
extends Application {

    /**
     * A single coordinate in the list which comprises the value of
     * the {@code coords} attribute of an {@code <area>} element.
     */
    private static class Coordinate {
        final double value;
        final boolean percentage;

        Coordinate(double value,
                   boolean percentage) {
            this.value = value;
            this.percentage = percentage;
        }

        double resolveAgainst(double size) {
            return percentage ? value * size / 100 : value;
        }

        static Coordinate parse(String s) {
            if (s.endsWith("%")) {
                return new Coordinate(
                    Double.parseDouble(s.substring(0, s.length() - 1)), true);
            } else {
                return new Coordinate(Double.parseDouble(s), false);
            }
        }

        @Override
        public String toString() {
            return getClass().getName() +
                "[" + value + (percentage ? "%" : "") + "]";
        }
    }

    @Override
    public void start(Stage stage) {
        WebView view = new WebView();

        Label destination = new Label(" ");
        destination.setPadding(new Insets(12));

        WebEngine engine = view.getEngine();
        engine.getLoadWorker().stateProperty().addListener((o, old, state) -> {
            if (state != Worker.State.SUCCEEDED) {
                return;
            }

            Document doc = engine.getDocument();
            try {
                XPath xpath = XPathFactory.newInstance().newXPath();

                Element img = (Element)
                    xpath.evaluate("//*[local-name()='img']",
                        doc, XPathConstants.NODE);

                String mapURI = img.getAttribute("usemap");
                String mapID = URI.create(mapURI).getFragment();
                Element map = doc.getElementById(mapID);
                if (map == null) {
                    // No <map> with matching id.
                    // Look for <map> with matching name instead.
                    map = (Element) xpath.evaluate(
                        "//*[local-name()='map'][@name='" + mapID + "']",
                        doc, XPathConstants.NODE);
                }

                NodeList areas = (NodeList)
                    xpath.evaluate("//*[local-name()='area']",
                        map, XPathConstants.NODESET);

                ((EventTarget) map).addEventListener("click", e -> {
                    Element area = getClickedArea(
                        (MouseEvent) e, areas, getClientBounds(img, engine));

                    if (area != null) {
                        destination.setText(area.getAttribute("href"));
                    } else {
                        destination.setText(" ");
                    }
                }, false);
            } catch (XPathException e) {
                e.printStackTrace();
            }
        });

        engine.load(
            WebViewImageMapTest.class.getResource("imgmap.html").toString());

        stage.setScene(new Scene(
            new BorderPane(view, null, null, destination, null)));
        stage.setTitle("Image Map Test");
        stage.show();
    }

    /**
     * Returns the bounds of an element relative to the viewport.
     */
    private Rectangle getClientBounds(Element element,
                                      WebEngine engine) {

        JSObject window = (JSObject) engine.executeScript("window");
        window.setMember("desiredBoundsElement", element);
        JSObject bounds = (JSObject) engine.executeScript(
            "desiredBoundsElement.getBoundingClientRect();");

        Number n;
        n = (Number) bounds.getMember("x");
        double x = n.doubleValue();
        n = (Number) bounds.getMember("y");
        double y = n.doubleValue();
        n = (Number) bounds.getMember("width");
        double width = n.doubleValue();
        n = (Number) bounds.getMember("height");
        double height = n.doubleValue();

        return new Rectangle(x, y, width, height);
    }

    private Element getClickedArea(MouseEvent event,
                                   NodeList areas,
                                   Rectangle imgClientBounds) {

        int clickX = event.getClientX();
        int clickY = event.getClientY();

        double imgX = imgClientBounds.getX();
        double imgY = imgClientBounds.getY();
        double imgWidth = imgClientBounds.getWidth();
        double imgHeight = imgClientBounds.getHeight();

        int count = areas.getLength();
        for (int i = 0; i < count; i++) {
            Element area = (Element) areas.item(i);

            String shapeType = area.getAttribute("shape");
            if (shapeType == null) {
                shapeType = "";
            }

            String[] rawCoords = area.getAttribute("coords").split(",");
            int numCoords = rawCoords.length;
            Coordinate[] coords = new Coordinate[numCoords];
            for (int c = 0; c < numCoords; c++) {
                coords[c] = Coordinate.parse(rawCoords[c].trim());
            }

            Shape shape = null;
            switch (shapeType) {
                case "rect":
                    double left = coords[0].resolveAgainst(imgWidth);
                    double top = coords[1].resolveAgainst(imgHeight);
                    double right = coords[2].resolveAgainst(imgWidth);
                    double bottom = coords[3].resolveAgainst(imgHeight);
                    shape = new Rectangle(
                        left, top, right - left, bottom - top);
                    break;
                case "circle":
                    double centerX = coords[0].resolveAgainst(imgWidth);
                    double centerY = coords[1].resolveAgainst(imgHeight);
                    double radius = coords[2].resolveAgainst(
                        Math.min(imgWidth, imgHeight));
                    shape = new Circle(centerX, centerY, radius);
                    break;
                case "poly":
                    double[] polygonCoords = new double[coords.length];
                    for (int c = polygonCoords.length - 1; c >= 0; c--) {
                        polygonCoords[c] = coords[c].resolveAgainst(
                            c % 2 == 0 ? imgWidth : imgHeight);
                    }
                    shape = new Polygon(polygonCoords);
                    break;
                default:
                    shape = new Rectangle(imgWidth, imgHeight);
                    break;
            }

            if (shape.contains(clickX - imgX, clickY - imgY)) {
                return area;
            }
        }

        return null;
    }

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

А вот и planets.gif, который я использовал: * 1 043 *

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...