| File: System\Windows\Forms\ActiveX\AxHost.AxPropertyDescriptor.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Drawing.Design; using System.Runtime.InteropServices; using System.Windows.Forms.ComponentModel.Com2Interop; using Windows.Win32.System.Ole; namespace System.Windows.Forms; public abstract partial class AxHost { internal class AxPropertyDescriptor : PropertyDescriptor { private readonly PropertyDescriptor _baseDescriptor; internal AxHost _owner; private readonly DispIdAttribute? _dispid; private TypeConverter? _converter; private UITypeEditor? _editor; private readonly List<Attribute> _updateAttributes = []; private int _flags; private const int FlagUpdatedEditorAndConverter = 0x00000001; private const int FlagCheckGetter = 0x00000002; private const int FlagGetterThrew = 0x00000004; private const int FlagIgnoreCanAccessProperties = 0x00000008; private const int FlagSettingValue = 0x00000010; internal AxPropertyDescriptor(PropertyDescriptor baseDescriptor, AxHost owner) : base(baseDescriptor) { _baseDescriptor = baseDescriptor; _owner = owner; // Get the category for this dispid. _dispid = baseDescriptor.GetAttribute<DispIdAttribute>(); if (_dispid is not null) { // Look to see if this property has a property page. // If it does, then it needs to be Browsable(true). if (!IsBrowsable && !IsReadOnly) { Guid g = GetPropertyPage(_dispid.Value); if (!Guid.Empty.Equals(g)) { AddAttribute(new BrowsableAttribute(true)); } } // Use the CategoryAttribute provided by the OCX. CategoryAttribute? cat = owner.GetCategoryForDispid(_dispid.Value); if (cat is not null) { AddAttribute(cat); } // Check to see if this a DataSource property. // If it is, we can always get and set the value of this property. if (PropertyType.GUID.Equals(s_dataSource_Guid)) { SetFlag(FlagIgnoreCanAccessProperties, true); } } } public override Type ComponentType { get { return _baseDescriptor.ComponentType; } } public override TypeConverter Converter { [RequiresUnreferencedCode(TrimmingConstants.PropertyDescriptorPropertyTypeMessage)] get { if (_dispid is not null) { UpdateTypeConverterAndTypeEditorInternal(force: false, Dispid); } return _converter ?? base.Converter; } } internal int Dispid => _baseDescriptor.TryGetAttribute(out DispIdAttribute? dispid) ? dispid.Value : PInvokeCore.DISPID_UNKNOWN; public override bool IsReadOnly => _baseDescriptor.IsReadOnly; public override Type PropertyType => _baseDescriptor.PropertyType; internal bool SettingValue => GetFlag(FlagSettingValue); private void AddAttribute(Attribute attr) { _updateAttributes.Add(attr); } public override bool CanResetValue(object o) { return _baseDescriptor.CanResetValue(o); } [RequiresUnreferencedCode(TrimmingConstants.EditorRequiresUnreferencedCode)] public override object? GetEditor(Type editorBaseType) { ArgumentNullException.ThrowIfNull(editorBaseType); if (_dispid is not null) { UpdateTypeConverterAndTypeEditorInternal(false, _dispid.Value); } return editorBaseType.Equals(typeof(UITypeEditor)) && _editor is not null ? _editor : base.GetEditor(editorBaseType); } private bool GetFlag(int flagValue) { return ((_flags & flagValue) == flagValue); } private unsafe Guid GetPropertyPage(int dispid) { try { using var propertyBrowsing = _owner.TryGetComScope<IPerPropertyBrowsing>(out HRESULT hr); if (hr.Failed) { return Guid.Empty; } Guid rval = Guid.Empty; if (propertyBrowsing.Value->MapPropertyToPage(dispid, &rval).Succeeded) { return rval; } } catch (COMException) { } catch (Exception t) { Debug.Fail(t.ToString()); } return Guid.Empty; } public override object? GetValue(object? component) { if ((!GetFlag(FlagIgnoreCanAccessProperties) && !_owner.CanAccessProperties) || GetFlag(FlagGetterThrew)) { return null; } try { // Some controls fire OnChanged() notifications when getting values of some properties. // To prevent this kind of recursion, we check to see if we are already inside a OnChanged() call. _owner.NoComponentChangeEvents++; return _baseDescriptor.GetValue(component); } catch (Exception) { if (!GetFlag(FlagCheckGetter)) { SetFlag(FlagCheckGetter, true); AddAttribute(new BrowsableAttribute(false)); _owner.RefreshAllProperties = true; SetFlag(FlagGetterThrew, true); } throw; } finally { _owner.NoComponentChangeEvents--; } } public void OnValueChanged(object? component) { OnValueChanged(component, EventArgs.Empty); } public override void ResetValue(object o) { _baseDescriptor.ResetValue(o); } private void SetFlag(int flagValue, bool value) { if (value) { _flags |= flagValue; } else { _flags &= ~flagValue; } } public override void SetValue(object? component, object? value) { if (!GetFlag(FlagIgnoreCanAccessProperties) && !_owner.CanAccessProperties) { return; } // State oldOcxState = owner.OcxState; try { SetFlag(FlagSettingValue, true); if (PropertyType.IsEnum && value is not null && value.GetType() != PropertyType) { _baseDescriptor.SetValue(component, Enum.ToObject(PropertyType, value)); } else { _baseDescriptor.SetValue(component, value); } } finally { SetFlag(FlagSettingValue, false); } OnValueChanged(component); if (_owner == component) { _owner.SetAxState(s_valueChanged, true); } } public override bool ShouldSerializeValue(object o) { return _baseDescriptor.ShouldSerializeValue(o); } internal void UpdateAttributes() { if (_updateAttributes.Count == 0) { return; } List<Attribute> attributes = [..AttributeArray!]; attributes.AddRange(_updateAttributes); AttributeArray = [.. attributes]; _updateAttributes.Clear(); } /// <summary> /// Called externally to flag that we need to update the editor or type converter. /// </summary> internal void UpdateTypeConverterAndTypeEditor(bool force) { // If this is an external request, flip the flag to false so we do the update on demand. if (GetFlag(FlagUpdatedEditorAndConverter) && force) { SetFlag(FlagUpdatedEditorAndConverter, false); } } /// <summary> /// Called externally to flag that we need to update the editor or type converter for a specific DISPID. /// </summary> internal unsafe void UpdateTypeConverterAndTypeEditorInternal(bool force, int dispid) { // Check to see if we're being forced here or if the work really needs to be done. if ((GetFlag(FlagUpdatedEditorAndConverter) && !force) || _owner.GetOcx() is null) { return; } try { using var propertyBrowsing = _owner.TryGetComScope<IPerPropertyBrowsing>(out HRESULT hr); if (hr.Failed) { return; } // Check for enums CALPOLESTR caStrings = default; CADWORD caCookies = default; hr = propertyBrowsing.Value->GetPredefinedStrings(dispid, &caStrings, &caCookies); if (hr.Failed) { Debug.Fail($"IPerPropertyBrowsing::GetPredefinedStrings(dispid={dispid}) failed: {hr}"); } if (hr == HRESULT.S_OK) { string?[] names = caStrings.ConvertAndFree(); uint[] cookies = caCookies.ConvertAndFree(); if (names.Length > 0 && cookies.Length > 0) { if (_converter is null) { _converter = new AxEnumConverter( this, new AxPerPropertyBrowsingEnum( this, _owner, names, cookies)); } else if (_converter is AxEnumConverter enumConverter) { enumConverter.RefreshValues(); if (enumConverter._com2Enum is AxPerPropertyBrowsingEnum axEnum) { axEnum.RefreshArrays(names, cookies); } } } return; } // Destroy the existing editor if we created the current one so if the items have // disappeared, we don't hold onto the old items. if (_converter is Com2EnumConverter) { _converter = null; } // If we didn't get any strings, try the property page editor. // // Check to see if this is a property that we have already massaged to be a .NET type. If it is, don't // bother with custom property pages. We already have a .NET Editor for this type. if (_baseDescriptor.GetAttribute<ComAliasNameAttribute>() is not null) { return; } Guid pageGuid = GetPropertyPage(dispid); if (pageGuid != Guid.Empty) { _editor = new AxPropertyTypeEditor(this, pageGuid); // Show any non-browsable property that has an editor through a property page. if (!IsBrowsable) { AddAttribute(new BrowsableAttribute(true)); } } } catch (Exception) { } finally { SetFlag(FlagUpdatedEditorAndConverter, true); } } } }