2010-12-01 46 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,因爲它是本地代碼,速度更快,但這是與上面不同的答案。