Wednesday, September 24, 2008

The Scroll Button Dilemma

To quote StrongBad, "Scroll buttons never looked so good! Scrollin' up and down like you knew they would..."



Or, perhaps not. We recently encountered a problem when trying to implement scroll buttons in Silverlight 2 Beta 2 that would:
1) Smoothly scoll up and down when being held down.
2) Use VSM (Visual State Manager) for MouseOver and Pressed animations.


Here's how we solved the problem and created scroll buttons truly worthy of some StrongBad adulation:





The problem is that instances of Button controls will never raise MouseLeftButtonUp and MouseLeftButtonDown events. In other words, Button instances only support handling the Click event directly, not the Mouse Up/Down events. We didn't want our users to have to repeatedly click in order to scroll - we wanted the scroll button to just smoothly scroll as long as the mouse button was held down.

This Silverlight.NET forum discussion features an excerpt from a help document that describes the behavior:

"Certain control classes (for example Button) provide control-specific handling for mouse events such as MouseLeftButtonDown. The control-specific handling typically involves handling the event at a class level rather than at the instance level, and marking the MouseLeftButtonDown event data's Handled value as true such that the event cannot be handled by instances of the control class, nor by other elements (at a class or instance level) anywhere further along the event route. In the case of Button, the class design does this so that the event Click can be raised instead."

So, Buttons will swallow the MouseLeftButtonDown/Up events by setting Handled to "true".
Our first thought was to just use an element (other than Button) which doesn't exhibit this behavior. However, using VSM requires using an element that has a customizable control template (derives from the Control class), and it seems as if all the controls that can be used by VSM exhibit the same event swallowing behavior as Buttons.

So, our solution was to build a custom "ScrollButton" class which inherits from Button. The ScrollButton class overrides the OnMouseLeftButtonDown and OnMouseLeftButtonUp handlers, and resets Handled back to "false" after executing the base method. This fairly simple change causes the events to fire properly, allowing our scroll buttons to actually scroll when held down. The code is below:

public partial class ScrollButton : Button{
public ScrollButton()
{
this.MouseLeftButtonDown += new MouseButtonEventHandler(ScrollButton_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseButtonEventHandler(ScrollButton_MouseLeftButtonUp);
}

void ScrollButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.Content = "mouse down";
}

void ScrollButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.Content = "mouse up";
}

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}

protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
e.Handled = false;
}

}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.