Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure GUI programming is hard

I am writing a utility program using a Swing GUI. I am trying to use Martin Fowler's Presentation Model to facilitate testing. My application will automatically store several user preferences using java.util.prefs.Preferences (i.e.: main window position and size). I spent several hours over the weekend trying to create a Clojure mock of the Preferences API (using EasyMock) so that I could test my presenter code, but could not get it working. Clojure GUI programming using non-OO style is hard for a long-time OO programmer. I feel that if I can discover/develop patterns for these things (mocking, "interfaces" for visual "classes", etc.), I can continue to use the same patterns throughout the rest of the application.

I have also been developing the same application in Scala to compare the programming patterns and have found it to be much more intuitive, even though I am trying to use Scala in a fairly strict functional style (excluding, of course, calls to Java classes like the Swing API -- which will have the same mutability issues in the Clojure version, but of course, will also be single-threaded).

In my Scala code, I create a class called MainFrame that extends JFrame and implements the trait MainView. MainView exposes all of the JFrame calls as abstract methods that I can implement in a mock object:

trait LabelMethods {
  def setText(text: String)
  //...
}

trait PreferencesMethods {
  def getInt(key: String, default: Int): Int
  def putInt(key: String, value: Int)
  //...
}

trait MainView {
  val someLabel: LabelMethods
  def addComponentListener(listener: ComponentListener)
  def getLocation: Point
  def setVisible(visible: Boolean)
  // ...
}

class MainFrame extends JFrame with MainView {
  val someLabel = new JLabel with LabelMethods
  // ...
}

class MainPresenter(mainView: MainView) {
  //...
  mainView.addComponentListener(new ComponentAdaptor {
    def componentMoved(ev: ComponentEvent) {
      val location = mainView.getLocation
      PreferencesRepository.putInt("MainWindowPositionX", location.x)
      PreferencesRepository.putInt("MainWindowPositionY", location.y)
  }
  mainView.someLabel.setText("Hello")
  mainView.setVisible(true)
}

class Main {
  def main(args: Array[String]) {
    val mainView = new MainFrame
    new MainPresenter(mainView)
  }
}

class TestMainPresenter {
  @Test def testWindowPosition {
    val mockPreferences = EasyMock.mock(classOf[PreferencesMethods])
    //... setup preferences expectation, etc.
    PreferencesRepository.setInstance(mockPreferences)

    val mockView = EasyMock.createMock(classOf[MainView])
    //... setup view expectations, etc.
    val presenter = new MainPresenter(mockView)
    //...
  }
}

I am using a pseudo-singleton (setInstance included so that a mock can replace the "real" version) for the Preferences, so the details are not shown. I know about the cake pattern, but found mine to be a little easier to use in this case.

I have struggled with doing the similar code in Clojure. Are there any good examples of open-source projects that do this kind of thing? I have read several books on Clojure (Programming Clojure, The Joy of Clojure, Practical Clojure), but have not seen these issues dealt with. I have also studied Rich Hickey's ants.clj, but his use of Swing in that example is pretty basic.

like image 555
Ralph Avatar asked Apr 11 '11 11:04

Ralph


1 Answers

Check these two options that are quite popular in the clojure world:

  • CLJFX if you are ok with moving from swing to javafx.
  • Seesaw if you rather continue in swing.

Edit: Fresh info: https://tonsky.me/blog/skija/

like image 61
AticusFinch Avatar answered Nov 14 '22 05:11

AticusFinch