The requested feature is simple: I want full mouse support in Silverlight. I want to use the mouse wheel and the right mouse button.
After reading numerous blog posts and coding several proofs of concept I found that it is no easy task to get there. (Yes, browsers should be named as part of the evil axis)
First the good news:
- Mouse wheel support is possible.
- I created a very handsome (if I may say so) decorator-like control to add mouse wheel support to a Silverlight application.
- You can download it here (look at the end of this post)
The bad news:
- Right mouse button support is a disaster.
- I did not add this to the decorator because I can not get a cross-browser solution to work correctly.
The details on the good news
Browsers have different ways of getting the wheel event data. First of all: the browser events are different. Here is how I solved that:
1: private void AttachBrowserMouseWheelEvent()
3: if (HtmlPage.IsEnabled)
5: if (HtmlPage.BrowserInformation.UserAgent.Contains("Firefox"))
7: HtmlPage.Plugin.AttachEvent("DOMMouseScroll", this.ProcessOnMouseWheel);
9: if (HtmlPage.BrowserInformation.UserAgent.Contains("MSIE") ||
10: HtmlPage.BrowserInformation.UserAgent.Contains("Opera") ||
13: HtmlPage.Plugin.AttachEvent("onmousewheel", this.ProcessOnMouseWheel);
Note that I only tested this code on IE7 and FF2 and FF3.
A major difference between this code and several examples I found on the web is that I register for the Plugin events, NOT the document or page events. Another difference is that I try to register only one single event; most samples on the web register all and might end up getting the same event multiple times.
Once you get the correct event you have to parse the event data and of course this is also different for different browsers.
1: public void ProcessOnMouseWheel(object sender, HtmlEventArgs e)
3: if (_isMouseOverChild)
5: double delta = 0;
7: ScriptObject eventObj = e.EventObject;
9: if (eventObj.GetProperty("wheelDelta") != null)
11: delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;
14: if (HtmlPage.Window.GetProperty("opera") != null)
15: delta = -delta;
17: else if (eventObj.GetProperty("detail") != null)
19: delta = -((double)eventObj.GetProperty("detail")) / 3;
21: if (HtmlPage.BrowserInformation.UserAgent.Contains("Macintosh"))
22: delta = delta * 3;
25: if (delta != 0)
27: bool handled = OnMouseWheel(delta);
28: if (handled)
Don’t get me started on this it only shows how decent people like me end up solving problems that shouldn’t be our’s in the first place.
So, how do we get this code in our precious little Silverlight applications? All examples that I found on the web require page-wide handling of the events or subclassing controls but I wanted a the event to be fired per Silverlight control and I wanted to be able to get rid of this helper code with the least amount of hassle as soon as Microsoft comes out with a real solution.
I wanted a decorator. Silverlight has no decorator (WPF does…) So I browsed to Codeplex and looked up the code on the Silverlight Viewbox which should be a decorator to find out how they solved that. Whoohoo for open source: I was able to copy-paste most of the code (let me know if I violated any copyrights doing that). This caused the client code to be able to look like this:
1: <UserControl x:Class="SilverlightApplication1.Page"
6: <m:MouseSupport MouseWheel="TextBoxOnMouseWheel">
7: <TextBox Text="Scroll the mouse wheel above this textbox."/>
As you can see: no special requirements, just a namespace and a wrapping control (decorator).
BTW: wouldn’t it be nice to have attached events (like attached properties) so you could write this:
2: <TextBox m:MouseSupport.MouseWheel="TextBoxOnMouseWheel" Text="Scroll the mouse wheel above this textbox."/>
Details on the bad news
For some reason publishers of browser plug-ins think it is necessary to bother the users of our (Rich Internet) applications with the fact that that particular area of the browser is using Silverlight or Flash/Flex/JavaFX by having a hard-coded context menu (right mouse button menu) that allows the user to configure the plug-in. As nice as that may be for a developer, for a user it is totally inappropriate. What would you think when you right-click in Word and you get a menu that allows you to change the wallpaper of your desktop?
So what to do? I tried to add oncontextmenu="return false;" to the Html body or the plug-in tag. This does work but not in Firefox and causes a non-XHTML 1.0 violation. In Firefox, the context menu is still appearing but by right-clicking a second time the event does trigger but that silly context menu is still there. So I resolved to kill this feature from the decorator. If you know how to fix it, let me know and I’ll add back in.
Disclaimers and download
The code is provided as is. No fees and no guarantees. Also: let me know if I violated any copyrights.
MouseSupport is a decorator which is used to add mouse events to Silverlight controls.It has been tested on Microsoft Internet Explorer 7 and Firefox versions 2 and 3.
- When switching to full screen Silverlight, the mouse wheel events are no longer being send to the control.
- The MouseWheel event is NOT a routed event. To catch the MouseWheel event on a parent decorate the parent with its own MouseSupport decorator.
- The MouseSupport decorator has several properties like Background, BorderBrush etc. that should not be used, the control should remain UI-less.
- The MouseSupport decorator has a Template property but it can NOT be set.
I ran into a very nasty ‘feature’ of Silverlight while making this. Somehow child controls of custom controls could not be referenced in code. I added a solution for this problem to the download. Let’s hope Microsoft fixes this soon.
Somehow my installation of Visual Studio 2008 does no longer support my Mouse Wheel. The IDE does not respond nor do the web browsers that I open for debugging. In particular for this project that was a real pain as you can imagine. Let me know if you know a solution for this problem.