User Tools

Site Tools


Sidebar

Dave Orme muses about data-first development.

My current work emphasizes data engineering and analysis using Kubernetes, Clojure, Scala, Eclipse, and Google Cloud Platform or AWS.


Blog

The Cloud

Scala, Clojure, and FP

Data-First Development

Agile

Older work

Coconut Palm Software home


Donate Bitcoin:

1Ecnr9vtkC8b9FvmQjQaJ9ZsHB127UzVD6

Keywords:

Kubernetes, Docker, Streaming Data, Spark, Scala, Clojure, OSGi, Karaf, GCP, AWS, SQL

Disclaimer:

Everything I say here is my own opinion and not necessarily that of my employer.

blog:declarative_data_binding_using_xscalawt

Declarative Data Binding using XScalaWT

This article builds upon the previous work showing how Scala can simplify SWT user interface development by introducing Declarative Databinding (DDB) for XScalaWT. DDB uses Eclipse / JFace Data Binding under the hood, and was designed with the following features in mind:

  • The API should follow–not hide–the underlying data binding API design
  • DDB will be implemented by applying common Scala idioms to reduce and/or eliminate Java syntax overhead
  • Code using DDB will more readily describe the programmer's intentions than the equivalent Java code (be more intentional)

Although XScalaWT's overall design is largely my own, DDB in particular owes a great debt to Shine, Andy Maleh's Glimmer data binding syntax. Thanks, Andy, for pushing the Eclipse community's data binding work farther than any of us imagined it could go and ultimately inspiring me to create XScalaWT with DDB.

Brief recap and Administrivia

