Forum: PC-Programmierung java swing jscrollpane


von tom (Gast)


Lesenswert?

Hallo allerseits,

folgendes Problem:
Ich habe einen Java-JFrame, der eine JScrollPane enthält, die wiederum 
ein großes JPanel enthält:
1
...
2
public MyFrame(){
3
   JPanel p = new JPanel();
4
   JScrollPane sp = new ScrollPane(p,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
5
   sp.getHorizontalScrollBar().addAdjustmentListener(irgendeinAdjustmentListener);
6
   sp.getVerticalScrollBar().addAdjustmentListener(irgendeinAdjustmentListener);
7
}
8
...

Dann wird das JPanel "bemalt", was relativ viel Zeit in Anspruch nimmt, 
da es sich um eine große Farbmatrix handelt.
Wenn ich jetzt scrolle, wird die
1
adjustmentValueChanged()
 Methode des AdjustmentListeners der aufrufenden JScrollBar aufgerufen, 
die ein
1
repaint()
 des JPanel zur Folge hat.

Zum Problem:
Lasse ich bei jedem repaint() des JPanel die gesamte Farbmatrix neu 
malen, zeigt er beim scrollen alles schön ABER es dauert zu lang, bis 
das gesamte JPanel neu gemalt ist.
Lasse ich das JPanel nicht neu malen beim scrollen, so wird dort, wohin 
ich srolle nur weißer Hintergrund angezeigt.

Wie schaffe ich es auf einfachem Wege, dass das die Farbmatrix des 
JPanel beim Scrollen überall zu sehen ist, ohne bei jedem Scrollen das 
gesamte JPanel neu zeichnen zu müssen?

von Markus M. (mark_m)


Lesenswert?

Du musst wahrscheinlich das "Scrollable interface" implementieren.

Grüsse

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

tom schrieb:
> Wie schaffe ich es auf einfachem Wege, dass das die Farbmatrix des
> JPanel beim Scrollen überall zu sehen ist, ohne bei jedem Scrollen das
> gesamte JPanel neu zeichnen zu müssen?

Am einfachsten indem man (nicht nur beim scrollen) das Bild auf ein 
BufferedImage zeichnet und immer wenn sich etwas geändert hat dieses neu 
malt und (im AWT Thread) repaint() aufruft.
Dieses Bild kann man dann in der Paintmethode gemütlich ausgeben, und 
muss auch nicht ständig neuzeichnen. Während das Bild gemalt wird, 
könnte man sogar einen Loadingindicator zeigen...

von tom (Gast)


Lesenswert?

Hallo Markus,
danke für die schnelle Antwort!

Zuerst mal klappt deine Lösung ziemlich gut.
Es gibt nur noch ein Problem:
Scrolle ich vertikal, so klappt alles super.
Scrolle ich aber horizontal, wird nur der letzte "Block" der Farbmatrix 
in horizontaler Richtung hinten dran gesetzt.
Scrolle ich DANACH wieder vertikal, passiert das gleiche in vertikaler 
Richtung.

Konstruktor JFrame:
1
...
2
CanvasPanel canvasPanel = new CanvasPanel();
3
ScrollPaneListener spListener = new ScrollPaneListener();
4
scrollPane = new JScrollPane(canvasPanel,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);        scrollPane.getHorizontalScrollBar().addAdjustmentListener(spListener);        scrollPane.getVerticalScrollBar().addAdjustmentListener(spListener);
5
...
6
[\code]
7
8
ScrollPaneListener:
9
[code]
10
class ScrollPaneListener implements AdjustmentListener{
11
   public void adjustmentValueChanged(AdjustmentEvent e){
12
      // nur wenn gescrollt wurde
13
      if(scrollPane.getHorizontalScrollBar().getValueIsAdjusting() || scrollPane.getVerticalScrollBar().getValueIsAdjusting()
14
         canvasPanel.setDrawMode(CanvasPanel.NONE);
15
   }
16
}

CanvasPanel (implementiert Interface Scrollable):
1
...
2
public void paint(Graphics g){
3
   if(drawMode==NONE) // wenn gescrollt wird
4
      return;
5
   else{
6
      ...
7
      // sonst male Farbmatrix
8
      // sehr zeitaufwendig
9
   }
10
}
11
...
12
// Scrollable Methoden
13
public Dimension getPrefferedScrollableViewportSize(){
14
   return this.getPreferredSize();
15
}
16
17
public int getScrollableUnitIncrement(Rectangle rect, int orientation, int dir){
18
   if(orientation==SwingConstants.HORIZONTAL)
19
      return horStepSize;
20
   return vertStepSize;
21
}
22
23
public int getScrollableBlockIncrement(Rectangle rect, int orientation, int dir){
24
   if(orientation==SwingConstants.HORIZONTAL)
25
      return horBlockSize;
26
   return vertBlockSize;
27
}
28
29
public int getScrollableTracksViewportWidth(){
30
   return false;
31
}
32
33
public int getScrollableTracksViewportHeight(){
34
   return false;
35
}
36
...

von tom (Gast)


Lesenswert?

Hallo Läubi,

danke für den Vorschlag.
Hab leider noch nie mit BufferedImage gearbeitet.
Hast du ein Codeschnipsel, das mir deine Idee verdeutlicht?

Danke

von Markus M. (mark_m)


Lesenswert?

Man könnte jetzt lange das Vorgehen Diskutieren. Ich denke ein Beispiel 
bringt dir mehr.

Oracle stellt ein schönes Tutorial bereit:
http://docs.oracle.com/javase/tutorial/uiswing/components/scrollpane.html#scrollable

Ich hoffe Du kommst mit Englisch einigermassen zurecht.

Grüsse

von tom (Gast)


Lesenswert?

Also ich habe es gelöst mit Läubis Weg und es funktioniert sehr gut.

CanvasPanel:
1
...
2
BufferedImage buffImage = new BufferedImage(breite,hoehe,BufferedImage.TYPE_INT_RGB);
3
...
4
@Override
5
public void paintComponent(Graphics g){
6
7
   super.paintComponent(g);
8
9
   if(buffImage==null)
10
      return;
11
   
12
   if(drawMode==NONE){ // wenn nur gescrollt wird
13
      g.drawImage(buffImage,0,0,null);
14
      return;
15
   }
16
17
   Graphics gImage = buffImage.createGraphics();
18
19
   /****
20
   Hier jetzt alle Zeichenaufrufe auf dem Objekt gImage!
21
   z.B. gImage.drawLine(0,0,10,10);
22
   ****/
23
24
   g.drawImage(buffImage,0,0,null);
25
}
26
...

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Wenn du jetzt das Zeichnen noch außerhalb des AWT Threads machst geht es 
noch besser ;-)
Das geht z.B. mit einer AtomicReference in etwa so:

1
AtomicReference<BufferedImage> buffImage = new AtomicReference();
2
...
3
@Override
4
public void paintComponent(Graphics g) {
5
   BufferedImage img = buffImage.get();
6
   super.paintComponent(g);
7
   if(img==null)
8
      return;
9
   g.drawImage(img,0,0,this);
10
}
11
12
public void setData(List<..> meineDaten,...) {
13
   BufferedImage img = new BufferedImage(breite,hoehe,BufferedImage.TYPE_INT_RGB);
14
   Graphics img = buffImage.createGraphics();
15
16
   /****
17
   Hier jetzt alle Zeichenaufrufe auf dem Objekt gImage!
18
   z.B. gImage.drawLine(0,0,10,10);
19
   ****/
20
   buffImage.set(img);
21
}
Die Signatur von setData ggf. an deine Bedürfnisse anpassen. Wenn du 
dann in einem Swingworker in der execute Methode setData aufrufst und in 
done dann repaint bleibt deine GUI reaktiv und blockiert auch nicht beim 
ersten Aufruf, ebenso nicht beim scrollen.

von tom (Gast)


Lesenswert?

Super, vielen Dank!

Thread closed

von tom (Gast)


Lesenswert?

Vllt noch mal eine Frage zu dem SwingWorker.
Wie benutze ich den? Die Oracle API hat mir nicht wirklich 
weitergeholfen.

Mein Programm soll folgendes machen:
Es gibt ein Haupt-JPanel, darin befindet sich eine JScrollPane und ein 
JButton. Die JScrollPane hält das CanvasPanel.
- Wird jetzt der Button gedrückt, werden die Daten geändert und das Bild 
im CanvasPanel mit diesen neuen Daten neu gemalt.
- Zusätzlich gibt es im Haupt-JPanel einen per JButton aktivierbaren 
Timer, der die neuen Daten aller x ms automatisch berechnet und das 
CanvasPanel automatisch aktualisiert.

Hab halt bisher immer in der actionPerformed() des Timers repaint() 
aufgerufen aber das ist mit meinem oben aufgeführten Code doch recht 
langsam.

MainPanel:
1
...
2
button1.addActionListener(new ActionListener(){
3
4
public void actionPerformed(ActionEvent e){
5
   daten = berechneNeueDaten();
6
   canvasPanel.setDaten(daten)
7
   canvasPanel.repaint();
8
}
9
});
10
11
button2.addActionListener(new ActionListener(){
12
   
13
   Timer timer;
14
15
   public void actionPerformed(ActionEvent e){
16
      timer = new Timer(stepTime, new ActionListener(){
17
       
18
         public void actionPerformed(ActionEvent e){
19
            daten = berechneNeueDaten();
20
            canvasPanel.setDaten(daten);
21
            canvasPanel.repaint();
22
         }
23
      });
24
25
      timer.start();
26
27
      // blockiere AWT-Thread bis OK gedrückt wird
28
      JOptionPane.showMessageDialog(parentFrame, "Timer mit OK stoppen", "",0);
29
   
30
      timer.stop();
31
   }
32
});
33
...

Wie könnte ich das ganze in einem SwingWorker realisieren, um deinen 
obigen Code für CanvasPanel nutzen zu können?

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Ich hab dir doch schon oben geschrieben wie man das (asyncron) machen 
könnte.
Du must einfach mal deine Gedanken/den Code etwas Strukturieren.
Es gibt hier doch mehrere Einzelaufgaben, die man sehr schön trennen:

- Das Zeichnen an sich
- Das Darstellen eines bereits gezeichneten Bildes
- Eine Regelmäßige Änderung der Daten (Timerbasiert)
- Die Steuerung von Parametern über das UI

Aktuell mantscht du einfach alles zusammen und das gibt Probleme. Der 
Swingworker ist z.B. eher für einmalige Tasks, nicht für wiederkehrende. 
Für Wiederkehrende gibt es den Timer, der ist aber eben nicht dafür um 
wiederkehrend lange Berechnungen zu machen. Dafür gibt es die 
SceduledExecutors und zusammen mit meinem obigen Beispiel sollte das 
dann auch gut zusammen spielen.

von tom (Gast)


Lesenswert?

Danke für deine Hilfe.
Habe für mein Projekt leider nicht mehr die Zeit, um mich in 
ScheduledExecutor einzuarbeiten. Werd daher wohl bei meiner 
Timer-Variante bleiben müssen, auch wenn sie langsam ist.

Also nochmal vielen Dank.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.