Button “hacks”

After that somewhat useless title, let me explain. I’m currently working on a kiosk application in Silverlight (there will be more about it later). And while working with it I had two issues with the Button that comes with Silverlight. First of all, as with most controls, it swallows some of the events that I needed, and second it raises the Click event before the VisualStateManager takes the state back to Normal. The second one is very specific to my kiosk application, or at least to touch screen interfaces…so I wrote a couple of custom buttons to help me with these problems…

The first problem about the lost events are due to a somewhat specific implementation, but could be useful in some other scenarios as well. In my case, I' was working on some buttons that were controlling a “3D” view of an arena. The buttons should be able to rotate, tilt and move the 3D object. Initially I just let them move it X degrees/pixels per click, but it felt unintuitive and needed some fixing. I wanted the buttons to keep moving the object while the button was down, and then stop when released. I already had some styled buttons that I was using, so I basically wanted to hook my events to the MouseLeftButtonDown/Up events and use a little Storyboard to handle the movement. And then let the Storyboard playing over and over again while the mouse button was down. Unfortunately, the Button class sets the Handled property in those events, causing them not to bubble up further in the visual tree.

This is very simple to fix though. Just inherit from the Button class, or potentially ButtonBase, and override the OnMouseLeftButtonDown and OnMouseLeftButtonUp. The implementation of those methods are simple, just call the base class and then set the Handled property to false before leaving the method…

public class EventBubblingButton : Button
{
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}
}

 

That’s all that is needed to get it working. Not a big deal, but very helpful in some cases.

The next problem is a bit weirder and kind of specific to my implementation. In my kiosk application, the buttons that are used for moving from one “page” to another basically causes the current control (“page”) to be unloaded and a new to be loaded in its place. Making it feel as going between pages in a browser.

When clicking a button, it will tell the VisualStateManager to change its state to Pressed. When the mouse button is released, the button raises the Click event and then tells the VisualStateManager to update the visual state (actually all of this is done in ButtonBase…but anyway…). In my case, the Click event causes the current control to be removed and replaced with a new one. This causes the button to feel very odd. It is hard to explain, but it does…I promise you. It sinks in like any other button, but then the controls is changed before the button pops back out again. So to fix this, I created another button to use in this scenario. My DelayedClickButton. It is a very simple button that delays the click event X amount of milliseconds using a DispatcherTimer. I happened to forget my original implementation of the button at the office, but I’m pretty sure I can pull it off again without too much problem…it is ridiculously simple but helpful…

public class DelayedClickButton : Button
{
public DelayedClickButton()
{
Delay = 200;
}

protected override void OnClick()
{
DispatcherTimer tmr = new DispatcherTimer();
tmr.Interval = TimeSpan.FromMilliseconds(Delay);
tmr.Tick += new EventHandler(tmr_Tick);
tmr.Start();
}

void tmr_Tick(object sender, EventArgs e)
{
((DispatcherTimer)sender).Tick -= tmr_Tick;
base.OnClick();
}

public int Delay
{
get;
set;
}
}

 

That’s all there is to it. Just remember to place the DispatcherTimer.Tick eventhandler in a “real” method instead of an anonymous one since you are calling base.OnClick(). In an anonymous method, that will fail considering that the anonymous method will be placed in a new type and thus not have a base.OnClick().

Anyhow…a pretty narrow little post, but it might help someone I hope. If nothing else, it might remind people that inheriting and overriding classes/controls can be very powerful and useful.

Cheers!

And BTW…since both buttons inherit from Button, they can still use the same styles and templates as all other buttons in your project. So there is no need to re-style or anything… Just add the correct namespace and switch the element name for the Button element…

Comments (1) -

THANK YOU SO MUCH!!!!!!!!!!!!

I had the EXACT same problem with a custom control navigating to another page and the click event firing before the VisualState was completed.

Thank you for a great solution!

Richard

Comments are closed