User Tools

Site Tools


blog:creating_a_swt_custom_control_in_scala

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
blog:creating_a_swt_custom_control_in_scala [2009/04/05 16:49]
djo
blog:creating_a_swt_custom_control_in_scala [2014/10/17 22:08] (current)
Line 1: Line 1:
 +====== Creating a SWT custom control in Scala ======
 +
 +I recently created a simple custom SWT control entirely in Scala, using some XScalaWT to simplify the UI code, to see how much simpler I could make the code using Scala.
 +
 +Creating a SWT custom control in Java requires quite a bit of boilerplate code.  Fortunately,​ while Scala cannot eliminate all this boilerplate,​ we will see how it can simplify it some.  Of course, suggestions for how to improve it are welcomed in the comments. ​ 8-)
 +
 +===== The control =====
 +
 +The SWT control we're creating displays a star that can be turned on and off by clicking it.
 +
 +It follows the following standard SWT conventions:​
 +
 +  - The standard SWT (parent, style) constructor
 +  - Reuses SWT property and event names: selected and selectionListener
 +
 +===== The code =====
 +
 +Let's step through the code:
 +
 +Notice that our default constructor only needs the standard SWT parameters and to call super. ​ So we simply define it as a part of the class parameters.
 +
 +Then we have constants, instance variables, and the internal layout of the control, which we will define using XScalaWT. ​ In this case, our XScalaWT is really simple since what we are doing is really simple. ​ More complicated custom controls would benefit even more from it.
 +
 +<code java>
 +class StarButton(parent : Composite, style : Int) extends Composite(parent,​ style) {
 +  val starOnImage = STAR_ON.image;​
 +  val starOffImage = STAR_OFF.image;​
 +  ​
 +  var starButton : Label = null;
 +
 +  this.contains(
 +    label(starButton=_,​ starOffImage, ​
 +          {e : MouseEvent => setSelected(!selected)})
 +  )
 +</​code>​
 +  ​
 +Here we instantiate a few images. ​ STAR_ON and STAR_OFF are constants declared elsewhere that wrap a JFace ImageDescriptor.
 +
 +The starButton is a SWT Label that will contain either starOnImage or starOffImage,​ depending on if the control is selected or not.
 +
 +The XScalaWT block instantiates the Label and puts the default starOffImage in the label. ​ It also adds a mouseDown listener that toggles the "​selected"​ property.
 +
 +The remainder of the code manages this property, fires the selection change event, and handles control resizing. ​ First, the property:
 +
 +<code java>
 +  var selected = false;
 +  ​
 +  def getSelected() = selected
 +  ​
 +  def setSelected(newValue : Boolean) = { 
 +    selected = newValue
 +    if (selected) starButton.setImage(starOnImage) ​
 +      else starButton.setImage(starOffImage)
 +    fireSelectionEvent
 +  }
 +</​code>​
 +
 +Here's a standard JavaBean property implemented in Scala. ​ Since SWT doesn'​t implement bound properties, we don't that here either. ​ We also don't use the @BeanProperty annotation since we need custom code in the #​setSelected method.
 +
 +Next, selection listener handling:
 +
 +<code java>
 +  private var selectionListeners : scala.List[SelectionListener] = Nil
 +  ​
 +  def addSelectionListener(l : SelectionListener) = 
 +        selectionListeners = l :: selectionListeners
 +  def removeSelectionListener(l : SelectionListener) = 
 +        selectionListeners.remove(_==l)
 +  ​
 +  private def fireSelectionEvent = {
 +    val detail = new Event()
 +    detail.widget = this
 +    detail.data = selected.asInstanceOf[Boolean].asInstanceOf[Object]
 +    val event = new SelectionEvent(detail)
 +    selectionListeners.foreach(_.widgetSelected(event))
 +  }
 +</​code>​
 +
 +The selection listener handling is pretty standard except that we use a few Scala-isms to simplify the code a bit.
 +
 +First, we use a scala.List instead of a java.util.List implementation.
 +
 +The add and remove selectionListener code uses Scala'​s list append (cons) operator to add the selection listener to the head of the list.  Notice that Scala'​s List#remove accepts a filter function instead of the element to remove. ​ This is only slightly more verbose than Java, but also a lot more powerful. ​ In this context, the underscore represents the parameter being passed to the function (the candidate being evaluated.
 +
 +Finally, we have the code for managing the control'​s size:
 +
 +<code java>
 +  addControlListener(new ControlAdapter() {
 +    override def controlResized(e : ControlEvent) = {
 +      val newSize = getSize()
 +      starButton.setBounds(0,​ 0, newSize.x, newSize.y)
 +    }
 +  });
 +  ​
 +  override def computeSize(xDefault : Int, yDefault : Int) = 
 +    starButton.computeSize(xDefault,​ yDefault)
 +}
 +</​code>​
 +
 +Aside from Scala'​s ability to skip the curly braces when the body of a function is a single line, this code is nearly exactly the same as the equivalent Java.
 +
 +===== Conclusion =====
 +
 +Scala is useful for simplifying SWT custom control creation. ​ For a simple control like this one, we don't see the benefit nearly as much as for a complicated custom control that could benefit greatly from XScalaWT. ​ However, I still find the resulting code clearer, with less line noise than the equiavalent Java.
 +
 +Can anyone see a way to simplify this further? ​ If so, please leave a comment.
 +
 +For reference, Here's the whole class:
 +
 +<code java>
 +class StarButton(parent : Composite, style : Int) extends Composite(parent,​ style) {
 +  val starOnImage = STAR_ON.image;​
 +  val starOffImage = STAR_OFF.image;​
 +  ​
 +  var starButton : Label = null;
 +  this.contains(
 +    label(starButton=_,​ starOffImage, ​
 +          {e : MouseEvent => setSelected(!selected)})
 +  )
 +  ​
 +  var selected = false;
 +  ​
 +  def getSelected() = selected
 +  ​
 +  def setSelected(newValue : Boolean) = { 
 +    selected = newValue
 +    if (selected) starButton.setImage(starOnImage) ​
 +      else starButton.setImage(starOffImage)
 +    fireSelectionEvent
 +  }
 +  ​
 +  private var selectionListeners : scala.List[SelectionListener] = Nil
 +  ​
 +  def addSelectionListener(l : SelectionListener) = 
 +        selectionListeners = l :: selectionListeners
 +  def removeSelectionListener(l : SelectionListener) = 
 +        selectionListeners.remove(_==l)
 +  ​
 +  private def fireSelectionEvent = {
 +    val detail = new Event()
 +    detail.widget = this
 +    detail.data = selected.asInstanceOf[Boolean].asInstanceOf[Object]
 +    val event = new SelectionEvent(detail)
 +    selectionListeners.foreach(_.widgetSelected(event))
 +  }
 +
 +  addControlListener(new ControlAdapter() {
 +    override def controlResized(e : ControlEvent) = {
 +      val newSize = getSize()
 +      starButton.setBounds(0,​ 0, newSize.x, newSize.y)
 +    }
 +  });
 +  ​
 +  override def computeSize(xDefault : Int, yDefault : Int) = 
 +    starButton.computeSize(xDefault,​ yDefault)
 +}
 +</​code>​
 +
 +~~LINKBACK~~
 +~~DISCUSSION:​closed~~