Index out of range exception in hierarchical grid with custum multi select behavior

8 posts, 1 answers
  1. Wesley
    Wesley avatar
    16 posts
    Member since:
    Jul 2012

    Posted 15 Nov 2012 Link to this post

    Hi,

    I have a hierarchical grid with custom multi select behavior.
    I recently upgraded to Q3 2012. Now I get an exception when I try to select multiple rows that have childrows with the mouse.
    This is the exception:
    System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
    Parameter name: index
       at System.ThrowHelper.ThrowArgumentOutOfRangeException()
       at System.Collections.Generic.List`1.get_Item(Int32 index)
       at System.Collections.ObjectModel.Collection`1.get_Item(Int32 index)
       at Telerik.WinControls.UI.GridRowBehavior.DoMultiFullRowSelect(Boolean shift, Boolean control)
       at Telerik.WinControls.UI.GridRowBehavior.DoMouseSelection(GridCellElement currentCell, Point currentLocation)
       at Telerik.WinControls.UI.GridRowBehavior.ProcessMouseSelection(Point mousePosition, GridCellElement currentCell)
       at Telerik.WinControls.UI.GridRowBehavior.OnMouseMove(MouseEventArgs e)
       at Telerik.WinControls.UI.BaseGridBehavior.OnMouseMove(MouseEventArgs e)
       at Telerik.WinControls.UI.RadGridView.OnMouseMove(MouseEventArgs e)
       at System.Windows.Forms.Control.WmMouseMove(Message& m)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
       at Telerik.WinControls.RadControl.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    ----------------------------------------

    I made a test application that reproduces the problem.
    The application contains 2 files:
    • Form1.cs
    • CustomRowInfos.cs

    Form1.cs:

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    using Telerik.WinControls.UI;
     
    namespace GridWithIndexedHierarchySpike
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                dummyGridView.Dock = DockStyle.Fill;
                dummyGridView.Parent = this;
                dummyGridView.BringToFront();
                dummyGridView.MultiSelect = true;
                dummyGridView.UseScrollbarsInHierarchy = true;
            }
     
            protected override void OnLoad(EventArgs e)
            {
                base.OnLoad(e);
     
                GridViewTemplate childTemplate = CreateChildTemplate();
                dummyGridView.Templates.Add(childTemplate);
                childTemplate.HierarchyDataProvider = new GridViewEventDataProvider(childTemplate);
     
                //grid events
                dummyGridView.RowSourceNeeded += gridView_RowSourceNeeded;
                dummyGridView.CreateRowInfo += CreateGridViewHierarchyRowInfoWithCustomSelectBehavior;
                dummyGridView.Templates[0].CreateRowInfo += CreateGridViewDataRowInfoWhitCustomSelectBehavior;
     
                //databind
                dummyGridView.DataSource = CreateDummyData(5);
            }
      
            void gridView_RowSourceNeeded(object sender, GridViewRowSourceNeededEventArgs e)
            {
                List<SomeData> data = CreateDummyData(2);
                foreach (SomeData someData in data)
                {
                    GridViewRowInfo row = e.Template.Rows.NewRow();
                    row.Cells[0].Value = someData.Name;
                    row.Cells[1].Value = someData.SomeDataId;
                    row.Cells[2].Value = someData.ParentDataId;
      
                    e.SourceCollection.Add(row);
                }
            }
     
            private static void CreateGridViewHierarchyRowInfoWithCustomSelectBehavior(object sender, GridViewCreateRowInfoEventArgs e)
            {
                if (e.RowInfo is GridViewHierarchyRowInfo)
                {
                    e.RowInfo = new MultiSelectGridViewHierarchyRowInfo(e.ViewInfo);
                }
            }
     
            private static void CreateGridViewDataRowInfoWhitCustomSelectBehavior(object sender, GridViewCreateRowInfoEventArgs e)
            {
                if (e.RowInfo is GridViewDataRowInfo)
                {
                    e.RowInfo = new MultiSelectGridViewDataRowInfo(e.ViewInfo);
                }
            }
     
            private GridViewTemplate CreateChildTemplate()
            {
                var template = new GridViewTemplate {AutoSizeColumnsMode = GridViewAutoSizeColumnsMode.Fill};
                var namecolumn = new GridViewTextBoxColumn("Name");
                var idColumn = new GridViewTextBoxColumn("ID");
                var parentColumn = new GridViewTextBoxColumn("Parent");
                template.Columns.AddRange(namecolumn, idColumn, parentColumn);
                return template;
            }
      
            private static List<SomeData> CreateDummyData(int nbrRows)
            {
                var data = new List<SomeData>();
                var someData = new SomeData { Name = Guid.NewGuid().ToString(), ParentDataId = null, SomeDataId = 0 };
      
                data.Add(someData);
                for (int i = 1; i <= nbrRows - 1; i++)
                {
                    var someChildData = new SomeData()
                    {
                        Name = Guid.NewGuid().ToString(),
                        ParentDataId = someData.SomeDataId,
                        SomeDataId = i
                    };
                    data.Add(someChildData);
                }
      
                return data;
            }
     
            public class SomeData
            {
                public int SomeDataId { get; set; }
                public int? ParentDataId { get; set; }
                public string Name { get; set; }
            }
        }
    }

    CustomRowInfos.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Telerik.WinControls.UI;
     
    namespace GridWithIndexedHierarchySpike
    {
        /// <summary>
        /// Hierarchy row with custom select behavior.
        /// When the row is (de)selected, all child rows are (de)selected
        /// </summary>
        public class MultiSelectGridViewHierarchyRowInfo : GridViewHierarchyRowInfo
        {
            public static bool ParentSilentUpdate;
            public static bool OverrideChildSelection;
     
            public MultiSelectGridViewHierarchyRowInfo(GridViewInfo owner)
                : base(owner)
            {
                ParentSilentUpdate = false;
            }
     
            protected override bool SetBooleanProperty(string propertyName, int propertyKey, bool value)
            {
                bool result = base.SetBooleanProperty(propertyName, propertyKey, value);
     
                if (result && propertyName == "IsSelected")
                {
                    if (!MultiSelectGridViewDataRowInfo.ChildSilentUpdate)
                    {
                        //parent row is selected/deselected;
                        SelectChildRows(value);
                    }
                }
     
                return result;
            }
     
     
            private void SelectChildRows(bool isSelected)
            {
                if (OverrideChildSelection) return;
     
                ParentSilentUpdate = true;
                foreach (MultiSelectGridViewDataRowInfo row in this.ChildRows)
                {
                    row.IsSelected = isSelected;
                }
                ParentSilentUpdate = false;
            }
        }
     
        /// <summary>
        /// Data row with custom select behavior.
        /// If the row is a child row of a parent row then
        /// the parent row is selected when the child rows (or one of its siblings) is selected,
        /// otherwise the parent row is deselected.
        /// </summary>
        public class MultiSelectGridViewDataRowInfo : GridViewDataRowInfo
        {
            public static bool ChildSilentUpdate;
            public MultiSelectGridViewDataRowInfo(GridViewInfo viewInfo)
                : base(viewInfo)
            {
                ChildSilentUpdate = false;
            }
     
            public object OriginalBoundItem { get; set; }
     
            protected override bool SetBooleanProperty(string propertyName, int propertyKey, bool value)
            {
               
                bool result = base.SetBooleanProperty(propertyName, propertyKey, value);
     
                if (result && propertyName == "IsSelected")
                {
                    if (!MultiSelectGridViewHierarchyRowInfo.ParentSilentUpdate)
                    {
                        //child row is selected
                        SelectParentRow(value);
                    }
                }
     
                return result;        
            }
     
            private void SelectParentRow(bool isSelected)
            {
                var parent = Parent as MultiSelectGridViewHierarchyRowInfo;
                if (parent == null) return;
     
                ChildSilentUpdate = true;
                if (isSelected)
                {
                    parent.IsSelected = true;
                }
                else
                {
                    //check if there are other rows
                    bool selected = GetParentRowSelection(parent);
                    parent.IsSelected = selected;
                }
                ChildSilentUpdate = false;
            }
     
            private bool GetParentRowSelection(MultiSelectGridViewHierarchyRowInfo parent)
            {
                //if there is one selected child row, return true
                return parent.ChildRows.Cast<MultiSelectGridViewDataRowInfo>().Any(row => row != this && row.IsSelected);
            }
        }
    }

  2. Emanuel Varga
    Emanuel Varga avatar
    1336 posts
    Member since:
    May 2010

    Posted 15 Nov 2012 Link to this post

    Hello Wesley,

    Just fix this method:
    private void SelectChildRows(bool isSelected)
    {
        if (OverrideChildSelection) return;
     
        ParentSilentUpdate = true;
        foreach (MultiSelectGridViewDataRowInfo row in this.ChildRows.Where(row => !row.IsSelected))
        {
            row.IsSelected = isSelected;
        }
        ParentSilentUpdate = false;
    }

    If you have any other questions, please let me know.


    Best Regards,
    Emanuel Varga
    WinForms MVP
  3. UI for WinForms is Visual Studio 2017 Ready
  4. Wesley
    Wesley avatar
    16 posts
    Member since:
    Jul 2012

    Posted 16 Nov 2012 Link to this post

    Hi Emanuel,

    I tried to change "ChildRows" in to "ChildRows.Where(row => row.IsSelected != isSelected)".
    I still get the same error.
    Did you mean something else with "fix the method"?

    Just to be clear:
    The same code worked in the past, but after I upgraded to Q3 2012 I got this problem.
    The code only craches when you use the mouse to multiselect. If you use shift+arrow multiple rows get selected without problems.

    How the multi select should work:
    • when a parent row is selected, all the child rows get selected automatically
    • when a child row is deselected and all siblings are deselected, the parent is deselected automatically
  5. Jack
    Admin
    Jack avatar
    2335 posts

    Posted 20 Nov 2012 Link to this post

    Hello Wesley,

    I found what causes the described issue: You are changing the selected rows collection at the same time when RadGridView iterates it. However, I am not sure how to address the issue because your selection logic is quite complex. 

    One possible option would be to call SuspendPropertyNotifications/ResumePropertyNotifications methods when setting the IsSelected property inside the SelectChildRows and SelectParentRow methods:
    private void SelectChildRows(bool isSelected)
    {
        if (OverrideChildSelection) return;
     
        ParentSilentUpdate = true;
        foreach (MultiSelectGridViewDataRowInfo row in this.ChildRows)
        {
            row.SuspendPropertyNotifications();
            row.IsSelected = isSelected;
            row.ResumePropertyNotifications();
        }
        ParentSilentUpdate = false;
    }

    If this does not help, please describe in detail the behavior of you selection logic and I will be glad to help further.

    I am looking forward to your reply.
     
    Regards,
    Jack
    the Telerik team
    Q3’12 of RadControls for WinForms is available for download (see what's new). Get it today.
  6. Wesley
    Wesley avatar
    16 posts
    Member since:
    Jul 2012

    Posted 21 Nov 2012 Link to this post

    Hi Jack,

    Suspending the property notifications causes the multiselect not to work as it should.

    This is how the multiselect should work:
    • when a parent row is selected, all the child rows get selected automatically
    • when a parent row is deselected, all the child rows get deselected automatically
    • As long as at least one child row of a parent row is selected, the parent row stays selected
    • When all child rows of a parent row are deselected, the parent row is deselected automatically

     

    If you take the 2 files I posted in the first post and put them in a project you can easily reproduce the problem.
    Any help would be appreciated.

    PS: The code in the first post worked in the past. The problem occured when I upgraded to Q3 2012.

  7. Jack
    Admin
    Jack avatar
    2335 posts

    Posted 22 Nov 2012 Link to this post

    Hello Wesley,

    Thank you for this clarification. 

    Now I understand the behavior that you want to achieve and the issue. It will take some time in order to find a proper solution. I will write back when I have further information.

    If you have other questions, do not hesitate to contact us.
     
    Kind regards,
    Jack
    the Telerik team
    Q3’12 of RadControls for WinForms is available for download (see what's new). Get it today.
  8. Jack
    Admin
    Jack avatar
    2335 posts

    Posted 28 Dec 2012 Link to this post

    Hello Wesley,

    I apologize for the late reply. 

    We identified the code which causes the selection issue and we will address it an internal build which is scheduled for the beginning of next month. The fix will be included also in our next official release - Q1 2012. The issue is now logged in our issue tracking system and you can use the following link to track its status. I updated also your Telerik points accordingly. 

    You will receive notification from our Visual Studio extension service that a new version is available. I will write also when the new release is ready.

    If you have any questions, do not hesitate to contact us.

    Regards,
    Jack
    the Telerik team
    Q3'12 SP1 of RadControls for WinForms is out now. See what's new.
  9. Answer
    Jack
    Admin
    Jack avatar
    2335 posts

    Posted 10 Jan 2013 Link to this post

    Hello Wesley,

    I am glad to inform you that we just released our latest internal build that addresses the selection issue observed in your application. You can download it through our Visual Studio Extensions.

    If you still experience issues, do not hesitate to contact us.

    Kind regards,
    Jack
    the Telerik team
    Q3'12 SP1 of RadControls for WinForms is out now. See what's new.
Back to Top
UI for WinForms is Visual Studio 2017 Ready