2010-12-01 12 views
24

Ich versuche, eine benutzerdefinierte UI basierend auf einem JWindow für den Zweck der Auswahl eines Bereichs des Bildschirms zur gemeinsamen Nutzung zu machen. Ich habe JWindow erweitert und Code hinzugefügt, um es in der Größe zu ändern und die Mitte des Fensters unter Verwendung AWTUtilities.setWindowShape() 'auszuschneiden'.Wie kann eine Swing JWindow ohne Flimmern skaliert werden?

Wenn ich den Code ausführe, erfahre ich ein Flimmern, da das Fenster in negativer x- und y-Richtung geändert wird, d. H. Oben und links. Offensichtlich wird das Fenster skaliert und gezeichnet, bevor die Komponenten aktualisiert werden. Im Folgenden finden Sie eine vereinfachte Version des Codes. Wenn sie ausgeführt wird, kann das obere Fenster verwendet werden, um das Fenster nach oben und nach links zu vergrößern. Der Hintergrund des Fensters ist grün, um zu verdeutlichen, wo die Pixel sind, die ich nicht anzeigen möchte.

Edit: den Code Verbesserte das Fenster richtig ein ComponentListener Verwendung zu formen und hinzugefügt unten eine Dummy-Komponente weiter zu flackern (ebenfalls aktualisiert Screenshots) veranschaulicht.

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) {} 
} 

Die überschriebene paint() Verfahren kann als Haltepunkt verwendet werden, oder der es Thread.sleep() uncommented sein kann, eine bessere Sicht auf die Aktualisierung zur Verfügung zu stellen, wie es geschieht.

Mein Problem scheint von der setBounds() Methode zu stammen, die bewirkt, dass das Fenster auf den Bildschirm gemalt wird, bevor es ausgelegt wird.


Fenster vor Ändern der Größe, wie es aussehen soll:

alt text


Fenster während größerer Ändern der Größe (nach oben und links) zum Haltepunkt am überschriebene paint() Methode gesehen):

alt text


Fenster während kleinere Größenänderung (unten und rechts), wie bei überschriebene paint() Verfahren bei Unterbrechungspunkt aus gesehen):

alt text


Zugegeben Diese Screenshots während aggressive Maus ziehen Bewegungen aufgenommen werden, aber das Flimmern wird ziemlich nervig auch für gemäßigtere Maus-Drags.

Der grüne Bereich auf der Größe zu größer Bildschirmabbildung zeigt den neuen Hintergrund, der gezeichnet wird, bevor ein Bild/Layout gemacht wird, scheint es in der zugrunde liegenden ComponentPeer oder native Window Manager passieren. Der blaue Bereich auf dem Bildschirm "Größe verkleinern zu" zeigt an, dass der Hintergrund von JPanel in den Blickpunkt gerückt ist, aber nicht mehr aktuell ist. Dies geschieht unter Linux (Ubuntu) und Windows XP.

Hat jemand einen Weg gefunden, um eine Window oder JWindow Größe in einen Backbuffer zu bringen, bevor irgendwelche Änderungen am Bildschirm vorgenommen werden und so diesen Flimmereffekt vermeiden? Vielleicht gibt es eine java.awt.... Systemeigenschaft, die eingestellt werden kann, um dies zu vermeiden, konnte ich jedoch nicht finden.


Edit # 2: Kommentieren Sie den Aufruf an AWTUtilities.setWindowShape() (und optional die Thread.sleep(10) Linie in paint() Kommentar-), dann die obere Abdeckung zieht um aggressiv, um klar zu der Art des Flimmerns zu sehen.

Bearbeiten # 3: Kann jemand dieses Verhalten unter Sun Java unter Windows 7 oder Mac OSX testen?

+2

@willjcroz: +1, sehr schön formulierte Frage. Und an alle: bitte ** ** ** ** keine Antwort geben ohne ** AUCH ** die Frage zu beantworten. – SyntaxT3rr0r

+1

