Попытка определить местоположение перетаскивания мыши с помощью сетки JavaFX

#javafx #mouseevent #gridpane

Вопрос:

Примечание: 25 лет работы с Java, 2,5 часа (почти) работы с JavaFX.

Я хочу иметь возможность выделить все ячейки сетки, по которым перемещается мышь, т. Е. Все, что пересекается с прямоугольником, загнанным в угол точкой щелчка и текущей точкой перетаскивания. Я могу это сделать, если все дети 1х1, но со смешанными размерами у меня нет радости.

Например, если в верхней строке 1 ячейка 1 столбца (A) и 1 ячейка 2 столбца (B), а в нижней-1 ячейка 2 столбца (C) и 1 ячейка 1 столбца (D), если я нажму A и перетащу вниз в C, я смогу выделить оба. Однако я не могу понять, когда я перетаскиваю в правую половину C, чтобы B был выделен.

Доска для Образцов:

доска для образцов

Приношу извинения за HSCE — это немного длинновато, но я чувствовал, что его удаление снизит читабельность.

 import java.util.*;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent; 
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.Node;
import javafx.stage.Stage;
import javafx.geometry.*;

public class AddTestSSCE extends Application
{
    ArrayList<StackPane> sPanes = new ArrayList<StackPane>(); // selected panes
    GridPane theGrid;
    Node startNode = null;
    int col0, col1;
    int row0, row1;

    @Override
    public void start(Stage stage) {
        theGrid = new GridPane();
        ColumnConstraints col = new ColumnConstraints(300);
        theGrid.getColumnConstraints().addAll(col, col, col);
        RowConstraints row = new RowConstraints(200);
        theGrid.getRowConstraints().addAll(row, row, row);
        addGridPane();
        theGrid.getStyleClass().add("bg-grid");
        Scene scene = new Scene(theGrid, 1024, 768);
        scene.getStylesheets().add("addtestssce.css");
        stage.setScene(scene);
        stage.show();
    }

    public void addGridPane() {
        theGrid.setHgap(10);
        theGrid.setVgap(10);
        theGrid.setPadding(new Insets(0, 10, 0, 10));

        StackPane theSP = sPAdd(new Label("A"));
        theGrid.add(theSP, 0, 0, 1, 1); 

        theSP = sPAdd(new Label("B"));
        theGrid.add(theSP, 1, 0, 2, 1);

        theSP = sPAdd(new Label("C"));
        theGrid.add(theSP, 0, 1, 2, 1);

        theSP = sPAdd(new Label("D"));
        theGrid.add(theSP, 2, 1, 1, 1);

        theGrid.addEventFilter(MouseEvent.MOUSE_PRESSED,     //Creating the mouse event handler 
        new EventHandler<MouseEvent>() { 
            @Override 
            public void handle(MouseEvent e) { 
                System.out.println("We're Moving!!");

                startNode = (Node)e.getTarget();
                sPanes.add((StackPane)startNode);
                col0 = GridPane.getColumnIndex(startNode).intValue();
                row0 = GridPane.getRowIndex(startNode).intValue();
                System.out.printf("Starting at %d %dn", col0, row0);
            }
        });

        theGrid.addEventFilter(MouseEvent.MOUSE_DRAGGED,     //Creating the mouse event handler 
        new EventHandler<MouseEvent>() { 
            Node lastNode = null;

            @Override 
            public void handle(MouseEvent e) { 
                Node target = (Node)e.getTarget();
                double xLoc = e.getX();
                double yLoc = e.getY();
                Bounds bs = target.localToScene(target.getBoundsInLocal());
                Node moveTarget;
                if( bs.contains(xLoc, yLoc) )
                {
                    moveTarget = target;
                }
                else
                {
                    moveTarget = getContainingNode((int)xLoc, (int)yLoc);
                }
                if( moveTarget != null amp;amp; lastNode != moveTarget )
                {
                    col1 = GridPane.getColumnIndex(moveTarget).intValue();
                    row1 = GridPane.getRowIndex(moveTarget).intValue();
                    doHighlighting();
                    lastNode = moveTarget;
                }
            }
        });
    }

