Images failing to display in the user interface

In the code below, I’m trying to create an image gallery that can be scrolled through. I’m doing this by iterating through an array and setting the current image container as visible. However, the image does not display and only shows up as blank for every image after the initial image, which is displayed successfully. I’m unsure why this is happening,as this code seems like it should work.

I am on scala version 3.3.0.

import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.control.{Menu, MenuBar, MenuItem, ScrollPane}
import scalafx.scene.image.{Image, ImageView}
import scalafx.scene.layout.{VBox, BorderPane}
import scalafx.stage.FileChooser
import java.io.File
import scalafx.Includes._
import scalafx.event.ActionEvent
import scalafx.scene.input.ScrollEvent

object DropdownMenuApp extends JFXApp {

  val imageContainer = new VBox() // Container for ImageView components
  val scrollPane = createScrollPane()
  var currentImageIndex = 0 // Keep track of the currently displayed image

  // Store images separately
  var images: Seq[Image] = Seq.empty

  stage = new JFXApp.PrimaryStage {
    title = "ScalaFX Dropdown Menu Example"
    width = 1000
    height = 800
    scene = new Scene {
      content = new BorderPane() {
        top = createMenuBar()
        center = scrollPane // Set the ScrollPane as the center of the BorderPane
      }
    }
  }

  def createMenuBar(): MenuBar = {
    val fileMenu = new Menu("File")
    val openItem = new MenuItem("Open")
    val saveItem = new MenuItem("Save")
    fileMenu.items = Seq(openItem, saveItem)

    val menuBar = new MenuBar()
    menuBar.menus = Seq(fileMenu)

    openItem.onAction = (e: ActionEvent) => openFile()
    menuBar
  }

  def createScrollPane(): ScrollPane = {
    val scrollPane = new ScrollPane()
    scrollPane.content = imageContainer // Set the container as the content
    scrollPane.hbarPolicy = ScrollPane.ScrollBarPolicy.AsNeeded
    scrollPane.vbarPolicy = ScrollPane.ScrollBarPolicy.AsNeeded
    scrollPane.fitToWidth = true
    scrollPane.fitToHeight = true

    val scrollEventHandler = (event: ScrollEvent) => {
      if (event.getDeltaY > 0) {
        // Scroll down, go to the next image
        showNextImage()
      } else {
        // Scroll up, go to the previous image (if available)
        showPreviousImage()
      }
    }

    scrollPane.onScroll = scrollEventHandler
    scrollPane
  }

  def openFile(): Unit = {
    val fileChooser = new FileChooser()
    fileChooser.title = "Open File"

    val extensionFilter = new FileChooser.ExtensionFilter("*.zip", "*.rar")
    fileChooser.extensionFilters.add(extensionFilter)

    val selectedFile = fileChooser.showOpenDialog(stage)
    if (selectedFile != null) {
      val filePath = selectedFile.getAbsolutePath()
      val folderpath = FileHandling.analyzeFile(filePath)
      val files: Array[File] = folderpath.listFiles()

      // Clear the existing content in the container
      imageContainer.children.clear()

      // Reset the current image index
      currentImageIndex = 0

      // Clear the images list
      images = Seq.empty

      for (file <- files) {
        val imagePath = file.toURI.toString
        val image = new Image(imagePath)
        images :+= image // Add the image to the list
        val newImageView = new ImageView(image) // Create a new ImageView for each image
        newImageView.preserveRatio = true
        imageContainer.children.add(newImageView)
      }

      // Update the current imageView
      updateDisplayedImage()
    }
  }

  def showNextImage(): Unit = {
    if (currentImageIndex < images.size - 1) {
      currentImageIndex += 1
      updateDisplayedImage()
    }
  }

  def showPreviousImage(): Unit = {
    if (currentImageIndex > 0) {
      currentImageIndex -= 1
      updateDisplayedImage()
    }
  }

  def updateDisplayedImage(): Unit = {
    if (images.nonEmpty) {
      for (i <- 0 until imageContainer.children.size()) {
        imageContainer.children(i).visible = false
      }
      imageContainer.children(currentImageIndex).visible = true
      val currentImage = images(currentImageIndex)
      println(s"Current Image Index: $currentImageIndex")
      println(s"Image URL: ${currentImage.getUrl()}")
      println(s"Image Width: ${currentImage.width}")
      println(s"Image Height: ${currentImage.height}")
    }
  }
}

