/// Thursday, November 10
Use XSLT Transformations to Ease Glade#
This article describes how to use the XslCodeBuilder to simplify cross-platform Gtk# application development.
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:
- Draw your GUI - use the glade utility to generate a .glade file
- Declare variables for widgets - each widget in a .glade file has a unique id
- Set event handlers - each widget has a set of 'signals' or events
- 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"/> 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
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.
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.