Java: Как нарисовать не прокручиваемый оверлей поверх окна просмотра ScrollPane?

Я хотел бы использовать ScrollPane для отображения изображения в своем окне просмотра, а также накладывать на него сетку (или прямоугольник, или любой другой тип регистрации / маркера местоположения). Мне нужно, чтобы наложение оставалось фиксированным при прокрутке (то есть изображение, кажется, перемещается "под" наложением). Я буду прокручивать вид в окне просмотра с фиксированной скоростью, чтобы обеспечить плавное движение, а оверлей должен указывать на определенное место в окне просмотра. Концептуально, представьте себе большую карту, прокручивающуюся в окне просмотра, и область просмотра, имеющую прямоугольник, который не перемещается (относительно самого окна просмотра), который отмечает область, в которую можно увеличить масштаб, основываясь на некоторых действиях пользователя.

Я предполагаю (но еще не подтвердил), что реализация ScrollPane эффективно обрабатывает рендеринг View (из резервного хранилища, не нужно перерисовывать весь View (или даже ViewPort) для каждой новой частичной экспозиции) и поэтому предпочел бы не иметь необходимости Переопределить метод paint().

Я посмотрел на LayerPane, и, хотя и не освоил его, кажется, что это подход грубой силы. ScrollPane является одним из компонентов SplitPane и будет перемещен / изменен в размере. Я ожидаю, что мне придется вручную поддерживать правильные отношения между ViewPort и LayerPane, используя абсолютное позиционирование. Я далеко не эксперт в разработке и реализации графического интерфейса, и я уверен, что мне не хватает возможностей, от очевидных до неясных и элегантных, которые знал бы опытный человек.

Я открыт для любых предложений, а не только для пути, по которому я пошел. Должен ли я (могу ли я) добавить JPanel как один компонент в свою SplitPane, затем добавить LayerPane к тому, который содержит мою ScrollPane и оверлей (другой JPanel) на разных слоях на LayerPane? Есть ли функциональность в ScrollPane для прямой поддержки? Я посмотрел учебники по Java Swing и документацию по API, но еще ничего не видел.

Благодарю.

ОБНОВИТЬ:

Спасибо за ссылку, trashgod. Это было намного проще, чем я ожидал. Нет необходимости в LayerPane, GlassPane, xor-композитинге... Я не знаю, почему мне не пришло в голову попытаться переопределить JViewport.paint() для рисования моего наложения - но с помощью приведенного ниже кода я проверил, что концепция дай мне то, что я ищу. Просто используйте подкласс JViewport, и он делает то, что я хотел (в этом случае наложение прямоугольника в центре области просмотра, в то время как изображение прокручивается внизу). Я не эксперт по GUI, и Java API невероятно широк; Я понятия не имею, как вы, ребята, держите все это в своей голове - но спасибо!

class MyViewport extends JViewport {
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2 = (Graphics2D)g;
        g2.setPaint(Color.BLACK);
        g2.drawRect(getWidth()/4,getHeight()/4,getWidth()/2,getHeight()/2);
    }
}

1 ответ

Решение

Обычно "Swing-программы должны переопределять paintComponent() вместо переопределения paint(), "как уже упоминалось в живописи в AWT и Swing: методы рисования. На основе ScrollPanePaint, который рисует под прокруткой содержимого, следующий пример переопределяет paint() рисовать над прокруткой контента.

ScrollPanePaint

import java.awt.*;
import javax.swing.*;

/**
 * @see https://stackru.com/a/10097538/230513
 * @see https://stackru.com/a/2846497/230513
 * @see https://stackru.com/a/3518047/230513
 */
public class ScrollPanePaint extends JFrame {

    private static final int TILE = 64;

    public ScrollPanePaint() {
        JViewport viewport = new MyViewport();
        viewport.setView(new MyPanel());
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewport(viewport);
        this.add(scrollPane);
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private static class MyViewport extends JViewport {

        public MyViewport() {
            this.setOpaque(false);
            this.setPreferredSize(new Dimension(6 * TILE, 6 * TILE));
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            g.setColor(Color.blue);
            g.fillRect(TILE, TILE, 3 * TILE, 3 * TILE);
        }
    }

    private static class MyPanel extends JPanel {

        public MyPanel() {
            this.setOpaque(false);
            this.setPreferredSize(new Dimension(9 * TILE, 9 * TILE));
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.lightGray);
            int w = this.getWidth() / TILE + 1;
            int h = this.getHeight() / TILE + 1;
            for (int row = 0; row < h; row++) {
                for (int col = 0; col < w; col++) {
                    if ((row + col) % 2 == 0) {
                        g.fillRect(col * TILE, row * TILE, TILE, TILE);
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new ScrollPanePaint();
            }
        });
    }
}

Примечание. Установка непрозрачного компонента (например, JTable) в качестве представления для области прокрутки приведет к странным визуальным ошибкам, например, к перемещению фиксированной синей рамки при прокрутке. Используйте setOpaque(false) на компоненте представления, чтобы исправить это.

Другие вопросы по тегам