[Java, Swing] 'overlay' panel laten zien tijdens resizen

Pagina: 1
Acties:

  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 22-11 00:51
Ik ben bezig met het maken van een gui, waarin ik tijdens het resizen van een frame een aantal dingen uitreken. Er wordt onder andere een plaatje gerescaled mbv

Java:
1
 image.getScaledInstance(width, height, scalingSetting);


Dit rescalen (en er zijn andere dingen die potentieel even kunnen duren, zoals het uitrekenen van de positie van nodes), kan een paar seconden duren op de hoogste setting, gedurende welke tijd de gui unresponsive en 'dirty' is.

Ik zie hiervoor 2 mogelijke oplossingen, die ik beiden zonder succes heb proberen te implementeren. De eerste is door de berekeningen uit te voeren door een SwingWorker (wat niet echt helpt omdat de gui toch pas kan worden hertekend wanneer de operatie klaar is) en de andere is door de gebruiker even een bericht te laten zien en de interface af te dekken. Dit is de methode waar ik meer mee probeer te doen. In zijn meest simpele vorm stel ik me zo voor dat ik twee panels kan gebruiken. Een met mijn user interface en een met een bericht ,"resizing...", op een panel die over het andere panel heen wordt getekend. Een voorbeeld hiervan staat in de post hieronder.

De laatste paar regels zijn daarbij waar het om gaat: Het ene panel wordt invisible gemaakt en de andere visible wanneer het bericht moet worden gezien en vice versa.

Echter, dit werkt niet. Het (rode) contents panel blijft actief. Ook als ik het probeer met een invokeLater(Runnable r) of een invokeAndWait(Runnable r), dingen doe met invalidate(), setComponentZOrder(Component c, int index) of werk met andere layoutmanagers. (ik zal jullie de voorbeelden besparen tbv de postlengte)

Als je echter de tweede aanroep
Java:
1
showMessage(false);
niet uitvoert, blijft ie na de resize wel keurig op blauw staan in plaats van rood.

Ik ben hier intussen bijna een volledige werkdag (<- stage) mee bezig, terwijl het niet eens een geplande feature was :X. Waarschijnlijk mis ik iets subtiels in de manier waarop 'componentResized' werkt of een gedraging van Swing die ik mis.

Wie kan mij vertellen hoe ik deze relatief simpele handeling kan uitvoeren?

[ Voor 34% gewijzigd door SilentStorm op 09-03-2007 15:18 ]

Localhost is where the heart is


  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 22-11 00:51
* Bump.

Ik heb de voorbeeldcode hier maar geplaatst, zodat de grootte van de startpost niet zo afschrikt.

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class Test extends JFrame implements ComponentListener
{
    private static final long serialVersionUID = 1L;
    private JPanel contentsPanel;
    private JPanel messagePanel;
    
    public static void main(String[] args)
    {   new Test();     
    }
    
    public Test()
    {   super("Test");
        setLayout(new GridBagLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        contentsPanel = new JPanel();
        messagePanel = new JPanel();
        
        //set sizes
        contentsPanel.setPreferredSize(new Dimension(300,300));
        messagePanel.setPreferredSize(new Dimension(300,300));
        
        //make a visual difference between the contents and the message panel
        contentsPanel.setBackground(Color.red);
        messagePanel.setBackground(Color.blue);
        
        //make the messagepanel invisible initially
        messagePanel.setVisible(false);
        
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;
        
        add(contentsPanel);
        add(messagePanel);
        addComponentListener(this); 
        
        pack();
        setVisible(true);
    }

    public void componentResized(ComponentEvent e)
    {   showMessage(true);
        
        //  do some cpu intensive operation 
        try
        {   System.out.println("start");
            Thread.sleep(1000);         
            System.out.println("stop");
        }
        catch (InterruptedException ie)
        {   ie.printStackTrace();           
        }
        showMessage(false);     
    }
    
    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e){}

    private void showMessage(boolean vis)
    {   contentsPanel.setVisible(!vis);
        messagePanel.setVisible(vis);
        repaint();
    }
}

