User Tools

Site Tools


blog:declarative_data_binding_using_xscalawt

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:declarative_data_binding_using_xscalawt [2009/03/21 22:51]
djo Wordsmithing
blog:declarative_data_binding_using_xscalawt [2014/10/17 22:08] (current)
Line 1: Line 1:
 +====== 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, [[http://​andymaleh.blogspot.com/​2007/​12/​glimmers-built-in-data-binding-syntax.html|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).
 +
 +{{:​java:​databinding:​databinding1-observables.png|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.
 +
 +{{:​java:​databinding:​databinding1-observables-binding.png|Bind a single field}}
 +
 +Java code to do this for one property looks something like the following:
 +
 +<code java>
 +Text firstName = new Text(parent,​ SWT.BORDER);​
 +
 +DataBindingContext bindingContext = new DataBindingContext();​
 +bindingContext.bindValue(
 +    SWTObservables.observeText(firstName,​ SWT.Modify), ​
 +    BeansObservables.observeValue(person,​ "​firstName"​),​
 +    null, null);
 +</​code>​
 +
 +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.
 +
 +{{:​java:​databinding:​databinding1-full-binding.png|Full binding of a simple user interface}}
 +
 +In XScalaWT, we can certainly translate this code directly into Scala and include it in our layouts:
 +
 +<code java>
 +text (
 +  { txt =>
 +    val bindingContext = new DataBindingContext()
 +    bindingContext.bindValue(
 +      SWTObservables.observeText(txt),​
 +      BeansObservables.observeValue(person,​ "​firstName"​), ​
 +      null, null)
 +  }
 +)
 +</​code>​
 +
 +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:
 +
 +<code java>
 +text (
 +  dataBindingContext(),​
 +  _.textObservable <=> (person-->'​firstName)
 +)
 +</​code>​
 +
 +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:
 +
 +  - 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.
 +  - 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.
 +  - The <=> operator along with a few bind() methods are added to the various IObservable subclasses.
 +  - 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:
 +
 +<code java>
 +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)
 +  }
 +}
 +</​code>​
 +
 +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:
 +
 +<code java>
 +Realm.runWithDefault(SWTObservables.getRealm(Display.getDefault()),​ new Runnable() {
 +  public void run() {
 +    // your code here...
 +  }
 +});
 +</​code>​
 +
 +Our view model or presentation model is the following:
 +
 +<code java>
 +class LoginViewModel {
 +  @BeanProperty
 +  var username : String = "";​
 +
 +  @BeanProperty
 +  var password : String = "";​
 +
 +  var loggedIn = false
 +  def login() = { loggedIn = true }
 +}
 +</​code>​
 +
 +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:
 +
 +<code java>
 +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()
 +}
 +</​code>​
 +
 +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~~