I’m trying to test out your code on my machine.

Where is this defined? Is it your custom code in another file? Could you share it?

Hello @spamegg1,

FWIW (I am not looking at this any further), I changed that line to

val folderpath = File("<path of a directory containing some images>")

and when I ran DropdownMenuApp, I got this:

Exception in thread "main" java.lang.ExceptionInInitializerError
	at scalafx.scene.control.ScrollPane$.$lessinit$greater$default$1(ScrollPane.scala:73)
	at DropdownMenuApp$.createScrollPane(main.scala:47)
	at DropdownMenuApp$.<clinit>(main.scala:15)
	at DropdownMenuApp.main(main.scala)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
	at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:436)
	at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:431)
	at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:724)
	at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:686)
	at javafx.scene.control.Control.<clinit>(Control.java:98)
	... 4 more
1 Like

There’s quite a few issues with this code, but the main one probably is that the images are arranged in a VBox, i.e. in a vertical stack, and scrolling only flips visibility, but does not move the stack relative to the scroll pane. So, when displaying sufficiently big pictures, the scroll pane will only show (part of) the first image view. Upon scrolling, the first image view will still be displayed, but with visibility disabled, i.e. blank, and the second image view, which is now marked visible, will still be off screen. If you try with a set of smallish images, you should see (part of) the vertical stack.

