Как можно изменить размер Swing JWindow без мерцания?

Я пытаюсь создать пользовательский интерфейс на основе JWindow с целью выбора области экрана для совместного использования. Я расширил JWindow и добавил код, чтобы изменить его размер и «вырезать» центр окна с помощью AWTUtilities.setWindowShape().

При запуске кода я испытываю мерцание, поскольку размер окна изменяется в отрицательных направлениях x и y, то есть вверх и влево. Похоже, что происходит изменение размера окна и его отрисовка перед обновлением компонентов. Ниже представлена ​​упрощенная версия кода. При запуске верхнюю панель можно использовать для изменения размера окна вверх и влево. Фон окна становится зеленым, чтобы было понятно, где находятся пиксели, которые я не хочу отображать.

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

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Area;

import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.LineBorder;

import com.sun.awt.AWTUtilities;

public class FlickerWindow extends JWindow implements MouseListener, MouseMotionListener{

    JPanel controlPanel;
    JPanel outlinePanel;
    int mouseX, mouseY;
    Rectangle windowRect;
    Rectangle cutoutRect;
    Area windowArea;

    public static void main(String[] args) {
        FlickerWindow fw = new FlickerWindow();
    }

    public FlickerWindow() {
        super();
        setLayout(new BorderLayout());
        setBounds(500, 500, 200, 200);
        setBackground(Color.GREEN);

        controlPanel = new JPanel();
        controlPanel.setBackground(Color.GRAY);
        controlPanel.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
        controlPanel.addMouseListener(this);
        controlPanel.addMouseMotionListener(this);

        outlinePanel = new JPanel();
        outlinePanel.setBackground(Color.BLUE);
        outlinePanel.setBorder(new CompoundBorder(new EmptyBorder(2,2,2,2), new LineBorder(Color.RED, 1)));

        add(outlinePanel, BorderLayout.CENTER);
        add(controlPanel, BorderLayout.NORTH);
        add(new JButton("Dummy button"), BorderLayout.SOUTH);
        setVisible(true);
        setShape();

        addComponentListener(new ComponentAdapter() {           
            @Override
            public void componentResized(ComponentEvent e) {
                setShape();
            }});
    }


    public void paint(Graphics g) {
        // un-comment or breakpoint here to see window updates more clearly
        //try {Thread.sleep(10);} catch (Exception e) {}
        super.paint(g);
    }

    public void setShape() {
        Rectangle bounds = getBounds();
        Rectangle outlineBounds = outlinePanel.getBounds();
        Area newShape = new Area (new Rectangle(0, 0, bounds.width, bounds.height));
        newShape.subtract(new Area(new Rectangle(3, outlineBounds.y + 3, outlineBounds.width - 6, outlineBounds.height - 6)));
        setSize(bounds.width, bounds.height);
        AWTUtilities.setWindowShape(this, newShape);
    }

    public void mouseDragged(MouseEvent e) {
        int dx = e.getXOnScreen() - mouseX;
        int dy = e.getYOnScreen() - mouseY;

        Rectangle newBounds = getBounds();
        newBounds.translate(dx, dy);
        newBounds.width -= dx;
        newBounds.height -= dy;

        mouseX = e.getXOnScreen();
        mouseY = e.getYOnScreen();

        setBounds(newBounds);
    }

    public void mousePressed(MouseEvent e) {
        mouseX = e.getXOnScreen();
        mouseY = e.getYOnScreen();
    }

    public void mouseMoved(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
    public void mouseReleased(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
}

Переопределенный метод paint() можно использовать в качестве точки останова или Thread.sleep() можно раскомментировать, чтобы обеспечить более четкое представление об обновлении по мере его появления.

Моя проблема, похоже, проистекает из метода setBounds(), который заставляет окно рисовать на экране перед тем, как выложить его.


Окно перед изменением размера, как должно выглядеть:

alt text


Окно во время увеличения размера окна (вверх и влево), как показано в точке останова при переопределении метода paint()):

alt text


Окно во время изменения размера меньше (вниз и вправо), как видно в точке останова при переопределении метода paint()):

