Search code examples
javaswingpaneljscrollpanerepaint

Parts of Application mysteriously fail to repaint


I've been on this a couple of hours now, and not making any progress.

I have a very simple application which loads a large (2000x2500px) image from disk.

It loads into a JPanel, which is contained by a JScrollPanel, which in turn is contained by the outermost JPanel.

This code is in Scala, but should be easy enough for a Java developer to read.

When I move the scrollbar, the application prints out an indication that it is being repainted, but the image is left partially blank. When I resize the application pane, everything is drawn correctly.

Is this a bug or am I missing something really basic?

I move the scroll-bar, the image is not properly repainted

Now I resize the application itself by a tiny amount, everything is correctly repainted

import java.awt._
import java.util.Date
import javax.imageio.ImageIO
import javax.swing._

object DisplayAnImage {
  def main(args: Array[String]) = {
    val rackStream = DisplayAnImage.getClass.getResourceAsStream("racks.jpg")
    val bi = ImageIO.read(rackStream)
    val outerMost = new  JFrame("Image display")
    outerMost.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
    outerMost.setLayout(new BorderLayout())

    val imagePane = new JPanel(true) {
      val c = new Canvas() {
        override def paint(g: Graphics): Unit = {
          println(s"drawing the image ${new Date()}")
          g.drawImage(bi, 0, 0, null)
        }
      }
      c.setSize(bi.getWidth, bi.getHeight)
      this.add(c)
    }

    val scroller = new JScrollPane(imagePane)
    scroller.setAutoscrolls(true)

    outerMost.add(scroller, BorderLayout.CENTER)
    outerMost.pack()
    outerMost.setSize(300, 300)
    outerMost.setVisible(true)
  }

}

Solution

With thanks to camackr - I modified the code to the following, it's simpler and works perfectly.

import java.awt._
import javax.imageio.ImageIO
import javax.swing._

object DisplayAnImage {
  def main(args: Array[String]) = {
    val rackStream = DisplayAnImage.getClass.getResourceAsStream("racks.jpg")
    val bi = ImageIO.read(rackStream)
    val outerMost = new  JFrame("Image display")
    outerMost.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
    outerMost.setLayout(new BorderLayout())

    val imagePane = new JPanel(true) {
      val jl = new JLabel() {
        override def paintComponent(g: Graphics): Unit = {
          g.drawImage(bi, 0, 0, null)
        }
        this.setPreferredSize(new Dimension(bi.getWidth,bi.getHeight))
      }
      this.add(jl)
    }

    val scroller = new JScrollPane(imagePane)
    scroller.setAutoscrolls(true)
    outerMost.add(scroller, BorderLayout.CENTER)
    outerMost.pack()
    outerMost.setSize(300, 300)
    outerMost.setVisible(true)
  }

}

Solution

  • A scrollpane works based on the preferred size of the component added to the scroll pane. You are doing custom painting but not providing a preferred size for your component.

    Why are you doing custom painting? Why would you use a Canvas? Why are you overriding paint(), custom painting is done by overriding paintComponent() when using Swing.

    Anyway, just use a JLabel to display the image and add the label to the scrollpane. The label will automatically determine the preferred size of itself based on the size of the image.