Scala's swing API has undergone a few changes since Scala 2.7. Besides the obvious bugfixes, we added more wrappers, a more comprehensive component caching mechanism, and revised the window base hierarchy. I'll go into details of the most important changes in 2.8 below, assuming you are familiar with Java Swing and the basics of scala.swing.
Window hierarchy
This is how the Java AWT/Swing window hierarchy looks like:While AWT's
Frame
and Dialog
share the common base class Window, the Swing extensions have no subclass relationship or extend a common interface even though they share common functionality not present in AWT. In an attempt to straighten things up a little, we redesigned the window base hierarchy in scala.swing which now looks as follows:I've put the corresponding peer type next to each class. Note the Mixin trait, which extracts common functionality of the Swing window classes. As of now, it contains the following methods:
def getJMenuBar: JMenuBar
def setJMenuBar(b: JMenuBar)
def setUndecorated(b: Boolean)
def setTitle(s: String)
def getTitle: String
def setResizable(b: Boolean)
def isResizable: Boolean
This design lets us factor common wrapper code into class
RichWindow
.Dialogs
The Dialog companion object in 2.8 wraps message utilities fromjavax.swing.JOptionPane
. Together with named and default arguments, dialog code becomes much more readable. Consider the following snippet from the Java Swing tutorials which opens a multiple choice dialog:
val options = Array[Object]("Yes, please", "No, thanks", "No eggs, no ham!")
JOptionPane.showOptionDialog(parent,
"Would you like some green eggs to go with that ham?",
"A Silly Question",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options(2))
It is a little tedious to remember the order and the meaning of those arguments. In particular, what is the title and what is the question message above? What is the parameter we set to
null
? In scala.swing, you can now write
Dialog.showOptions(parent,
message = "Would you like some green eggs to go with that ham?",
title = "A Silly Question",
messageType = Dialog.Message.Question,
optionType = Dialog.Options.YesNoCancel,
entries = Seq("Yes, please", "No, thanks", "No eggs, no ham!"),
initial = 2)
Now it's clear what's the title and what's the message. The
null
argument (which specifies the icon BTW) does not appear since the icon
parameter is set to null
by default. Note that named arguments are not only more descriptive but their order at call site is also arbitrary .Events
Scala swing components publish key events now. As usual, the event classes are a straightforward adaption of the Java classes and can all be found in package scala.swing.event. Another important feature we were missing in 2.7 was the possibility to consume input events. Fortunately, that has been fixed now. Moreover, ticket #1442 taught us that some Swing components behave differently depending on whether certain listeners are installed or not. Therefore, scala.swing publishers install their Swing/AWT listeners lazily now. This means we install Java listeners only when a scala.swing reaction is added to the corresponding publisher and uninstall listeners when the reaction is removed. Consequently, we are little more efficient but more importantly avoid to spuriously trigger side-effects that Swing relies on in the first place (this is one of the few places where we actually do save additional state - the reactions - and by not rigorously synchronizing the wrapper's with the peer's state we got what we paid for).Wrapper caches
The following is a change under the hood which most users won't notice but for those who are interested, here are a few details.As I said earlier, we can get the peer of every wrapper. The other way works as well. Every peer is associated with a wrapper if scala.swing created one already and as long as it hasn't been reclaimed by the garbage collector. This backward association is established by a wrapper cache. So why do we cache wrappers in the first place? Note that duplicate wrappers for the same Java Swing component are not a problem for equality, since
scala.swing.UIElements
are instances of scala.Proxy
and hence redirect equals
and hashCode
to their peer. The main reason we do cache components is that we don't have control over every creation site. Consider a scroll panel for an example. It internally creates a set of scroll bars that clients can obtain a handle to. In contrast to wrappers created by user code, we have no control over the creation of those components. Since we don't want to create a new wrapper every time the client obtains a handle, we store them in a cache of weak references (we might wanna use soft references in the future). Because of subtyping, there can be wrappers of different types for the same Java Swing component. Right now, we create wrappers based on the static type of a Java Swing component, which in some situations is not very useful. We might change that to a more dynamic approach in the future, that's why the caching API is still subject to change.
6 comments:
Good news. Is there some progress on the Tree Component?
That's interesting. Are you planning to add some GUI builders?
This is great news! Thanks for scala.swing!
Great job Ingo, scala.swing keeps getting better!
Manfred -
The tree control already works nicely for many (most?) common use cases, which I've described here.
There's some tricky design decisions ahead around how best to handle mutable trees, inserting, updating, editing and so forth. I've been sidetracked the last month, but I'll get back to it in earnest after Christmas.
Feedback is always welcome -- I'll respond quickly to any needs you have in using it.
just an idea, but: would it be possible to create a scenegraph versionof swing? something like java scenario project on java.net. also, it would be nice to have a dsl like javafx for swing (scengraph, animations, effects).
I always enjoy working with Scala Swing. Thanks!
I was wondering if the named and default arguments in Scala 2.8+ could be put to use in the Scala Swing API.
Post a Comment