Предварительно рассчитать размеры компонентов вне экрана?

У меня есть JScrollPane с настроенным прокручиваемым представлением, соответствующим его ширине, что позволяет/требует только вертикальной прокрутки. Он заполнен различным количеством JLabels, которые почти всегда переносятся по словам из-за их длины. Я использую BoxLayout, чтобы убедиться, что они всегда на всю ширину, расположены вертикально, и в основном это работает.

Основным требованием к дизайну является точное знание где каждой из этих меток JLabel, чтобы полосу прокрутки можно было программно переместить по центру. Если вы использовали NetBeans, вы должны быть знакомы с этой идеей; Поиск в Chrome, хотя он и не прыгает туда, также указывает, где что находится.

Проблема, с которой я сталкиваюсь, заключается в том, что в какой-то момент макет просто сдается. По тому, как полоса прокрутки прыгает во время прокрутки, вы можете сказать, что на самом деле она еще не все рассчитала. Из-за этого сообщаемые значения getY бесполезны.

Я добавил следующий фрагмент кода отладки;

this.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
        public void adjustmentValueChanged(AdjustmentEvent evt) {
            //getVerseBlock grabs a component from the view, with sanity checks:
            System.out.println(ChapterTab.this.getVerseBlock(255).getY());
            System.out.println(ChapterTab.this.getVerseBlock(255).getHeight());
        }
    });

Как и ожидалось — но нежелательно — позиция Y последнего (или любого другого) элемента продолжает увеличиваться по мере прокрутки панели, поскольку все больше и больше JLabels правильно вычисляют свой размер. Проверка getHeight() действительно показывает, что они установлены на высоте одной строки, не подозревая о необходимости переноса слов, пока они не станут видимыми.

После того, как вся панель была прокручена, она счастлива... на данный момент. Он не отменяет расчет, когда эти JLabels снова исчезают из поля зрения. Но он также не повторно вычисляет при изменении размера окна, в результате чего значения снова становятся бесполезными. (И, кстати, отбросьте математику: компоненты, расположенные слишком высоко над визуализируемой областью, также будут сообщать о неправильной высоте, потенциально увеличивая высоту своего контейнера намного больше новой!)

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

EDIT: меня очень беспокоит, что люди, которые не знают ответа, думают, что это только потому, что они не видят код. Все нормально! Вы не можете знать все; если вы раньше не сталкивались с проблемой и не решили ее, это не делает вас меньшим программистом, и я не ожидаю, что вы ответите. Но мне удалось сократить проблему до очень простого одного файла:

package jscrolltest;


import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;

public class JScrollTest {

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {

        JFrame frame = new JFrame("JScrollTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());

        ScrollableView view = new ScrollableView();
        view.setLayout(new BoxLayout(view, BoxLayout.Y_AXIS));

        JScrollPane pnlScroll = new JScrollPane(view);
        pnlScroll.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        pnlScroll.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        view.add(new JLabel("<html>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc convallis sapien sed tempor scelerisque."));
        view.add(new JLabel("<html>Donec ut leo nec ligula tempus eleifend. Proin in porttitor velit."));
        view.add(new JLabel("<html>Sed a facilisis orci. Nunc a diam feugiat, suscipit nunc nec, porttitor velit."));
        view.add(new JLabel("<html>Maecenas sagittis, est et bibendum luctus, tortor orci hendrerit enim, vitae rhoncus augue libero sit amet est. Phasellus a neque et magna gravida euismod dignissim ac elit."));
        view.add(new JLabel("<html>Ut neque urna, ultrices fermentum quam et, tristique tempus nulla. Curabitur non feugiat leo."));
        view.add(new JLabel("<html>Aenean eu viverra ligula, eu tempor turpis. Suspendisse eu nunc ac urna blandit egestas quis id augue. "));
        view.add(new JLabel("<html>Suspendisse a pulvinar est. Maecenas id congue neque. Donec eleifend nisi quis nisl faucibus sollicitudin."));
        view.add(new JLabel("<html>Aliquam rutrum nulla neque, sit amet sollicitudin massa ultrices quis. Interdum et malesuada fames ac ante ipsum primis in faucibus."));
        view.add(new JLabel("<html>Praesent ut auctor nisl, eget convallis neque. Quisque et vestibulum massa."));
        view.add(new JLabel("<html>Quisque consectetur ex cursus risus interdum, tristique imperdiet ante viverra. Sed et nulla eget sem dapibus fringilla."));

        frame.getContentPane().add(pnlScroll, BorderLayout.CENTER);

        pnlScroll.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
            public void adjustmentValueChanged(AdjustmentEvent evt) {
                System.out.println(view.getComponent(9).getY());
                System.out.println(view.getComponent(9).getHeight());
            }
        });

