Creating and Using Invisible Window IDs

Editor’s note: I first became aware of Paul Jansen when I licensed his FMTools in the late 1990s, and I finally had the pleasure of meeting him last June at dotFMP after 20 years of online and voice communication. It’s an honor and a privilege to welcome him to FileMaker Hacks as a guest author.

FileMaker is pretty flexible. As developers we are given options as to how to reference things; fields, scripts and layouts can be referenced by name or number/id. Whilst we know that referencing things by name is more fragile, it is still a very useful capability to have, but it is definitely more robust to reference by ID…

Windows are a bit of an odd one out; we can only reference them by name.

As windows are probably the most likely things to have their names changed during normal day to day usage of a FileMaker Database it would be really useful if we could keep track of them by an ID independent of the displayed window title. Another benefit of having access to an ID to identify a window is that we then have to option to very easily store and access window specific variables. Kevin has made use of window specific variables based upon the window name in several articles and I suggest that access to a numeric ID that was independent of the window title would be an improvement.

The idea for this article came from a product idea posted to the FileMaker Community requesting native support for window IDs (https://community.filemaker.com/ideas/1169). In the comments to this Idea, David Wikström was kind enough to share a technique he uses to combine a binary representation of a window ID number with the window title using invisible characters. David’s solution (which you can download from the Idea thread on the community site) uses a number of scripts and custom functions and stores data in several global variables using XML.

Since version 16, I like to use the native JSON functionality instead of XML and I was inspired by David’s work to change the storage mechanism to JSON. As I discovered when converting another utility solution (BrowserNav) to use JSON, it is possible to significantly reduce the number of global variables used for data storage as the native functions make it really easy to access the information we need. I also decided that I wanted to make adding window IDs as simple and lightweight as possible so that it can easily be added to an existing database.

So here is my solution…

Demo file: WindowID_JSON.fmp12 (requires FM 16 or later)

The window ID is really more like a layout number; it is a simple number based upon the creation order of the windows. So when you open a solution the the first window will have a window ID of 1. Open another window and it’s window ID will be 2 and so on. These window IDs are stored in a JSON array with the index position in the array being the window ID.

[
	null,
	{
		"id" : 1,
		"name" : "WindowID_JSON",
		"open" : "2019-01-11 1:09:27 PM",
		"portalFilterValue" : "p"
	},
	{
		"close" : "2019-01-11 2:17:33 PM",
		"id" : 2,
		"name" : "WindowID_JSON - 2",
		"open" : "2019-01-11 1:49:30 PM"
	},
	{
		"id" : 3,
		"name" : "WindowID_JSON",
		"open" : "2019-01-11 2:17:32 PM",
		"windowMessage" : "Example of window scoped variable text"
	}
]

So let’s start by reviewing the contents of the JSON array. The first entry in the array is null as there is no window zero and it is just easier to use the window ID as the array index pointer. The JSON for each window also includes a time stamp for when the window was opened and in the case of window 2, a time stamp when the window was closed. I decided not to reuse the window ID of closed windows as it avoids the possibility of contamination (a new window picking up data from the array that belonged to the previous owner of that window ID). It also makes it easier to count the items in the array and add 1 to get the window ID of the next new window.

The JSON for the first window contains the object “portalFilterValue” and the JSON for the third window includes “windowMessage”. These are examples of how this technique can be used to store window specific information. Whilst it would be nice to have variables that are limited to the scope of a window, it seems to me that this is a reasonable alternative that is available now.

Now let’s take a look at the windows represented by the above JSON which is stored in a global variable $$WINDOW.JSON.

The two windows shown above are the same layout in the same file. They appear to have exactly the same name whilst window one has the portal filtered to show only fruits beginning with ‘p’ and window two is displaying the contents of the “windowMessage” object. The final thing to note is that the window IDs are calculated from the window names. The window names are actually different and each includes an invisible binary ID…

Lifting the hood (or ‘looking under the bonnet’ if you are in the UK)

In order to manage the window IDs we need to make use of the OnWindowOpen and OnWindowClose script triggers which are set in File Options:

The script ‘WindowClose’ is currently very simple; a single Set Variable step to add the close timestamp to $$WINDOW.JSON.

‘WindowOpen’ on the other hand has quite a lot to do:

  1. Remove any invisible characters from the window title
  2. Calculate a new window ID – the number of items in the JSON array + 1
  3. Store the window information in a new array item in $$WINDOW.JSON
  4. Convert the window ID to binary number
  5. Transform the binary number into invisible characters and append it to the window title

Two recursive custom functions are needed to convert to and from binary (the release of 17 means that all developers have access to custom functions which is great):

  • Dec2Bin(integer)
  • Bin2Dec(binary)

There are also 3 custom functions specific to the technique:

  • Win.ID( WindowTitle) – extracts the invisible binary ID from the Window Name and converts it to an integer ( I tried to name the parameter ‘WindowName’ but FileMaker wouldn’t let me!)
  • Win.NewName(OldName;NewName) – This function is designed to be used by the ‘Set Window Title’ script step and ensures that the new name includes the invisible ID and also updates the variable $$WINDOW.JSON
  • Win.Name(WindowID) – constructs the full Window Name with the invisible binary ID appended from the integer WindowID using the name stored in $$WINDOW.JSON

David Wikström’s brilliant idea to convert the Window ID number to binary means that we only need two different invisible characters instead of 10.

So to convert our integer Window ID to an invisible binary number we use:

Substitute ( Dec2Bin ( $windowID )
 ; [ 0 ; Char ( 8203 ) ]
 ; [ 1 ; Char ( 65279 ) ]
)

This is then easily concatenated with the Window Name to produce what is referred to in the scripts as $WindowNameID. To extract the integer Window ID from this we use the Win.ID() custom function:

Bin2Dec (
	Substitute (
		Filter ( Get(WindowName) ; 
			Char ( 8203 ) & Char ( 65279 ) ) 
		; [ Char ( 8203 ) ; 0 ] 
		; [ Char ( 65279 ) ; 1 ]
	)
)

The Filter function removes everything except the two invisible characters which are then converted to a binary number using substitute and to an integer using the Bin2Dec custom function. This can then be used to access window specific information in the $$WINDOW.JSON array. For example:

JSONGetElement ( $$WINDOW.JSON ; "[" & Win.ID("") & "].windowMessage" )

With all the pieces in place Opening and closing windows will be managed automatically. There are a few other scenarios where we need to take a little care. When renaming windows, we need to use the following script step to ensure that the invisible ID is not lost and that the $$WINDOW.JSON variable is updated.

Set Window Title [ Current Window ; 
	New Title: Win.NewName ( $oldName ; $newName ) ]

We can also select windows based on the Window ID:

Select Window [ Name: Win.Name ( $windowID ) ; Current file ]

Finally there is one FileMaker anomaly to deal with. The GTRR script step does NOT trigger the OnWindowOpen script when set to show the related records in a new window, so we need to run this ourselves to ensure the newly created window is processed correctly.

Go to Related Record [ From table: “ChildTable” 
	; Using layout: “ChildTable” (ChildTable) ; New window ]
Perform Script [ “WindowOpen” ]

Do take a look at the sample file; I’d be delighted to receive feedback and suggestions for improvement and optimisation.

Finally, many thanks to Kevin Frank for the opportunity to post on filemakerhacks.com and to David Wikström for the initial inspiration to explore the invisible approach to window IDs.

My plan is to review the demo file (taking into account any feedback to this article) and submit it as a module over at http://modularfilemaker.org. I will add a comment to this article when the module has been submitted.


Afterthought: I wonder why FileMaker refers to “Window Name” everywhere except in the script step “Set Window Title”? Oddly the options for this script step also refer to “Window Name”!

3 thoughts on “Creating and Using Invisible Window IDs

  1. Kevin Frank

    Hi Paul,

    I was not aware that spawning a new window via Go To Related Record would bypass an OnWindowOpen trigger. I just tested to confirm. Thanks for a great article.

    Kevin

    Reply
  2. Matthew

    Great idea! Thanks for sharing this with the community.

    This isn’t working in a loop, so there probably aren’t 6 or more digits worth of iterations to worry about. However, if devs start to add more tags than the few example given here, and/or in files that spawn a lot of windows (cards for UI, utility windows for processing, and so on…), and given that some users absolutely refuse to close their sessions at the end of the workday, I wonder if Insert Calculated Result might be subbed in for Set Variable, rather than risk degrading performance by manipulating large blocks of text with Set Variable.

    https://community.filemaker.com/message/788977

    Reply
    1. Paul Jansen Post author

      Matthew, It would be great if you could expand on “This isn’t working in a loop” Do you mean that the OnWindowOpen trigger isn’t running?

      Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.