Localhost is where the heart is


  • akaIDIOT
  • Registratie: Januari 2005
  • Laatst online: 03-10 23:33
Ik heb zelf ook ooit problemen gehad met een JProgressBar die maar niet wilde herteken als swing bezig was. De oplossing was toen de berekeningen zelf in een losse thread te gooien.
Ook heb ik slechte ervaringen met componenten over elkaar heen plaatsen. Het resultaat was toen inderdaad dat het 'tijdelijke' object aan het eind opeens zichtbaar wordt, terwijl die dat al tijden had moeten zijn... :? De oplossing daar was toen het huidige panel even op te slaan, removen en het tijdelijke panel er op te pleuren. Als 'het' klaar is dan simpelweg de procedure omdraaien. Je slaat je twee JPanels op, dus ik zou zeggen dat dit even snel te testen is.
Verder heb ik er geen ervaring mee, maar is misschien de klasse JLayeredPane iets waar je mee kan stunten? ;)

*stu!ter* *boink*


  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 22-11 00:51
akaIDIOT schreef op vrijdag 09 maart 2007 @ 23:36:
Ik heb zelf ook ooit problemen gehad met een JProgressBar die maar niet wilde herteken als swing bezig was. De oplossing was toen de berekeningen zelf in een losse thread te gooien.
Ook heb ik slechte ervaringen met componenten over elkaar heen plaatsen. Het resultaat was toen inderdaad dat het 'tijdelijke' object aan het eind opeens zichtbaar wordt, terwijl die dat al tijden had moeten zijn... :? De oplossing daar was toen het huidige panel even op te slaan, removen en het tijdelijke panel er op te pleuren. Als 'het' klaar is dan simpelweg de procedure omdraaien. Je slaat je twee JPanels op, dus ik zou zeggen dat dit even snel te testen is.
Verder heb ik er geen ervaring mee, maar is misschien de klasse JLayeredPane iets waar je mee kan stunten? ;)
Hmm, ik heb het berekenen al in een aparte SwingWorker thread staan in mijn echte programma en een sleep is volgens mij non-blocking (nog even met 'wait' geprobeerd voor de zekerheid, maar het effect is hetzelfde).

Het gebruik van JLayeredPane geeft hierbij geen verandering. Volgens mij blokt het resizen zelf het repainten.. dus dat die call naar repaint() pas wordt uitgevoerd als het frame het resizen voltooid heeft. Ik zou 'm dan eigenlijk moeten afvangen voordat ie daadwerkelijk begint met resizen of iets moeten doen met override.. maar hoe?

hieronder een code voorbeeld met een layeredpane en een aparte worker thread, zoals je voorstelde:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.concurrent.ExecutionException;

import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingWorker;


public class Test extends JFrame implements ComponentListener
{
    private static final long serialVersionUID = 1L;
    private JLayeredPane contents;
    private JPanel contentsPanel;
    private JPanel messagePanel;
    
    public static void main(String[] args)
    {    new Test();        
    }
    
    public Test()
    {   super("Test");
        setLayout(new GridBagLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        contentsPanel = new JPanel();
        messagePanel = new JPanel();
        
        //set sizes
        contentsPanel.setPreferredSize(new Dimension(300,300));
        messagePanel.setPreferredSize(new Dimension(300,300));
        
        //make a visual difference between the contents and the message panel
        contentsPanel.setBackground(Color.red);
        messagePanel.setBackground(Color.blue);
                
        contents = new JLayeredPane();
        contents.setLayout(new GridBagLayout());
        contents.setPreferredSize(new Dimension(400,400));
        
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;
        c.gridx = 0;
        c.gridy = 0;
        
        contents.add(messagePanel, c, new Integer(1));
        contents.add(contentsPanel, c, new Integer(0));        
        add(contents,c);
        
        addComponentListener(this);    
        
        pack();
        setVisible(true);
    }

    public void componentResized(ComponentEvent e)
    {   showMessage(true);     
        System.out.println("start");    
        //do some cpu intensive operation
        work();
        System.out.println("stop");
      
        showMessage(false);        
    }
    
    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e){}

