/// Thursday, November 10

Use XSLT Transformations to Ease Glade#

Summary

This article describes how to use the XslCodeBuilder to simplify cross-platform Gtk# application development.

Introduction

In a previous article, I described the implementation of XslCodeBuilder to inject code generated by an XSLT transformation. In this article we'll apply XslCodeBuilder to ease Gtk# development.

There are at least two ways to build a Gtk# application. The first is to use the Gtk# library as is, manually creating widgets and compose them in a hierarchy to build your GUI. The easier way is to use the Glade# library. Draw your GUI using the Glade 2 utility then save the project creating XML metadata in a .glade file. The .glade file is loaded by the glade-sharp library to render your GUI.

Glade development is summed up in these steps:

  1. Draw your GUI - use the glade utility to generate a .glade file
  2. Declare variables for widgets - each widget in a .glade file has a unique id
  3. Set event handlers - each widget has a set of 'signals' or events
  4. Load the .glade file and run

The rest of the article describes the use of XslCodeBuilder to semi-automate these steps.

XslCodeBuilder and the Glade stylesheet

A .glade file is simply an XML file. It's simple enough to create a stylesheet to generate Boo code to perform the steps described above:

01 <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
02  <xsl:output method="text" />
03  <xsl:template match="/">
04      <xsl:apply-templates  select="glade-interface/widget[1]"/>
05      def RunGlade():
06              RunGlade(null)
07      end
08
09      def RunGlade(init as callable):
10          Application.Init()
11          gxml = Glade.XML("view.glade", "window1", null)
12          gxml.Autoconnect(self)
13
14          if init != null:
15              init()
16          end
17
18          Application.Run()
19      end
20  </xsl:template>
21
22  <!-- build variables -->
23  <xsl:template match="widget">
24      [Widget]
25      <xsl:value-of select="@id"/> as <xsl:value-of select="substring(@class, 4)"/>
26      <xsl:apply-templates select="child/widget" />
27  </xsl:template>
28 </xsl:stylesheet>

Line 03 is the entry point into the stylesheet. The apply-templates statement starts a recursive descent into the XML file invoking any template that matches the current XML node. In this example, there is a template defined for match="widget". The template emits a declaration like this:

[Widget]
id as widgetClass

After all the widgets are found, processing continues at line 05 which emits overloaded methods of RunGlade(). The XSL-savvy will realize this stylesheet only acts on the first widget from the root and all its children. This is just a sample stylesheet. In all honesty I'm a Glade# NoOB. I need a better understanding of Glade to create a robust stylesheet.

Anyway, here is the source of a basic Glade# application using XslCodeBuilder and the stylesheet:

import Gtk from "gtk-sharp"
import Glade from "glade-sharp"
import MGutz.CodeInjection from "MGutz.CodeInjection"

[InjectCode(XslCodeBuilder, "view.glade;glade.xsl")]
public class GladeView:
    def Run():
        RunGlade()

InjectCode generates this code and stores it in a file so you can step through it in the debugger if needed:

    [Widget]
    window1 as Window
    [Widget]
    vbox1 as VBox
    [Widget]
    toolbar1 as Toolbar
    [Widget]
    openButton as ToolButton
    [Widget]
    quitButton as ToolButton
    [Widget]
    scrolledwindow1 as ScrolledWindow
    [Widget]
    textview1 as TextView

    def RunGlade():
            RunGlade(null)
    end

    def RunGlade(init as callable):
        Application.Init()
        gxml = Glade.XML("view.glade", "window1", null)
        gxml.Autoconnect(self)

        if init != null:
            init()
        end

        Application.Run()
    end

Create and compile a simple controller then run it:

o = GladeView()
o.Run()

If you click on either button nothing will happen. We need to hook events and display a FileChooserDialog when the Open button is clicked.

[InjectCode(XslCodeBuilder, "view.glade;glade.xsl")]
public class GladeView:
    def Run():
        RunGlade(Init)

    def Init():
        quitButton.Clicked += OnQuitButtonClicked
        openButton.Clicked += OnOpenButtonClicked

    def OnQuitButtonClicked():
        Application.Quit()

    def OnOpenButtonClicked():
        fcd = FileChooserDialog(Title: "Select File")
        fcd.AddButton("OK", ResponseType.Accept)
        fcd.AddButton("Cancel", ResponseType.Cancel)
        try:
            response = cast(ResponseType, fcd.Run())
            if (response == ResponseType.Accept):
                file = fcd.Filename
                using reader = StreamReader(file):
                    textview1.Buffer.InsertAtCursor(reader.ReadToEnd())
        ensure:
            fcd.Destroy()

The overloaded RunGlade(callable) is used to set event handlers. The handlers are self-explanatory. Run the controller, then click open

File loaded

Conclusion

I've shown how to use XslCodeBuilder to transform Glade's XML metadata to inject Boo code into a class.

Glade masters are probably thinking this doesn't save much time. I agree somewhat. The point is it does save time. When partial classes are merged into Boo event handlers can be autogenerated into a partial file to survive round-trips between regeneration. I didn't attempt to do that in this example as it's bad kharma to modify the source of the decorated class.

Comments:

Great! Thanks for your work...

Do you realize that you're quite near to implement a compiled XML general class instantiator รก la MyXaml?

Please, compare your work with this article.
 
Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?