|
//------------------------------------------------------------------------------
// <copyright file="COM2IPerPropertyBrowsingHandler.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Windows.Forms.ComponentModel.Com2Interop {
using System.Runtime.Remoting;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Diagnostics;
using System;
using System.Collections;
using Microsoft.Win32;
using System.Globalization;
using System.Drawing.Design;
[System.Security.SuppressUnmanagedCodeSecurityAttribute()]
internal class Com2IPerPropertyBrowsingHandler : Com2ExtendedBrowsingHandler {
public override Type Interface {
get {
return typeof(NativeMethods.IPerPropertyBrowsing);
}
}
public override void SetupPropertyHandlers(Com2PropertyDescriptor[] propDesc) {
if (propDesc == null) {
return;
}
for (int i = 0; i < propDesc.Length; i++) {
propDesc[i].QueryGetBaseAttributes += new GetAttributesEventHandler(this.OnGetBaseAttributes);
propDesc[i].QueryGetDisplayValue += new GetNameItemEventHandler(this.OnGetDisplayValue);
propDesc[i].QueryGetTypeConverterAndTypeEditor += new GetTypeConverterAndTypeEditorEventHandler(this.OnGetTypeConverterAndTypeEditor);
}
}
private Guid GetPropertyPageGuid(NativeMethods.IPerPropertyBrowsing target, int dispid) {
// check for a property page
Guid guid;
int hr = target.MapPropertyToPage(dispid, out guid);
if (hr == NativeMethods.S_OK) {
return guid;
}
return Guid.Empty;
}
internal static string GetDisplayString(NativeMethods.IPerPropertyBrowsing ppb, int dispid, ref bool success) {
string[] strVal = new string[1];
int hr = ppb.GetDisplayString(dispid, strVal);
if (hr == NativeMethods.S_OK) {
success = (strVal[0] != null);
//Debug.Assert(success, "IPerPropertyBrowsing::GetDisplayString returned NULL and S_OK -- this is not a valid state. This component does not property implement IPerPropertyBrowsing. (component class=" + TypeDescriptor.GetClassName(ppb) + ")");
return strVal[0];
}
else {
success = false;
}
return null;
}
/// <include file='doc\COM2IPerPropertyBrowsingHandler.uex' path='docs/doc[@for="Com2IPerPropertyBrowsingHandler.OnGetAttributes"]/*' />
/// <devdoc>
/// Here is where we handle IVsPerPropertyBrowsing.GetLocalizedPropertyInfo and IVsPerPropertyBrowsing. HideProperty
/// such as IPerPropertyBrowsing, IProvidePropertyBuilder, etc.
/// </devdoc>
private void OnGetBaseAttributes(Com2PropertyDescriptor sender, GetAttributesEvent attrEvent) {
NativeMethods.IPerPropertyBrowsing target = sender.TargetObject as NativeMethods.IPerPropertyBrowsing;
if (target != null) {
// we hide IDispatch props by default, we we need to force showing them here
bool validPropPage = !Guid.Empty.Equals(GetPropertyPageGuid(target, sender.DISPID));
if (sender.CanShow && validPropPage) {
if (typeof(UnsafeNativeMethods.IDispatch).IsAssignableFrom(sender.PropertyType)) {
attrEvent.Add(BrowsableAttribute.Yes);
}
}
}
}
private void OnGetDisplayValue(Com2PropertyDescriptor sender, GetNameItemEvent gnievent) {
try {
if (sender.TargetObject is NativeMethods.IPerPropertyBrowsing) {
// if we are using the dropdown, don't convert the value
// or the values will change when we select them and call back
// for the display value.
if (sender.Converter is Com2IPerPropertyEnumConverter || sender.ConvertingNativeType) {
return;
}
bool success = true;
string displayString = GetDisplayString((NativeMethods.IPerPropertyBrowsing)sender.TargetObject, sender.DISPID, ref success);
if (success) {
gnievent.Name = displayString;
}
}
}
catch {
}
}
private void OnGetTypeConverterAndTypeEditor(Com2PropertyDescriptor sender, GetTypeConverterAndTypeEditorEvent gveevent) {
if (sender.TargetObject is NativeMethods.IPerPropertyBrowsing) {
NativeMethods.IPerPropertyBrowsing ppb = (NativeMethods.IPerPropertyBrowsing)sender.TargetObject;
bool hasStrings = false;
// check for enums
NativeMethods.CA_STRUCT caStrings = new NativeMethods.CA_STRUCT();
NativeMethods.CA_STRUCT caCookies = new NativeMethods.CA_STRUCT();
int hr = NativeMethods.S_OK;
try {
hr = ppb.GetPredefinedStrings(sender.DISPID, caStrings, caCookies);
}
catch(ExternalException ex){
hr = ex.ErrorCode;
Debug.Fail("An exception occurred inside IPerPropertyBrowsing::GetPredefinedStrings(dispid=" + sender.DISPID + "), object type=" + new ComNativeDescriptor().GetClassName(ppb) + ". This is caused by an exception (usually an AV) inside the object being browsed, and is not a problem in the properties window.");
}
// Terminate the existing editor if we created the current one
// so if the items have disappeared, we don't hold onto the old
// items.
if (gveevent.TypeConverter is Com2IPerPropertyEnumConverter) {
gveevent.TypeConverter = null;
}
if (hr != NativeMethods.S_OK) {
hasStrings = false;
}
else {
hasStrings = true;
}
if (hasStrings) {
OleStrCAMarshaler stringMarshaler = new OleStrCAMarshaler(caStrings);
Int32CAMarshaler intMarshaler = new Int32CAMarshaler(caCookies);
if (stringMarshaler.Count > 0 && intMarshaler.Count > 0) {
gveevent.TypeConverter = new Com2IPerPropertyEnumConverter(new Com2IPerPropertyBrowsingEnum(sender, this, stringMarshaler, intMarshaler, true));
}
else {
//hasStrings = false;
}
}
// if we didn't get any strings, try the proppage edtior
//
if (!hasStrings){
// this is a _bit_ of a backwards-compat work around...
// many older ActiveX controls will show a property page
// for all properties since the old grid would only put up the
// [...] button for "(Custom)". If we have a conversion editor,
// don't allow this to override it...
//
if (sender.ConvertingNativeType){
return;
}
Guid g = GetPropertyPageGuid(ppb, sender.DISPID);
if (!Guid.Empty.Equals(g)){
gveevent.TypeEditor = new Com2PropertyPageUITypeEditor(sender, g, (UITypeEditor)gveevent.TypeEditor);
}
}
}
}
// this is just here so we can identify the enums that we added
private class Com2IPerPropertyEnumConverter : Com2EnumConverter {
private Com2IPerPropertyBrowsingEnum itemsEnum;
public Com2IPerPropertyEnumConverter(Com2IPerPropertyBrowsingEnum items) : base(items) {
this.itemsEnum = items;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destType) {
if (destType == typeof(string) && !itemsEnum.arraysFetched) {
object curValue = itemsEnum.target.GetValue(itemsEnum.target.TargetObject);
if (curValue == value || (curValue != null && curValue.Equals(value))) {
bool success = false;
string val = GetDisplayString((NativeMethods.IPerPropertyBrowsing)itemsEnum.target.TargetObject, itemsEnum.target.DISPID, ref success);
if (success) {
return val;
}
}
}
return base.ConvertTo(context, culture, value, destType);
}
}
// This exists for perf reasons. We delay doing this until we
// are actually asked for the array of values.
//
private class Com2IPerPropertyBrowsingEnum : Com2Enum {
internal Com2PropertyDescriptor target;
private Com2IPerPropertyBrowsingHandler handler;
private OleStrCAMarshaler nameMarshaller;
private Int32CAMarshaler valueMarshaller;
internal bool arraysFetched;
//private bool standardValuesQueried;
public Com2IPerPropertyBrowsingEnum(Com2PropertyDescriptor targetObject, Com2IPerPropertyBrowsingHandler handler, OleStrCAMarshaler names, Int32CAMarshaler values, bool allowUnknowns) : base(new string[0], new object[0], allowUnknowns) {
this.target = targetObject;
this.nameMarshaller = names;
this.valueMarshaller = values;
this.handler = handler;
this.arraysFetched = false;
}
/// <include file='doc\COM2IPerPropertyBrowsingHandler.uex' path='docs/doc[@for="Com2IPerPropertyBrowsingHandler.Com2IPerPropertyBrowsingEnum.Values"]/*' />
/// <devdoc>
/// Retrieve a copy of the value array
/// </devdoc>
public override object[] Values {
get {
EnsureArrays();
return base.Values;
}
}
/// <include file='doc\COM2IPerPropertyBrowsingHandler.uex' path='docs/doc[@for="Com2IPerPropertyBrowsingHandler.Com2IPerPropertyBrowsingEnum.Names"]/*' />
/// <devdoc>
/// Retrieve a copy of the nme array.
/// </devdoc>
public override string[] Names {
get {
EnsureArrays();
return base.Names;
}
}
/*internal bool StandardValuesQueried {
get {
this.standardValuesQueried = value;
}
} */
// ensure that we have processed the caStructs into arrays
// of values and strings
//
private void EnsureArrays() {
if (this.arraysFetched) {
return;
}
this.arraysFetched = true;
try {
// marshal the items.
object[] nameItems = nameMarshaller.Items;
object[] cookieItems= valueMarshaller.Items;
NativeMethods.IPerPropertyBrowsing ppb = (NativeMethods.IPerPropertyBrowsing)target.TargetObject;
int itemCount = 0;
Debug.Assert(cookieItems != null && nameItems != null, "An item array is null");
if (nameItems.Length > 0){
object[] valueItems = new object[cookieItems.Length];
NativeMethods.VARIANT var = new NativeMethods.VARIANT();
int cookie;
Debug.Assert(cookieItems.Length == nameItems.Length, "Got uneven names and cookies");
// for each name item, we ask the object for it's corresponding value.
//
Type targetType = target.PropertyType;
for (int i = nameItems.Length - 1; i >= 0; i--) {
cookie = (int)cookieItems[i];
if (nameItems[i] == null || !(nameItems[i] is string)) {
Debug.Fail("Bad IPerPropertyBrowsing item [" + i.ToString(CultureInfo.InvariantCulture) + "], name=" + (nameItems == null ? "(unknown)" : nameItems[i].ToString()));
continue;
}
var.vt = (short)NativeMethods.tagVT.VT_EMPTY;
int hr = ppb.GetPredefinedValue(target.DISPID, cookie, var);
if (hr == NativeMethods.S_OK && var.vt != (short)NativeMethods.tagVT.VT_EMPTY) {
valueItems[i] = var.ToObject();
if (valueItems[i].GetType() != targetType) {
if (targetType.IsEnum) {
valueItems[i] = Enum.ToObject(targetType, valueItems[i]);
}
else {
try {
valueItems[i] = Convert.ChangeType(valueItems[i], targetType, CultureInfo.InvariantCulture);
}
catch {
// oh well...
}
}
}
}
var.Clear();
if (hr == NativeMethods.S_OK) {
itemCount++;
continue;
}
else if (itemCount > 0){
// shorten the arrays to ignore the failed ones. this isn't terribly
// efficient but shouldn't happen very often. It's rare for these to fail.
//
Array.Copy(nameItems, i, nameItems, i+1, itemCount);
Array.Copy(valueItems, i, valueItems, i+1, itemCount);
}
}
// pass this data down to the base Com2Enum object...
string[] strings = new string[itemCount];
Array.Copy(nameItems, 0, strings, 0, itemCount);
base.PopulateArrays(strings, valueItems);
}
}
catch (Exception ex) {
base.PopulateArrays(new string[0], new object[0]);
Debug.Fail("Failed to build IPerPropertyBrowsing editor. " + ex.GetType().Name + ", " + ex.Message);
}
}
protected override void PopulateArrays(string[] names, object[] values) {
// we call base.PopulateArrays directly when we actually want to do this.
}
public override object FromString(string s) {
EnsureArrays();
return base.FromString(s);
}
public override string ToString(object v) {
// If the value is the object's current value, then
// ask GetDisplay string first. This is a perf improvement
// because this way we don't populate the arrays when an object is selected, only
// when the dropdown is actually opened.
//
if (target.IsCurrentValue(v)) {
bool success = false;
string displayString = Com2IPerPropertyBrowsingHandler.GetDisplayString((NativeMethods.IPerPropertyBrowsing)target.TargetObject, target.DISPID, ref success);
if (success) {
return displayString;
}
}
// couldn't get a display string...do the normal thing.
//
EnsureArrays();
return base.ToString(v);
}
}
}
}
|