Ich würde gerne die Antwort auf die gleiche Frage wissen - ich habe es seit Jahren gehasst, aber noch nie eine hacky Workaround gefunden. Getestet auf Windows 7 und obwohl ich nicht die gleichen Effekte wie du bekomme bekomme ich etwas sehr ähnliches. Größenänderungsartefakte werden für jede Java-Anwendung angezeigt, nicht nur für Ihr Beispiel. Seltsamerweise funktioniert das Verschieben eines Fensters in Ordnung, nur das Ändern der Größe ist nervtötend (wahrscheinlich, weil Windows 7 das Verschieben von Fenstern selbst übernimmt und nicht die Größenänderung in Java auslöst - im Gegensatz zu Windows XP, wenn ich mich richtig erinnere). – Domchi

+0

@Domchi: danke für die Bestätigung, dass es unter Windows 7 passiert :-). Das Verschieben des Fensters auf XP scheint glatt ohne Neuanstriche. Vielleicht beziehen Sie sich auf Fenster "Reparatur" auf XP ist schlecht. Compositing WMs (wie in Vista/Win7, OSX und Compiz etc unter Linux) behandeln die "Reparatur" von zuvor verdeckten Teilen von Windows, ohne dass sie Repaints anfordern, hier könnte XP vielleicht herunterfallen. – willjcroz

Antwort

1

Ich habe gerade Ihr Beispiel versucht. Ich habe wirklich ein kleines Flattern gesehen, als ich das Fenster verkleinert habe. Ich habe versucht, den Code-Fragment aus setBounds() beginnen zu ersetzen und endet bei AWTUtilities.setWindowShape(this, newShape); von

setSize(newBounds.width, newBounds.height); 

und sah, dass das Schnippen verschwunden. Also, ich würde Ihnen diese Lösung vorschlagen, wenn Sie keine besonderen Gründe haben, setBounds zu verwenden.

+0

Danke für Ihre Eingabe. Um das richtige Größenänderungsverhalten zu erhalten (dieses Beispiel repräsentiert den Anwendungsfall der Größenänderung nach oben und nach links) muss ich sowohl das Fenster verschieben als auch seine Größe ändern (der 'JWindow'-Ursprung oben links muss geändert werden), daher die Verwendung von 'setBounds()'. – willjcroz

+1

@AlexR: ok, aber viele andere Plattformen AWTUtilities.setWindowShape (this, newShape); funktioniert nicht nur versucht und fehlgeschlagen. Fedora Linux/Ubuntu und OpenJDK. – YumYumYum

1

Ein anderer Ansatz könnte darin bestehen, darauf zu warten, dass der Benutzer das Ziehen/Ändern vor dem Malen beendet. Verwenden Sie möglicherweise eine Begrenzungsbox, um dem Benutzer die Größe des Fensters anzuzeigen, bis das Größenänderungs-Ereignis abgeschlossen ist.

Ich habe bemerkt, dass viele Anwendungen mit Fenster entweder diesen Ansatz nehmen oder mit dem Flackern umgehen. Fast alle Apps, bei denen ich eine aggressive Skalierung versucht habe, haben das gleiche Problem (nicht Java), so dass das Problem möglicherweise eher beim Grafiksubsystem als bei Swing selbst liegt.

+0

Ja, ich denke gerade über den Bounding-Box-Ansatz nach, das einzige Problem dabei ist, dass der Benutzer Präzision braucht (indem er einen exakten Bereich des Bildschirms auswählt) und es daher unerwünscht ist, dass die Schnittstelle zwischen zwei Zuständen wechselt. Wie bei anderen Apps haben Sie versucht, die Größe in Eclipse zu ändern? Trotz seines komplexen Layouts hat es für mich kein Flackern, ruckelt ja, aber flimmerfrei! Dies führt mich zu der Annahme, dass ich Glück haben könnte, SWT zu lernen und zu verwenden. – willjcroz

+2

