My two last blog posts tackled two aspects of working with control add-ins: making any property or method accessible to C/AL, and making any event accessible to C/AL The first was a simple trick, the second was a bit more complex. Together, these two tricks enable you to easily interact with whatever Windows Forms control the add-in shows: you can access any of inner control’s members – properties, methods, or events. At this stage, there is a very simple, but robust framework in place, which lets you publish any kind of control and make it fully available to C/AL. Cool.
But, couldn’t we make it a bit cooler? How about being able to instantiate any control, and having C/AL decide which control to instantiate, instead of hardcoding it in the C# code?
Yes, we can do this.
The problem
When NAV instantiates a control, it invokes the CreateControl method which returns a reference to the an instance of System.Windows.Forms.Control or its descendant that was instantiated within this method. This method must “know” what kind of control to instantiate. To enable C/AL to instantiate any kind of control, you must enable C/AL to let the CreateControl method know about this type, however, before this method is invoked, the control itself is inaccessible through C/AL.
The solution
Sure enough, you can’t call the control add-in methods or properties before the NAV extensibility framework invokes the CreateControl method, however, at the point when the CreateControl method is invoked, the control add-in itself is already instantiated and C/AL can communicate to it.
So, the solution to this problem is simple: expose a property of type System.Type, and an event. Then raise the event from within the CreateControl method. When the event trigger fires in C/AL, you can assign the desired System.Type to this property. Finally, the CreateControl method can dynamically instantiate a control of this type and return it to NAV.
The skeleton for the solution would be this:
Then, you could do this in C/AL:
(RadioButton is, of course, a DotNet variable of subtype System.Windows.Forms.RadioButton.
And this would, ultimately, achieve this:
Okay, we are missing some error handling in the code, but in a nutshell, this gets the job done.
The problem for the solution
As much as this gets the job done, it creates a couple of problems. First of all, it again easily hides away any methods or properties on the InnerControl, that your desired inner control may have, and that System.Windows.Forms.Control class does not have. For example, with RadioButton, as shown in the example above, this would not compile:
The reason is, the Checked property is a member introduced in the RadioButton class, and since Control is the type of the InnerControl property, the Checked member is inaccessible at compile time.
A solution that crossed my mind was to change the C# declaration of the property to this:
However, this does not solve the problem for two reasons, first is – dynamic framework is not available to the Windows Client, and the second is, C/AL thinks it’s smarter than C# and does an unnecessary type member check, preventing the whole thing from working – unnecessarily.
So, let’s take a look at the first problem – the fact that dynamic framework is not available. All of the code above would compile, but it would also result in this:
This error: “Dynamic operations can only be performed in homogenous AppDomain” means that This can be fixed fairly easily. In your Windows Client folder (typically C:\Program Files (x86)\Microsoft Dynamics NAV\71\RoleTailored Client) locate the config file (Microsoft.Dynamics.Nav.Client.exe.config), then change the value of the NetFx40_LegacySecurityPolicy setting to false, like this:
Save the file, of course. This workaround enables you to run the control add-in (it shows the radio button again) but it still does not allow you to do this:
Here we’ve reached a yet another of illogical limitation of C/AL compiler – even though C# (and .NET itself) do allow compilation of the construct above, C/AL is unaware of dynamic types, and it does an unnecessary check for itself. Keep this in mind – it is the C/AL that is preventing this, not .NET.
Good. Now we know that exposing the type as dynamic does not solve anything, courtesy of C/AL compiler that thinks it knows C# better than C#.
So, how can we enable C/AL to set any property on the inner control, regardless of its type? You can use reflection, of course. The PropertyInfo class allows you to get or set property values by name, and its use is very simple. This would be a possibility
This would then enable you to write this:
And of course, this would set the Checked property on the inner control to true, showing the radio button as selected.
Doing the same with methods would require some more work. You would need a method that receives a desired method name, a collection of parameters as key/value pairs (for example, a Dictionary), and it should return System.Object. It would then use the MethodInfo class to dynamically invoke a method using reflection. If I weren’t too tired for today, and if it belonged to this post, I’d go write this piece of code now. Instead, I’ll leave this for a future post.
In my next post I’ll stay a bit longer with the indexed properties, the mechanics behind them, and give some tips, tricks, and hints for using them in your Control Add-ins and interoperability in general.
For now, I’m switching back to my vacation mode. If this was the view you had from your balcony every evening (even if it were for a very short while) you’d do the same, trust me.
Hi Vjeko
Sorry to bother you in your vacation. This is a cool solution. What would I do if I wanted to create a X number of buttons but not just one ? I would also need an event fired when the user pushes any of the buttons.
Pingback: Trick: Instantiating any control in a Control Add-in | Pardaan.com
Pingback: Directions US Presentation and Files | Vjeko.com
Pingback: Directions US Presentation and Files – 9/16, Navigate Into Success |
Hello vjeko,
I am getting the error described below when i run the page from nav
A DotNet variable has not been instantiated. Attempting to call Text in Page Control AddIn Test: AddIn::ControlAddInReady
Am i missing any thing ?
Vjeko, my guess is this is not possible with javascript control add-ins. Am I right?
Of course you are right – how would you instantiate a .NET control add-in from JavaScript? However, something very similar to this is what I normally talk about at my TechDays sessions, just check my TechDays talks from 2015 and 2016.