From Anthony: Xojo IDE Scripting

Background

Today I had the need to develop a method by which GraffitiSuite users could determine what version of GraffitiSuite a particular product was taken from. Normally I go through the rigmarole of telling them to follow these steps:

  1. Open the demo project
  2. Expand the App object
  3. Expand the Notes section
  4. Select “ChangeLog”
  5. The top line is the version

But a customer pointed out that this can be a fairly big ask when GraffitiSuite is so expansive. Coupled with that, as I thought more about it, once you take a product from the demo and add it to your project you really have no way of discerning what version that particular product is! So I needed something better. Something that stayed with each product, regardless of what project it was in. Enter IDE Scripting.

My plan was to add a Constant or Note for each part of GraffitiSuite that can be easily updated. Browsing through the documentation I saw a lot of listed caveats.

CONST Trials

You can use the DoCommand("NewConstant") but you can’t modify that constant in any way. It’ll always be named UntitledN, with no value type specified. Makes it quite hard to script something like this. Continuing to read the documentation, I learned that ConstantValue("ConstantName") only works if the constant already exists! So what’s the solution? Well, the only thing I could determine to do was to manually add my desired Constant to each and every object instance in the project that needed it. Time consuming, but it gets me where I need to be and I only have to do it once…ever!

After that, I needed to test the ability to set that Constant’s value. I added a constant called “GraffitiSuiteVersion” to a class(named “Class1”) within my project folder(named “GraffitiSuite”) and hammered out the following code real quick:

If SelectProjectItem("Class1") Then
ConstantValue("GraffitiSuiteVersion") = "29.0"
End If

And it didn’t work. OK, so something’s funny here. Maybe I need to specify that it’s in the GraffitiSuite folder? Let’s try again:

If SelectProjectItem("GraffitiSuite.Class1") Then
ConstantValue("GraffitiSuiteVersion") = "29.0"
End If

Result? Also didn’t work, but it did select the correct project item, so something must be up with ConstantValue, right? Correct! ConstantValue ALSO requires a somewhat full path, so this does work:

ConstantValue("Class1.GraffitiSuiteVersion") = "29.0"

Note that we don’t need the folder name for whatever reason.

Folder Traversal

Project traversal similarly has some caveats. For instance, once you use Sublocations("LocationName") to get your stringified list of valid locations, split that out in to an array, and begin iterating, you quickly see that SelectProjectItem("FolderName") returns false(for folders). The documentation tells us that this means the location doesn’t exist, even though the IDE just told us that it does. The documentation also tells us that we should be using TypeOfCurrentLocation As String to determine what type of location we’re on, but since Folders can’t be selected with IDE Scripting, we just ignore that and move on. In short, for my purposes, I can safely assume that any time SelectProjectItem in this specific script returns false, I’m looking at a Folder. Great. That’s done. I can now tell the difference between a folder and everything else, which is important for what I’m trying to achieve, and it looks something like this:

Dim itemList As String = Sublocations( "GraffitiSuite" )
Dim items() As String = itemList.Split(ChrB(9))

Dim itemName As String
For intCycle as Integer = 0 to items.ubound
itemName = items(intCycle)
if not SelectProjectItem( itemName ) then '// Folder
'// Do something with the folder here
else '// Class, Module, Window, ContainerControl, etc.
'// Update our Constant value here
end if
Next

The next thing we have to worry about with traversal is folders within folders within folders. This is simple enough to quash by recursively calling a method on each folder we encounter and iterating its contents. Creating such a method in XojoScript is really straightforward, so I just needed to plan out my attack. First I needed to figure out exactly how Sublocation() worked. Looking at the output with a call to Print(itemList) I can see that only top-level folders and items are returned here, so I need to drill down. I figured my method for this recursion should have a signature something like sub getItems( ParentName as String ) so that I could easily just pass in the parent folder name and knock down every folder or object within in one swoop. Inside this method I need to use the code above for determining whether the current object is a Folder or not, then either set the Constant value or call getItems with the path of the item.

Filling all of this out, we end up with the following:

Sub getItems( ParentName as String )
Dim itemList As String = Sublocations( ParentName )
Dim items() As String = itemList.Split(ChrB(9))

Dim itemName As String
For intCycle as Integer = 0 to items.ubound
itemName = items(intCycle)
if not SelectProjectItem( itemName ) then '// Folder
getItems( ParentName + "." + itemName )
else '// Class, Module, Window, ContainerControl, etc.
ConstantValue( itemName + ".GraffitiSuiteVersion" ) = currentVersion
end if
Next
end sub

getItems( "GraffitiSuite" )

Excellent! Testing that did what I want, but if you notice there’s a variable in that code block that’s unaccounted for: currentVersion

Getting the Current Version

I’ve saved the easiest step for last. I added a Constant called GraffitiSuiteVersion to my project’s App object and set its value to “29.0” for Release 29. From there, writing the code to get the current version in to that variable is super simple by comparison:

dim currentVersion as String = ConstantValue( "App.GraffitiSuiteVersion" )

Result

dim currentVersion as String = ConstantValue( "App.GraffitiSuiteVersion" )

sub getItems( ParentName as String )
Dim itemList As String = Sublocations( ParentName )
Dim items() As String = itemList.Split(ChrB(9))

Dim itemName As String
For intCycle as Integer = 0 to items.ubound
itemName = items(intCycle)
if not SelectProjectItem( itemName ) then '// Folder
getItems( ParentName + "." + itemName )
else '// Class, Module, Window, ContainerControl, etc.
ConstantValue( itemName + ".GraffitiSuiteVersion" ) = currentVersion
end if
Next
end sub

getItems( "GraffitiSuite" )

That’s it! I now have an IDE Script that I run once for Desktop Edition and once for Web Edition any time I release a new version and every object in the project gets its version updated. Could this be simpler to do? Sure. There’s a lot about the IDE Scripting implementation that doesn’t make sense, and some important notes are noticeably absent from the documentation, but this is a powerful tool. In about an hour I solved a problem that will surely result in fewer emails and tickets asking “How do I tell what version of GraffitiSuite I’m using?”

UPDATE

With all of that working, I decided to get rid of the constant App.CurrentVersion and just use Shared Build Settings version numbers. For that, I replaced Line 1 above with:

dim currentVersion as String = PropertyValue( "App.MajorVersion" ) + "." + PropertyValue( "App.MinorVersion" )