Monthly Archives: September 2009

DataGridRow.GetIndex returns wrong index when inserting items to observable collection

This week I ran into a very strange behavior with the Silverlight DataGrid.

I added new items via an ObservableCollection to a set of existing items which are already bound to a standard DataGrid. If the new items are inserted on a lower index than the currently displayed range of items the result of the DataGridRow.GetIndex is not correct.
This has several side effects:

  • The hover effect does not work correct
  • Some rows lose the entire hover behavior
  • The alternating row background is set wrong
  • DataGridRow.GetIndex returns old index
  • Scrollbar jumps sometimes during thumb-track (I was not able to isolate this so far…)

 

It’s hard to explain all these in detail; therefore it created a short video.

image 

The remove notification from the observable collection works perfect (see last section in the video). All gets updated including the result of GetIndex and the AlternatingRowBackground for each row is correct.

I guess this is a misbehavior in the DataGrid, because of the fact that the Remove operation is working correctly.

First of all: fully rebind the DataGrid is not a solution for me, because the DataGrid is updating itself every few minute (remove/insert/add rows) from a service.

Can someone from the Silverlight team verify my assumption?

Silverlight Version is 3.0.40818.0.

Source code and Demo.

Thanks in advance,
Beat Kiener

Advertisements
Tagged

Mouse wheel behavior with native UIElement MouseWheel event

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.

Tagged