(If you've read all my previous posts on this topic, you can make sure you have the latest nightly Scala Development Tool build and skip to the next section.)

In my previous posts Simplifying SWT with Scala and XScalaWT Stylesheets, I introduced the idea of using Scala to simplify one of the most painful parts of Eclipse RCP programming, user interface coding. We observed that the pain of SWT interface coding does not have to do with SWT so much as with the imperative style of coding. By creating a domain specific language (DSL) in Scala called XScalaWT that represents user interfaces declaratively rather than imperatively, we were able to eliminate all of the Java boilerplate code, leaving us with very simple, straightforward, intuitive code.

And for those who are not ready go jump ship from Java wholesale (ie: probably nearly all of us now), I started this series with the post, Using Scala to Create Eclipse RCP Applications, describing how to create mixed Scala and RCP applications.

One last administrative detail before we get started: Since I started this series I've gotten reports that the latest nightly Scala Eclipse builds are better than the latest stable build. I recently upgraded and have found this to be true.

A brief introduction to Eclipse Data Binding

Eclipse Data Binding (EDB), previously called JFace Data Binding, is a library that lets you describe how data flows from your business model objects to your user interface in an Eclipse application.

It works by providing a generic facade around observer pattern implementations, giving all observer patterns the exact same API. When all things that you can observe, IObservables if you will, have the same API, then you can hook a generic object called a Binding between them. This binding listens to change events on both sides. When a change is detected on one side, the new value is retrieved and copied to the other side.

For example, calling #addModifyListener on a Text widget requires a ModifyListener object. But detecting changes to a JavaBean bound property requires calling #addPropertyChangeListener and passing a PropertyChangeListener.

We can achieve a generic observer pattern API by wrapping the Text widget with a TextObservableValue (implementing the IObservableValue interface) and wrapping the JavaBean property with a JavaBeansObservableValue (also implementing the IObservableValue interface).

Observables on a pair of fields

Once we have this, we can then use a generic ValueBinding object (that knows how to synchronize arbitrary IObservableValue objects) to monitor both objects for changes and automatically keep them in sync.

Bind a single field

Java code to do this for one property looks something like the following:

Text firstName = new Text(parent, SWT.BORDER);
 
DataBindingContext bindingContext = new DataBindingContext();
bindingContext.bindValue(
    SWTObservables.observeText(firstName, SWT.Modify), 
    BeansObservables.observeValue(person, "firstName"),
    null, null);

This Java code is a whole lot better than the equivalent code that manually registers listeners and keeps these properties in sync.

The result is that you can create a whole user interface by repeating this pattern for each field that you want to bind.

Full binding of a simple user interface

In XScalaWT, we can certainly translate this code directly into Scala and include it in our layouts:

text (
  { txt =>
    val bindingContext = new DataBindingContext()
    bindingContext.bindValue(
      SWTObservables.observeText(txt),
      BeansObservables.observeValue(person, "firstName"), 
      null, null)
  }
)

Here, inside the node for the “text” object, we create an anonymous function that accepts as a parameter the actual Text widget that is created. Since we now have a reference to this Text widget, we can now use regular data binding API calls to bind it just like we did in Java.

But we will ask, as we have previously, “Can we do better?”

Implicitly Bindable

And in Scala, the answer is nearly always “Yes!”

Here is the same example, rewritten to use XScalaWT's data binding:

text (
  dataBindingContext(),
  _.textObservable <=> (person-->'firstName)
)

In the code above, the “⇔” operator is read “connects to” or “maps to” and is what calls bindingContext#bind(). The “_.textObservable” calls a method that we added to org.eclipse.swt.widgets.Text that returns an IObservableValue bound to the “text” property of the text object.

Generically then, XScalaWT adds four bits of syntactic sugar that greatly simplify binding:

  1. All SWT widgets have xxxObservable methods for each property “xxx” that can be observed. More formally, for every static method in the SWTObservables class of the form observeXxx(widget), XScalaWT adds a corresponding xxxObservable method to widget returning the same observable.
  2. Data binding contexts in XScalaWT are associated with a container. All objects inside that container inherit its data binding context. If you don't specify an explicit data binding context, XScalaWT will create a new one for each binding.
  3. The ⇔ operator along with a few bind() methods are added to the various IObservable subclasses.
  4. The –> operator is added to java.lang.Object, and directly returns a JavaBeansObservableValue bound to the property named by the parameter.

Note that these methods are only added to the corresponding receivers when XScalaWTBinding has been imported; these additions are then lexically scoped to the container performing the import.

A complete example

Now that we know how to bind controls in XScalaWT, we can present a fully-worked example using the Model-View-Presenter pattern. As Andy Maleh did in his Glimmer example, I will present a login dialog example in XScalaWT.

First, we need an SWT snippet that starts everything and prints out results:

object DataBindingMain {
  def main(args : Array[String]) = {
    val loginData = new LoginViewModel()
    val display = new Display()
    runWithDefaultRealm {
      val loginView = new LoginView(loginData)
      runEventLoop(loginView.createDialog())
    }
    println("Username:  " + loginData.username)
    println("Password:  " + loginData.password)
    println("Logged in: " + loginData.loggedIn)
  }
}

In this example, loginData is our presentation or view model. Naturally, LoginView is the View itself.

The only new things here are:

  • runEventLoop, which runs the SWT event loop on the specified Shell until the shell is disposed.
  • A convenience function “runWithDefaultRealm” that does the same thing as the following Java code:
Realm.runWithDefault(SWTObservables.getRealm(Display.getDefault()), new Runnable() {
  public void run() {
    // your code here...
  }
});

Our view model or presentation model is the following:

class LoginViewModel {
  @BeanProperty
  var username : String = "";
 
  @BeanProperty
  var password : String = "";
 
  var loggedIn = false
  def login() = { loggedIn = true }
}

Here the “@BeanProperty” annotations direct Scala to automatically generate Java setters and getters for the specified fields.

We also declare a stub login() method that will be called when the user wishes to log in.

The view itself can now be written as:

class LoginView(loginData : LoginViewModel) {
  def createDialog = {
    object loginStyles extends Stylesheet(
	  $[Control] (
	    setBackground(SWT.COLOR_WHITE)
	  )
	)
 
    val window = shell("Please log in",
      dataBindingContext(),
      group("User information",
        setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)),
        setLayout(new GridLayout(1, false)),
 
        label("Username"),
        text (
          setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)),
          _.textObservable <=> (loginData-->'username)  //'// Map Text-->text to loginData-->username
        ),
 
        label(""),
 
        label("Password"),
        *[Text](SWT.BORDER | SWT.PASSWORD) (     // The manual, "specify everything" syntax
          setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)),
          _.textObservable <=> (loginData-->'password)  //'// Map Text-->text to loginData-->password
        )
      ),
 
      // OK/Cancel buttons
      composite (
        setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false)),
        setLayout(new GridLayout(2, true)),
 
        button("&OK", {e : SelectionEvent => loginData.login(); closeShell(e) }),
        button("&Cancel", {e : SelectionEvent => closeShell(e) })
      )
    )
    loginStyles.apply(window)
 
    window.pack
    val size = window.getSize()
    window.setSize(250, size.y)
 
    window
  }
 
  def closeShell(e : SelectionEvent) = e.widget.asInstanceOf[Control].getShell().close()
}

If you've been following this series, all of this code should be familiar by now. If the Scala syntax trips you up a bit, I highly recommend the book Programming Scala by Odersky et al.

Again, we notice that the various concerns are well-expressed with minimal line noise getting in the way of understanding what the code is doing. We also notice that except for the convention of mapping the data binding context to a container, XScalaWT's DDB syntax just simplifies data binding without hiding anything or adding anything magical on top of the Eclipse data binding API.

Conclusion

Scala has proven to be a useful library to use in Java projects for greatly simplifying user interface development. The resulting code is easier to read and more directly expresses the programmer's intentions.

Yep, the latest code is still available at http://bitbucket.org/djo/xscalawt/. :-)

~~LINKBACK~~ ~~DISCUSSION:closed~~

blog/declarative_data_binding_using_xscalawt.txt · Last modified: 2014/10/17 22:08 (external edit)