Как увеличить JPanel, не перемещая центр: математика или Swing?

Я пытаюсь сделать масштабируемую карту с помощью Swing. Карта представляет собой JPanel в JScrollPane. При увеличении карта меняет размер, а paint() рисует элементы в другом положении. Все это прекрасно работает.

Однако ScrollPane не менял область просмотра при увеличении размера изображения, поэтому увеличение всегда перемещало элементы, на которые я смотрел, за пределы экрана. Я пытался решить это с помощью scrollRectToVisible(), но мне не удается получить правильные координаты для прямоугольника, либо потому, что я не умею делать геометрию, либо потому, что я не очень хорошо понимаю Swing.

Вот что у меня есть:

public class MapPanel extends JPanel {
    [...]

public void setZoom(double zoom) {
    // get the current viewport rectangle and its center in the scaled coordinate system
    JViewport vp = (JViewport) this.getParent();
    Rectangle rect = vp.getViewRect(); 
    Point middle = getMiddle(rect); 
    Dimension dim = rect.getSize();

    // zoom in
    scaler.setZoom(zoom);
    setPreferredSize(scaler.transform(dim));    
    this.revalidate();  

// calculate the full size of the scaled coordinate system  
    Dimension fullDim = scaler.transform(dim); 
    // calculate the non-scaled center of the viewport
    Point nMiddle = new Point((int) ((double) (middle.x)/fullDim.width*dim.width),(int) ((double) (middle.y)/fullDim.height*dim.height));

    // this should do the trick, but is always a bit off towards the origin
    scrollRectToVisible(getRectangleAroundPoint(nMiddle)); 

    // the below alternative always zooms in perfectly to the center of the map 
    // scrollRectToVisible(getRectangleAroundPoint(new Point(400,300)));
}

private Rectangle getRectangleAroundPoint(Point p){
    Point newP = scaler.transform(p);
    Dimension d = railMap.getDimension();
    Point corner = new Point(newP.x-d.width/2,newP.y-d.height/2);
    return new Rectangle(corner,d);
}

private Point getMiddle(Rectangle r){
    return new Point(r.x+r.width/2,r.y+r.height/2);
}
}

А вот класс Scaler (который, я думаю, не делает ничего особенно удивительного):

public class Scaler {
    private double zoom = 1;

public void setZoom(double zoom) {
    this.zoom = zoom;
}


public Point transform(Point2D p){
    return new Point((int) (p.getX()*zoom), (int) (p.getY()*zoom));
}


public Dimension transform(Dimension d){
    return new Dimension((int) (d.width*zoom), (int) (d.height*zoom));
}

}

Кто может сказать мне, где что-то идет не так? Мне кажется, я сделал правильный расчет текущего центра карты, и с фиксированной точкой масштабирования это работает...

Редактировать: поэтому здесь сложно создать новый прямоугольник области просмотра на основе старого прямоугольника области просмотра.


person roelandvanbeek    schedule 04.10.2012    source источник
comment
железнодорожная карта? Вы бы не работали над чем-то вроде JMRI, не так ли? ;-)   -  person geowar    schedule 21.05.2017


Ответы (2)


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

public class TestZooming {

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

    public TestZooming() {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException classNotFoundException) {
                } catch (InstantiationException instantiationException) {
                } catch (IllegalAccessException illegalAccessException) {
                } catch (UnsupportedLookAndFeelException unsupportedLookAndFeelException) {
                }

