|
using System;
using System.Collections.Generic;
using System.Globalization;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Workflow.ComponentModel;
using System.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Reflection;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Windows.Forms.Design;
using System.Diagnostics.CodeAnalysis;
namespace System.Workflow.ComponentModel.Design
{
internal sealed partial class ActivityBindForm : Form
{
//ui control
private ActivityBindFormWorkflowOutline workflowOutline = null;
private IServiceProvider serviceProvider = null;
private ITypeDescriptorContext context = null;
private Type boundType = null;
//returns
private ActivityBind binding = null;
private bool createNew = false; //create new field or use existing
private bool createNewProperty = false; //create property or field
private string newMemberName = string.Empty;
private const string MemberTypeFormat = "MemberType#{0}"; //non-localiazable, used for image list keys
private string ActivityBindDialogTitleFormat; // = "Bind '{0}' to Activity Property";
private string PropertyAssignableFormat; // = "Selected property of type '{0}' is assignable to the target property type '{1}'.";
private string DescriptionFormat; // = " It has description '{0}'.";
private string EditIndex; // = " You may change index(es) on the selected tree item either by clicking on a node with the left mouse button or by pressing Alt-I.";
private string PleaseSelectCorrectActivityProperty; // = "Select a property of type '{0}'. Currently selected property of type \"{1}\" isn't assignable to the target type.";
private string PleaseSelectActivityProperty; // = "Select a property of type \"{0}\" on an activity from the workflow activity tree.";
private string IncorrectIndexChange; // = "New index expression \"{0}\" is incorrect.";
private string CreateNewMemberHelpFormat; // = "Enter new member name you want to be created on the root activity for property promotion, then choose the kind of member between either a field or a property.\nNew member will be of type '{0}'.";
System.Windows.Forms.ImageList memberTypes = null;
List<CustomProperty> properties;
public ActivityBindForm(IServiceProvider serviceProvider, ITypeDescriptorContext context)
{
this.context = context;
this.serviceProvider = serviceProvider;
InitializeComponent();
this.createProperty.Checked = true; //make the property to be the default emitted entity
this.helpTextBox.Multiline = true;
//Set dialog fonts
IUIService uisvc = (IUIService)this.serviceProvider.GetService(typeof(IUIService));
if (uisvc != null)
this.Font = (Font)uisvc.Styles["DialogFont"];
//add images to the tree-view's imagelist
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ActivityBindForm));
ActivityBindDialogTitleFormat = resources.GetString("ActivityBindDialogTitleFormat");
PropertyAssignableFormat = resources.GetString("PropertyAssignableFormat");
DescriptionFormat = resources.GetString("DescriptionFormat");
EditIndex = resources.GetString("EditIndex");
PleaseSelectCorrectActivityProperty = resources.GetString("PleaseSelectCorrectActivityProperty");
PleaseSelectActivityProperty = resources.GetString("PleaseSelectActivityProperty");
IncorrectIndexChange = resources.GetString("IncorrectIndexChange");
CreateNewMemberHelpFormat = resources.GetString("CreateNewMemberHelpFormat");
this.memberTypes = new System.Windows.Forms.ImageList();
this.memberTypes.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("memberTypes.ImageStream")));
this.memberTypes.TransparentColor = AmbientTheme.TransparentColor;
//this.memberTypes.Images.SetKeyName(0, "Field_Public");
//this.memberTypes.Images.SetKeyName(1, "Field_Internal");
//this.memberTypes.Images.SetKeyName(2, "Field_Protected");
//this.memberTypes.Images.SetKeyName(3, "Field_Private");
//this.memberTypes.Images.SetKeyName(4, "Property_Public");
//this.memberTypes.Images.SetKeyName(5, "Property_Internal");
//this.memberTypes.Images.SetKeyName(6, "Property_Protected");
//this.memberTypes.Images.SetKeyName(7, "Property_Private");
//this.memberTypes.Images.SetKeyName(8, "Constant_Public");
//this.memberTypes.Images.SetKeyName(9, "Constant_Internal");
//this.memberTypes.Images.SetKeyName(10, "Constant_Protected");
//this.memberTypes.Images.SetKeyName(11, "Constant_Private");
//this.memberTypes.Images.SetKeyName(12, "Event_Public");
//this.memberTypes.Images.SetKeyName(13, "Event_Internal");
//this.memberTypes.Images.SetKeyName(14, "Event_Protected");
//this.memberTypes.Images.SetKeyName(15, "Event_Private");
//this.memberTypes.Images.SetKeyName(16, "Delegate_Public");
//this.memberTypes.Images.SetKeyName(17, "Delegate_Internal");
//this.memberTypes.Images.SetKeyName(18, "Delegate_Protected");
//this.memberTypes.Images.SetKeyName(19, "Delegate_Private");
//this.memberTypes.Images.SetKeyName(20, "Index_Public");
//this.memberTypes.Images.SetKeyName(21, "Index_Internal");
//this.memberTypes.Images.SetKeyName(22, "Index_Protected");
//this.memberTypes.Images.SetKeyName(23, "Index_Private");
//preload custom properties before getting type from the type provider (as it would refresh the types)
this.properties = CustomActivityDesignerHelper.GetCustomProperties(context);
}
#region return properties
public ActivityBind Binding
{
get
{
return this.binding;
}
}
public bool CreateNew
{
get
{
return this.createNew;
}
}
public bool CreateNewProperty
{
get
{
return this.createNewProperty;
}
}
public string NewMemberName
{
get
{
return this.newMemberName;
}
}
#endregion
private void ActivityBindForm_Load(object sender, EventArgs e)
{
this.Text = string.Format(CultureInfo.CurrentCulture, ActivityBindDialogTitleFormat, context.PropertyDescriptor.Name);
if (this.context.PropertyDescriptor is DynamicPropertyDescriptor)
this.boundType = PropertyDescriptorUtils.GetBaseType(this.context.PropertyDescriptor, PropertyDescriptorUtils.GetComponent(context), serviceProvider);
if (this.boundType != null)
{
//lets get the same type through the type provider (otherwise this type may mismatch with the one obtained from the design time types)
ITypeProvider typeProvider = this.serviceProvider.GetService(typeof(ITypeProvider)) as ITypeProvider;
if (typeProvider != null)
{
Type designTimeType = typeProvider.GetType(this.boundType.FullName, false);
this.boundType = (designTimeType != null) ? designTimeType : this.boundType;
}
}
//create outline control
this.workflowOutline = new ActivityBindFormWorkflowOutline(this.serviceProvider, this);
this.dummyPanel.BorderStyle = BorderStyle.None;
this.dummyPanel.SuspendLayout();
this.dummyPanel.Controls.Add(this.workflowOutline);
this.workflowOutline.Location = new Point(3, 3);
this.workflowOutline.Size = new Size(199, 351);
this.workflowOutline.Dock = DockStyle.Fill;
this.dummyPanel.ResumeLayout(false);
this.workflowOutline.AddMemberKindImages(this.memberTypes);
//make the outline view load initial state
this.workflowOutline.ReloadWorkflowOutline();
//expand just the root node
this.workflowOutline.ExpandRootNode();
//now we need to select the activity/path which was previously set
//NOTE: we would have to expand all nodes on the way to make doc outline control populate their children
Activity activity = PropertyDescriptorUtils.GetComponent(context) as Activity;
if (activity == null)
{
IReferenceService rs = this.context.GetService(typeof(IReferenceService)) as IReferenceService;
if (rs != null)
activity = rs.GetComponent(this.context.Instance) as Activity;
}
ActivityBind previousBinding = context.PropertyDescriptor.GetValue(context.Instance) as ActivityBind;
if (activity != null && previousBinding != null)
{
Activity previousBindActivity = Helpers.ParseActivity(Helpers.GetRootActivity(activity), previousBinding.Name);
if (previousBindActivity != null)
this.workflowOutline.SelectActivity(previousBindActivity, ParseStringPath(GetActivityType(previousBindActivity), previousBinding.Path));
}
if (this.properties != null)
{
List<String> customPropertyNames = new List<String>();
foreach (CustomProperty customProperty in this.properties)
customPropertyNames.Add(customProperty.Name);
// set default name
this.memberNameTextBox.Text = DesignerHelpers.GenerateUniqueIdentifier(this.serviceProvider, activity.Name + "_" + context.PropertyDescriptor.Name, customPropertyNames.ToArray());
}
this.newMemberHelpTextBox.Lines = string.Format(CultureInfo.CurrentCulture, CreateNewMemberHelpFormat, GetSimpleTypeFullName(this.boundType)).Split(new char[] { '\n' });
}
private void OKButton_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.None;
this.createNew = (this.bindTabControl.SelectedIndex != this.bindTabControl.TabPages.IndexOf(this.existingMemberPage));
if (this.createNew)
{
//
this.createNewProperty = this.createProperty.Checked;
this.newMemberName = this.memberNameTextBox.Text;
//validate name based on the prop promotion dialog
this.DialogResult = ValidateNewMemberBind(this.newMemberName);
}
else
{
this.DialogResult = ValidateExistingPropertyBind();
}
}
private DialogResult ValidateExistingPropertyBind()
{
Activity activity = this.workflowOutline.SelectedActivity;
PathInfo member = this.workflowOutline.SelectedMember;
string propertyPath = this.workflowOutline.PropertyPath; //the path on the PathInfo will be incorrect if user had changed indexes
if (activity == null || member == null)
{
string message = SR.GetString(SR.Error_BindDialogNoValidPropertySelected, GetSimpleTypeFullName(this.boundType));
DesignerHelpers.ShowError(this.serviceProvider, message);
return DialogResult.None;
}
Type parsedPropertyType = member.PropertyType;
//lets get the same type through the type provider (otherwise this type may mismatch with the one obtained from the design time types)
ITypeProvider typeProvider = this.serviceProvider.GetService(typeof(ITypeProvider)) as ITypeProvider;
if (typeProvider != null && parsedPropertyType != null)
{
Type designTimeParsedType = typeProvider.GetType(parsedPropertyType.FullName, false);
parsedPropertyType = (designTimeParsedType != null) ? designTimeParsedType : parsedPropertyType;
}
if (this.boundType != parsedPropertyType && !TypeProvider.IsAssignable(this.boundType, parsedPropertyType))
{
string message = SR.GetString(SR.Error_BindDialogWrongPropertyType, GetSimpleTypeFullName(parsedPropertyType), GetSimpleTypeFullName(this.boundType));
DesignerHelpers.ShowError(this.serviceProvider, message);
return DialogResult.None;
}
//this is the selected activity which property is being bound
Activity bindingActivity = PropertyDescriptorUtils.GetComponent(this.context) as Activity;
//this is the name of the property we are binding
string propertyName = context.PropertyDescriptor.Name;
if (bindingActivity == activity && member != null && member.Path.Equals(propertyName, StringComparison.Ordinal))
{
DesignerHelpers.ShowError(this.serviceProvider, SR.GetString(SR.Error_BindDialogCanNotBindToItself));
return DialogResult.None;
}
if (activity != null && member != null)
{
//
ActivityBind bind = new ActivityBind(activity.QualifiedName, propertyPath);
ValidationManager manager = new ValidationManager(this.serviceProvider);
PropertyValidationContext propertyValidationContext = new PropertyValidationContext(this.context.Instance, DependencyProperty.FromName(this.context.PropertyDescriptor.Name, this.context.Instance.GetType()));
manager.Context.Append(this.context.Instance); //
ValidationErrorCollection errors;
using (WorkflowCompilationContext.CreateScope(manager))
{
errors = ValidationHelpers.ValidateProperty(manager, bindingActivity, bind, propertyValidationContext);
}
if (errors != null && errors.Count > 0 && errors.HasErrors)
{
string message = string.Empty;
for (int i = 0; i < errors.Count; i++)
{
ValidationError error = errors[i];
message += error.ErrorText + ((i == errors.Count - 1) ? string.Empty : "; ");
}
message = SR.GetString(SR.Error_BindDialogBindNotValid) + message;
DesignerHelpers.ShowError(this.serviceProvider, message);
return DialogResult.None;
}
else
{
this.binding = bind;
return DialogResult.OK;
}
}
return DialogResult.None;
}
[SuppressMessage("Microsoft.Globalization", "CA130:UseOrdinalStringComparison", MessageId = "System.String.Compare(System.String,System.String,System.Boolean,System.Globalization.CultureInfo)", Justification = "This is a design time method and so there is no security issue")]
private DialogResult ValidateNewMemberBind(string newMemberName)
{
Activity activity = PropertyDescriptorUtils.GetComponent(context) as Activity;
if (activity == null)
{
IReferenceService rs = this.context.GetService(typeof(IReferenceService)) as IReferenceService;
if (rs != null)
activity = rs.GetComponent(this.context.Instance) as Activity;
}
string errorMsg = null;
try
{
ValidationHelpers.ValidateIdentifier(context, newMemberName);
}
catch
{
errorMsg = SR.GetString(SR.Error_InvalidLanguageIdentifier, newMemberName);
}
// get all the members of the custom activity to ensure uniqueness
Type customActivityType = CustomActivityDesignerHelper.GetCustomActivityType(context);
SupportedLanguages language = CompilerHelpers.GetSupportedLanguage(context);
foreach (MemberInfo memberInfo in customActivityType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
{
if (string.Compare(memberInfo.Name, newMemberName, language == SupportedLanguages.VB, CultureInfo.InvariantCulture) == 0)
{
errorMsg = SR.GetString(SR.Failure_FieldAlreadyExist);
break;
}
}
// ctor name should be checked separately
if (errorMsg == null && string.Compare(customActivityType.Name, newMemberName, language == SupportedLanguages.VB, CultureInfo.InvariantCulture) == 0)
errorMsg = SR.GetString(SR.Failure_FieldAlreadyExist);
if (errorMsg == null)
{
ActivityBind newBind = new ActivityBind(ActivityBind.GetRelativePathExpression(Helpers.GetRootActivity(activity), activity), newMemberName);
IDesignerHost host = this.context.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host == null)
throw new InvalidOperationException(SR.GetString(SR.General_MissingService, typeof(IDesignerHost).FullName));
this.binding = newBind;
}
else
{
DesignerHelpers.ShowError(context, errorMsg);
}
return ((errorMsg == null) ? DialogResult.OK : DialogResult.None);
}
private void cancelButton_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
}
private void SelectedActivityChanged(Activity activity, PathInfo memberPathInfo, string path)
{
string helpMessage = string.Empty;
string desiredType = GetSimpleTypeFullName(this.boundType);
if (memberPathInfo != null)
{
if (path == null || path.Length == 0)
{
helpMessage = string.Format(CultureInfo.CurrentCulture, PleaseSelectActivityProperty, desiredType);
}
else
{
string memberName = MemberActivityBindTreeNode.MemberName(memberPathInfo.Path);
string memberType = GetSimpleTypeFullName(memberPathInfo.PropertyType);
string memberDescription = GetMemberDescription(memberPathInfo.MemberInfo);
if (TypeProvider.IsAssignable(this.boundType, memberPathInfo.PropertyType))
helpMessage = string.Format(CultureInfo.CurrentCulture, PropertyAssignableFormat, memberType, desiredType) + ((memberDescription.Length > 0) ? string.Format(CultureInfo.CurrentCulture, DescriptionFormat, memberDescription) : string.Empty);
else
helpMessage = string.Format(CultureInfo.CurrentCulture, PleaseSelectCorrectActivityProperty, desiredType, memberType);
helpMessage += ((MemberActivityBindTreeNode.MemberName(path).IndexOfAny(new char[] { '[', ']' }) != -1) ? EditIndex : string.Empty);
}
}
else
{
helpMessage = string.Format(CultureInfo.CurrentCulture, PleaseSelectActivityProperty, desiredType);
}
this.helpTextBox.Lines = helpMessage.Split(new char[] { '\n' });
}
private List<PathInfo> PopulateAutoCompleteList(Activity activity, PathInfo path)
{
List<PathInfo> currentPropertyList = new List<PathInfo>();
Type activityType = GetActivityType(activity);
PathInfo[] subProps = (activityType != null) ? ProcessPaths(activityType, path) : null;
if (subProps != null)
currentPropertyList.AddRange(subProps);
return currentPropertyList;
}
private Type GetActivityType(Activity activity)
{
Type activityType = null;
IDesignerHost designerHost = this.serviceProvider.GetService(typeof(IDesignerHost)) as IDesignerHost;
WorkflowDesignerLoader loader = this.serviceProvider.GetService(typeof(WorkflowDesignerLoader)) as WorkflowDesignerLoader;
if (designerHost != null && loader != null && activity.Parent == null)
{
ITypeProvider typeProvider = this.serviceProvider.GetService(typeof(ITypeProvider)) as ITypeProvider;
if (typeProvider != null)
activityType = typeProvider.GetType(designerHost.RootComponentClassName, false);
}
if (activityType == null)
activityType = activity.GetType();
return activityType;
}
#region help - related
private void ActivityBindForm_HelpButtonClicked(object sender, CancelEventArgs e)
{
e.Cancel = true;
GetHelp();
}
protected override void OnHelpRequested(HelpEventArgs e)
{
e.Handled = true;
GetHelp();
}
private void GetHelp()
{
DesignerHelpers.ShowHelpFromKeyword(this.serviceProvider, typeof(ActivityBindForm).FullName + ".UI");
}
#endregion
//given activity and current path, process all immediate children properties of the selected property
private PathInfo[] ProcessPaths(Type activityType, PathInfo topProperty)
{
List<PathInfo> paths = new List<PathInfo>();
if (topProperty == null)
{
paths.AddRange(GetSubPropertiesOnType(activityType, string.Empty));
}
else //topProperty != null
{
//sub properties on activity properties
paths.AddRange(GetSubPropertiesOnType(topProperty.PropertyType, topProperty.Path));
}
return paths.ToArray();
}
private PathInfo[] GetArraySubProperties(Type propertyType, string currentPath)//(PathInfo pathInfo)
{
List<PathInfo> paths = new List<PathInfo>();
if (propertyType != typeof(string))//ignore char item[int] on the string
{
List<MethodInfo> getterMethodInfos = new List<MethodInfo>();
MemberInfo[] arrayMembers = null;
try
{
arrayMembers = propertyType.GetDefaultMembers();
}
catch (NotImplementedException)
{
//Even if we encounted a RTTTypeWrapper that doesnt implement GetDefaultMemebers dont crash.
//we should atleast be able to continue to bind to other members of the type.
}
catch (ArgumentException)
{
// This is a work-around for DevDiv Bugs 109401. Type.GetDefaultMembers() can throw
// ArgumentException in certain circumstances. In order to avoid crashing the designer host
// (typically VS), we must handle the exception and ignore the offending type.
}
if (arrayMembers != null && arrayMembers.Length > 0)
{
foreach (MemberInfo member in arrayMembers)
{
if (member is PropertyInfo)
getterMethodInfos.Add((member as PropertyInfo).GetGetMethod());
}
}
if (propertyType.IsArray)
{
MemberInfo[] getMembers = propertyType.GetMember("Get"); //arrays will always implement that
if (getMembers != null && getMembers.Length > 0)
{
foreach (MemberInfo member in getMembers)
if (member is MethodInfo)
getterMethodInfos.Add(member as MethodInfo);
}
}
foreach (MethodInfo info in getterMethodInfos)
{
string indexString = ConstructIndexString(info);
if (indexString != null)
{
//add array accessor
paths.Add(new PathInfo(currentPath + indexString, info, info.ReturnType));
}
}
}
return paths.ToArray();
}
private string ConstructIndexString(MethodInfo getterMethod)
{
StringBuilder indexString = new StringBuilder();
ParameterInfo[] parameters = getterMethod.GetParameters();
if (parameters != null && parameters.Length > 0)
{
indexString.Append("[");
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo parameter = parameters[i];
string subIndex = GetIndexerString(parameter.ParameterType);
if (subIndex == null)
return null;
indexString.Append(subIndex);
if (i < parameters.Length - 1)
indexString.Append(",");
}
indexString.Append("]");
}
return indexString.ToString();
}
private string GetIndexerString(Type indexType)
{
//The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double, and Single.
object defaultIndexerInstance = null;
if (IsTypePrimitive(indexType))
{
try
{
//we'll just new the instance and get the string value out of the default one
defaultIndexerInstance = Activator.CreateInstance(indexType);
}
catch
{
defaultIndexerInstance = null;
}
}
else if (indexType == typeof(string))
{
defaultIndexerInstance = "\"<name>\"";
}
return (defaultIndexerInstance != null) ? defaultIndexerInstance.ToString() : null;
}
PropertyInfo[] GetProperties(Type type)
{
List<PropertyInfo> members = new List<PropertyInfo>();
members.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy));
if (type.IsInterface)
{
Type[] interfaces = type.GetInterfaces();
foreach (Type implementedInterface in interfaces)
{
members.AddRange(implementedInterface.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy));
}
}
return members.ToArray();
}
//quite expensive - uses reflection to go over all public properties/field/events
private PathInfo[] GetSubPropertiesOnType(Type typeToGetPropertiesOn, string currentPath)
{
List<PathInfo> paths = new List<PathInfo>();
if (typeToGetPropertiesOn == typeof(string) || (TypeProvider.IsAssignable(typeof(System.Delegate), typeToGetPropertiesOn) && !this.boundType.IsSubclassOf(typeof(Delegate))))//ignore char item[int] on the string
return paths.ToArray();
currentPath = (string.IsNullOrEmpty(currentPath)) ? string.Empty : currentPath + ".";
ITypeProvider typeProvider = this.serviceProvider.GetService(typeof(ITypeProvider)) as ITypeProvider;
foreach (PropertyInfo property in GetProperties(typeToGetPropertiesOn))
{
MethodInfo getterMethod = property.GetGetMethod();
Type memberType = BindHelpers.GetMemberType(property);
if (memberType == null)
continue;
if (typeProvider != null)
{
Type designTimeMemberType = typeProvider.GetType(memberType.FullName, false);
memberType = (designTimeMemberType != null) ? designTimeMemberType : memberType;
}
//if (memberType == typeof(WorkflowParameterBindingCollection) && string.IsNullOrEmpty(currentPath))
//{
// //special case for the parameters collection on an activity itself (when path is empty)
// Activity activity = this.workflowOutline.SelectedActivity;
// if(getterMethod != null && typeToGetPropertiesOn == activity.GetType())
// {
// WorkflowParameterBindingCollection collection = getterMethod.Invoke(activity, null) as WorkflowParameterBindingCollection;
// if (collection != null)
// {
// foreach (WorkflowParameterBinding parameterBinding in collection)
// {
// //note that the currentPath is always empty
// paths.Add(new PathInfo(property.Name + "[\"" + parameterBinding.ParameterName + "\"].Value", typeof(object)));
// }
// }
// }
//}
//if it's a primitive and not equal to the desired type, skip it.
//skip properties of type object if the target property is not object
if (IsPropertyBrowsable(property) &&
getterMethod != null && memberType != null &&
(!IsTypePrimitive(memberType) || TypeProvider.IsAssignable(this.boundType, memberType)) &&
!((this.boundType != typeof(object) && memberType == typeof(object))))
{
//some properties are indexers... analyze the parameters on the getter method
// C#: at design time indexer property is called "this" while at runtime it gets renamed to "Item"
// VB: indexer is called Item at design and runtime .
string propertyName = property.Name;
propertyName = currentPath + propertyName + ConstructIndexString(getterMethod);
paths.Add(new PathInfo(propertyName, property, memberType));
paths.AddRange(GetArraySubProperties(memberType, propertyName));
}
}
//
foreach (FieldInfo field in typeToGetPropertiesOn.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy))//BindingFlags.Static is needed for the const fields
{
Type fieldType = BindHelpers.GetMemberType(field);
if (fieldType == null)
continue;
if (TypeProvider.IsAssignable(typeof(DependencyProperty), fieldType))
continue; //dont want to show all static public dependency properties fields
if (typeProvider != null)
{
Type designTimeFieldType = typeProvider.GetType(fieldType.FullName, false);
fieldType = (designTimeFieldType != null) ? designTimeFieldType : fieldType;
}
//if it's a primitive and not equal to the desired type, skip it.
//
if (IsPropertyBrowsable(field) && fieldType != null &&
(!IsTypePrimitive(fieldType) || TypeProvider.IsAssignable(this.boundType, fieldType)) && //primitive fields should only be shown for primitive properties
!(this.boundType != typeof(object) && fieldType == typeof(object)) && //fields of type object should only be shown for properties of type object
!(!TypeProvider.IsAssignable(typeof(Delegate), this.boundType) && TypeProvider.IsAssignable(typeof(Delegate), fieldType)))//fields of type delegate should only be shown for delegate properties
{
string fieldName = currentPath + field.Name;
paths.Add(new PathInfo(fieldName, field, BindHelpers.GetMemberType(field)));
paths.AddRange(GetArraySubProperties(fieldType, fieldName));
}
}
//we will populate events only if the target type is event (since it is always going to be the last valid entry in the path)
if (this.boundType.IsSubclassOf(typeof(Delegate)))//System.MulticastDelegate ???
{
foreach (EventInfo eventInfo in typeToGetPropertiesOn.GetEvents(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy))
{
Type eventType = BindHelpers.GetMemberType(eventInfo);
if (eventType == null)
continue;
if (typeProvider != null)
{
Type designTimeEventType = typeProvider.GetType(eventType.FullName, false);
eventType = (designTimeEventType != null) ? designTimeEventType : eventType;
}
if (IsPropertyBrowsable(eventInfo) && eventType != null && TypeProvider.IsAssignable(this.boundType, eventType))
paths.Add(new PathInfo(currentPath + eventInfo.Name, eventInfo, eventType));
}
}
return paths.ToArray();
}
private string GetMemberDescription(MemberInfo member)
{
object[] descriptions = member.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (descriptions != null && descriptions.Length > 0)
{
DescriptionAttribute description = descriptions[0] as DescriptionAttribute;
return (description != null) ? description.Description : string.Empty;
}
return string.Empty;
}
//given user typed path, find all properties along it and return them in the list
private List<PathInfo> ParseStringPath(Type activityType, string path)
{
if (string.IsNullOrEmpty(path))
return null;
List<PathInfo> pathInfoList = new List<PathInfo>();
PathWalker pathWalker = new PathWalker();
PathMemberInfoEventArgs finalEventArgs = null;
PathErrorInfoEventArgs errorEventArgs = null;
pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs)
{
finalEventArgs = eventArgs; //store the latest args
pathInfoList.Add(new PathInfo(eventArgs.Path, eventArgs.MemberInfo, BindHelpers.GetMemberType(eventArgs.MemberInfo)));
};
pathWalker.PathErrorFound += delegate(object sender, PathErrorInfoEventArgs eventArgs)
{
errorEventArgs = eventArgs; //store the error args
};
pathWalker.TryWalkPropertyPath(activityType, path);
return pathInfoList;
}
private bool IsPropertyBrowsable(MemberInfo property)
{
object[] attributes = property.GetCustomAttributes(typeof(BrowsableAttribute), false);
if (attributes.Length > 0)
{
BrowsableAttribute attribute = attributes[0] as BrowsableAttribute;
if (attribute != null)
return attribute.Browsable;
else
{
AttributeInfoAttribute attributeInfoAttribute = attributes[0] as AttributeInfoAttribute;
if (attributeInfoAttribute != null)
{
ReadOnlyCollection<object> argumentValues = attributeInfoAttribute.AttributeInfo.ArgumentValues;
if (argumentValues.Count > 0)
return Convert.ToBoolean(argumentValues[0], CultureInfo.InvariantCulture);
}
}
}
return true;
}
//primitive types - we dont expand them
private static bool IsTypePrimitive(Type type)
{
return type.IsPrimitive || type.IsEnum || type == typeof(Guid) || type == typeof(IntPtr) || type == typeof(string) || type == typeof(DateTime) || type == typeof(TimeSpan);
}
//
private string GetSimpleTypeFullName(Type type)
{
if (type == null)
return string.Empty;
StringBuilder typeName = new StringBuilder(type.FullName);
Stack<Type> types = new Stack<Type>();
types.Push(type);
while (types.Count > 0)
{
type = types.Pop();
while (type.IsArray)
type = type.GetElementType();
if (type.IsGenericType && !type.IsGenericTypeDefinition)
{
foreach (Type parameterType in type.GetGenericArguments())
{
typeName.Replace("[" + parameterType.AssemblyQualifiedName + "]", GetSimpleTypeFullName(parameterType));
types.Push(parameterType);
}
}
}
return typeName.ToString();
}
#region Class ActivityBindFormWorkflowOutline
internal enum BindMemberAccessKind
{
Public = 0, Internal = 1, Protected = 2, Private = 3,
}
internal enum BindMemberKind
{
Field = 0, Property = 4, Constant = 8, Event = 12, Delegate = 16, Index = 20 //Index doesnt have any image
};
//tree node to be used in the activity bind form for activity nodes
private class ActivityBindTreeNode : WorkflowOutlineNode
{
public ActivityBindTreeNode(Activity activity)
: base(activity)
{ }
}
private class DummyActivityBindTreeNode : WorkflowOutlineNode
{
public DummyActivityBindTreeNode(Activity activity)
: base(activity)
{ }
}
//used for members of activities or their nested members
private class MemberActivityBindTreeNode : ActivityBindTreeNode
{
//all member nodes have activity property set to the closest activity in the tree
private PathInfo pathInfo = null;
private BindMemberKind kind = BindMemberKind.Property;
private BindMemberAccessKind accessKind = BindMemberAccessKind.Public;
public MemberActivityBindTreeNode(Activity activity, PathInfo pathInfo)
: base(activity)
{
this.pathInfo = pathInfo;
string memberName = MemberName(this.PathInfo.Path);
// Field Property Constant Event Delegate Index
if (this.pathInfo.MemberInfo is EventInfo)
{
this.kind = BindMemberKind.Event;
this.accessKind = BindMemberAccessKind.Public; //this.accessKind = (this.pathInfo.MemberInfo as EventInfo).Attributes
}
else if (this.pathInfo.MemberInfo is FieldInfo)
{
FieldInfo fieldInfo = this.pathInfo.MemberInfo as FieldInfo;
if ((fieldInfo.Attributes & FieldAttributes.Static) != 0 && (fieldInfo.Attributes & FieldAttributes.Literal) != 0)
{
this.kind = BindMemberKind.Constant;
}
else
{
if (TypeProvider.IsAssignable(typeof(Delegate), fieldInfo.FieldType))
this.kind = BindMemberKind.Delegate;
else
this.kind = BindMemberKind.Field;
}
this.accessKind = (fieldInfo.IsPublic) ? BindMemberAccessKind.Public : ((fieldInfo.IsFamily) ? BindMemberAccessKind.Internal : (fieldInfo.IsPrivate) ? BindMemberAccessKind.Private : BindMemberAccessKind.Protected);
}
else if (this.pathInfo.MemberInfo is PropertyInfo)
{
this.kind = BindMemberKind.Property;
PropertyInfo propertyInfo = this.pathInfo.MemberInfo as PropertyInfo;
this.accessKind = BindMemberAccessKind.Public; //
}
else if (memberName.IndexOfAny("[]".ToCharArray()) != -1)
{
this.kind = BindMemberKind.Index;
this.accessKind = BindMemberAccessKind.Public; //
}
else
{
this.kind = BindMemberKind.Property;
this.accessKind = BindMemberAccessKind.Public; //
}
}
public override void RefreshNode()
{
base.RefreshNode();
this.Text = MemberName(this.PathInfo.Path);
this.ForeColor = Color.DarkBlue;
}
public PathInfo PathInfo
{
get
{
return this.pathInfo;
}
set
{
this.pathInfo = value;
}
}
public bool MayHaveChildNodes
{
get
{
Type memberType = (this.pathInfo != null) ? this.pathInfo.PropertyType : null;
if (memberType == null)
return false;
if (IsTypePrimitive(memberType) || (TypeProvider.IsAssignable(typeof(System.Delegate), memberType)) || (memberType == typeof(object)))
return false;
return true;
}
}
public BindMemberKind MemberKind
{
get
{
return this.kind;
}
}
public BindMemberAccessKind MemberAccessKind
{
get
{
return this.accessKind;
}
}
internal static string MemberName(string path)
{
string memberName = path;
//need to show just the latest portion of the path
int index = memberName.LastIndexOf('.');
memberName = (index != -1 && (index + 1) < memberName.Length) ? memberName.Substring(index + 1) : memberName;
return memberName;
}
}
private class ActivityBindFormWorkflowOutline : WorkflowOutline
{
private ActivityBindForm parent = null;
private Activity selectedActivity = null;
private PathInfo selectedPathInfo = null;
public ActivityBindFormWorkflowOutline(IServiceProvider serviceProvider, ActivityBindForm parent)
: base(serviceProvider)
{
this.parent = parent;
base.NeedsExpandAll = false;
this.Expanding += new System.Windows.Forms.TreeViewCancelEventHandler(this.treeView1_BeforeExpand);
base.TreeView.BeforeLabelEdit += new NodeLabelEditEventHandler(TreeView_BeforeLabelEdit);
base.TreeView.AfterLabelEdit += new NodeLabelEditEventHandler(TreeView_AfterLabelEdit);
base.TreeView.LabelEdit = true;
base.TreeView.KeyDown += new KeyEventHandler(TreeView_KeyDown);
}
void TreeView_KeyDown(object sender, KeyEventArgs e)
{
//F2 -> start editing index
if (e.KeyCode == Keys.F2 && base.TreeView.SelectedNode != null)
{
base.TreeView.SelectedNode.BeginEdit();
e.Handled = true;
e.SuppressKeyPress = true;
}
}
void TreeView_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
{
//allow editing the label only if it's an array member
MemberActivityBindTreeNode memberNode = e.Node as MemberActivityBindTreeNode;
e.CancelEdit = (memberNode == null) || !memberNode.Text.Contains("[") || !memberNode.Text.Contains("]");
}
void TreeView_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
{
//
string oldLabel = e.Node.Text;
string newLabel = e.Label;
if (oldLabel == null || newLabel == null)
{
e.CancelEdit = true;
return;
}
MemberActivityBindTreeNode memberNode = e.Node as MemberActivityBindTreeNode;
bool incorrectChange = false;
//sanity check (member name has not been changed, still have opening/closing square brackets, same number of commas)
if (newLabel.IndexOf("[", StringComparison.Ordinal) == -1 || !newLabel.EndsWith("]", StringComparison.Ordinal))
{
incorrectChange = true;
}
else
{
string oldMemberName = oldLabel.Substring(0, oldLabel.IndexOf("[", StringComparison.Ordinal));
string newMemberName = newLabel.Substring(0, newLabel.IndexOf("[", StringComparison.Ordinal));
incorrectChange = !oldMemberName.Equals(newMemberName, StringComparison.Ordinal);
}
//re-parse, update pathinfo member
if (!incorrectChange)
{
ActivityBindTreeNode parentNode = memberNode.Parent as ActivityBindTreeNode;
MemberActivityBindTreeNode memberParentNode = parentNode as MemberActivityBindTreeNode;
Type memberType = (memberParentNode != null) ? memberParentNode.PathInfo.PropertyType : this.parent.GetActivityType(parentNode.Activity);
//we will try to parse just the latest member path since the previous is assumed to be valid
List<PathInfo> reparsedPathInfoList = this.parent.ParseStringPath(memberType, newLabel);
if (reparsedPathInfoList == null || reparsedPathInfoList.Count == 0)
{
incorrectChange = true;
}
else
{
PathInfo newPathInfo = reparsedPathInfoList[reparsedPathInfoList.Count - 1]; //get the last item in the list
if (newPathInfo.Path.Equals(newLabel, StringComparison.Ordinal))
memberNode.PathInfo = newPathInfo;
else
incorrectChange = true;
}
}
if (incorrectChange)
{
DesignerHelpers.ShowError(this.parent.serviceProvider, string.Format(CultureInfo.CurrentCulture, this.parent.IncorrectIndexChange, newLabel));
e.CancelEdit = true;
}
}
private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
//see if the first child is the dummy node, replace it with members...
ActivityBindTreeNode node = e.Node as ActivityBindTreeNode;
if (node != null)
{
if (node.Nodes.Count > 0 && (node.Nodes[0] is DummyActivityBindTreeNode))
{
//Poluate child members on this node...
MemberActivityBindTreeNode memberNode = node as MemberActivityBindTreeNode;
List<PathInfo> members = this.parent.PopulateAutoCompleteList(node.Activity, (memberNode != null) ? memberNode.PathInfo : null);
List<TreeNode> nodes = new List<TreeNode>();
foreach (PathInfo mamberPathInfo in members)
{
MemberActivityBindTreeNode childMemberNode = CreateMemberNode(node.Activity, mamberPathInfo);
if (childMemberNode != null)
{
RefreshNode(childMemberNode, false);
nodes.Add(childMemberNode);
}
}
base.TreeView.BeginUpdate();
try
{
node.Nodes.RemoveAt(0);
e.Node.Nodes.AddRange(nodes.ToArray());
}
finally
{
base.TreeView.EndUpdate();
}
}
}
}
public void AddMemberKindImages(ImageList memberTypes)
{
for (int i = 0; i < memberTypes.Images.Count; i++)
{
Image image = memberTypes.Images[i];
this.TreeView.ImageList.Images.Add(string.Format(CultureInfo.InvariantCulture, MemberTypeFormat, i), image); //member type key is non-localizable
}
}
protected override WorkflowOutlineNode CreateNewNode(Activity activity)
{
//can't bind to commented or locked activities...
if (!activity.Enabled || Helpers.IsActivityLocked(activity))
return null;
ActivityBindTreeNode treeNode = new ActivityBindTreeNode(activity);
treeNode.Nodes.Add(new DummyActivityBindTreeNode(activity));
return treeNode;
}
private MemberActivityBindTreeNode CreateMemberNode(Activity activity, PathInfo pathInfo)
{
MemberActivityBindTreeNode memberNode = new MemberActivityBindTreeNode(activity, pathInfo);
if (memberNode.MayHaveChildNodes)
memberNode.Nodes.Add(new DummyActivityBindTreeNode(activity));
return memberNode;
}
protected override void OnRefreshNode(WorkflowOutlineNode node)
{
Activity activity = node.Activity;
if (activity == null)
return;
MemberActivityBindTreeNode memberNode = node as MemberActivityBindTreeNode;
if (memberNode != null)
{
node.RefreshNode();
int imageNumber = (int)memberNode.MemberKind + (int)memberNode.MemberAccessKind;
node.ImageIndex = node.SelectedImageIndex = this.TreeView.ImageList.Images.IndexOfKey(string.Format(CultureInfo.InvariantCulture, MemberTypeFormat, imageNumber)); //it's non-localizable
}
else
{
ActivityBindTreeNode activityNode = node as ActivityBindTreeNode;
//if (this.activityNodeFont == null)
// this.activityNodeFont = new Font(Font, FontStyle.Bold);
//activityNode.NodeFont = this.activityNodeFont;
base.OnRefreshNode(node);
}
}
protected override void OnNodeSelected(WorkflowOutlineNode node)
{
//notify the form that activity selection has been changed
this.selectedActivity = (node != null) ? node.Activity : null;
MemberActivityBindTreeNode memberNode = node as MemberActivityBindTreeNode;
this.selectedPathInfo = (memberNode != null) ? memberNode.PathInfo : null;
string path = PropertyPath;
this.parent.SelectedActivityChanged(this.selectedActivity, this.selectedPathInfo, path);
}
public void SelectActivity(Activity activity, List<PathInfo> pathInfoList)
{
WorkflowOutlineNode node = base.GetNode(activity);
//now walk all activity children along the property path list
if (node != null)
{
//expand the node to make sure dummy node is populated with the members
node.Expand();
if (pathInfoList != null && pathInfoList.Count > 0)
{
for (int i = 0; i < pathInfoList.Count; i++)
{
//there are several options here - it could be a regular property, an array or an indexer property
//we'd do couple attempts at trying to find the right node (taking into account the fact that indexes could be changed by the user)
PathInfo currentPathInfo = pathInfoList[i];
MemberActivityBindTreeNode matchingChildNode = null;
//this is an indexer, there could have been a non-indexer before
int indexOfOpenBracket = currentPathInfo.Path.IndexOf('[');
if (indexOfOpenBracket != -1)
{
string indexPropertyName = currentPathInfo.Path.Substring(0, indexOfOpenBracket);
if (node.Text.Equals(indexPropertyName, StringComparison.Ordinal))
{
//need to get back to the parent and select a different child with an index
//see if we need to get the properties on the parent node again...
if (i > 0 && pathInfoList[i - 1].Path.Equals(indexPropertyName, StringComparison.Ordinal))
node = node.Parent as WorkflowOutlineNode;
}
}
//find a child node with the same PathInfo member as the given one
foreach (TreeNode childNode in node.Nodes)
{
MemberActivityBindTreeNode memberTreeNode = childNode as MemberActivityBindTreeNode;
if (memberTreeNode != null && memberTreeNode.PathInfo.Equals(currentPathInfo))
{
matchingChildNode = memberTreeNode;
break;
}
//actual indexes may be different from the default ones...
//if it's a indexer property (this[]), indexes might mismatch
if (memberTreeNode != null && memberTreeNode.Text.Contains("[") && currentPathInfo.Path.Contains("["))
{
//need to compare parameter type index collections...
string currentPropertyName = GetMemberNameFromIndexerName(currentPathInfo.Path);
string treeNodePropertyName = GetMemberNameFromIndexerName(memberTreeNode.Text);
if (string.Equals(currentPropertyName, treeNodePropertyName, StringComparison.Ordinal) && IsSamePropertyIndexer(currentPathInfo.MemberInfo, memberTreeNode.PathInfo.MemberInfo))
{
matchingChildNode = memberTreeNode;
memberTreeNode.PathInfo = currentPathInfo;
memberTreeNode.Text = MemberActivityBindTreeNode.MemberName(currentPathInfo.Path);
break;
}
}
}
//havent found matching tree node in the list of children - will exit
if (matchingChildNode == null)
break;
node = matchingChildNode;
node.Expand();
}
}
base.TreeView.SelectedNode = node;
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = 50;
timer.Start();
}
}
private string GetMemberNameFromIndexerName(string fullName)
{
int indexOfSeparator = fullName.IndexOf('[');
if (indexOfSeparator != -1)
fullName = fullName.Substring(0, indexOfSeparator);
return fullName;
}
private void timer_Tick(object sender, EventArgs e)
{
System.Windows.Forms.Timer timer = sender as System.Windows.Forms.Timer;
if (timer != null)
{
timer.Stop();
timer.Tick -= new EventHandler(timer_Tick);
}
if (base.TreeView.SelectedNode != null)
base.TreeView.SelectedNode.EnsureVisible();
this.Focus();
}
private bool IsSamePropertyIndexer(MemberInfo member1, MemberInfo member2)
{
if (member1 == null || member2 == null)
return false;
PropertyInfo property1 = member1 as PropertyInfo;
PropertyInfo property2 = member2 as PropertyInfo;
MethodInfo methodInfo1 = member1 as MethodInfo;
MethodInfo methodInfo2 = member2 as MethodInfo;
ParameterInfo[] parameters1 = (property1 != null) ? property1.GetIndexParameters() : (methodInfo1 != null) ? methodInfo1.GetParameters() : null;
ParameterInfo[] parameters2 = (property2 != null) ? property2.GetIndexParameters() : (methodInfo2 != null) ? methodInfo2.GetParameters() : null;
if (parameters1 == null || parameters1.Length == 0 || parameters2 == null || parameters2.Length == 0 || parameters1.Length != parameters2.Length)
return false;
for (int i = 0; i < parameters1.Length; i++)
{
if (parameters1[i].ParameterType != parameters2[i].ParameterType)
return false;
}
return true;
}
public void ExpandRootNode()
{
TreeNode node = base.RootNode;
if (node != null)
{
node.Collapse();
node.Expand();
}
}
public Activity SelectedActivity
{
get
{
return this.selectedActivity;
}
}
public PathInfo SelectedMember
{
get
{
return this.selectedPathInfo;
}
}
public string PropertyPath
{
get
{
MemberActivityBindTreeNode memberNode = base.TreeView.SelectedNode as MemberActivityBindTreeNode;
string path = string.Empty;
while (memberNode != null)
{
path = (path.Length == 0) ? memberNode.Text : memberNode.Text + "." + path;
memberNode = memberNode.Parent as MemberActivityBindTreeNode;
}
return path;
}
}
}
#endregion
#region Class PathInfo
private class PathInfo
{
private string path;
private MemberInfo memberInfo;
private Type propertyType;
public PathInfo(string path, MemberInfo memberInfo, Type propertyType)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException("path");
if (propertyType == null)
throw new ArgumentNullException("propertyType");
if (memberInfo == null)
throw new ArgumentNullException("memberInfo");
this.path = path;
this.propertyType = propertyType;
this.memberInfo = memberInfo;
}
public string Path
{
get { return this.path; }
}
public MemberInfo MemberInfo
{
get { return this.memberInfo; }
}
public Type PropertyType
{
get { return this.propertyType; }
}
//show bind's path information only (no activity id here)
public override string ToString()
{
return this.path;
}
public override bool Equals(object obj)
{
PathInfo otherInfo = obj as PathInfo;
if (otherInfo == null)
return false;
return this.path.Equals(otherInfo.path, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return this.path.GetHashCode();
}
}
#endregion
}
}
|