Как можно изменить размер Swing JWindow без мерцания? - PullRequest
24 голосов
/ 01 декабря 2010

Я пытаюсь создать пользовательский интерфейс на основе 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...., которое можно установить, чтобы избежать этого, хотя я не смог его найти.


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

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

Ответы [ 3 ]

2 голосов
/ 04 декабря 2010

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

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

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

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

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

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

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

1 голос
/ 02 декабря 2010

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

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

1 голос
/ 01 декабря 2010

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

setSize(newBounds.width, newBounds.height);

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

...