views:

294

answers:

1

I recently purchased the book Filthy Rich Clients and i found it really useful and fun. Building on one example from the book i tried implementing a custom ScrollPane that displays a "shadow" on the bottom of its view over the component to be displayed. I ended up with the code below. It works but not perfectly. Specifically when i scroll the pane by dragging the scroll bar everything works ok and the painting is really smooth. But when i scroll with the mouse scroll the shadow flickers and i have no idea why. Can anyone help me?

EDIT: Same thing happens for any component in the scroll pane. Edited the code to display two frames to see the problem.

EDIT 2 : I have isolated the issue to the way the scroll pane handles the mouse wheel event. When scrolling the scroll pane copies the contents of the view port slightly up or down depending on the orientation of the scroll and then draws the region that comes into view. My code makes the whole component "dirty" but that is after the component has shifted the contents. So momentarily you see the "shadow" gradient out of place until a repaint is issued. Any ideas on how to disable this functionality?

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Container;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.RepaintManager;

public class Test {

    public static void main(String[] args) {
     JFrame f = new JFrame("Table");
     JFrame f1 = new JFrame("Text Area");
     Object[] names = new Object[] { "Title", "Artist", "Album" };
     String[][] data = new String[][] {
       { "Los Angeles", "Sugarcult", "Lights Out" },
       { "Do It Alone", "Sugarcult", "Lights Out" },
       { "Made a Mistake", "Sugarcult", "Lights Out" },
       { "Kiss You Better", "Maximo Park", "A Certain Trigger" },
       { "All Over the Shop", "Maximo Park", "A Certain Trigger" },
       { "Los Angeles", "Sugarcult", "Lights Out" },
       { "Do It Alone", "Sugarcult", "Lights Out" },
       { "Made a Mistake", "Sugarcult", "Lights Out" },
       { "Kiss You Better", "Maximo Park", "A Certain Trigger" },
       { "All Over the Shop", "Maximo Park", "A Certain Trigger" },
       { "Los Angeles", "Sugarcult", "Lights Out" },
       { "Do It Alone", "Sugarcult", "Lights Out" },
       { "Made a Mistake", "Sugarcult", "Lights Out" },
       { "Kiss You Better", "Maximo Park", "A Certain Trigger" },
       { "All Over the Shop", "Maximo Park", "A Certain Trigger" },
       { "Los Angeles", "Sugarcult", "Lights Out" },
       { "Do It Alone", "Sugarcult", "Lights Out" },
       { "Made a Mistake", "Sugarcult", "Lights Out" },
       { "Kiss You Better", "Maximo Park", "A Certain Trigger" },
       { "All Over the Shop", "Maximo Park", "A Certain Trigger" },
       { "Los Angeles", "Sugarcult", "Lights Out" },
       { "Do It Alone", "Sugarcult", "Lights Out" },
       { "Made a Mistake", "Sugarcult", "Lights Out" },
       { "Kiss You Better", "Maximo Park", "A Certain Trigger" },
       { "All Over the Shop", "Maximo Park", "A Certain Trigger" },
       { "Going Missing", "Maximo Park", "A Certain Trigger" } };
     JTable table = new JTable(data, names);
     f.getContentPane().add(new ShadowScrollPane(table));
     f1.getContentPane().add(new ShadowScrollPane(new JTextArea(20, 50)));
     RepaintManager.setCurrentManager(new RepaintManager(){
      @Override
      public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
       Container con = c.getParent();
       while (con instanceof JComponent) {
        if (!con.isVisible()) {
                        return;
                    }
        if (con instanceof ShadowScrollPane ) {
         c = (JComponent)con;
         x = 0;
         y = 0;
         w = con.getWidth();
         h = con.getHeight();
        }
        con = con.getParent();
       }
       super.addDirtyRegion(c, x, y, w, h);
      }
     });
     f.pack();
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     f.setVisible(true);
     f1.pack();
     f1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     f1.setVisible(true);
    }

}

@SuppressWarnings("serial")
class ShadowScrollPane extends JScrollPane {

    private final int h = 50;
    private BufferedImage img = null;
    private BufferedImage shadow = new BufferedImage(1, h, BufferedImage.TYPE_INT_ARGB);

    public ShadowScrollPane(JComponent com) {
     super(com);
     Graphics2D g2 = shadow.createGraphics();
     g2.setPaint(new Color(50, 50, 50));
     g2.fillRect(0, 0, 1, h);
     g2.setComposite(AlphaComposite.DstIn);
     g2.setPaint(new GradientPaint(0, 0, new Color(0, 0, 0, 0f), 0, h, new Color(1, 1, 1, 0.6f)));
     g2.fillRect(0, 0, 1, h);
     g2.dispose();
    }

    @Override
    public void paint(Graphics g) {
     if (img == null || img.getWidth()!=getWidth() || img.getHeight() != getHeight()) {
      img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
     }
     Graphics2D g2 = img.createGraphics();
     super.paint(g2);
     Rectangle bounds = getViewport().getVisibleRect();
     g2.scale(bounds.getWidth(), -1);
     int y = (getColumnHeader()==null)?0:getColumnHeader().getHeight();
     g2.drawImage(shadow, bounds.x, -bounds.y - y-h, null);
     g2.scale(1,-1);
     g2.drawImage(shadow, bounds.x, bounds.y + bounds.height-h+y, null);
     g2.dispose();
     g.drawImage(img, 0, 0, null);
    }
}
+1  A: 

Have you tried calling setWheelScrollingEnabled(false) on the ScrollPane object?

From the javadoc:

Enables/disables scrolling in response to movement of the mouse wheel. Wheel scrolling is enabled by default.

Update following the comment by Savvas below.

Perhaps the "setScrollMode(int)" method on the viewport can help you. This method will determine how swing scrolls the viewport.

You can get the viewport directly from the ScrollPane with the getViewPort() method. You have the following options:

BLIT_SCROLL_MODE
BACKINGSTORE_SCROLL_MODE
SIMPLE_SCROLL_MODE

According to the javadoc BLIT_SCROLL_MODE will use Graphics.copyArea so perhaps try one of the others.

Aaron
i want to keep the scrolling behavior. What i don't want is to keep the way the scroll pane draws it self when scrolling. It is not using the repaint method apparently but uses the Graphics.copyArea(x, y, width, height, dx, dy) method. After that the repaint is called to paint the newly introduced area and because i have marked the entire component as dirty, my paint method kicks in. But after the copyArea so there is flashing
Savvas Dalkitsis
Thanx. That worked like a charm. Both JViewport.BACKINGSTORE_SCROLL_MODE and JViewport.SIMPLE_SCROLL_MODE work like i want.
Savvas Dalkitsis