Howdy!
Today I wanted to just share abit about some development experiences in building my NDI Telestrator project. NDI Telestrator is a graphic annotation software built for the NDI protocol, which allows you to draw over a background and send the overlay over NDI for use in a vision mixer / production environment - pretty cool hey! Compared to the official (and now discontinued) application, I have a few improvements such as being able to export and recall annotations, as well as having multiple layers of annotations.
The official NDI Telestrator application. It’s paid; and also doesn’t work with NDI 4 or NDI 5.
The Issue
Today I was working on fixing a bug which caused my dropdown buttons to disappear right after clicking on them, have a look at the below recording.
Why was this happening?
After double checking my code and making sure that I didn’t make an error, I had a closer look at the Metro UI library that I was using.
It turns out that SplitButton.cs:331
was being called after line 306 (i.e. the line that executes my intended action to open the dropdown box) - this meant that my code to open the dropdown box was nullified. I need to somehow open the dropdown box on click without letting that ButtonClick
event handler fire…
Why is that line there?
The code itself is correct, because it’s a button (hence SplitButton), rather than a dropdown box / combo box. There exists a DropdownButton
component which one might try to use instead given its eponymous name, however I needed the functionality to set and get its value, which only SplitButton
provided.
EDIT: Actually, not 100% if a
DropdownButton
would have worked instead.
I definitely must have tried it unsuccessfully last year when first designing this application…
SplitButton
is intended to be a button - which should trigger an event related to the selected value. However, I was using the SplitButton
as a fancy dropdown list - where clicking on the button shouldn’t actually call any operational function. Instead I wanted the action of pressing the main button to follow suit with opening the dropdown list.
As per original design
- The
SplitButton
component would create two WPFButton
components, one for the main button (this.button
) and the other to trigger the dropdown menu (this.expanderButton
). - When the
SplitButton
component loads,OnApplyTemplate()
would register theButtonClick
event handler to the main button’s click event - When the main button is pressed, the
ButtonClick
event handler will trigger the assigned user command (line 306) then hide the dropdown menu (line 311)
So if I assign the user command to open the dropdown menu, line 311 will just close the menu 😢
The library is code is correct, but my use for it violates its programmed behaviour
How can we fix this?
Sometimes you got to reflect on your mistakes.
Ideally we could just remove line 311 from the UI library, but that would require extra code maintenance. Otherwise we could try removing the ButtonClick
event handler from the main button’s click event. But, both the main button and ButtonClick
event handler were enforced with the private
access modifier - which means that only SplitButton
instance itself can access those properties… or can they…
Reflection
Reflection is a .NET paradigm that allows you to gain information about an object’s / assembly’s structure - such as its classes and the classes' methods, attributes, and properties - regardless of their access level (i.e. private
and protected
data can be accessed!).
By utilising reflection we could modify the behaviour of a particular SplitButton
instance without having to modify its source code! So how exactly do we do that?
Step 1. Figuring out what to fix
Firstly, we need to figure out how to hack into the SplitButton code.
To make the dropdown show, we could perhaps add an additional event handler to the main button’s click event - however it will execute after ButtonClick
finishes - and would hence cause a visual bug where the dropdown could close momentarily after opening, for it to then open again shortly after. Additionally, our event handler might not even fire if the <Event>.Handled
property is set to true by any earlier event handler.
Instead, we need to remove the original ButtonClick
event handler, and then add our own event handler. Performing this operation would remove the call to the user command (line 306), but we technically don’t need that command and could just toggle the dropdown menu in the reflection code ourselves!
Step 2. Applying reflection
The <Object>.getType()
function allows us to access metafunctions related to a given class. Using GetField
and GetMethod
with different BindingFlags
mask filters, we can locate the button
field and ButtonClick
method respectively for a given object of that class type.
With our now accessible reference to the main button component, as well as the ButtonClick
event handler, we can deregister the event handler, and then register our own event handler to toggle the dropdown!
I refactored the reflection code so that I could reuse it for other dropdown boxes that existed in the application (which at the time of writing, were two).
Note: Reflection might fail during object initialisation, where properties may not yet be available, so if you need to implement a similar sort of functionality override in your own application, I would recommend hooking into the component’s
Loaded
event
Conclusion
As you can see, it’s all working!
Reflection is pretty cool, but also rather slow as the lookups are performed dynamically when called. If you ever need to reflect things in your code, cache where possible!
… or find a better library / write better code
Now to figure out how to increase rendering and display performance…