Search code examples
c#.netwindowswinformspropertygrid

PropertyGrid scroll position not changing when set


I have a PropertyGrid in a Windows Forms application. Whenever its SelectedObject changes, it resets its VerticalScroll value to 0. I need it to remain where it was. The code below doesn't seem to do anything. I've tried PerformLayout and a bunch of other solutions with no success. Any ideas?

int pos = MyGrid.VerticalScroll.Value;
MyGrid.SelectedObject = SomeDifferentObject;
MyGrid.VerticalScroll.Value = pos;

Solution

  • The VerticalScroll property is not relevant for the PropertyGrid class. It references the internal properties of the VScrollBar control. The Scroll event is never rised.
    The VScrollBar is child of the System.Windows.Forms.PropertyGridInternal.PropertyGridView control, a class that is not directly accessible.

    You can get it anyway, casting PropertyGrid to Control or ignoring the Browsable(false) attribute of the PropertyGrid's Controls property, then find the VScrollBar child, store the current value then set it again after you have changed the SelectedObject property.
    Note that this new Control can have a different number of properties than the previous, the Scroll bar will just be set where it was before.
    To be more precise, you could get the number of properties shown before and after the new SelectedObject is set and perform a relative scroll.

    Here, I find the PropertyGridView by its AccessibilityObject.Role which is AccessibleRole.Table, but you can also find it by its Text ("PropertyGridView"):

    var vScroll = propertyGrid1.Controls.OfType<Control>()
        .Where(ctl => ctl.AccessibilityObject.Role == AccessibleRole.Table)
        .First().Controls.OfType<VScrollBar>().First();
    
    var vScrollValue = vScroll.Value;
    propertyGrid1.SelectedObject = [Some Other Object];
    vScroll.Value = vScrollValue;
    

    If you want (as previously described), reposition the scroll bar in the same relative position, the code might become:

    BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance;
    
    var propGrid = propertyGrid1.Controls.OfType<Control>()
        .Where(ctl => ctl.AccessibilityObject.Role == AccessibleRole.Table).First();
    var totalProperties = (int)propGrid.GetType().GetField("totalProps", flags).GetValue(propGrid);
    
    var vScroll = propGrid.Controls.OfType<VScrollBar>().FirstOrDefault();
    
    float relativeScroll = 0f;
    if (vScroll != null && totalProperties > 0) {
        relativeScroll = vScroll.Value / (float)totalProperties;
    }
    
    propertyGrid1.SelectedObject = [Some Other Object];
    
    totalProperties = (int)propGrid.GetType().GetField("totalProps", flags).GetValue(propGrid);
    vScroll.Value = (int)(relativeScroll * totalProperties);