alt text


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

Зеленая область на снимке экрана с увеличенным размером показывает новый фон, который рисуется до того, как будет выполнено какое-либо рисование / макет; похоже, это происходит в базовом ComponentPeer или собственном диспетчере окон. Синяя область на снимке экрана «уменьшить размер» показывает фон JPanel, который отображается, но теперь он устарел. Это происходит в Linux (Ubuntu) и Windows XP.

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


Редактировать №2: закомментируйте вызов AWTUtilities.setWindowShape() (и при необходимости раскомментируйте строку Thread.sleep(10) в paint()), затем агрессивно перетащите верхнюю панель, чтобы четко увидеть природу мерцания.

Редактировать №3: Может ли кто-нибудь протестировать это поведение под Sun Java в Windows 7 или Mac OSX?


person willjcroz    schedule 01.12.2010    source источник
comment
@willjcroz: +1, очень красиво сформулированный вопрос. И для всех: пожалуйста, НЕ голосуйте за любой ответ, не ТАКЖЕ проголосовав за вопрос.   -  person SyntaxT3rr0r    schedule 01.12.2010
comment
Я тоже хотел бы знать ответ на тот же вопрос - я ненавидел его годами, но так и не нашел ни одного хитрого обходного пути. Протестировано на Windows 7, и хотя я получаю не совсем такие же эффекты, как вы, я получаю нечто очень похожее. Артефакты изменения размера видны для каждого приложения Java, а не только для вашего примера. Любопытно, что перемещение окна работает нормально, только изменение размера - отстой (вероятно, потому, что Windows 7 сама обрабатывает перемещение окон и не запускает изменение размера в Java - в отличие от Windows XP, если я правильно помню).   -  person Domchi    schedule 02.12.2010
comment
@Domchi: спасибо за подтверждение того, что это происходит в Windows 7 :-). Перемещение окна на XP кажется плавным, без перерисовок. Возможно, вы имеете в виду «ремонт» окон на плохой XP. Компоновка WM (как в Vista / Win7, OSX, Compiz и т. Д. В Linux) обрабатывает «ремонт» ранее скрытых частей окон без запроса перекраски, здесь, я думаю, XP может упасть.   -  person willjcroz    schedule 02.12.2010
comment
@willjcroz: Да, вы правы - я имел в виду тот факт, что Win7 кэширует текущее изображение окна, поэтому, если EDT застрял по какой-либо причине, вы все равно можете перемещать свое окно и видеть внутренности вместо серого прямоугольника как как только переместишь окошко как на ХР.   -  person Domchi    schedule 06.01.2011


Ответы (3)


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

Видите ли, Swing выполняет всю свою работу, за исключением того, что фактически получает немного места из ОС. Все рисунки, виджеты и т. Д. Представляют собой код Java. Дело не столько в том, что это работает слишком медленно, сколько в том, что он работает без преимущества ускорения 2D-видеокарты и уловок рендеринга ОС.

Помните DirectDraw? Сейчас он есть во всем, а оконные рамы гладкие, как масло. Но если вы когда-нибудь возьмете компьютер, на котором его по какой-то причине нет (скажем, установка XP без драйверов), вы заметите именно этот тип замедления.

С Swing, поскольку он управляет всем своим пространством, ОС не может выполнять какие-либо из этих уловок рендеринга, чтобы вам помочь.

Кто-то может предложить некоторую оптимизацию, которая устранит вашу проблему на вашем компьютере, но я обеспокоен тем, что на самом деле это не решит основную проблему - Swing работает медленно и не может работать быстрее.

Вам следует изучить собственные наборы инструментов. AWT в порядке, но отсутствует много виджетов и т. Д. Он родной и встроенный, поэтому должен быть достаточно быстрым, если это все, что вам нужно. Я неравнодушен к SWT, который используют Eclipse, Vuze (среди прочих). Он сочетает в себе естественность AWT с простотой и функциями Swing и, конечно же, работает везде.

