c# - IExtenderProvider 根据对象类型仅添加一些属性

标签 c# winforms windows-forms-designer

我遇到了一个问题,我不知道这是否确实可行(如果有一种“hacky”方式,我全力以赴,但我还没有找到)。

我有一个 IExtenderProvider 组件,我用它来拥有自己的 UITypeEditor 用于第三方控件的某些属性(我无法更改,很明显原因)。

这些控件不一定继承自同一个基类(如果继承,基类不一定具有我想要扩展的属性,而这些属性是在同一个类中定义的)。

因此,假设我想为属性 ImageGlyphLargeGlyphSmallGlyph< 创建一个替代属性 在他们身上。

所以我有类似的东西:

[ProvideProperty("LargeGlyphCustom", typeof (object))]
[ProvideProperty("GlyphCustom", typeof(object))]
[ProvideProperty("SmallImageCustom", typeof(object))]
[ProvideProperty("LargeImageCustom", typeof(object))]
[ProvideProperty("ImageCustom", typeof(object))]
public class MyImageExtender : Component, IExtenderProvider
{
  private readonly Type[] _extendedTypes =
  {
    typeof (OtherControl),
    typeof (SomeOtherControl),
    typeof (AControl),
    typeof (AButton)
  };

  bool IExtenderProvider.CanExtend(object o)
  {
    if (!DesignMode) return false;
    return _extendedTypes.Any(t => t.IsInstanceOfType(o));
  } 

  // Implement the property setter and getter methods
}

到目前为止,还不错。我可以在我期望的类型的控件上看到我的属性。

但是,这些是控件中属性的替换(只是为了更改 UITypeEditor)。

我的方法的问题在于,我在所有 扩展类型中看到了所有 扩展属性。

假设,如果 AButton 只有 Image,我只想看到 ImageCustom 而不是 SmallImageCustomLargeImageCustom

所以我的方法是这样做:

[ProvideProperty("LargeGlyphCustom", typeof (OtherControl))]
// other properties
[ProvideProperty("ImageCustom", typeof(AButton))]
public class MyImageExtender : Component, IExtenderProvider
// ...

这似乎工作正常,现在我只在 AButton 上看到 ImageCustom,在 OtherControl 上看到 LargeGlyphCustom .

现在的问题是,如果我想在 AButtonOtherControl 中显示 ImageCustom,我曾想过这样做:

[ProvideProperty("ImageCustom", typeof(AButton))]
[ProvideProperty("ImageCustom", typeof(OtherControl))]
public class MyImageExtender : Component, IExtenderProvider

但这不起作用,我只能在 AButton 上看到 ImageCustom,但在 OtherControl 上看不到。

反编译 ProvidePropertyAttribute 的源代码,发生这种情况的原因“可以说”很清楚。它在内部创建一个 TypeId,我怀疑是 WinForms 设计器使用的,如下所示:

public override object TypeId
{
  get
  {
    return (object) (this.GetType().FullName + this.propertyName);
  }
}

这使得 TypeId 为 "ProvidePropertyAttributeImageCustom",因此无法区分不同的接收器类型。

我将测试派生 ProvidePropertyAttribute 并创建一个不同的 TypeId,因为它看起来可以覆盖,但我希望 winforms 设计人员期望特定的 ProvidePropertyAttribute 类型而不是派生类型(winforms 设计者对这些东西很挑剔)。

哎呀,ProvidePropertyAttribute密封的,所以我无法派生和制作我的自定义 TypeId,看来(不是我寄予厚望这根本行得通)

与此同时,有没有人做过类似的事情并且知道我可以使用的东西?

最佳答案

我知道这是一个快速的答案,但这几天让我抓狂,所以我选择了一条看起来效果不错的不同路线。

由于目标(正如我在问题中所解释的那样)是更改某些属性上的 UITypeEditor,因此我制作了一个覆盖属性的非可视组件(使用自定义 TypeDescriptor) 在这些属性上,并在那里分配我的自定义 UITypeEditor

我用了this answer作为实现属性覆盖 TypeDescriptor 的基础。

更新

根据记录,链接答案中提供的解决方案有效,但是它有一个问题,即 TypeDescriptionProvider 会被派生类获取,但是返回的 TypeDescriptor只会返回基础对象的属性(您在父 TypeDescriptor 中为其传递的对象),从而导致 winforms 设计器之类的破坏。

