I have done it following this code in the past:
class DictionaryPropertyGridAdapter : ICustomTypeDescriptor
{
    IDictionary _dictionary;
    public DictionaryPropertyGridAdapter(IDictionary d)
    {
        _dictionary = d;
    }
    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }
    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }
    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }
    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }
    EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }
    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }
    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return _dictionary;
    }
    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }
    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }
    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }
    PropertyDescriptorCollection
        System.ComponentModel.ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
    }
    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        ArrayList properties = new ArrayList();
        foreach (DictionaryEntry e in _dictionary)
        {
            properties.Add(new DictionaryPropertyDescriptor(_dictionary, e.Key));
        }
        PropertyDescriptor[] props =
            (PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor));
        return new PropertyDescriptorCollection(props);
    }
}
class DictionaryPropertyDescriptor : PropertyDescriptor
{
    IDictionary _dictionary;
    object _key;
    internal DictionaryPropertyDescriptor(IDictionary d, object key)
        : base(key.ToString(), null)
    {
        _dictionary = d;
        _key = key;
    }
    public override Type PropertyType
    {
        get { return _dictionary[_key].GetType(); }
    }
    public override void SetValue(object component, object value)
    {
        _dictionary[_key] = value;
    }
    public override object GetValue(object component)
    {
        return _dictionary[_key];
    }
    public override bool IsReadOnly
    {
        get { return false; }
    }
    public override Type ComponentType
    {
        get { return null; }
    }
    public override bool CanResetValue(object component)
    {
        return false;
    }
    public override void ResetValue(object component)
    {
    }
    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }
}
private void Form1_Load(object sender, System.EventArgs e)
{
    IDictionary d = new Hashtable();
    d["Hello"] = "World";
    d["Meaning"] = 42;
    d["Shade"] = Color.ForestGreen;
    propertyGrid1.SelectedObject = new DictionaryPropertyGridAdapter(d);
}
Worked well for us.