                JFrame frame = new JFrame();
                frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);
                frame.setLayout(new BorderLayout());
                final ZoomPane pane = new ZoomPane();
                frame.add(new JScrollPane(pane));
                frame.setVisible(true);

                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        pane.centerInViewport();
                    }

                });

            }
        });

    }

    protected class ZoomPane extends JPanel {

        private Image background;
        private Image scaled;
        private float zoom = 1f;

        private Dimension scaledSize;
        private JViewport con;

        public ZoomPane() {

            try {
                background = ImageIO.read(new File("..."));
                scaled = background;
                scaledSize = new Dimension(background.getWidth(this), background.getHeight(this));
            } catch (IOException ex) {
                ex.printStackTrace();
            }

            InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "plus");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.SHIFT_DOWN_MASK), "plus");
            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "minus");

            am.put("plus", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    setZoom(getZoom() + 0.1f);
                }
            });
            am.put("minus", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    setZoom(getZoom() - 0.1f);
                }
            });

            setFocusable(true);
            requestFocusInWindow();

        }

        @Override
        public void addNotify() {

            super.addNotify();

        }

        public float getZoom() {
            return zoom;
        }

        public void setZoom(float value) {
            if (zoom != value) {
                zoom = value;

                if (zoom < 0) {
                    zoom = 0f;
                }

                int width = (int) Math.floor(background.getWidth(this) * zoom);
                int height = (int) Math.floor(background.getHeight(this) * zoom);
                scaled = background.getScaledInstance(width, height, Image.SCALE_SMOOTH);
                scaledSize = new Dimension(width, height);

                if (getParent() instanceof JViewport) {

                    int centerX = width / 2;
                    int centerY = height / 2;

                    JViewport parent = (JViewport) getParent();
                    Rectangle viewRect = parent.getViewRect();
                    viewRect.x = centerX - (viewRect.width / 2);
                    viewRect.y = centerY - (viewRect.height / 2);
                    scrollRectToVisible(viewRect);
                }

                invalidate();
                repaint();

            }
        }

        @Override
        public Dimension getPreferredSize() {

            return scaledSize;

        }

        @Override
        protected void paintComponent(Graphics g) {

            super.paintComponent(g);

            if (scaled != null) {

                g.drawImage(scaled, 0, 0, this);

            }

        }

        protected void centerInViewport() {

            Container container = getParent();
            if (container instanceof JViewport) {

                JViewport port = (JViewport) container;
                Rectangle viewRect = port.getViewRect();

                int width = getWidth();
                int height = getHeight();

                viewRect.x = (width - viewRect.width) / 2;
                viewRect.y = (height - viewRect.height) / 2;

                scrollRectToVisible(viewRect);

            }

        }
    }
}

Что касается того, почему у вас не работает, я не могу сказать, я не могу запустить пример, но, может быть, это по крайней мере даст вам некоторые идеи...

person MadProgrammer    schedule 04.10.2012
comment
Спасибо. Однако, если я правильно понимаю, вы всегда приближаетесь к центру изображения. Это сработало для меня. Чего не было, так это вычисления центра текущей точки обзора и приближения к этому центру. Как вы думаете, сможете ли вы приспособить свой пример к этому? Я думаю, что это должно быть очень похоже на вашу функцию centerInViewport(), за исключением того, что viewRect.x и .y должны рассчитываться немного по-другому. Я тоже думаю, что знаю, как (см. мой пример), но это не работает... - person roelandvanbeek; 04.10.2012
comment
Неважно, я решил это с помощью некоторых подсказок из вашего примера. Опубликую свое решение для полноты, как только закончатся восемь часов. - person roelandvanbeek; 04.10.2012

Решил это. Ура. До сих пор не уверен, где на самом деле что-то пошло не так, но перемещение исходного прямоугольника (спасибо @MadProgrammer), а не создание нового, и правильное округление в масштабере, возможно, помогли.

private Point getViewportCenter() {
    JViewport vp = (JViewport) this.getParent();
    Point p = vp.getViewPosition();
    return new Point(p.x+vp.getWidth()/2,p.y+vp.getHeight()/2);
}

private void setViewportCenter(Point p) {
    JViewport vp = (JViewport) this.getParent();
    Rectangle viewRect = vp.getViewRect();

    viewRect.x = p.x - viewRect.width / 2;
    viewRect.y = p.y - viewRect.height / 2;

    scrollRectToVisible(viewRect);
}

public void setZoom(double zoom) {
    // determine unscaled center and dimensions
    Point oCenter = scaler.inverseTransform(getViewportCenter());
    Dimension dim = railMap.getDimension();

    // zoom
    scaler.setZoom(zoom);

    // fix size and viewport
    setPreferredSize(scaler.transform(dim));
    setViewportCenter(scaler.transform(oCenter)); // should be a transformed point

    // finish
    invalidate();
    repaint();
}
person roelandvanbeek    schedule 04.10.2012