Quantcast
Channel: Life of a Programmer Geek » clojure
Viewing all articles
Browse latest Browse all 4

Model-View-Controller GUI in Clojure

$
0
0

I’ve been experimenting with ways of coding GUIs which loosely follow the Model-View-Controller paradigm in Clojure, with some success. This post describes a simple working example – a gui that draws the text you type in the box.

Screenshot-GUI Test

The idea is to have a mutable object which encapsulates the application state (model), and gui code which reflects the state (view), and provides a way to change it (controller). In this case, the model is just a string, the view is the panel that draws the string, and the controller is the text field. When you type in the text field and hit enter, the model is changed, and the panel draws the new string.

Creating the Model

Clojure is incredibly well thought out in terms of mutability and concurrency, and provides several primitives for dealing with mutable objects. I used a Clojure ref object as the model, which wraps a string. The model is created at the top level, and pushed down as an argument to the GUI code (which conveniently closes over it, making it a persistently available pointer). The following code creates the model:

(ref "Hello MVC!")

Reading the Model
The text field is initialized with the value of the model with the following code:

...(doto (JTextField.)  (.setText @model)...

note – “@” serves to dereference a Clojure reference, yielding in this case the string wrapped by the model object.

The graphics panel reads and draws the model’s every time it paints itself, with the following code:

(paint [g](doto g  ...    (.drawString @model 20 40)...


Mutating the Model

The model is mutated in the code of the ActionListener added to the text field:

(dosync (ref-set model new-text))

note – dosync performs a transaction, which is necessary when mutating Clojure references. (ref-set reference new-value) sets the value of the reference.

Listening to the Model
Clojure has this really cool “add-watch” feature where you can attach an update function to any mutable object! The function gets called every time the object changes. The following line attaches a watch function to the model which repaints the panel when the model changes:

(add-watch model "repaint" (fn [k r o n] (.repaint panel)))

The Code
Here is the code, totaling 74 lines. Any comments are appreciated.

If anyone knows how I can get nice syntax-highlighted HTML for this code, please let me know!

;A test program exploring how to structure GUI code in Clojure;The GUI draws whatever you type in the text field nicely in the panel below.;license: Public domain

(import '(javax.swing JFrame JLabel JTextField JButton JPanel)      '(java.awt.event ActionListener)      '(java.awt GridBagLayout GridBagConstraints Color Font RenderingHints))

(defn make-model [] (ref "Hello MVC!"))

(defn make-graphics-panel [model](let [panel  (proxy [JPanel] []    (JPanel [] (println "in constructor"))    (paint [g]       (doto g                  ;clear the background         (.setColor (. Color black))         (.fillRect 0 0 (.getWidth this) (.getHeight this))

                  ;draw the text         (.setRenderingHint (. RenderingHints KEY_ANTIALIASING)                    (. RenderingHints VALUE_ANTIALIAS_ON))         (.setFont (Font. "Serif" (. Font PLAIN) 40))         (.setColor (. Color white))         (.drawString @model 20 40))))]

                  ;repaint when the model changes  (add-watch model "repaint" (fn [k r o n] (.repaint panel)))  panel))

(defn make-text-field [model](doto (JTextField.)  (.setText @model)  (.addActionListener   (proxy [ActionListener] []     (actionPerformed [e]   (let [new-text (.getActionCommand e)]     (dosync (ref-set model new-text))))))))

(defn make-gui-panel [model](defn make-text-field-constraints []  (let [c (GridBagConstraints.)]    (set! (.fill c) (. GridBagConstraints HORIZONTAL))    (set! (.weightx c) 1)    c))

(defn make-panel-constraints []  (let [c (GridBagConstraints.)]    (set! (.gridy c) 1)    (set! (.weighty c) 1)    (set! (.fill c) (. GridBagConstraints BOTH))    c))

(let [gridbag (GridBagLayout.)      text-field (make-text-field model)      panel (make-graphics-panel model)]                  ;set up the gridbag constraints  (doto gridbag    (.setConstraints text-field (make-text-field-constraints))    (.setConstraints panel (make-panel-constraints)))                  ;add the components to the panel and return it  (doto (JPanel.)    (.setLayout gridbag)    (.add text-field)    (.add panel))))

(defn show-in-frame [panel width height frame-title](doto (JFrame. frame-title)  (.add panel)  (.setSize width height)  (.setVisible true)))

(show-in-frame (make-gui-panel (make-model)) 300 110 "GUI Test")

Viewing all articles
Browse latest Browse all 4

Latest Images

Trending Articles



Latest Images