    void doHighlighting()
    {
        int c0, c1, r0, r1;

        c0 = col0 > col1 ? col1 : col0;
        c1 = !(col0 > col1) ? col1 : col0;
        r0 = row0 > row1 ? row1 : row0;
        r1 = !(row0 > row1) ? row1 : row0;

        Rectangle2D rec1 = new Rectangle2D(c0, r0, c1-c0 1, r1-r0 1);

        System.out.printf("Box: %d %d %d %dn", c0, c1, r0, r1);
        List<Node> nodes = theGrid.getChildren();
        for( Node node : nodes )
        {
            StackPane sp = (StackPane)node;
            unhighlight(sp);
            int col = GridPane.getColumnIndex(sp).intValue();
            int row = GridPane.getRowIndex(sp).intValue(); 

            if( occupiesCell(sp, rec1) )
            {
                highlight(sp);
            }
        }
    }

    boolean occupiesCell(Node node, Rectangle2D r1)
    {
        boolean result = false;
        int col = GridPane.getColumnIndex(node).intValue();
        int row = GridPane.getRowIndex(node).intValue(); 
        int wid = GridPane.getColumnSpan(node).intValue();
        int hei = GridPane.getRowSpan(node).intValue(); 

        Rectangle2D r2 = new Rectangle2D( col, row, wid, hei);

        return r2.intersects(r1);
    }

    void unhighlight(Node node)
    {
        if( !(node instanceof StackPane) )
        {
            return;
        }
        StackPane label = (StackPane)node;
        List<String> cList = label.getStyleClass();
        cList.remove("b2");
        cList.add("b1");
    }

    void highlight(Node node)
    {
        if( !(node instanceof StackPane) )
        {
            return;
        }
        StackPane label = (StackPane)node;
        List<String> cList = label.getStyleClass();
        cList.remove("b1");
        cList.add("b2");
    }

    private Node getContainingNode(int xLoc, int yLoc)
    {
        Node tgt = null;

        for( Node node : theGrid.getChildren() )
        {
            Bounds boundsInScene = node.localToScene(node.getBoundsInLocal());
            if( boundsInScene.contains(xLoc, yLoc) )
            {
                return node;
            }
        }

        return t>
    }

    private StackPane sPAdd(Label label)
    {
        StackPane gPPane = new StackPane();
        gPPane.getChildren().add(label);
        gPPane.getStyleClass().addAll("b1", "grid-element");
        GridPane.setFillHeight(gPPane, true);
        GridPane.setFillWidth(gPPane, true);

        return gPPane;
    }

    public static void main(String[] args)
    {
        launch();
    }
}
 
 .bg-grid {
    -fx-background-color: slategrey;
}
.grid-element {
    -fx-border-width: 10; 
    -fx-border-color: rgb(225, 128, 217);
    -fx-background-color: rgb(247, 146, 146);
    -fx-font: 36 arial;
}

.b1 {
    -fx-text-base-color: white;
    -fx-border-color: rgb(225, 128, 217);
}
.b2 {
    -fx-text-base-color: lightgray;
    -fx-border-color: rgb(233, 228, 86);
}
 

Комментарии:

1. Не проще ли зарегистрировать обработчики мыши в каждой из ячеек StackPane вместо регистрации обработчика в сетке? И даже с вашей стратегией, действительно ли вам вообще нужно пачкать руки координатами? Разве цель, на которую вы получаете ссылку, не является просто узлом, который вы хотите выделить/осветить в любом случае?

2. Я попробовал несколько обработчиков, но не регистрировался при перетаскивании из одной ячейки в другую. Однако проблема с определением местоположения столбца в ячейках с несколькими столбцами остается.

3. Для нескольких обработчиков, я думаю, вам придется позвонить startFullDrag() в какой-то момент. (В данный момент я не могу легко получить доступ к документам Java.) И теперь я понимаю, в чем проблема. Так что, возможно, это в основном правильный подход, но просто вычислите координаты прямоугольника, нарисованного мышью относительно сетки (как вы уже делаете), создайте BoundingBox представление этого прямоугольника, затем повторите все дочерние элементы сетки и проверьте boundsInParent() , пересекается ли их ограничивающая рамка.

4. Вам нужно только местоположение пикселя для создания BoundingBox . У него есть boolean intersects(Bounds) метод, который вы можете вызвать, передавая каждому дочернему узлу boundInParent()

5. @didntgoboom Да, вы можете ответить на свой собственный вопрос (и принять его как рабочий ответ через некоторое время).

Ответ №1:

После небольшого общения с @james_d и @slaw, наконец, пришло рабочее решение.

 import java.util.*;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent; 
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.Node;
import javafx.stage.Stage;
import javafx.geometry.*;
import javafx.css.PseudoClass;