    private void showMessage(boolean vis)
    {   int pos = vis ? 0 : 1;
        contents.setLayer(contentsPanel,pos);
        repaint();
    }
    
    private void work()
    {   SwingWorker w = new LazyWorker();
        w.execute();
        try
        {   w.get();
        }
        catch (InterruptedException e){}
        catch (ExecutionException e){}    }
    
    class LazyWorker extends SwingWorker<Void, Void>
    {
        @Override
        protected Void doInBackground() throws Exception 
        {   synchronized(this)
            {   try
                {   wait(1000);
                }
                catch (InterruptedException e){}        
            }
            return null;
        }       
    }    
}


edit: wat betreft je over-elkaar-heen-teken acties: als je de tweede 'waitmessage ' (ie eerste post) weglaat gaat het wel goed. Het over elkaar heen zetten zelf geeft hier geen problemen, voor zover ik kan zien.

Localhost is where the heart is


  • akaIDIOT
  • Registratie: Januari 2005
  • Laatst online: 03-10 23:33
Ik ben niet bekend met de SwingWorker, maar als je het in een annonieme thread gooit werkt dit bij mij:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class Test extends JFrame implements ComponentListener {
    private static final long serialVersionUID = 1L;
    private JPanel contentsPanel;
    private JPanel messagePanel;
    
    public static void main(String[] args) {
        new Test();
    }
    
    public Test() {
        super("Test");
        setLayout(new GridBagLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        contentsPanel = new JPanel();
        messagePanel = new JPanel();
        
        //set sizes
        contentsPanel.setPreferredSize(new Dimension(300,300));
        messagePanel.setPreferredSize(new Dimension(300,300));
        
        //make a visual difference between the contents and the message panel
        contentsPanel.setBackground(Color.red);
        messagePanel.setBackground(Color.blue);
        
        //make the messagepanel invisible initially
        messagePanel.setVisible(false);
        
        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;
        
        add(contentsPanel);
        add(messagePanel);
        addComponentListener(this);
        
        pack();
        setVisible(true);
    }
    
    public void componentResized(ComponentEvent e) {
        showMessage(true);
        new Thread(new Runnable(){
            public void run() {
                //do some cpu intensive operation
                try {
                    System.out.println("start");
                    Thread.sleep(1000);
                    System.out.println("stop");
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
                showMessage(false);
            }
        }).start();
    }
    
    public void componentHidden(ComponentEvent e){}
    public void componentMoved(ComponentEvent e){}
    public void componentShown(ComponentEvent e){}
    
    private void showMessage(boolean vis) {
        contentsPanel.setVisible(!vis);
        messagePanel.setVisible(vis);
        repaint();
    }
}


Volgens mij was je bedoeling dat na het resizen het ding eerst blauw, dan na een tijdje weer rood wordt. Dat doet het in ieder geval. Merk op dat de showMessage(false) binnen de Thread staat, iets wat in je worken zie ik net niet het geval is. Anders wordt de tread gestart en de kleur meteen weer op rood gezet ;). Misschien werkt dat ook, probeer maar eens wat, dit lijkt iig te werken.

Ik gebruik Java 1.6, maar dat maakt denk ik in dit geval niets uit.

*stu!ter* *boink*


  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 22-11 00:51
Swing workers (in ieder geval in de vorm van bovenstaand voorbeeld) zijn nieuw in 1.6. Ik doe er ook voor het eerst iets mee :)

Als ik jou code run (copy/paste, geen typefouten oid), heb ik precies hetzelfde effect als met de mijne: Het panel blijft ten alle tijde rood. De bedoeling is dat hij tijdens het resizen blauw zou zijn (ik wil -in mijn complete programma- de gebruiker vertellen dat de gui even niet beschikbaar is tijdens het resizen) en daarna weer terugspringt op rood.

In het ergste geval zou ik hier een extra frame voor kunnen gebruiken, maar ik denk dat dit gewoon mogelijk moet zijn. Waarschijnlijk wordt die paint code echter pas uit wordt gevoerd wanneer de resize compleet is..

'showMessage(false)' hoeft bij de worker trouwens niet binnen de thread. de 'get()' methode is blocking, dus 'work()' returned pas wanneer de worker klaar is met wachten.