        frame.setSize(200, 100);
        frame.setVisible(true);
    }

    public static class ScrollableView extends JPanel implements Scrollable {

        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 10;
        }

        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return ((orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width) - 10;
        }

        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        public boolean getScrollableTracksViewportHeight() {
            return false;
        }
    }
}

Как видите, в этом нет ничего особенного; буквально минимум, необходимый для создания фрейма, вставки JScrollPane, настройки его таким образом, чтобы вид соответствовал ширине, и размещения некоторых JLabels вертикально с помощью BoxLayout. Именно так, как описано, никаких особых ухищрений.


person DigitalMan    schedule 01.04.2015    source источник
comment
Возможно, how-to-scroll-to-a-specific -location-in-a-jscrollpane это то, что вы хотите?   -  person tomse    schedule 01.04.2015
comment
Прокрутка до определенного места выполнена и работает. Это получение того конкретного места, которое вызывает проблему.   -  person DigitalMan    schedule 01.04.2015
comment
Once the entire panel has been scrolled through, it's happy... - смысла нет. Прокрутка не влияет на размеры. JLabels, which almost always word-wrap - метки не переносятся по словам или вы используете HTML? ... a way to ensure that every single component in this container will always have completely accurate and current dimension and location values. Компоненты должны иметь текущие размеры после того, как компонент был добавлен на панель, и вы использовали revalidate() на панели для вызова менеджера компоновки. Опубликуйте SSCCE, демонстрирующий вашу проблему.   -  person camickr    schedule 02.04.2015
comment
@camickr JLabel будет нормально переносить слова, если у него есть родитель ограниченной ширины, как я уже говорил. В этом случае JLabels за пределами экрана либо не имеет ширины, либо не получает вызов paint(), который фактически вызывает перенос слов (известный недостаток их дизайна; они не будут запускать эти вычисления до тех пор, пока не будут раскрашены, поэтому числа являются правильными после того, как метки были показаны один раз). В этом случае SSCCE составляет около 6 полных файлов, так что пока я подожду, пока ответит кто-то, более знакомый с ним.   -  person DigitalMan    schedule 02.04.2015
comment
"In this case a "SSCCE" is about 6 full files..." -- тогда, возможно, пришло время реорганизовать ваш код, чтобы попытаться уменьшить его цикломатическую сложность и, таким образом, разрешить меньшие тестируемые блоки. В противном случае вы могли бы долго ждать, так как @camickr знает чертовски много (и намного больше, чем я).   -  person Hovercraft Full Of Eels    schedule 02.04.2015
comment
Учитывая, что вопрос вообще не должен требовать просмотра какого-либо моего кода, поскольку это просто не тот вопрос (вот что мне нужно сделать, а не что здесь не так/пожалуйста, исправьте это), я не уверен, что люди здесь мог бы справиться с этим, если бы они не были предоставлены каждому пользовательскому классу, который я использую.   -  person DigitalMan    schedule 02.04.2015
comment
@DigitalMan Вы, очевидно, не читали, что такое SSCCE. Люди здесь на самом деле, скорее всего, помогут, если вы предоставите правильный ввод. Ваше описание очень расплывчато, и если мы не сможем воспроизвести проблему (всю идею SSCCE), очень трудно помочь вам решить проблему.   -  person Guillaume Polet    schedule 02.04.2015
comment
Аминь @GuillaumePolet. Я хочу сказать, что почти невозможно догадаться, что может быть не так или как это исправить без SSCCE. Да, его создание потребует много труда, но это будет потрачено с пользой.   -  person Hovercraft Full Of Eels    schedule 02.04.2015
comment
@HovercraftFullOfEels Суть SSCCE в том, что в 50% случаев при его написании вы действительно находите решение самостоятельно. Таким образом, SSCCE — самое простое и быстрое решение. В конце концов, вы либо нашли решение, либо можете дать его как правильный вход SO людям. И особенно Swing ТАК люди очень хорошо знают Swing.;   -  person Guillaume Polet    schedule 02.04.2015
comment
they will not run those calculations until painted, - Я добавил ComponentListener на панель и заставил Paint() каждого компонента на панели, и, насколько я могу судить, он работает нормально.   -  person camickr    schedule 02.04.2015
comment
@camickr Не могли бы вы продемонстрировать, как именно? Я пытался реализовать это в полной программе несколько раз, но безрезультатно.   -  person DigitalMan    schedule 02.04.2015
comment
Итак, теперь вам нужен код? Теперь вы знаете, что мы чувствуем. ...Precisely as described, Не совсем так. Вы используете HTML в метках, которые не были описаны. Вы реализовали Scrollable, что означает, что вы НЕ используете функциональность по умолчанию, поэтому вы могли допустить ошибку и неправильно реализовать метод, что вызвало проблему. Это чрезвычайно распространено! Независимо от того, насколько мы хороши, мы все делаем ошибки, и это нормально, но нам нравится видеть SSCCE, чтобы мы могли исключить очевидное. Кроме того, даже если мы не знаем ответа, SSCCE дает нам отправную точку для решения собственных проблем.   -  person camickr    schedule 03.04.2015
comment
I've tried implementing that in the full program - зачем тебе это по полной программе? Цель SSCCE состоит в том, чтобы упростить проблему и сначала найти решение для SSCCE, а затем применить знания к реальному коду. Сказать, что это не работает, не дает нам никакой информации. Опять же, мы не знаем, как вы реализовали логику. Покажите нам код. Может проблема в реализации, а может проблема в подходе. Пока мы не увидим код, мы не можем сказать!   -  person camickr    schedule 03.04.2015
comment
@camickr Я реализовал это как часть моего предыдущего устранения неполадок. Я занимаюсь этим уже больше месяца; ТАКОЕ мое абсолютное, позитивное, крайнее средство, когда единственная надежда состоит в том, чтобы найти кого-то еще, кто решил точно такую ​​же причудливую проблему. Однако я не вызывал paint() явно, так как мне сказали, что это плохая идея. Я попробую.   -  person DigitalMan    schedule 03.04.2015


