自定义绘画代码未正确更新,我尝试尊重剪辑区域。 运行 看看吧

Custom painting code does not update correctly and I try to honor the clip area. Run it and see

这是运行可用的java代码。如果您想对其进行测试,请复制下面的两个代码摘录。编译和 运行 Triangleframe.java

我正在 JPanel 上绘制两个三角形(以后可能会添加更多)。我点击 三角形之一并拖动它。在我决定遵循 Oracle 在本课中推荐的剪辑区域之前,这曾经(有点)起作用: Custom painting

我从repaint()切换到repaint(x,y,with,height)的原因是当我尝试拖动其中一个三角形时,重新绘制非常缓慢,并且不太适合跟随鼠标指针要么(滞后?)。我的理由是留在边界内并仅重新绘制我正在使用的屏幕部分可以解决问题。它确实修复了延迟,但现在只要按下鼠标按钮,我正在重新绘制的边界框似乎就不会移动。三角形仅在边界框内移动。直到我释放鼠标按钮(此时创建了一个新三角形)。最好我只重绘三角形而不重绘边界框,但为了方便起见,我尝试先解决这个问题。希望三角形能够重叠。

查看代码中的注释以获得更深入的解释。

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle; 
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JPanel;

//********************************************************************
//*** This is a stripped down version of the code which shows      ***
//*** the problem. Just create an instance of the class by running *** 
//*** TriangleFrame.java. Then try to drag either triangle with    ***
//*** the mouse.                                                   ***
//********************************************************************
public class TrianglePanel extends JPanel implements MouseListener,
    MouseMotionListener {

    Triangle triangle1 = null;
    Triangle triangle2 = null;
    Rectangle boundingBox = null;

    int lastXPos = 0;
    int lastYPos = 0;
    boolean draggable = false;

    public TrianglePanel() {

        triangle1 = new Triangle(new Point(100, 10), new Point(50, 100),
                new Point(150, 100));
        triangle2 = new Triangle(new Point(250, 10), new Point(150, 100),
                new Point(350, 100));
        lastXPos = this.getX();
        lastYPos = this.getY();

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    public void mouseReleased(MouseEvent e) {

        triangle1.createNewTriangle();
        triangle2.createNewTriangle();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        draggable = false;
        if (triangle1.getPos().contains(e.getPoint())
                || triangle2.getPos().contains(e.getPoint())) {
            draggable = true;
            lastXPos = e.getX();
            lastYPos = e.getY();
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (draggable) {
            isInside(triangle1, e);
            isInside(triangle2, e);
        }
    }

    private void isInside(Triangle t, MouseEvent e) {

        if (t.getPos().contains(e.getPoint()))
            updatePos(t, e);
    }

    //*****************************************************************
    //*** Next I try to do the right thing by only repainting the   ***   
    //*** portion of the panel that I use.                          ***
    //*** Well, almost. To make it as simple as possible for now    ***
    //*** I use a boundingbox rectangle and repaint within those    ***
    //*** bounds. The problem seem to be that the rest of the panel ***
    //*** does not want to repaint anything outside the bounding    ***
    //*** box, until I release the mousebutton(after click+dragging)***
    //*** When the mousebutton is released, a new triangle is created**
    //*** in the same spot. Se mousereleased method. Otherwise      ***
    //*** I would only be able to drag the triangle once            ***
    //*****************************************************************
    private void updatePos(Triangle t, MouseEvent event) {

        boundingBox = t.getPos().getBounds();

        // stored as final variables to avoid repeat invocations to methods.
        // is this a problem? Anybody care to explain?
        final int CURR_X = boundingBox.x;
        final int CURR_Y = boundingBox.y;
        final int CURR_W = boundingBox.width;
        final int CURR_H = boundingBox.height;
        final int OFFSET = 1;

        if ((CURR_X != event.getX()) || (CURR_Y != event.getY())) {

            // paint over the bounding-box of the old triangle
            repaint(CURR_X, CURR_Y, CURR_W + OFFSET, CURR_H + OFFSET);

            // update x-coordinates
            int xPos = event.getX();
            int[] xPoints = t.getPos().xpoints; // get old x coordinates
            for (int i = 0; i < xPoints.length; i++) {
                xPoints[i] = xPoints[i] - (lastXPos - xPos); // create new x
                                                             // coordinates
            }
            lastXPos = xPos;

            // update y-coordinates
            int yPos = event.getY();
            int[] yPoints = t.getPos().ypoints; // get old y coordinates
            for (int i = 0; i < yPoints.length; i++) {
                yPoints[i] = yPoints[i] - (lastYPos - yPos); // create new y
                                                             // coordinates
            }
            lastYPos = yPos;

            // paint inside bounding box of the new triangle
            repaint(boundingBox.x, boundingBox.y, boundingBox.width + OFFSET,
                    boundingBox.height + OFFSET);

            // repaint the whole panel (not recommended).
            // repaint(); //-> makes dragging the triangle sluggish.
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.red);
        triangle1.draw(g);
        triangle2.draw(g);
    }

    // not used
    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    /**
     * 
     * Inner Triangle Class.
     * A polygon object is used for convenience to
     * create the Triangle. Otherwise I would
     * have to deal with Points all through the program. Which means
     * 3 coordinate pairs = 6 coordinates, which
     * means more code.
     * 
     */
    private class Triangle {

        private Polygon polygon;

        private Triangle(Point p1, Point p2, Point p3) {

            polygon = new Polygon();
            polygon.addPoint(p1.x, p1.y);
            polygon.addPoint(p2.x, p2.y);
            polygon.addPoint(p3.x, p3.y);
        }

        public Polygon getPos() {
            return polygon;
        }

        public void createNewTriangle() {
            polygon = new Polygon(polygon.xpoints, polygon.ypoints,
                    polygon.npoints);
        }

        public void draw(Graphics g) {
            g.fillPolygon(polygon);
        }

    } // end inner class Triangle
} // end outer class TrianglePanel

为了您的方便,我提供了 class 包含主要方法(运行nable from here):

import java.awt.Dimension;
import javax.swing.JFrame;

public class TriangleFrame extends JFrame {

public TriangleFrame() {
    this.setTitle("Draggable triangles. Click one and drag it with the   mouse.");
    TrianglePanel panel = new TrianglePanel();
    panel.setPreferredSize(new Dimension(500, 500));
    this.add(panel);
    pack();
    setVisible(true);
}
public static void main(String[] args) {
    new TriangleFrame();

}

}

多边形缓存边界框。如果直接修改坐标,必须调用Polygon.invalidate():

// paint inside bounding box of the new triangle
t.getPos().invalidate();
boundingBox = t.getPos().getBounds();
repaint(boundingBox);

但是,使用 Polygon.translate(int deltaX, int deltaY),它会为您完成所有工作。 (修改坐标并确保边界框在下次调用时正确)。我也用了repaint(Rectangle).

The triangle is only moving withing the bounding box.

因为正如您在问题中所述,您只在 mouseReleased 上创建了一个新的 Triange,因此边界框永远不会改变。

我个人从来不喜欢拖动 "painted" 对象。我宁愿处理真实的组件并将它们拖到屏幕上。

如果您对这种方法感兴趣,可以查看 Playing With Shapes。它显示了如何使用 ShapeIcon 创建 ShapeComponent。您可以使用 Polygon class.

轻松创建 ShapeIcon

然后您可以使用 Component Mover 在屏幕上拖动组件。 ComponentMover class 只是使用 setLocation() 方法移动组件。

当您使用该方法时,您无需担心新的多边形坐标,因为多边形被绘制为图标,它始终以与标签相同的偏移量绘制。

我尝试使用重绘边界框,但是当我快速移动三角形时,重绘在之前的位置留下了三角形的痕迹。

这是我创建的 GUI。

这是三角形面板 class。

package com.ggl.triangle;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;

//********************************************************************
//*** This is a stripped down version of the code which shows      ***
//*** the problem. Just create an instance of the class by running *** 
//*** TriangleFrame.java. Then try to drag either triangle with    ***
//*** the mouse.                                                   ***
//********************************************************************
public class TrianglePanel extends JPanel implements MouseListener,
        MouseMotionListener {

    private static final long serialVersionUID = 5615435125201426466L;

    private static final boolean DEBUG = false;

    private List<Triangle> triangles;

    private Triangle dragTriangle;

    private int originalXPos = 0;
    private int originalYPos = 0;

    private boolean draggable = false;

    public TrianglePanel() {
        setPreferredSize(new Dimension(600, 400));

        triangles = new ArrayList<>();
        triangles.add(new Triangle(Color.RED, new Point(100, 10), new Point(50,
                100), new Point(150, 100)));
        triangles.add(new Triangle(Color.BLUE, new Point(350, 10), new Point(
                250, 100), new Point(450, 100)));

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (draggable) {
            repaint();
        }

        this.originalXPos = 0;
        this.originalYPos = 0;

        this.draggable = false;
    }

    @Override
    public void mousePressed(MouseEvent e) {
        this.draggable = false;

        Point p = e.getPoint();
        if (DEBUG) {
            System.out.println("mousePressed: x: " + p.x + ", y: " + p.y);
        }

        for (Triangle triangle : triangles) {
            if (DEBUG) {
                System.out.println(triangle);
            }

            if (triangle.contains(p.x, p.y)) {
                if (DEBUG) {
                    System.out.println("Mouse pressed point is contained "
                            + "in the triangle");
                }

                originalXPos = p.x;
                originalYPos = p.y;
                dragTriangle = triangle;
                draggable = true;
                break;
            } else {
                if (DEBUG) {
                    System.out.println("Mouse pressed point is not "
                            + "contained in the triangle");
                }
            }
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (draggable) {
            dragTriangle.moveTriangle(e.getX() - originalXPos, e.getY()
                    - originalYPos);
            originalXPos = e.getX();
            originalYPos = e.getY();

            repaint();
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        for (Triangle triangle : triangles) {
            triangle.draw(g);
        }
    }

    // not used
    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    /**
     * 
     * Inner Triangle Class. A polygon object is used for convenience to create
     * the Triangle. Otherwise I would have to deal with Points all through the
     * program. Which means 3 coordinate pairs = 6 coordinates, which means more
     * code.
     * 
     */
    private class Triangle {

        private Color color;

        private Polygon polygon;

        private Triangle(Color color, Point p1, Point p2, Point p3) {
            this.color = color;

            polygon = new Polygon();
            polygon.addPoint(p1.x, p1.y);
            polygon.addPoint(p2.x, p2.y);
            polygon.addPoint(p3.x, p3.y);
        }

        public boolean contains(int x, int y) {
            return polygon.contains(x, y);
        }

        public void moveTriangle(int x, int y) {
            polygon.translate(x, y);
        }

        public void draw(Graphics g) {
            g.setColor(color);
            g.fillPolygon(polygon);
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("Triangle [polygon=");
            polygonToString(builder, polygon);
            builder.append("]");

            return builder.toString();
        }

        private void polygonToString(StringBuilder builder, Polygon polygon) {
            for (int i = 0; i < polygon.npoints; i++) {
                Point p = new Point(polygon.xpoints[i], polygon.ypoints[i]);
                builder.append(p);
                if (i < (polygon.npoints - 1)) {
                    builder.append(",");
                } else {
                    builder.append("]");
                }
            }
        }

    } // end inner class Triangle

} // end outer class TrianglePanel

这是 TriangleFrame class。

package com.ggl.triangle;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TriangleFrame extends JFrame {

    private static final long serialVersionUID = -4599398094173430071L;

    public TriangleFrame() {
        init();
    }

    private void init() {
        this.setTitle("Draggable triangles. Click one and drag "
                + "it with the mouse.");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        TrianglePanel panel = new TrianglePanel();
        this.add(panel);
        pack();
    }

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                TriangleFrame frame = new TriangleFrame();
                frame.setVisible(true);
            }

        };

        SwingUtilities.invokeLater(runnable);
    }
}

让我知道这在您的计算机上是否足够快。