A user of EMUCenter showed me his new Media Center remote a few days ago on my forums, and I discovered that Microsoft has left out the '#' and '*' buttons on the latest generation of their control.
Of course, MS has always stated that there is no guarantee that those particular buttons will be present. But this leaves us with a dilemma.
There are now basically no 'spare' buttons present on the remote control. If we need to create a context menu (or a 'more' menu, like right-clicking on an icon in Windows) we are presented with a problem - which button on the remote can we override?
The ideal button is 'i', but unfortunately MS has restricted this. We can't override it. I used to use '#' and '*' extensively, but now they aren't present in new remotes. My applications often use the text buttons, so the numbers are off limits, so are all the 'play' buttons (eg. skip forward etc.) because someone may be watching media while accessing the application.
So...are there any more options?
At the end of the day, the answer is basically 'no'. Which means we either severely limit our applications to suit this situation, or we have to put in a little extra work. I'm going to detail my current 'suggestion' for how to take care of this problem. I have chosen to replace a seperate button with a press and hold. So we simply hold the 'Select' button down for a second or so in order to bring up my context menus.
One major caveat - this solution does NOT work for mouse input due to the fact that there is very little mouse monitoring available in Media Center. We can still respond to a click as per normal, but we can't capture a click-and-hold or a right-click.
The biggest problem is that this involves a significantly more complicated setup, because the 'Clicked' member of the 'ClickHandler' class only responds to the user releasing the button. We can't capture the start of the button press. So we basically have to throw out 'ClickHandler' and replace it with something else.
A suitable handler is 'KeyHandler'. But with a KeyHandler, we can't capture mouse input - which causes the issue with the mouse that I explained earlier.
So, we need...
A KeyHandler to handle the 'Space' button.
A KeyHandler to handle the 'Enter' button.
A ClickHandler to handle mouse input.
A Timer to gauge the amount of time our button has been held.
A Bool to set if the timer has expired.
These can be created as locals...
<Locals>
<ClickHandler Name="MouseHandler" HandleEnterSpaceKeys="false" HandlerStage="Bubbled"/>
<KeyHandler Name="SpaceHandler" Key="Space" Handle="true" HandlerStage="Bubbled" Repeat="false"/>
<KeyHandler Name="EnterHandler" Key="Enter" Handle="true" HandlerStage="Bubbled" Repeat="false"/>
<Timer Name="PressTimer" Key="Space" Handle="true" HandlerStage="Bubbled" Interval="1000"/>
<cor:Bool Name="TimerExpired" Bool="false"/>
</Locals>
So, the first step is to recognise that our button has been pressed. We will do this with the Space handler, to begin with.
Unlike the ClickHandler.Clicked, SpaceHandler.Pressed turns on the moment we press the button, and because we have disabled 'Repeat' when we declared it, it will STAY on for as long as the user holds down the button.
<Rules>
<Changed Source="[SpaceHandler.Pressed]">
</Changed>
</Rules>
This will capture when the space-bar is pressed. But this is a changed condition, which means that it will be fired both when the button is pressed, and when the button is released. So in order to ensure the button is being pressed, we need to add a condition to the rule.
<Conditions>
<Equality Source="[SpaceHandler.Pressed]" Value="true"/>
</Conditions>
And finally, we need to turn on the timer.
<Actions>
<Set Target="[TimerExpired]" Value="false"/>
<Set Target="[PressTimer.Enabled]" Value="true"/>
</Actions>
There. The next step is to handle the timer 'ticking' to indicate that we have been holding for long enough. Our first step is to deactivate the timer, then we set the boolean Local we created.
<Changed Target="[PressTimer.Tick]">
<Actions>
<Set Target="[PressTimer.Enabled]" Value="false"/>
<Set Target="[TimerExpired]" Value="true"/>
</Actions>
</Changed>
Finally, we need to respond to the release of the button. We do this with the exact opposite statement to the one we used to detect the start of the click.
Once we have created our rule, we then simply disable the timer (when the timer has not yet expired) and perform any actions required.
<Changed Source="[SpaceHandler.Pressed]">
<Conditions>
<Equality Source="[SpaceHandler.Pressed]" Value="false"/>
<Equality Source="[TimerExpired]" Value="false"/>
</Conditions>
<Actions>
<Set Target="[PressTimer.Enabled]" Value="false"/>
<!--Do anything here you would do if the user gave a traditional click event. -->
</Actions>
</Changed>
There are two options with how we behave. If we want to respond to our 'held down' event immediately once the timer has expired (ie. it is bringing up your popup window immediately after 1 second, even if the user has their button still held down) - you do this by adding the action code to the 'Tick' handler. The other method is to respond after the user releases the button, which you do by copying the above rule and changing the 'TimerExpired' test to 'false'.
You can of course add any animations, sound effects etc. that you want to any of the rules above.
Another Quick Warning
If your popup/context menu contains elements that respond to a button press and you bring up the popup as part of the timer event (ie. as soon as the timer ticks, the popup appears), you'll find the user still has their Select button pressed down.
When the user RELEASES their select button, the ClickHandler on the currently selected item is triggered. This is BAD.
I have an established work-around for this. I create a boolean property on my main Interface object called 'StillHeldDown'. In the rule that starts the timer, I set 'StillHeldDown' to 'true', and turn it off in the rule that detects that the button was released early.
In the 'Clicked' rule within the UI items that are in my context menu, I check for 'StillHeldDown'. If it's 'true', I set it back to 'false'. If it's 'false', I do whatever I would normally do with a mouse click.
This basically creates a buffer that 'eats' the first Clicked event that occurs on the item
Posted
Dec 03 2008, 07:33 AM
by
IgnoranceIsBliss