public class AddTestSSCE extends Application
{
    private static final PseudoClass highlight = PseudoClass.getPseudoClass("highlight"); 
    ArrayList<StackPane> sPanes = new ArrayList<StackPane>(); // selected panes
    GridPane theGrid;
    Node startNode = null;
    int x0, y0, x1, y1;

    @Override
    public void start(Stage stage) {
        theGrid = new GridPane();
        ColumnConstraints col = new ColumnConstraints(300);
        theGrid.getColumnConstraints().addAll(col, col, col);
        RowConstraints row = new RowConstraints(200);
        theGrid.getRowConstraints().addAll(row, row, row);
        addGridPane();
        theGrid.getStyleClass().add("bg-grid");
        Scene scene = new Scene(theGrid, 1024, 768);
        scene.getStylesheets().add("addtestssce.css");
        stage.setScene(scene);
        stage.show();
    }

    public void addGridPane() {
        theGrid.setHgap(10);
        theGrid.setVgap(10);
        theGrid.setPadding(new Insets(0, 10, 0, 10));

        StackPane theSP = sPAdd(new Label("A"));
        theGrid.add(theSP, 0, 0, 1, 1); 

        theSP = sPAdd(new Label("B"));
        theGrid.add(theSP, 1, 0, 2, 1);

        theSP = sPAdd(new Label("C"));
        theGrid.add(theSP, 0, 1, 2, 1);

        theSP = sPAdd(new Label("D"));
        theGrid.add(theSP, 2, 1, 1, 1);

        theGrid.addEventFilter(MouseEvent.MOUSE_PRESSED,     //Creating the mouse event handler 
        new EventHandler<MouseEvent>() { 
            @Override 
            public void handle(MouseEvent e) { 
                System.out.println("We're Moving!!");

                startNode = (Node)e.getTarget();
                sPanes.add((StackPane)startNode);
                x0 = x1 = (int)e.getX();
                y0 = y1 = (int)e.getY();
                doHighlighting();
                System.out.printf("Starting at %d %dn", x0, y0);
            }
        });

        theGrid.addEventFilter(MouseEvent.MOUSE_DRAGGED,     //Creating the mouse event handler 
        new EventHandler<MouseEvent>() { 

            @Override 
            public void handle(MouseEvent e) { 
                Node target = (Node)e.getTarget();
                x1 = (int)e.getX();
                y1 = (int)e.getY();
                Bounds bs = target.localToScene(target.getBoundsInLocal());
                Node moveTarget;
                if( bs.contains(x1, y1) )
                {
                    moveTarget = target;
                }
                else
                {
                    moveTarget = getContainingNode( x1, y1);
                }
                if( moveTarget != null )
                {
                    doHighlighting();
                }
            }
        });
    }

    void doHighlighting()
    {
        int c0, c1, r0, r1;

        c0 = x0 > x1 ? x1 : x0;
        c1 = !(x0 > x1) ? x1 : x0;
        r0 = y0 > y1 ? y1 : y0;
        r1 = !(y0 > y1) ? y1 : y0;

        Bounds dragged = new BoundingBox(c0, r0, c1-c0 1, r1-r0 1);

        for (Node child : theGrid.getChildren()) 
        {
            child.pseudoClassStateChanged(highlight, dragged.intersects(child.getBoundsInParent())); 
        }
    }

    private Node getContainingNode(int xLoc, int yLoc)
    {
        Node tgt = null;

        for( Node node : theGrid.getChildren() )
        {
            Bounds boundsInScene = node.localToScene(node.getBoundsInLocal());
            if( boundsInScene.contains(xLoc, yLoc) )
            {
                return node;
            }
        }

        return t>
    }

    private StackPane sPAdd(Label label)
    {
        StackPane gPPane = new StackPane();
        gPPane.getChildren().add(label);
        gPPane.getStyleClass().addAll("b1", "grid-element");
        GridPane.setFillHeight(gPPane, true);
        GridPane.setFillWidth(gPPane, true);

        return gPPane;
    }

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

и CSS:

 .bg-grid {
    -fx-background-color: slategrey;
}
.grid-element {
    -fx-border-width: 10; 
    -fx-border-color: rgb(225, 128, 217);
    -fx-background-color: rgb(247, 146, 146);
    -fx-font: 36 arial;
}

.grid-element:highlight {
    -fx-text-base-color: lightgray;
    -fx-border-color: rgb(233, 228, 86);
}
 

Комментарии:

1. Вам все еще нужно startNode , target , getContainingNode() , и т. Д.? Сейчас это просто выглядит излишним.