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.
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
EDIT: Actually, not 100% if a
DropdownButtonwould 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
SplitButtoncomponent would create two WPF
Buttoncomponents, one for the main button (
this.button) and the other to trigger the dropdown menu (
- When the
OnApplyTemplate()would register the
ButtonClickevent handler to the main button’s click event
- When the main button is pressed, the
ButtonClickevent 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 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.
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
<Object>.getType() function allows us to access metafunctions related to a given class. Using
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
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…