Global variable fails between databases

I have an issue where a global variable is not passed or updated to a second db. As a test I created a new db,“From”, with one column (labeled “A”) and two procedures. An “.Initialize” with:
“global gxA”
and “GetVar” with two lines:
gxA=A
message gxA.
I then created a second db, “To”, with one column "B and one procedure “Place A” with lines:
B=gxA
message gxA
I then entered some text into From, A and ran the GetVar, activated “From” and ran the “Place A” action. The first time I ran this it worked as expected. I then went and changed the text in “From” “A”. Now it fails with the message “Contents of variable [gxA] have not been defined.”.

A work around is not practical as I have many databases with dozens of procedures that use global variables to transfer data between them. This problem only seems to occur in procedures or databases where a specific global variable is altered and subsequently fails. Also, this seems to have started with the last update to Version 10.2.0.b39 (4486).

I can’t duplicate this problem. It’s working for me every time. The only thing I can think of that could cause this is if one of the databases, probably the To database, now has a FileGlobal named gxA. One could be created by creating a form object capable of setting the contents of gxA.

If one database is working with a global variable named gxA and the other is working with a fileglobal with the same name, then one of those variables isn’t being assigned any content.

An easy way to check for the existence of a hidden fileglobal named gxA (as Dave is suggesting) is to run this code that will list it if it is in either fie.

message “From:”+arraystrip(arrayfilter(dbinfo(“filevariables”,“From”),cr(),{?(import()=“gxA”,import(),“”)}),cr())
message “To:”+arraystrip(arrayfilter(dbinfo(“filevariables”,“To”),cr(),{?(import()=“gxA”,import(),“”)}),cr())

The code simply gets all the fileglobals from each file and filters out everything except the gxA variable if it exists.

There’s an even easier way, since Panorama X has a function for doing this. Simply use:

scopes("gxA")

This will list all of the scopes that have been defined with this variable name. Probably the result will be:

fileglobal
global

By the way, there is also a function called scopevalue( that lets you access a particular scope even if it would normally be hidden.

To make sure that your procedures can’t be confused by stray fileglobal variables, I would change GetVar to:

letglobal gxA = A

and change Place A to

B = scopevalue("global","gxA")

Thank you all for the help, it really did.
I am curious as to how variable defined as “global” would get defined also as a “fileglobal”?

It won’t be. You will have two different variables with the same name. One of them will be global, and the other fileglobal.

There are a couple of different ways that a fileglobal variable can be declared. One is for a procedure to declare it with a statement like FileGlobal, Define, or LetFileGlobal, and the other is by a form object that has been setup to set the value of a variable by that name. If a variable by that name doesn’t already exist when the form opens, one will be created. You can choose the type of variable that will be created in the Form Properties, but the default is FileGlobal.

When a database opens, its initial window, or windows will open before the .Initialize procedure runs, so it’s possible for a form object to have already created a fileglobal by that name before the .Initialize declares the global variable. When both variables exist, the fileglobal takes precedence within the database where it was declared, but the global variable is the only one that can be seen by other databases.

If the form object was in the From database, then it set the fileglobal, and the To database found that the global variable hadn’t been assigned a value.

If the form object was in the To database, then the From database set the global, but all the To database knew was that its fileglobal hadn’t been assigned a value.

I believe that you are right, I’m sure a form object created the fileglobal. Just found out how that happens. Again, thank you.

This is a timely question for me as I was going to post the same question just now after puzzling over why my database global variables stopped working as I have expected in the same way corky37 described. I’m wondering if one of the experts could shed a little more light on how to define global variables that work across databases.

In my suite of database, the first to open has a .Initialize procedure that lists the all the global variables immediately and defines some of them in the Initialize and others later, maybe in another database. I’ve done this for over 30 years. Just this week I changed the order of something and my UserName global variable (and others) worked in the initial database, but was undefined in other databases that had previously displayed in forms fine.

I’m not sure why this didn’t work: Global UserName followed immediately by UserName=lookup from the preferences database didn’t cross over, but LetGlobal UserName=lookup… worked fine.

I hope I’m being clear in the question, but it seems like something has changed in variable priorities that I’m not getting. It “seems” that prior, if a Global variable was created, it had priority everywhere, but now FileGlobals trump Globals and the order of how things are defined and what happens automatically in other databases blocks the value of the Global variable.

I follow Dave’s description where a form opens has to create a FileGlobal for a display element if none has been given a value, but my .Initialize procedure opens all the needed databases using the OpenSecret command, and only opens forms when I go to them later and open a form. Does OpenSecret also scan all the forms in the database and create FileGlobal variables before any forms are opened?

I’m still confused as my scheme has always been in the very first .Initialize procedure to list the Globals, OpenSecret the supporting databases, then start giving values to the Globals, then later go to a database and open a form where all the Global values work in procedures and forms. This “seems” to have changed recently.

Nothing has changed. Variable scope works the way it always has since fileglobal variables were first introduced 30 years ago.

Fileglobals have always “trumped” globals, from the very beginning.

Locals trump all other types of variables.
Next is windowglobal, then fileglobal, then global.
The lowest priority is database fields.

No, but when the form is opened, the fileglobal variables will be created.

I know why. There is a fileglobal variable named UserName, perhaps created by a form, or some other procedure (remember also that permanent variables are actually fileglobal variables). The Global statement creates a second variable with the same name, so now you have two - the global and the fileglobal. The assignment on the next line will use the fileglobal, because fileglobals always have priority over globals.

When you use letglobal, the value will be assigned into the global variable, because it’s all one line instead of separated into two separate statements. But you still have two variables with the same name.

In 1990 global variables were the only way to pass variables between databases. But it’s now 35 years later. Here in 2024, Panorama has many ways to pass values between databases. For example you can use the fileglobalvalue( function to access a fileglobal value in another database.

If you want to use global variables for this, you can, but as you are discovering this can be tricky and error prone unless you are very careful. My recommendation is to avoid the use of global variables - in Panorama X there is ALWAYS another way to keep a value instead of using a global.

Just to reiterate - the way that globals and fileglobals interact has not changed recently. It has not changed in the last 5, 10, or even 25 years. And it never will change.

What has changed is that you have made a change in your databases that is creating a fileglobal that you apparently aren’t aware of. I’m not sure if you read all of the answer’s to @corky37’s question, they provide all the information you need to diagnose this problem.

Jim, thanks for the information. I haven’t had quite the understanding of the variable hierarchy before (I’m self taught since '91) The part Dave mentioned about the initial windows opening before the .Initialize runs was my last helpful clue. The UserName variable gets used in a window that opens automatically as it was open the last time it quit. So it is then a fileglobal followed shortly after by a duplicate Global. I’ve operated since the beginning as if the .Initialize commands run before anything else happens.

The short term solution was to make the Quit procedure in the main database have a MakeSecret statement right before the quit command so all windows are closed. Then when it opens next time the Global statement and assignment stay Global and can be used by the other db’s. I’ll be more careful around these matters in the future.

So you are trying to intentionally use a global variable in a form. My advice would be to never do that.

There’s a much simpler way to make sure that only specific windows open when the database opens.

Here is my suggestion for a long term solution.

Change the Text Editor object to use the Formula option as the data source.

Set the formula to

catcherror("",globalvalue("UserName"))

Set the procedure to:

letglobal UserName = TextEditingResult

With this configuration you’ll be able to edit the global value, but opening the form won’t create a fileglobal variable. You should be able to set this up in a couple of minutes and it won’t require any other changes to your existing code.

The name you are using, UserName, sounds to me like it should probably be a preference rather than a global variable. However, this would require all of your existing code that access the variable to be changed.