Newbie question: Writing a GUI with clojure

270 views
Skip to first unread message

Joe Van Dyk

unread,
Aug 4, 2009, 10:33:30 PM8/4/09
to Clojure
Hey,

New to Java and Clojure. My possibly relevant experience is with Gtk
and Ruby and C++ programming.

I'd like to develop a GUI in Clojure. I'm guessing I want to use
Swing. This application will be targeted towards scientists who are
used to working with the most horribly-designed Tk UI known to man, so
I'm sure they will be fine with Swing.

So, where's the best place to start?

What I've been doing:

- Watched the peepcode
- Working my way through Stuart's book
- Playing with netbean's GUI designer

Is it possible to use the netbean designer and clojure at the same
time? What's the best way of doing that? I'm used to writing GUIs in
C++, would Clojure have a drastically different approach (as far as
application logic and event handling)?

John Harrop

unread,
Aug 5, 2009, 9:01:57 AM8/5/09
to clo...@googlegroups.com
I've not done much GUI work in Clojure yet, but what I have done has not involved any GUI builders. Just Clojure functions, some calling Swing methods.

A lot simplifies once you have some useful utility functions and macros. For example:

(defmacro jbutton
  "Creates a JButton with the specified text, which when clicked executes
the body with an ActionEvent bound to event."
  [text [event] & body]
  `(let [b# (JButton. ~text)]
     (.addActionListener b#
       (proxy [ActionListener] []
         (actionPerformed [~event] ~@body)))))

(defn add-all
  "Adds the specified things, in order to obj. Useful for obj = a Swing
JPanel with a BoxLayout or a FlowLayout and things = Swing components."
  [obj & things]
  (doseq [thing things] (.add obj thing)))

(defn box-layout-panel
  "Given a JPanel or other component that can have a layout, gives it a
BoxLayout with the specified orientation."
  [panel orientation]
  (.setLayout panel (BoxLayout. panel orientation)))

(defn horizontal-box-layout-panel
  "Given a JPanel or other component that can have a layout, gives it a
BoxLayout with horizontal orientation."
  ([panel]
    (box-layout-panel panel BoxLayout/LINE_AXIS)
    panel)
  ([]
    (horizontal-box-layout-panel (JPanel.))))

(defn horizontal-glue
  []
  (Box/createHorizontalGlue))

(defn left-component
  "Given a JComponent, returns a component that will try to display it at its
preferred size on the left side of its container."
  [component]
  (let [result (horizontal-box-layout-panel)]
    (.add result component)
    (.add result (horizontal-glue))
    result))

(defn combine-borders
  "Combines multiple borders into one. The first argument will be the outermost,
followed by the next argument, and the next; the last argument will be the
innermost."
  ([border]
    border)
  ([outer inner]
    (BorderFactory/createCompoundBorder outer inner))
  ([outermost nxt & more]
    (reduce combine-borders (cons outermost (cons nxt more)))))

and so forth.

I've even got:

(def *instruction-columns* 60)

(def *instruction-inset* 10)

(defn instructions
  "Turns strings, or other objects, using str, into instruction text and returns
a component suitable for displaying these in a Swing UI."
  [& strings]
  (doto (JTextArea. (apply str strings))
    (.setEditable false)
    (.setLineWrap true)
    (.setWrapStyleWord true)
    (.setColumns *instruction-columns*)
    (.setOpaque false)
    (.setBorder (combine-borders
                  (lowered-bevel-border)
                  (empty-border *instruction-inset*)))))

which produces a component that displays essentially a multi-line label with a 10-pixel space around its perimeter; as the name says, useful for putting instruction strings in a dialog box or whatever without worrying about where to put manual line breaks. I've used this in wizards, which often have large chunks of textual instructions as well as a few form fields and back, next, cancel buttons.

Naturally, other code does things like take a passed-in JPanel and pop up a JDialog with a specified title, the JPanel front and center, and OK and Cancel buttons or similar at the bottom, with a supplied block of code to execute when OK is pressed and the behavior that both Cancel and the close button close and dispose the JDialog without doing anything; the macro for this expands into code that evaluates to nil on cancel and whatever the OK code returns on OK.

It can take as little as ten minutes to slap something like one of these things together and another ten to test it. With these kinds of reusable UI fragments, both "framed" elements (like jbutton and instructions generate) and "framing" ones (like the JDialog generator macro), slapping together a GUI in code becomes easy.

You might have noticed indications of my preference for using nested BoxLayouts to build things. I like this because BoxLayout respects a component's preferred size and nested BoxLayouts tend to be well behaved when subjected to window or component resizings. GridBagLayout would be the only other contender for flexibility and size-control of components, but BoxLayout nesting lends itself much better to a compositional style of assembly of the GUI component tree, and thus to a functional style of code and to use of reusable fragments like the above examples.

Last but not least, do keep in mind the need for Swing actions to take place on the EDT. SwingWorker macro in the works, and someone else posted code here a while ago that I shamelessly copied that invokes actions on the EDT. I may have made some modifications:

(defmacro do-on-edt
  "Evaluates body on the event dispatch thread. Does not block. Returns nil."
  [& body]
  `(SwingUtilities/invokeLater (fn [] ~@body)))