РЕДАКТИРОВАТЬ: после прочтения еще нескольких ваших комментариев становится ясно, что вы полностью понимаете, как происходит работа с окнами - я не хочу показаться снисходительным. Не только это, но вас больше интересует изменение размера, к чему мой комментарий не имеет большого отношения. Я бы по-прежнему рекомендовал SWT, потому что это собственный код и быстрее, но это другой ответ, чем тот, что был выше.

person Robert    schedule 04.12.2010
comment
спасибо за мысли, не волнуйтесь, я не чувствовал покровительства;). Да, SWT действительно выглядит как способ обойти это в каком-то будущем проекте. Но поскольку у этого проекта есть неизбежный крайний срок и тот факт, что этот аспект пользовательского интерфейса находится в апплете, который требует быстрой загрузки, SWT не предлагает мне решение прямо сейчас. - person willjcroz; 05.12.2010
comment
также проблема, похоже, действительно существует на уровне AWT, лежащем в основе JWindow Swing. В частности, нативный одноранговый узел ComponentPeer и соответствующий ему нативный код не предлагают способа попросить дочерний компонент отрисовать себя в предоставленный буфер перед любыми обновлениями устройства экрана. Таким образом, это кажется проблемой для всех компонентов AWT верхнего уровня и (тяжелых) компонентов Swing. - person willjcroz; 05.12.2010

Я только что попробовал твой пример. Я действительно заметил небольшое дрожание при изменении размера окна. Я попытался заменить фрагмент кода, начиная с setBounds() и заканчивая AWTUtilities.setWindowShape(this, newShape); на

setSize(newBounds.width, newBounds.height);

и увидел, что щелчки исчезли. Итак, я предлагаю вам это решение, если у вас нет особых причин использовать setBounds.

person AlexR    schedule 01.12.2010
comment
Спасибо за ваш вклад. Чтобы получить правильное поведение при изменении размера (этот пример представляет собой вариант использования изменения размера вверх и влево), мне нужно как переместить окно, так и изменить его размер (необходимо изменить начало левого верхнего угла JWindow), следовательно, использование setBounds(). - person willjcroz; 01.12.2010
comment
@AlexR: хорошо, но многие другие платформы AWTUtilities.setWindowShape (this, newShape); не работает просто попробовал и потерпел неудачу. Fedora linux / Ubuntu и OpenJDK. - person ; 17.07.2011

Другой подход может заключаться в том, чтобы дождаться, пока пользователь завершит перетаскивание / изменение размера перед рисованием. Возможно, используйте ограничивающую рамку, чтобы показать пользователю размер окна, пока не завершится его событие изменения размера.

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

person Lam Chau    schedule 02.12.2010
comment
да, в настоящее время я рассматриваю подход ограничивающего прямоугольника, единственная проблема заключается в том, что пользователю нужна точность (выбор точной области экрана), и поэтому изменение интерфейса между двумя состояниями нежелательно. Что касается других приложений, пробовали ли вы изменять размер в Eclipse? Несмотря на сложную компоновку, на мой взгляд, у него нулевое мерцание, отрывистое, да, но без мерцания! Это наводит меня на мысль, что мне, возможно, больше повезет с изучением и использованием SWT. - person willjcroz; 02.12.2010
comment
Да, я полагаю, что подергивание / мерцание - одна из причин для меня. В любом случае нежелательно видеть перекраску. Я изменял размер своей IDE, пока писал свой комментарий - так что, полагаю, мы были на одной странице. Я работаю с некоторыми приложениями SWT / RCP на работе, поскольку они обеспечивают довольно хорошую структуру, но оставляют желать лучшего с точки зрения расширяемости. Модель событий, которую он использует, тоже не очень удобна для работы. Вы пробовали опробовать платформу NetBeans? Я думаю, что это фрейм на основе Swing, на который стоит обратить внимание. - person Lam Chau; 03.12.2010
comment
PS: Я использовал старую машину, чтобы проверить вашу и попытаться исправить (одноядерное 2.0 с Windows XP) - я все еще мог видеть мерцание / подергивание, когда я делал какие-либо изменения размера. - person Lam Chau; 03.12.2010