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: > 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? > > > 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)? > > 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. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en -~----------~----~----~----~------~----~------~--~---