Thank you for this example. It helped me a lot!
These techniques are definitely useful and Iāve learned some things from this discussion. Out of curiosity, I would have just put the shared variable in a module that was available to both screens. What are the downsides to doing that?
Some folk consider this the Xojo equivalent of āglobal variablesā and frown upon them.
But it does work perfectly well, and makes a lot of sense.
Imagine your app needs to know what the userās email address is, and displays it on 3 different screens.
You might store it (encrypted?) as a preference setting, but load it into such a āglobalā variable when the app starts, and refer to it whenever it is needed.
One downside is that - as a basic variable type - if you amend the value, it would not propagate to open windows automatically.
They would need to poll the value now and then to keep up to date.
Thatās not great UI
I can imagine a few ways around that.
One would be to make the variable a computed property, so that when the value is āsetā, it has the ability to push new values to any open window.
The discussion above has talked about giving a window a property of āparentā, through which it can communicate to another screen.
In iOS apps, I suspect there is usually only ever 1 settings screen, 1 design screen, 1 password screen at any time, (because you donāt have the multi window expectation of desktop)
So my own solution to all of this is a combination of the āmodule-level variableā and the āreferring to a variable which is another screenā
In the module I have Public variables like this
theSettingsScreen as wndSettings
thePasswordScreen as wndPassword
theDesignScreen as wndDesigner
When these actual screens open, the first line in Opening is
theDesignScreen = self
And then I can ātalkā to this screen from any other screen like this:
if theDesignScreen <> nil then theDesignScreen.methodName
if theDesignScreen <> nil then theDesignScreen.property3 = EmailAddress
This particular method only really works as long as there can only be a single instance of any window type.
(Its fine to close an instance and create a new one as long as there is only one of any type at any time)
These setting changes could / should be wrapped up in special methods for the purpose, as Greg suggests, or these special methods could be the āsetā part of computed variables.
Another way to handle this is to use a messaging system where you literally send the updated value to any class thatās listening for changes.
Typically I would create a module called Messaging and inside that, a Class Interface named Receiver with a single method:
Sub MessageReceived(name as String, data as Variant)
To the module you add a property and two methods:
Private mReceivers() as WeakRef
Sub AddReceiver(obj as Messaging.Receiver)
mReceivers.add new WeakRef(obj)
End Sub
Sub Send(name as String, data as variant = nil)
For I as integer = 0 to mReceivers.lastIndex
If mReceivers(i) <> nil and mReceivers(i).value <> nil then
Receiver(mReceivers(i).value).MessageReceived(name, data)
Else
mReceivers.RemoveAt(i)
i = i - 1
End if
Next I
End Sub
Once this has been added to your project, youāll need to add a line of code to the Opening event of any class that needs to receive data.
Messaging.AddReceiver(Self)
ā¦And add the Class Interface to the class, either by right-clicking the item in the navigator and selecting Add Interface or by clicking the Interfaces button below the Super field in the Inspector. In that dialog, check the box for Messaging.Receiver. Make sure the checkbox for creating the method(s) is checked at the bottom, something about #pragma error IIRC. then click OK. Your class should now have a method called MessageReceived with a signature that we defined above.
Nowā¦ whenever you need to send a value to one or more receivers, you just write code like this:
Messaging.Send(āuser ageā, 23)
All of the classes that registered to receive messages (that still exist) will be notified of the change.
A quick note about the WeakRefs: The reason why theyāre used here is so that the messaging system doesnāt inadvertently hold references to all of the classes that are listeners. Otherwise your app would leak receivers if you didnāt remember to remove them. I just find this way easier in the long run.
I use this pattern in all my apps and it is extremely useful.
It is used to
- update values entered by the user
- telling the parent screen that something changed in a presented modal screen
- notify all open screens and singleton classes that the user made an in-app purchase and update the UI / unlock some features
- Communicate between App.HandleURL and the presented screen
- ā¦
I also extended the pattern a bit to send a delayed message when the receiver hasnāt been instantiated yet:
Messaging.SendDelayed(afterMsec as Integer, name as string, data as Variant)