Ответы (1)


В этом случае JLabels за пределами экрана либо не имеет ширины, либо не получает вызов paint(), который фактически вызывает перенос слов (известный недостаток их дизайна; они не будут запускать эти вычисления до тех пор, пока не будут раскрашены,

Вы говорите, что это проблема дизайна, поэтому единственным решением, которое я могу придумать, будет в лучшем случае взлом. Кажется, работает для вашего SSCCE:

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

public class JScrollTest {

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {

        JFrame frame = new JFrame("JScrollTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());

        final ScrollableView view = new ScrollableView();
        view.setLayout(new BoxLayout(view, BoxLayout.Y_AXIS));

        JScrollPane pnlScroll = new JScrollPane(view);
        pnlScroll.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        pnlScroll.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

        view.add(new JLabel("<html>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc convallis sapien sed tempor scelerisque."));
        view.add(new JLabel("<html>Donec ut leo nec ligula tempus eleifend. Proin in porttitor velit."));
        view.add(new JLabel("<html>Sed a facilisis orci. Nunc a diam feugiat, suscipit nunc nec, porttitor velit."));
        view.add(new JLabel("<html>Maecenas sagittis, est et bibendum luctus, tortor orci hendrerit enim, vitae rhoncus augue libero sit amet est. Phasellus a neque et magna gravida euismod dignissim ac elit."));
        view.add(new JLabel("<html>Ut neque urna, ultrices fermentum quam et, tristique tempus nulla. Curabitur non feugiat leo."));
        view.add(new JLabel("<html>Aenean eu viverra ligula, eu tempor turpis. Suspendisse eu nunc ac urna blandit egestas quis id augue. "));
        view.add(new JLabel("<html>Suspendisse a pulvinar est. Maecenas id congue neque. Donec eleifend nisi quis nisl faucibus sollicitudin."));
        view.add(new JLabel("<html>Sed a facilisis orci. Nunc a diam feugiat, suscipit nunc nec, porttitor velit."));
        view.add(new JLabel("<html>Maecenas sagittis, est et bibendum luctus, tortor orci hendrerit enim, vitae rhoncus augue libero sit amet est. Phasellus a neque et magna gravida euismod dignissim ac elit."));
        view.add(new JLabel("<html>Ut neque urna, ultrices fermentum quam et, tristique tempus nulla. Curabitur non feugiat leo."));
        view.add(new JLabel("<html>Aenean eu viverra ligula, eu tempor turpis. Suspendisse eu nunc ac urna blandit egestas quis id augue. "));
        view.add(new JLabel("<html>Suspendisse a pulvinar est. Maecenas id congue neque. Donec eleifend nisi quis nisl faucibus sollicitudin."));
        view.add(new JLabel("<html>Aliquam rutrum nulla neque, sit amet sollicitudin massa ultrices quis. Interdum et malesuada fames ac ante ipsum primis in faucibus."));
        view.add(new JLabel("<html>Praesent ut auctor nisl, eget convallis neque. Quisque et vestibulum massa."));
        view.add(new JLabel("<html>123Quisque consectetur ex cursus risus interdum, tristique imperdiet ante viverra. Sed et nulla eget sem dapibus fringilla."));

        frame.getContentPane().add(pnlScroll, BorderLayout.CENTER);

        pnlScroll.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
            public void adjustmentValueChanged(AdjustmentEvent evt) {
                System.out.println(view.getComponent(14).getY());
                System.out.println(view.getComponent(14).getHeight());
            }
        });

        frame.setSize(300, 200);
        frame.setVisible(true);
    }

    public static class ScrollableView extends JPanel implements Scrollable, ComponentListener {

        public ScrollableView()
        {
            addComponentListener( this );
        }

        public Dimension getPreferredScrollableViewportSize() {
            return getPreferredSize();
        }

        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 10;
        }

        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return ((orientation == SwingConstants.VERTICAL) ? visibleRect.height : visibleRect.width) - 10;
        }

        public boolean getScrollableTracksViewportWidth() {
            return true;
        }

        public boolean getScrollableTracksViewportHeight() {
            return false;
        }

        //  Implement ComponentListener

        public void componentResized(ComponentEvent e)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    int width = getParent().getSize().width;

                    if (width == 0) return;

                    BufferedImage bi = new BufferedImage(width, 600, BufferedImage.TYPE_INT_RGB);
                    Graphics g = bi.getGraphics();

                    for (Component c: getComponents())
                    {
                        c.paint(g);
                    }

                    revalidate();
                    repaint();
                }
            });
        }

        public void componentHidden(ComponentEvent e) {}

        public void componentMoved(ComponentEvent e) {}

        public void componentShown(ComponentEvent e) {}

    }
}

Не знаю, нужны ли функции invokeLater(), revalidate() и repaint(), я просто просмотрел их. Кроме того, я просто выбрал случайную высоту для BufferedImage. Насколько я знаю, это может работать даже с высотой 1.

person camickr    schedule 03.04.2015
comment
После небольшого изучения науки я сделал несколько открытий, по крайней мере, в отношении этого в SSCCE. Нет необходимости в invokeLater() (проверено, что он уже находится в EDT), или revalidate(), или repaint()... или даже в ширине. BufferedImage 1x1 в порядке. Видите ли, вот почему Swing доставляет мне проблемы. - person DigitalMan; 03.04.2015