2010-12-01 72 views
24

我正在尝试基于JWindow制作自定义用户界面,以便选择要共享的屏幕区域。我扩展了JWindow并添加了代码以使其可调整大小,并使用AWTUtilities.setWindowShape()“切出”窗口的中心。Swing JWindow如何调整大小而不闪烁?

运行代码时,由于窗口在负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()方法断点处看到)调整较大(达和左)在

alt text


窗口:调整大小,因为它应该是之前


窗口

alt text

如在断点见于重写 paint()方法调整大小更小(向下和向右)期间

窗口):

alt text


授予这些屏幕截图中侵略性鼠标拖动运动采取但闪烁变得相当即使对于更温和的鼠标拖动也很烦人。

将大小调整为更大的屏幕截图上的绿色区域显示在绘制/布局完成之前绘制的新背景,它似乎发生在底层ComponentPeer或本机窗口管理器中。 “调整为较小”屏幕截图上的蓝色区域显示JPanel的背景被推入视图,但现在已过时。这发生在Linux(Ubuntu)和Windows XP下。

有没有人找到一种方法来使WindowJWindow调整到后台缓冲区,然后对屏幕进行任何更改,从而避免这种闪烁效果?也许有一个java.awt....系统属性可以设置,以避免这种情况,我找不到一个。


编辑#2:注释掉调用AWTUtilities.setWindowShape()(以及可选地取消注释paint()Thread.sleep(10)的线)然后拖动顶板周围积极为了清楚地看到闪烁的性质。

编辑#3:是否有人能够在Windows 7或Mac OSX上的Sun Java下测试此行为?

+2

@willjcroz:+1,非常好的问题。对所有人:请做**不** ** **没有**的任何回答**也提出问题。 – SyntaxT3rr0r 2010-12-01 12:30:30

+1

我想知道同样问题的答案 - 我讨厌它多年,但从来没有发现即使是一个hacky的解决方法。在Windows 7上测试过,尽管我没有得到与你相同的效果,但我得到的东西非常相似。每个Java应用程序都会显示调整大小的人工制品,而不仅仅是您的示例。奇怪的是,移动一个窗口工作正常,只调整大小(可能是因为Windows 7本身处理移动窗口,并不会触发Java中的调整大小 - 如果我没有记错的话,不像Windows XP)。 – Domchi 2010-12-02 17:17:12

+0

@Domchi:感谢在Windows 7上确认它发生的事情:-)。在XP上移动窗口看起来很顺利,没有重绘。也许你是指XP上的窗口'修复'很差。合成WM(如Vista/Win7,Linux上的OSX和Compiz等)可以处理以前被遮挡的窗口部分的“修复”,而不需要重新绘制,这是我认为XP可能会崩溃的地方。 – willjcroz 2010-12-02 18:59:10

回答

1

我刚刚试过你的例子。调整窗口大小时,我真的看到了一些小小的闪烁。我试图通过

setSize(newBounds.width, newBounds.height); 

更换的代码片段从setBounds()开始和结束在AWTUtilities.setWindowShape(this, newShape);,看到轻弹消失。所以,我会建议你这个解决方案,除非你有任何特殊的理由使用setBounds

1

另一种方法可能是等待用户在绘画前完成拖动/调整大小。也许使用边界框来显示用户窗口的大小,直到他们的大小调整事件完成之后。

我注意到很多窗口化的应用程序会采取这种方法或处理闪烁。几乎所有的应用程序我都尝试过这种具有相同问题(非Java)的积极调整大小,所以问题可能只在于图形子系统而不是Swing本身。

2

我承认这不是一个特别有用的答案,但了解Swing究竟是什么可能会有所帮助。

请参阅Swing完成所有工作,但实际上没有从操作系统中获取一点空间。所有的绘图,小部件等都是Java代码。这并不是因为它运行缓慢,因为它没有2D图形卡加速和OS渲染技巧的好处。

记住DirectDraw?现在一切都有了,窗户都是黄油光滑的。但是如果你曾经因为某种原因抢到了一台没有它的电脑(比如说XP安装没有驱动程序),你会注意到恰恰是这种减速类型。

随着Swing,因为它管理着自己的所有空间,操作系统无法做任何这些渲染技巧来帮助你。

有人可能会附带一些优化来修复您的计算机上的问题,但我担心它只是不能解决基本问题 - Swing很慢并且无法变得更快。

您应该查看本机工具包。 AWT是可以的,但缺少很多小部件/等等。它是原生的,并且是内置的,所以如果这就是你所需要的,它应该足够快。我偏爱SWT,这是Eclipse,Vuze(等等)使用的。它将AWT的本性与Swing的轻松和功能相结合,当然还可以在任何地方运行。

编辑:在阅读了一些更多的评论后,你完全理解了窗口的发生 - 我不想屈就于居高临下。不仅如此,而且您对调整大小更感兴趣,我的评论没有那么多关系。我仍然推荐SWT,因为它是本地代码,速度更快,但这是与上面不同的答案。