(defmacro get-on-edt
  "Evaluates body on the event dispatch thread, and returns whatever body
evaluates to. Blocks until body has been evaluated."
  [& body]
  `(let [ret# (LinkedBlockingDeque.)]
     (SwingUtilities/invokeLater (fn [] (.put ret# (do ~@body))))
     (.take ret#)))

(defmacro future-on-edt
  "Evaluates body on the event dispatch thead and returns a future that will
eventually hold the result of evaluating that body."
  [& body]
  `(future (get-on-edt ~@body)))

The REPL does NOT run on the EDT, so GUI code tests from the REPL should be wrapped in do-on-edt. That returns nil; the other two furnish the result of evaluating the body code back to the calling thread. All but get-on-edt can be invoked from the EDT safely; get-on-edt will deadlock if called on the EDT, since invokeLater won't do anything until pending events are processed, but one of those pending events will be the code that's blocking on the LinkedBlockingDeque waiting for the invokeLater. It can be called from SwingWorkers, futures, and agents safely though, and directly at the REPL. I should make get-on-edt smarter actually:

(defmacro get-on-edt
  "Evaluates body on the event dispatch thread, and returns whatever body
evaluates to. Blocks until body has been evaluated."
  [& body]
  `(if (SwingUtilities/isEventDispatchThread)
     (do ~@body)
     (let [ret# (LinkedBlockingDeque.)]
       (SwingUtilities/invokeLater (fn [] (.put ret# (do ~@body))))
       (.take ret#))))

There, fixx0red.

I have a .clj file full of the most reusable bits of Swing interop code like these. The (ns ...) at the top is chock full of Swing classes. The code that uses this .clj file tends to only need to import one or two, on top of :use-ing that .clj file, so building up a load of little utilities like these in a file even saves on big complex (ns ...) work at the top of your other UI-related source files.

By the way, feel free to use all of the above as public domain code. Well, unless the earlier posted of the foo-on-edt functions complains anyway, but I think he offered them in the same spirit, and I'm not sure code snippets that short are copyrightable anyway.

Sean Devlin

unread,
Aug 5, 2009, 9:40:32 AM8/5/09
to Clojure
If you really want to get into Swing I suggest taking a look at the
following book:

http://oreilly.com/catalog/9780596009076/

It's got tons of "I didn't knnow you could do that..." ideas

On Aug 5, 9:01 am, John Harrop <jharrop...@gmail.com> wrote:

Chas Emerick

unread,
Aug 5, 2009, 9:53:11 AM8/5/09
to clo...@googlegroups.com
We use NetBeans' matisse designer for static form layout, and we hook
up the components' behaviour using clojure. This is pretty easy,
especially if you configure matisse to expose the components in a form
by creating them with 'public final' visibility, thereby making them a
(.componentName component) call away.

- Chas

Joe Van Dyk

unread,
Aug 5, 2009, 4:59:12 PM8/5/09
to Clojure
On Aug 5, 6:01 am, John Harrop <jharrop...@gmail.com> wrote:
> On Tue, Aug 4, 2009 at 10:33 PM, Joe Van Dyk <joevan...@gmail.com> wrote:
> > Hey,
>
> > New to Java and Clojure.  My possibly relevant experience is with Gtk
> > and Ruby and C++ programming.
>
> > I'd like to develop a GUI in Clojure.  I'm guessing I want to use
> > Swing.  This application will be targeted towards scientists who are
> > used to working with the most horribly-designed Tk UI known to man, so
> > I'm sure they will be fine with Swing.
>
> > So, where's the best place to start?
>
> I've not done much GUI work in Clojure yet, but what I have done has not
> involved any GUI builders. Just Clojure functions, some calling Swing
> methods.
>
> A lot simplifies once you have some useful utility functions and macros. For
> example:
<snip>

Thanks for the code! I'll definitely use it when needing to add/
modify GUI widgets.

There's no "official" swing wrapper in clojure yet, right?

Joe

Joe Van Dyk

unread,
Aug 5, 2009, 4:51:39 PM8/5/09
to Clojure
On Aug 5, 6:53 am, Chas Emerick <cemer...@snowtide.com> wrote:
> We use NetBeans' matisse designer for static form layout, and we hook  
> up the components' behaviour using clojure.  This is pretty easy,  
> especially if you configure matisse to expose the components in a form  
> by creating them with 'public final' visibility, thereby making them a  
> (.componentName component) call away.

You wouldn't want to whip up a quick example (or blog post) for me,
would ya?

Julian

unread,
Aug 5, 2009, 6:59:33 PM8/5/09
to Clojure
On Aug 6, 6:51 am, Joe Van Dyk <joevan...@gmail.com> wrote:
> You wouldn't want to whip up a quick example (or blog post) for me,
> would ya?
Taking a quick look in the files section of this group found this:
http://clojure.googlegroups.com/web/clojure-gui-and-netbeans.pdf?hl=en&gda=f1jT-U4AAAC-wnUK1KQ919yJcmM1ACuZF_7r2-2rkSjhF_gc_N1Bbpenpc0tTgTfOT8mbP3D_UHFOPRGngYCB6zi_choflON47Cl1bPl-23V2XOW7kn5sQ

=)
JG

Anne Ogborn

unread,
Aug 5, 2009, 6:06:42 PM8/5/09
to clo...@googlegroups.com

I normally go to the swing tutorials on Sun's website when I have questions.

I'm writing a GUI in clojure. I'm using IntelliJ's (not great) FormDesigner.

Clojure is in some sense 'on top of' Java, since it uses the Java libs.
Writing a Swing GUI in Clojure hasn't been that much different than writing it in java, except that you can automate away a lot of the ceremony that Swing so loves.

Many people hate layout managers and just lay things out with XYLayout.
It certainly makes it easier to lay something out if you're not using a visual designer type package.

The only real trick to using swing is understanding that most everything that modifies the UI has to happen in the event thread. So, if you, say, have a cancel button in a dialog for a long operation, and the cancel button goes away when the operation completes (assuming the dialog stays open, maybe to display some results) then you need to use SwingWorker to run the code that removes the button in the proper thread.
Fortunately this doesn't apply to most property settings, so if you change the text in a label or turn a button green you're OK.

And of course if you click a button and that makes another button go away, that's fine, since you're already IN the event thread.






Jonathan Smith

unread,
Aug 5, 2009, 10:54:57 PM8/5/09
to Clojure
I'll second the book recommendation,
have the hacks sitting here on my desk and has been very useful so
far.

I've found that the easiest way for me to do UIs has been to write
helper functions that make the components and stitch them together
then return them inside hashmaps.

Then I write a bunch of other functions that add listeners (or that
return functions which add listeners) onto the different components
that I stored in hashmaps. I'm still a little bit wary of the
performance implications of doing it that way, but so far it has made
my UIs code a lot more intelligible (I think), than using a lot of
Java directly.
Reply all
Reply to author
Forward
0 new messages