--------------------------------------------------------------------------------------

edit: Ik was nog even op zoek naar de betreffende javadocs en documentatie van repaint om wat dieper in te gaan op het repainten en de volgorde en buffers die daarbij gehanteerd worden. Ik las daarbij dit artikel weer door...
The paint request originates from a call to repaint() on an extension of javax.swing.JComponent:
  • 1. JComponent.repaint() registers an asynchronous repaint request to the component's RepaintManager, which uses invokeLater() to queue a Runnable to later process the request on the event dispatching thread.
  • 2. The runnable executes on the event dispatching thread and causes the component's RepaintManager to invoke paintImmediately() on the component, which does the following:
    • 1. uses the clip rectangle and the opaque and optimizedDrawingEnabled properties to determine the 'root' component from which the paint operation must begin (to deal with transparency and potentially overlapping components).
    • 2. if the root component's doubleBuffered property is true, and double-buffering is enabled on the root's RepaintManager, will convert the Graphics object to an appropriate offscreen graphics.
    • 3. invokes paint() on the root component (which executes (A)'s JComponent.paint() steps #2-4 above), causing everything under the root which intersects with the clip rectangle to be painted.
    • 4. if the root component's doubleBuffered property is true and double-buffering is enabled on the root's RepaintManager, copies the offscreen image to the component using the original on-screen Graphics object.
NOTE: if multiple calls to repaint() occur on a component or any of its Swing ancestors before the repaint request is processed, those multiple requests may be collapsed into a single call back to paintImmediately() on the topmost Swing component on which repaint() was invoked. For example, if a JTabbedPane contains a JTable and both issue calls to repaint() before any pending repaint requests on that hierarchy are processed, the result will be a single call to paintImmediately() on the JTabbedPane, which will cause paint() to be executed on both components.
Een manier om dus om de .. vertragende/bufferende repaint(), met z'n invokeLater uit te komen, is om zelf direct paintImmediately() aan te roepen. Ik heb dat geprobeerd door bij mijn laatste voorbeeld op regel 79

Java:
1
paintImmediately(0, 0, contents.getWidth(), contents.getHeight());

neer te zetten in plaats van dit:
Java:
1
repaint();


Met deze aanpassing doet de code precies wat ik wil.
Dezelfde soort aanpassing maken in jou voorbeeld op regel 76 geeft hetzelfde effect:

Java:
1
contentsPanel.paintImmediately(0,0,getWidth(), getHeight());

of
Java:
1
messagePanel.paintImmediately(0,0,getWidth(), getHeight());


Hiermee is mijn probleem opgelost. Bedankt voor het meedenken en de inspiratie, akaIDIOT! :)

En nu ga ik maar slapen, want ik moet er morgen weer om 7 uur uit :P

Localhost is where the heart is


  • akaIDIOT
  • Registratie: Januari 2005
  • Laatst online: 03-10 23:33
Fijn dat het werkt, maar ben wel benieuwd waarom de code die ik poste het bij jou niet doet... Ik draaide het geheel vanuit NetBeans, en daar verscheen vlak na een resize er telkens een blauw vierkant van - hoe kan het ook anders - 300 x 300 px, een seconde lang, voor het weer rood werd...

Maakt ook niet zoveel uit, denk dat ik nog eens naar die worker ga kijken, klinkt wel weer als een leuk Java-abstractie-laagje voor swing :)

Succes met de rest van je stageopdracht! ;)

*stu!ter* *boink*


  • SilentStorm
  • Registratie: November 2000
  • Laatst online: 22-11 00:51
Dank je :) Geen idee waarom jou voorbeeld anders gaat bij mij, mogelijk zorgen andere hardware-eigenschappen ervoor dat er eerder tijd was om paintImmediately intern aan te roepen of zoiets. Volgens de howto kun je een lading threading-gerelateerde problemen krijgen als je geen gebruik maakt van invokeLater; die hebben we allebei niet gebruikt..

Anyway, het zit erin en werkt :)

Localhost is where the heart is

Pagina: 1