This week I was struggling with routed events from a control surrounded in a popup control.
My intention was, to create an attached behavior to support mouse wheel scrolling for any control like a combo box. The behavior should use the native UIElement.MouseWheel without any javascript code, because javascript is not supported in out of browser scenario.
public sealed class MouseWheelBehavior : Behavior<FrameworkElement> { protected override void OnAttached() { this.AssociatedObject.MouseWheel +=new MouseWheelEventHandler(AssociatedObject_MouseWheel); } }
Well it sounds easy, but the problem is that the mouse wheel event is not bubbling up the visual tree as expected.
Below is the pure visual tree of the combo box control template:
<Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ComboBox"> <Grid MouseWheel="Grid_MouseWheel"> <Popup x:Name="Popup" MouseWheel="Popup_MouseWheel"> <Border x:Name="PopupBorder" MouseWheel="PopupBorder_MouseWheel" > <ScrollViewer MouseWheel="ScrollViewer_MouseWheel"> <ItemsPresenter MouseWheel="ItemsPresenter_MouseWheel"/> </ScrollViewer> </Border> </Popup> </Grid> </ControlTemplate> </Setter.Value>
When the popup of the combo box is open and the mouse wheel get raised, it bubbles up the tree until it reaches the popup control.
The following events get fired:
- ItemsPresenter_MouseWheel
- ScrollViewer_MouseWheel
- PopupBorder_MouseWheel
Popup_MouseWheel and Grid_MouseWheel are missing.
After I checked the visual parent of the PopupBorder in the visual tree I noticed that the visual tree ends in a canvas panel (VisualTreeHelper.GetParent (PopupBorder) is Canvas == true).
In the Silverlight.net forum I found the verification: “If the custom control contains a Popup control, do not rely on walking the visual tree, because Popup content is in a separate visual tree.” (http://silverlight.net/forums/t/36899.aspx).
Because of this fact, the routed event cannot bubble up tree and my behavior never gets the routed event.
Nothing else remains for me, as to find each popup control in the sub tree of the associated object and to attach/detach the MouseWheel event for the Popup.Child instance. Fortunately the Popup.Child property is set whereas VisualTreeHelper.GetChild ( popup ) returns null here.
Below you can find the behavior source I’ve wrote. Please note that the scrolling is done through the UI-Automation-API. This allows quite simple to scroll any element which implements the IScrollProvider interface including controls like DataGrid, ListView, Combobox, ScrollViewer, etc. Earlier I posted a MouseWheelService class which describes this in more details.
public sealed class MouseWheelBehavior : Behavior<FrameworkElement> { List<Popup> popups = new List<Popup>(); protected override void OnAttached() { // note: do it in first LayoutUpdated event, because earlier the // template is not applied to the control and the sub tree is not fully loaded this.AssociatedObject.LayoutUpdated += new EventHandler(AssociatedObject_LayoutUpdated); } protected override void OnDetaching() { // dettach event foreach (Popup popup in popups) { if (popup.Child != null) popup.Child.MouseWheel += new MouseWheelEventHandler(popup_MouseWheel); } } void popup_MouseWheel(object sender, MouseWheelEventArgs e) { Point mousePosition = e.GetPosition(null); e.Handled = true; // horizontal scrolling? bool ctrlKey = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control; // go through all element beneath the current mouse position IEnumerable<UIElement> elements = VisualTreeHelper.FindElementsInHostCoordinates(mousePosition, this.AssociatedObject); foreach (UIElement element in elements) { // get automation peer (if already created for this control) AutomationPeer automationPeer = FrameworkElementAutomationPeer.FromElement(element); if (automationPeer == null) { // create automation peer for element automationPeer = FrameworkElementAutomationPeer.CreatePeerForElement(element); } //expect null: some elements doesn't have an automation peer implemented if (automationPeer != null) { // try to get scroll provider // note: TextBoxAutomationPeer does not implement IScrollProvider IScrollProvider scrollProvider = automationPeer.GetPattern(PatternInterface.Scroll) as IScrollProvider; if (scrollProvider != null) { // set scoll amount ScrollAmount scrollAmount = ScrollAmount.NoAmount; if (e.Delta < 0) scrollAmount = ScrollAmount.SmallIncrement; else if (e.Delta > 0) scrollAmount = ScrollAmount.SmallDecrement; // is scrolling horizontal possible if (scrollProvider.HorizontallyScrollable && ctrlKey) { scrollProvider.Scroll(scrollAmount, System.Windows.Automation.ScrollAmount.NoAmount); // break the further serach in the uielement collection break; // foreach } else if (scrollProvider.VerticallyScrollable) { scrollProvider.Scroll(System.Windows.Automation.ScrollAmount.NoAmount, scrollAmount); // break the further serach in the uielement collection break; // foreach } // don't break here, because of encapsulated scroll viewers such as in the treeview from the sl-toolkit //break; } } } } void AssociatedObject_LayoutUpdated(object sender, EventArgs e) { // no longer need for this event this.AssociatedObject.LayoutUpdated -= new EventHandler(AssociatedObject_LayoutUpdated); // search popups (note: it's possible to have more than one in a template) FindPopups(this.AssociatedObject); // attach event foreach (Popup popup in popups) { if (popup.Child != null) popup.Child.MouseWheel += new MouseWheelEventHandler(popup_MouseWheel); } } private void FindPopups(DependencyObject parent) { if (parent == null) return; int childnum = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < childnum; i++) { var child = VisualTreeHelper.GetChild(parent, i); if (child is Popup) { this.popups.Add(child as Popup); // note: do not return here, because of other popup control in the neighbor childs } else { // search in the neighbor childs FindPopups(child); } } } }
Last but not least, I’ve posted a wish in the Silverlight 4 wishlist to integrate this separate visual tree into the “standard” tree so that the routed event can bubble up the tree as expected.

Good job.
I just made (last thursday – Sep, 03, 2009) the same behavior and published at the Expression Gallery. I solved the popup problem in a way similar to yours.
I liked your idea of using the ctrl key for horizontal scroll. My behavior does horizontal scroll only when there is no vertical scroll to do. Do you mind if I implement this on the behavior I published?
I’m considering implementing this behavior using javascript as well because the native mouse wheel event is not yet supported on Mac. I’m waiting on feedback for that
By: Kelps on September 8, 2009
at 11:39 am
I forgot to leave the link for the behavior I implemented so you can take a look at it. Here it is:
http://gallery.expression.microsoft.com/en-us/MouseWheelScroll
By: Kelps on September 8, 2009
at 11:42 am
Hi Kelps,
no, I don’t mind if you add the ctrl behavior to your code ( maybe you can put a link to my blog into your code comments
)
I think it is worth to implement the behavior with java script also, because for a better support of Mac.
I did exactly this with a very generic implementation. I would call it “global behavior to attach to the root element of the application”.
http://blog.thekieners.com/2009/04/06/how-to-enable-mouse-wheel-scrolling-in-silverlight-without-extending-controls/
Fortunately it does not struggle with the broken tree of a popup control. And a further advantage with java script is that the SL plugin doesn’t need to have the focus, it works with the first mouse wheel event whereas the native mouse wheel event does not get fired until the SL plugin has its focus.
Do you have experience with out of browser scenario on a Mac? I think there is no way to use the mouse wheel, right?
Best regards,
Beat Kiener
By: beatkiener on September 9, 2009
at 6:01 am
How to use this to add Mouse Wheel scrolling to AutoCompletebox popup? Previous solution from //http://blog.thekieners.com/2009/04/06/how-to-enable-mouse-wheel-scrolling-in-silverlight-without-extending-controls/
does not make Autocompletebox popup list scrollable.
Andrus.
By: Andrus on October 16, 2009
at 10:31 am