Okay, the previous paragraph does not work for .NET Micro Framework 3.0 - there are almost 300 buttons defined now. However, I'm strongly against the way it was done (more on this later), and this article at least shows how easily you can rename the constants.
Understanding the problem is very important thing. Now let's look what we can do with it right now.
If you don't need more than 13 buttons, this trick will work without any dramatical changes to the application:
The following applies to 2.x version only. Thank you for voting!
However, using such button requires some additional code. If you try to use it to construct ButtonEventArgs, an ArgumentOutOfRangeException is thrown.
UncheckedButtonEventArgs
I personally do not see any single reason why the ButtonEventArgs should put any constraints on the button value, but at least the class is not sealed, so we can create our own and inherit it.
But there are two problems. The first one is, that the ButtonEventArgs.Button is field, so it can't be overriden (and I doubt it would have been marked as virtual either). The second problem is, that it is declared as readonly; in other words, it can be modified only in the constructor. So we have the worst situation for deriving - we have to hide the underlaying member:
public class UncheckedButtonEventArgs : ButtonEventArgs
{
private readonly Button _button;
public new Button Button { get { return _button; } }
public UncheckedButtonEventArgs(PresentationSource source, TimeSpan timestamp, Button button)
: base(null /* buttonDevice */, source, timestamp, (button > Button.None && button <= Button.Last) ? button : Button.Last)
{
_button = button;
}
}
For compatibility reasons, we set the base's Button to the desired value if it is in the valid range, and to Button.Last otherwise. The buttonDevice is not used at all, so I pass null to the base, although I'm not so sure how good decision is this for the future.
This solution, however, does not work on its own. Due to hiding the base's member, you have to deal directly with the UncheckedButtonEventArgs type to access the new one. This simple check can be done in the button handler:
protected override void OnButtonDown(ButtonEventArgs e)
{
Button button = e is UncheckedButtonEventArgs ? (e as UncheckedButtonEventArgs).Button : e.Button;
...
}
Injecting your buttons into the WPF input system
Using the GPIOButtonInputProvider is a bit trickier. Open it and locate the ButtonPad class. It has a method called Interrupt, which looks like this:
void Interrupt(Cpu.Pin port, bool state, TimeSpan time)
{
RawButtonActions action = state ? RawButtonActions.ButtonUp : RawButtonActions.ButtonDown;
RawButtonInputReport report = new RawButtonInputReport(sink.source, time, button, action);
// Queue the button press to the input provider site.
sink.Dispatcher.BeginInvoke(sink.callback, report);
}
This is the final method which pushes the input into the WPF system. The dispatcher then calls sink.callback (delegate to InputProviderSite.ReportInput method) which more or less causes the PreviewButtonDown and ButtonDown events to be fired and routed. Unfortunately, the ButtonEventArgs is instantiated inside this process and we cannot just change it. Moreover, due to lack of input system extensibility in WPF initial release, we aren't able to replace this process with another one.
Not everything is lost yet. We can't plug us into the input system, but at least we can fire the events manually. The ButtonDown and ButtonUp are routed events so we need to fire them throughout the control tree. This is exactly what UIElement.RaiseEvent(RoutedEventArgs args) does. The last thing we need to decide is on which element we should call it. But that's a simple one, on the one with focus! Now we can put together the method which the dispatcher needs to call (and its delegate signature):
private delegate void RaiseEventCallback(RoutedEventArgs args);
private void RaiseEvent(RoutedEventArgs args)
{
UIElement target = Buttons.FocusedElement;
if (target != null)
target.RaiseEvent(args);
}
Put it directly to the GPIOButtonInputProvider class and create a delegate and assign the method to it in the constructor:
public sealed class GPIOButtonInputProvider
{
...
private ReportInputCallback callback; // current delegate to the InputSite
private RaiseEventCallback eventCallback; // delegate to our method
// This class maps GPIOs to Buttons processable by Microsoft.SPOT.Presentation
public GPIOButtonInputProvider(PresentationSource source)
{
...
callback = new ReportInputCallback(site.ReportInput); // current delegate
eventCallback = new RaiseEventCallback(RaiseEvent); // our new one
...
}
And finally, modify the Interrupt routine above (in ButtonPad class) to send the exended buton range to our method instead of the InputProviderSite:
...
// Queue the button press to the input provider site.
if (button > Button.None && button <= Button.Last)
sink.Dispatcher.BeginInvoke(sink.callback, report);
else
{
RoutedEventArgs args = new UncheckedButtonEventArgs(null, time, button);
args.RoutedEvent = state ? Buttons.ButtonDownEvent : Buttons.ButtonUpEvent;
sink.Dispatcher.BeginInvoke(sink.eventCallback, args);
}
}
Well, everything is ready now, you are free to use your buttons in the provider:
...
ButtonPad[] buttons = new ButtonPad[]
{--
// Associate the buttons to the pins as setup in the emulator/hardware
new ButtonPad(this, Button.Up , Cpu.Pin.GPIO_Pin0),
new ButtonPad(this, FunnyButton.UltraViolet , Cpu.Pin.GPIO_Pin1),
};
...
And my last note today: Keep in mind this is a simple workaround. If you would really want to make it the right way, you should also call the preview events using the tunnel routing strategy and check if the input was handled before firing the actual button events. Have a nice day!