Last week I made 2 minesweeper games, which were fun and all, but the overwhelming response from my friends was "wtf! can't flag tiles with right click!". It took all the fun out of the game.
This weekend I noticed that Google Docs makes nice use of right click gestures and my mission began- enable flagging tiles via right-click in Minesweeper!
Easier said than done, but with the help of some previous investigators in Flash and Silverlight, I was able to hack out a passable solution that works on IE and Firefox (OS X & Windows). Alas, Safari has beaten me once again.
Once I was able to get the right-click event, I needed to figure out which element the mouse clicked on. In Silverlight 2 there's now a HitTest API which made this easy- just hit test with the mouse position and get the first element:
I ended up implementing my own variant of RoutedEvents, BubblingEvents (for lack of a better name) and made a super minimal version of them for Silverlight. The code is similar enough to RoutedEvents to be able to start quickly.
To declare a new BubblingEvent:
Handling the event is very similar to WPF where the handler is set up for the type:
Once I got this part going then I realized that my mouse wheel handling code could really benefit from this mechanism as well; it was a very easy modification to convert it to use BubblingEvents.
The cool part is that since the event handling registration is done based on types, I could easily and efficiently create a helper that would register for all MouseWheel events on every ScrollViewer in an entire application:
Code files used here:
BubblingEvent.cs- implementation of extensible routed events in Silverlight.
ContextMenuGenerator.cs- provides bubbling ContextMenu (right-click) events in Silverlight.
MouseWheelGenerator.cs- provides bubbling MouseWheel events in Silverlight.
ScrollableScrollViewer.cs- makes all scrollviewers in an application scroll with the mouse wheel.
Samples:
Right-click enabled Minesweeper.
Test app for scrolling scrollbars and right click. Source
This weekend I noticed that Google Docs makes nice use of right click gestures and my mission began- enable flagging tiles via right-click in Minesweeper!
Easier said than done, but with the help of some previous investigators in Flash and Silverlight, I was able to hack out a passable solution that works on IE and Firefox (OS X & Windows). Alas, Safari has beaten me once again.
Once I was able to get the right-click event, I needed to figure out which element the mouse clicked on. In Silverlight 2 there's now a HitTest API which made this easy- just hit test with the mouse position and get the first element:
UIElement element in Application.Current.RootVisualThe next step was to figure out a good strategy for propagating the event. In WPF the RoutedEvent class fits the bill perfectly- it's allows propagating any event through the visual tree. Unfortunately even though RoutedEvents are in Silverlight, they are not extensible and I couldn't add my own. Bummer.
.HitTest(new Point(e.OffsetX, e.OffsetY))
I ended up implementing my own variant of RoutedEvents, BubblingEvents (for lack of a better name) and made a super minimal version of them for Silverlight. The code is similar enough to RoutedEvents to be able to start quickly.
To declare a new BubblingEvent:
public static readonly BubblingEvent<ContextMenuEventArgs> ContextMenuEventThis declares a new event with event args of type ContextMenuEvent which will bubble up the visual tree.
= new BubblingEvent<ContextMenuEventArgs>("ContextMenu", RoutingStrategy.Bubble);
Handling the event is very similar to WPF where the handler is set up for the type:
ContextMenuGenerator.ContextMenuEvent.Then it's just a matter of flagging the tiles from the event. The event propagation automatically stops when the handled flag is set to true, unless you register for all events- just like WPF's as well.
RegisterClassHandler(typeof(Page),
Page.HandleContextMenuEvent, false);
private static void HandleContextMenuEvent(object sender, ContextMenuEventArgs e) {
((Page)sender).OnContextMenu(e);
}
protected virtual void OnContextMenu(ContextMenuEventArgs e) {
e.Handled = true;
}
Once I got this part going then I realized that my mouse wheel handling code could really benefit from this mechanism as well; it was a very easy modification to convert it to use BubblingEvents.
The cool part is that since the event handling registration is done based on types, I could easily and efficiently create a helper that would register for all MouseWheel events on every ScrollViewer in an entire application:
public static class ScrollableScrollViewer {One call to ScrollableScrollViewer.Initialize() and scrollbars now work 'as expected' :)
private static bool initialized = false;
public static void Initialize() {
if (!ScrollableScrollViewer.initialized) {
MouseWheelGenerator.MouseWheelEvent.
RegisterClassHandler(typeof(ScrollViewer),
ScrollableScrollViewer.HandleMouseWheel, false);
ScrollableScrollViewer.initialized = true;
}
}
private static void HandleMouseWheel
(object sender, MouseWheelEventArgs e) {
ScrollViewer sv = (ScrollViewer)sender;
double verticalOffset = sv.VerticalOffset;
if (e.Delta > 0 && verticalOffset > 0) {
sv.ScrollToVerticalOffset(verticalOffset - e.Delta * 50);
e.Handled = true;
}
else if (e.Delta < 0 && verticalOffset < sv.ScrollableHeight) {
sv.ScrollToVerticalOffset(verticalOffset - e.Delta * 50);
e.Handled = true;
}
}
}
Code files used here:
BubblingEvent.cs- implementation of extensible routed events in Silverlight.
ContextMenuGenerator.cs- provides bubbling ContextMenu (right-click) events in Silverlight.
MouseWheelGenerator.cs- provides bubbling MouseWheel events in Silverlight.
ScrollableScrollViewer.cs- makes all scrollviewers in an application scroll with the mouse wheel.
Samples:
Right-click enabled Minesweeper.
Test app for scrolling scrollbars and right click. Source