Tuesday, February 24, 2009

How To Show Command Links in a PropertyGrid control

I like PropertyGrid control.

It's really neat.

But it is documented very poorly on MSDN. Once again, .NET Reflector saves the day.

Visual Studio has PropertyGrid controls all over the place, and somehow they got the SelectedObject to show commands on the PropertyGrid command panel.

MSDN does not describe how they did that
Here: "PropertyGrid Class (System.Windows.Forms)" http://msdn.microsoft.com/en-us/library/system.windows.forms.propertygrid.aspx (see CommandsVisible property)
Or Here: "Getting the Most Out of the .NET Framework PropertyGrid Control" http://msdn.microsoft.com/en-us/library/aa302326.aspx

Here I will describe how to get those command links to appear.

See also "why I hate iserviceprovider", (posted feb 20, 2009) on beefycode.com http://beefycode.com/


 

PropertyGrid control is a black box with a chain of undocumented dependency injection thingies.


 

A better implementation IMO would have been to expose command links in a PropertyGrid control when the object referenced in PropertyGrid.SelectedObject implements IMenuCommandService.


 

Services Services

Command links appear in the PropertyGrid when all these conditions are met:

  1. PropertyGrid.SelectedObject is set to the instance of an object that implements the IComponent interface
  2. The IComponent implementation has a value for ISite
  3. ISite is a service provider, it must provide
    1. an instance of IMenuCommandService (i.e. your ISite implementation should have a ServiceContainer, and in that ServiceContainer is an IMenuCommandService)
    2. OR it must provide an instance of IDesignerHost
  4. If you are using IMenuCommandService (3.a) then add commands to the IMenuCommandService using AddVerb(new DesignerVerb(…))
    The PropertyGrid gets it's commands from IMenuCommandService.Verbs
    The DesignerVerb is pretty straight forwards actually, the Text will show up as the link, and clicking on the command link will invoke the Handler.
  5. If you are using IDesignerHost (3.b) …
    … yeah … well … good luck with that.
    Anyway, according to .NET Reflector, your IDesignerHost will have its GetDesigner(component) method called where component is the selected object instance.
    It needs to return an IDesigner instance.
    The PropertyGrid gets its commands from IDesigner.Verbs. It needs to be a collection of DesignerVerb instances.

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. This is the simplest way to do this:

    Public Class GridObject
    Implements IComponent

    Public Event Disposed(sender As Object, e As System.EventArgs) Implements System.ComponentModel.IComponent.Disposed

    Private disposedValue As Boolean

    Private _site As ISite
    Public Property Site As System.ComponentModel.ISite Implements System.ComponentModel.IComponent.Site
    Get
    If Me._site Is Nothing Then
    Me._site = New GridObjectSite()
    End If
    Return Me._site
    End Get
    Set(value As System.ComponentModel.ISite)
    Me._site = value
    End Set
    End Property

    Protected Overridable Sub Dispose(disposing As Boolean)
    If Not Me.disposedValue Then
    If disposing Then
    End If
    End If
    Me.disposedValue = True
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
    Me.Dispose(True)
    GC.SuppressFinalize(Me)
    End Sub

    End Class

    Public Class GridObjectSite
    Implements ISite
    Implements IMenuCommandService

    Private _verbs As DesignerVerbCollection
    Public ReadOnly Property Verbs As System.ComponentModel.Design.DesignerVerbCollection Implements System.ComponentModel.Design.IMenuCommandService.Verbs
    Get
    If Me._verbs Is Nothing Then
    Me._verbs.Add(New DesignerVerb("TEST", Nothing))
    End If
    Return Me._verbs
    End Get
    End Property

    Public Function GetService(serviceType As System.Type) As Object Implements System.IServiceProvider.GetService
    If serviceType Is GetType(IMenuCommandService) Then
    Return Me
    End If
    Return Nothing
    End Function

    '//all the other methods, you dont need to do anything with

    End Class

    ReplyDelete