我制作了一个通用的属性覆盖器 TypeDescriptionProvider。到目前为止,它工作得很好。这是实现。查看linked answer解释这是从哪里来的:

  1. 提供商:

    internal class PropertyOverridingTypeDescriptionProvider : TypeDescriptionProvider
    {
        private readonly Dictionary<Type, ICustomTypeDescriptor> _descriptorCache = new Dictionary<Type, ICustomTypeDescriptor>();
        private readonly Func<PropertyDescriptor, bool> _condition;
        private readonly Func<PropertyDescriptor, Type, PropertyDescriptor> _propertyCreator;
    
        public PropertyOverridingTypeDescriptionProvider(TypeDescriptionProvider parentProvider, Func<PropertyDescriptor, bool> condition, Func<PropertyDescriptor, Type, PropertyDescriptor> propertyCreator) : base(parentProvider)
        {
            _condition = condition;
            _propertyCreator = propertyCreator;
        }
    
        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            lock (_descriptorCache)
            {
                ICustomTypeDescriptor returnDescriptor;
                if (!_descriptorCache.TryGetValue(objectType, out returnDescriptor))
                {
                    returnDescriptor = CreateTypeDescriptor(objectType);
                }
                return returnDescriptor;
            }
        }
    
        private ICustomTypeDescriptor CreateTypeDescriptor(Type targetType)
        {
            var descriptor = base.GetTypeDescriptor(targetType, null);
            _descriptorCache.Add(targetType, descriptor);
            var ctd = new PropertyOverridingTypeDescriptor(descriptor, targetType, _condition, _propertyCreator);
            _descriptorCache[targetType] = ctd;
            return ctd;
        }
    }
    
  2. 这是实际的 TypeDescriptor:

    internal class PropertyOverridingTypeDescriptor : CustomTypeDescriptor
    {
        private readonly ICustomTypeDescriptor _parent;
        private readonly PropertyDescriptorCollection _propertyCollection;
        private readonly Type _objectType;
        private readonly Func<PropertyDescriptor, bool> _condition;
        private readonly Func<PropertyDescriptor, Type, PropertyDescriptor> _propertyCreator;
    
        public PropertyOverridingTypeDescriptor(ICustomTypeDescriptor parent, Type objectType, Func<PropertyDescriptor, bool> condition, Func<PropertyDescriptor, Type, PropertyDescriptor> propertyCreator)
            : base(parent)
        {
            _parent = parent;
            _objectType = objectType;
            _condition = condition;
            _propertyCreator = propertyCreator;
            _propertyCollection = BuildPropertyCollection();
        }
    
        private PropertyDescriptorCollection BuildPropertyCollection()
        {
            var isChanged = false;
            var parentProperties = _parent.GetProperties();
    
            var pdl = new PropertyDescriptor[parentProperties.Count];
            var index = 0;
            foreach (var pd in parentProperties.OfType<PropertyDescriptor>())
            {
                var pdReplaced = pd;
                if (_condition(pd))
                {
                    pdReplaced = _propertyCreator(pd, _objectType);
                }
                if (!ReferenceEquals(pdReplaced, pd)) isChanged = true;
                pdl[index++] = pdReplaced;
            }
            return !isChanged ? parentProperties : new PropertyDescriptorCollection(pdl);
        }
    
        public override object GetPropertyOwner(PropertyDescriptor pd)
        {
            var o = base.GetPropertyOwner(pd);
            return o ?? this;
        }
    
        public override PropertyDescriptorCollection GetProperties()
        {
            return _propertyCollection;
        }
        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            return _propertyCollection;
        }
    }
    

下面是您如何使用它。我评论过这个:

private void ChangeTypeProperties(Type modifiedType, params string[] propertyNames)
{
    // Get the current TypeDescriptionProvider
    var curProvider = TypeDescriptor.GetProvider(modifiedType);
    // Create a replacement provider, pass in the parent, this is important
    var replaceProvider = new PropertyOverridingTypeDescriptionProvider(curProvider,
        // This the predicate that says wether a `PropertyDescriptor` should be changed
        // Here we are changing only the System.Drawing.Image properties,
        // either those whose name we pass in, or all if we pass none
        pd =>
            typeof (System.Drawing.Image).IsAssignableFrom(pd.PropertyType) &&
            (propertyNames.Length == 0 || propertyNames.Contains(pd.Name)),

        // This our "replacer" function. It'll get the source PropertyDescriptor and the object type.
        // You could use pd.ComponentType for the object type, but I've
        // found it to fail under some circumstances, so I just pass it
        // along
        (pd, t) =>
        {
            // Get original attributes except the ones we want to change
            var atts = pd.Attributes.OfType<Attribute>().Where(x => x.GetType() != typeof (EditorAttribute)).ToList();
            // Add our own attributes
            atts.Add(new EditorAttribute(typeof (MyOwnEditor), typeof (System.Drawing.Design.UITypeEditor)));
            // Create the new PropertyDescriptor
            return TypeDescriptor.CreateProperty(t, pd, atts.ToArray());
        }
    );
    // Finally we replace the TypeDescriptionProvider
    TypeDescriptor.AddProvider(replaceProvider, modifiedType);
}

现在,根据我的问题的要求,我创建了一个简单的插入式组件,我将其放在基本表单上,它就是这样做的:

public class ToolbarImageEditorExtender : Component
{
    private static bool _alreadyInitialized;
    public ToolbarImageEditorExtender()
    {
        // no need to reinitialize if we drop more than one component
        if (_alreadyInitialized)
            return;
        _alreadyInitialized = true;
        // the ChangeTypeProperties function above. I just made a generic version
        ChangeTypeProperties<OtherControl>(nameof(OtherControl.Glyph), nameof(OtherControl.LargeGlyph));
        ChangeTypeProperties<AButton>(nameof(AButton.SmallImage), nameof(AButton.LargeImage));
        // etc.
    }
}

到目前为止,它产生了奇迹。

关于c# - IExtenderProvider 根据对象类型仅添加一些属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34509662/

相关文章:

c# - 如何在datagridview组合框列中绑定(bind)数据

c# - Entity Framework 只保存最后一行(主要细节)

c# - 视觉继承 - 将设计器中的控件添加到 TableLayoutPanel 中托管的面板

c# - 覆盖 OnPaint

c# - equals 和 ID 比较之间的性能差异

c# - 使用 'WlanScan' 刷新 WiFi 网络列表(将 api 语法从 c# 转换为 vba...或解决方法?)

c# - 在 Windows 7 上加载嵌入式资源

c# - WinForms:进入主菜单后不会触发文本框离开事件

c# - WinForms - 在控制之前捕获键

c# - 如何使用 CSV 文件中的新数据刷新/更新 DataGridView?