Ja, ich nehme an, ruckartige/flackern sind eins in der mir. Es ist auf keinen Fall wünschenswert, eine Neulackierung zu sehen. Ich habe meine IDE geändert, während ich meinen Kommentar geschrieben habe - also waren wir auf der gleichen Seite. Ich arbeite mit einigen SWT/RCP-Apps bei der Arbeit, so wie es ein ziemlich gutes Framework bietet, aber in Bezug auf Erweiterbarkeit viel zu wünschen übrig lässt. Das Event-Modell, das es verwendet, ist auch nicht sehr freundlich mit beiden zu arbeiten. Haben Sie versucht, die NetBeans-Plattform auszuprobieren? Ich denke, das ist ein Swing-basierter Frame, auf den es sich zu untersuchen lohnt. –

+0

PS: Ich habe eine alte Maschine benutzt, um sie zu testen und eine Reparatur zu versuchen (2.0 Single Core mit Windows XP) - ich konnte immer noch Flimmern/Ruckeln sehen, wenn ich irgendwelche Größenanpassungen machte. –

2

Ich gebe zu, das ist keine besonders hilfreiche Antwort, aber zu verstehen, was genau Swing ist, kann helfen.

Sehen Sie, Swing erledigt seine ganze Arbeit, kurz, um tatsächlich ein wenig Platz zu schaffen, aus dem OS zu ziehen. Alle Zeichnungen, Widgets usw. sind Java-Code. Es ist nicht so sehr, dass dies zu langsam läuft, da es ohne die Vorteile der 2D-Grafikkartenbeschleunigung und der OS-Rendering-Tricks läuft.

Remember DirectDraw? Alles hat es heute, und Fensterscheiben sind butterweich. Aber wenn Sie jemals einen Computer nehmen, der es aus irgendeinem Grund nicht hat (sagen wir, eine XP-Installation ohne Treiber) bemerken Sie genau diese Art von Verlangsamung.

Mit Swing, da es seinen eigenen Speicherplatz verwaltet, kann das Betriebssystem keinen dieser Rendertricks ausführen, um Ihnen zu helfen.

Jemand kann mit einer Optimierung kommen, die Ihr Problem auf Ihrem Computer behebt, aber ich bin besorgt, dass es das Grundproblem nicht wirklich beheben wird - Swing ist langsam und kann nicht schneller werden.

Sie sollten in native Toolkits schauen. AWT ist OK, aber viele Widgets/etc fehlen. Es ist nativ und integriert, also sollte es viel schneller sein, wenn das alles ist, was Sie brauchen. Ich bin teilweise auf SWT, was Eclipse, Vuze (unter anderem) verwenden. Es verbindet die Natürlichkeit von AWT mit der Leichtigkeit und den Funktionen von Swing und läuft natürlich überall.

EDIT: Es ist ziemlich klar nach dem Lesen von mehr von Ihren Kommentaren, dass Sie absolut verstehen, wie die Fensterung passiert - ich will nicht als herablassend kommen. Nicht nur das, aber Sie sind mehr an der Größenanpassung interessiert, mit der mein Kommentar nicht so viel zu tun hat. Ich würde SWT immer noch empfehlen, weil es nativen Code und schneller ist, aber das ist eine andere Antwort als die obige.

+0

danke für deine Gedanken, keine Sorge, ich fühlte mich nicht bevormundet;). Ja, SWT sieht nach einer Möglichkeit aus, um dies für ein zukünftiges Projekt zu umgehen. Aber da dieses Projekt eine bevorstehende Frist hat und die Tatsache, dass dieser UI-Aspekt in einem Applet ist, das schnelle Ladezeiten erfordert, bietet SWT keine Lösung, die mir jetzt keine Lösung bietet. – willjcroz

+0

scheint auch das Problem tatsächlich innerhalb der AWT-Schicht zu existieren, die Swings "JWindow" zugrunde liegt. Insbesondere der native Peer, 'ComponentPeer', und sein entsprechender nativer Code bieten keine Möglichkeit, nach einer untergeordneten Komponente zu fragen, die sich vor irgendwelchen Aktualisierungen des Bildschirmgeräts in einen bereitgestellten Puffer malt. Dies scheint daher ein Problem für alle AWT- und (schwergewichtigen) Swing-Komponenten der obersten Ebene zu sein. – willjcroz

+0

+1 für Ihre Vorschläge – willjcroz

Verwandte Themen