Я пытаюсь создать пользовательский интерфейс на основе 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()
, который заставляет окно рисовать на экране перед тем, как выложить его.
Окно перед изменением размера, как должно выглядеть:
Окно во время увеличения размера окна (вверх и влево), как показано в точке останова при переопределении метода paint()
):
Окно во время изменения размера меньше (вниз и вправо), как видно в точке останова при переопределении метода paint()
):
Конечно, эти скриншоты делаются во время агрессивных движений мышью, но мерцание становится довольно раздражающим даже при более умеренном перетаскивании мыши.
Зеленая область на снимке экрана с увеличенным размером показывает новый фон, который рисуется до того, как будет выполнено какое-либо рисование / макет; похоже, это происходит в базовом ComponentPeer
или собственном диспетчере окон. Синяя область на снимке экрана «уменьшить размер» показывает фон JPanel
, который отображается, но теперь он устарел. Это происходит в Linux (Ubuntu) и Windows XP.
Кто-нибудь нашел способ заставить Window
или JWindow
изменить размер заднего буфера до того, как на экране будут внесены какие-либо изменения, и таким образом избежать этого эффекта мерцания? Возможно, есть системное свойство java.awt....
, которое можно установить, чтобы этого избежать, но я не смог его найти.
Редактировать №2: закомментируйте вызов AWTUtilities.setWindowShape()
(и при необходимости раскомментируйте строку Thread.sleep(10)
в paint()
), затем агрессивно перетащите верхнюю панель, чтобы четко увидеть природу мерцания.
Редактировать №3: Может ли кто-нибудь протестировать это поведение под Sun Java в Windows 7 или Mac OSX?