Question

I am using a PropertyGrid to display the content of an object to the user.

This PropertyGrid is synchronized with an Excel sheet, assuming a cell value match a property value.

As the user selects a property in the PropertyGrid, the application highlights the corresponding cell in the Excel sheet which is opened beside. I can do this using the SelectedGridItemChanged event.

Now, I want to have a property selected in my PropertyGrid when the user selects a cell in the Excel sheet.

void myWorkbook_SheetSelectionChangeEvent(NetOffice.COMObject Sh, Excel.Range Target)
{
    if (eventMask > 0)
        return;

    try
    {
        eventMask++;

        this.Invoke(new Action(() => 
        {
            propertyGrid1.SelectedGridItem = ... // ?
        }
    }
    finally
    {
        eventMask--;
    }
}

I noticed that the SelectedGridItem can be written to.

Unfortunately I do not find a way to access the GridItems collection of my PropertyGrid so I can look for the right GridItem and select it.

How can I do this?

Was it helpful?

Solution

You can get all the GridItems from the root. I am using the below code to retrieve all the griditems within a property grid

private GridItem Root
    {
        get
        {
            GridItem aRoot = myPropertyGrid.SelectedGridItem;
            do
            {
                aRoot = aRoot.Parent ?? aRoot;
            } while (aRoot.Parent != null);
            return aRoot;
        }
    }

and pass the root to the below method

private IList<GridItem> GetAllChildGridItems(GridItem theParent)
    {
        List<GridItem> aGridItems = new List<GridItem>();
        foreach (GridItem aItem in theParent.GridItems)
        {
            aGridItems.Add(aItem);
            if (aItem.GridItems.Count > 0)
            {
                aGridItems.AddRange(GetAllChildGridItems(aItem));
            }
        }
        return aGridItems;
    }

OTHER TIPS

I came up against this quite a bit a project so I wrote an extension method for it:

if (!SetupManagerSettings.BootStrapperLocation.IsFile()) // Just another extension method to check if its a file
{
    settingsToolStripMenuItem.Checked = true;  // Event handler OnChecked ensures the settings panel is unhidden
    settingsPropertyGrid.ActivateControl();

    settingsPropertyGrid.SelectPropertyGridItemByName("BootStrapperLocation"); // Here is the extension method 

    return false;
}

Here is the extension method with a private, supporting method for traversing the hierarchy of objects (if this applies to your object model):

    public static bool SelectPropertyGridItemByName(this PropertyGrid propertyGrid, string propertyName)
    {
        MethodInfo getPropEntriesMethod = propertyGrid.GetType().GetMethod("GetPropEntries", BindingFlags.NonPublic | BindingFlags.Instance);

        Debug.Assert(getPropEntriesMethod != null, @"GetPropEntries by reflection is still valid in .NET 4.6.1 ");

        GridItemCollection gridItemCollection = (GridItemCollection)getPropEntriesMethod.Invoke(propertyGrid, null);

        GridItem gridItem = TraverseGridItems(gridItemCollection, propertyName);

        if (gridItem == null)
        {
            return false;
        }

        propertyGrid.SelectedGridItem = gridItem;

        return true;
    }

    private static GridItem TraverseGridItems(IEnumerable parentGridItemCollection, string propertyName)
    {
        foreach (GridItem gridItem in parentGridItemCollection)
        {
            if (gridItem.Label != null && gridItem.Label.Equals(propertyName, StringComparison.OrdinalIgnoreCase))
            {                   
                return gridItem;
            }

            if (gridItem.GridItems == null)
            {
                continue;
            }

            GridItem childGridItem = TraverseGridItems(gridItem.GridItems, propertyName);

            if (childGridItem != null)
            {
                return childGridItem;
            }
        }

        return null;
    } 

I looked at the above options and did not like them that much, I altered it a bit found that this works well for me

bool TryFindGridItem(PropertyGrid grid, string propertyName, out GridItem discover)
{
    if (grid is null)
    {
        throw new ArgumentNullException(nameof(grid));
    }

    if (string.IsNullOrEmpty(propertyName))
    {
        throw new ArgumentException("You need to provide a property name", nameof(propertyName));
    }

    discover = null;
    var root = pgTrainResult.SelectedGridItem;
    while (root.Parent != null)
        root = root.Parent;

    foreach (GridItem item in root.GridItems)
    {
        //let's not find the category labels 
        if (item.GridItemType!=GridItemType.Category)
        {
            if (match(item, propertyName))
            {
                discover= item;
                return true;

            }
        }
        //loop over sub items in case the property is a group

        foreach (GridItem child in item.GridItems)
        {
            if (match(child, propertyName))
            {
                discover= child;
                return true;
            }
        }


        //match based on the property name or the DisplayName if set by the user
        static bool match(GridItem item, string name)
            => item.PropertyDescriptor.Name.Equals(name, StringComparison.Ordinal) || item.Label.Equals(name, StringComparison.Ordinal);


    }
    return false;
}

it uses a local method named match, if you're version of C# does not allow it then just put it external to the method and perhaps give it a better name.

Match looks at the DisplayName as well as the property name and returns true if either is a match, this might not be what you would like so then update that.

In my usecase I need to select the property based on a value the user can select so call the above method like this:

if (TryFindGridItem(pgMain, propertyName, out GridItem gridItem))
{                
     gridItem.Select();
}

It could theoretically never not find it unless the user selects a string that is not proper this way the if can be updated using an else .. I rather keep it safe then have a null-pointer exception if I can't find the name specified.

Also I am not going into sub classes and lists of classes as my use case doesn't need that, if yours does than perhaps make recursive calls in the GridItem.

Small note, replace GridItem with var and the code brakes as at compile time the IDE has no clue what a GridItemCollection returns…

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top