Some other (potential) issues:

  • deltaY of 0.0 triggers scroll prev
  • no filtering on file types
  • deprecated API, should be JFXApp3 (and do setup in #start(), avoiding “Toolkit not inizialized” error)
2 Likes

Here’s the code for file handling.

import java.io.{File, FileOutputStream}
import com.github.junrar.Archive
import com.github.junrar.Junrar
import java.io.FileInputStream
import java.util.zip.{ZipEntry, ZipInputStream}
import java.nio.file.{Path, Paths, Files}
import scala.util.control.NonFatal
object FileHandling {

  def analyzeFile(filePath: String): File = {

    val fileExtension = filePath.takeRight(3)
    val fileNameRaw = new java.io.File(filePath).getName
    val destinationDir = Files.createTempDirectory("comic_temp")
    val path: Path = destinationDir
    var extractedDir: File = null
    //Files.createDirectory(path)
    println(destinationDir)
    if (fileExtension == "zip") {

      extractZip(filePath, destinationDir.toString())
      extractedDir = new File(destinationDir.toString())
      println(destinationDir)
      recursiveFileMover(extractedDir, extractedDir)
      extractedDir

    } else if (fileExtension == "rar") {
      Junrar.extract(filePath, destinationDir.toString())
      println(destinationDir)
      extractedDir = new File(destinationDir.toString())
      val files = extractedDir.listFiles()
      recursiveFileMover(extractedDir, extractedDir)
      extractedDir

    } else {

      println("Invalid file format")

      extractedDir

    }

  }

  def extractZip(zipPath: String, destination: String): Unit = {
    val buffer = new Array[Byte](1024)
    val zinput = new ZipInputStream(new FileInputStream(zipPath))
    var ze = zinput.getNextEntry()

    while (ze != null) {
      val newFile = new File(destination + File.separator + ze.getName())
      new File(newFile.getParent()).mkdirs()

      val fos = new FileOutputStream(newFile)

      var len = zinput.read(buffer)

      while (len > 0) {
        fos.write(buffer, 0, len)
        len = zinput.read(buffer)

      }
      fos.close()
      ze = zinput.getNextEntry()
    }
    zinput.closeEntry()
    zinput.close()
  }

  def recursiveFileMover(folderPath: File, destinationFolder: File): Unit = {
    if (folderPath.isDirectory) {
      for (entry <- folderPath.listFiles()) {
        if (entry.isDirectory) {
          recursiveFileMover(entry, destinationFolder)
          entry.delete()
        } else {
          val fileName = entry.getName
          val destinationPath = new File(destinationFolder, fileName)

          try {
            Files.move(entry.toPath, destinationPath.toPath)
            println(s"Moved ${entry.toPath} to ${destinationPath.toPath}")
          } catch {
            case NonFatal(err) =>
              println(s"Failed to move ${entry.toPath}: ${err.getMessage}")
          }
        }
      }
    }
  }

}

How can I refactor the code so I can see the next image in the folder regardless of image size?

My understanding is that you want to show only one image and you are trying to use ScrollPane to navigate between images.

It will be quite tricky to make ScrollPane to do what you want. It is intended to show fragment of a larger view. You can just simply add two buttons to navigate. Use single ImageView and just switch images in it. Here is modification of your example code (with a slight change to load .png images from a selected directory):

import scalafx.application.JFXApp3
import scalafx.event.ActionEvent
import scalafx.scene.Scene
import scalafx.scene.control.{Button, Menu, MenuBar, MenuItem}
import scalafx.scene.image.{Image, ImageView}
import scalafx.scene.layout.BorderPane
import scalafx.stage.FileChooser
import scalafx.Includes.*

object ImageBrowserApp extends JFXApp3 {

  var ImageView: ImageView = _
  var currentImageIndex    = 0 // Keep track of the currently displayed image

  // Store images separately
  var images: Seq[Image] = Seq.empty

  override def start(): Unit = {
    ImageView = new ImageView:
      preserveRatio = true

    stage = new JFXApp3.PrimaryStage {
      title = "ScalaFX Image Browser Example"
      width = 1000
      height = 800
      scene = new Scene {
        root = new BorderPane() {
          top = createMenuBar()
          center = ImageView
          left = new Button("<"):
            onAction = _ => showPreviousImage()
          right = new Button(">"):
            onAction = _ => showNextImage()
        }
      }
    }
  }

  def createMenuBar(): MenuBar = {
    val fileMenu = new Menu("File")
    val openItem = new MenuItem("Open")
    val saveItem = new MenuItem("Save")
    fileMenu.items = Seq(openItem, saveItem)

    val menuBar = new MenuBar()
    menuBar.menus = Seq(fileMenu)

    openItem.onAction = (e: ActionEvent) => openFile()
    menuBar
  }

  def openFile(): Unit = {
    val fileChooser = new FileChooser()
    fileChooser.title = "Open File"

    //    val extensionFilter = new FileChooser.ExtensionFilter("*.zip", "*.rar")
    //    fileChooser.extensionFilters.add(extensionFilter)

    val selectedFile = fileChooser.showOpenDialog(stage)
    if (selectedFile != null) {
      //  val filePath           = selectedFile.getAbsolutePath()
      //  val folderpath         = FileHandling.analyzeFile(filePath)
      val folderpath = if selectedFile.isFile then selectedFile.getParentFile else selectedFile
      val files      = folderpath.listFiles().filter(_.getName.endsWith(".png"))

      // Reset the current image index
      currentImageIndex = 0

      // Clear the images list
      images = Seq.empty

      for (file <- files) {
        val imagePath = file.toURI.toString
        val image     = new Image(imagePath)
        images :+= image // Add the image to the list
      }

      // Update the current imageView
      updateDisplayedImage()
    }
  }

  def showNextImage(): Unit = {
    if (currentImageIndex < images.size - 1) {
      currentImageIndex += 1
      updateDisplayedImage()
    }
  }

  def showPreviousImage(): Unit = {
    if (currentImageIndex > 0) {
      currentImageIndex -= 1
      updateDisplayedImage()
    }
  }

  def updateDisplayedImage(): Unit = {
    if (images.nonEmpty) {
      val currentImage = images(currentImageIndex)
      ImageView.image = currentImage
      println(s"Current Image Index: $currentImageIndex")
      println(s"Image URL: ${currentImage.getUrl()}")
      println(s"Image Width: ${currentImage.width}")
      println(s"Image Height: ${currentImage.height}")
    }
  }
}
1 Like

This block of the code threw

‘;’ expected but ‘=’ found.

override def start(): Unit = {
    ImageView = new ImageView:
      preserveRatio = true

    stage = new JFXApp3.PrimaryStage {
      title = "ScalaFX Image Browser Example"
      width = 1000
      height = 800
      scene = new Scene {
        root = new BorderPane() {
          top = createMenuBar()
          center = ImageView
          left = new Button("<"):
            onAction = _ => showPreviousImage()
          right = new Button(">"):
            onAction = _ => showNextImage()
        }
      }
    }
  }

I’m unsure why

Which line of code is the error on?