File: System.Activities.Presentation\System\Activities\Presentation\DesignObjectWrapper.cs
Project: ndp\cdf\src\NetFx40\Tools\System.Activities.Presentation.csproj (System.Activities.Presentation)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Activities.Presentation
{
    using System;
    using System.Activities.Presentation.Model;
    using System.Activities.Presentation.Converters;
    using System.Activities.Presentation.PropertyEditing;
    using System.Activities.Presentation.View;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Runtime;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Threading;
 
    /// <summary>
    /// DesignObjectWrapper. this class is used to enable more detailed control over edting model objects. especially, if underlying object 
    /// requires some more complex logic when setting property values - i.e. value of a real property is splitted in the ui to different design properties
    /// (like in ArgumentDesigner - actual argument's property depends on two factors: direction (in, out, ...) and actual CLR type.
    /// the DesignObjectWrapper contains that logic and is able to interact with underlying real object, but from ui perspective offeres different set of properties.
    /// 
    /// the model can be presented as follows:
    /// 
    ///      UI                |           interaction logic             |           actual model
    /// -----------------------+-----------------------------------------+-------------------------------
    ///                         
    ///    FakeModelItem  <---------------- DesignObjectWrapper ---------------------> ModelItem
    ///                                             ^
    ///                                             |
    ///                            DesignObjectWrapper implementation
    ///                            
    ///  Where:
    ///  - FakeModelItem - is a class which exposes any properties which are required to edit actual model. those properties do not have
    ///                    to exist on the real object, you are responsible to provide getters (required),  setters (optional) and validation (optional)
    ///                    code for them. In UI, you can access that property using Content property.
    ///                    
    /// - DesignObjectWrapper - implementing that class you have to provide a set of property descriptors (get, set, validate, name, type) methods for each of your property
    ///                    It is required that you provide static implementation for following method:
    ///                         PropertyDescriptorData[] InitializeTypeProperties()
    ///                    After you are done with editing of this object, call Dispose, so it unhooks from property change notificatons
    ///                         
    /// - ModelItem      - actual model you bind to. DesignObjectWrapper implmentation registers for PropertyChanged notifications from that object, and will notify you via
    ///                 OnReflectedObjectPropertyChanged. This object can be accessed using ReflectedObject property
    /// 
    /// </summary>
    abstract class DesignObjectWrapper : ICustomTypeDescriptor, INotifyPropertyChanged, IDisposable
    {
        protected static readonly string HasErrorsProperty = "HasErrors";
        protected static readonly string ContentProperty = "Content";
        protected static readonly string ValidationErrorSuffix = "ValidationError";
        protected static readonly string AutomationIdProperty = "AutomationId";
        protected internal static readonly string TimestampProperty = "Timestamp";
        readonly static string[] DefaultProperties = new string[] { HasErrorsProperty, AutomationIdProperty, TimestampProperty };
        static IDictionary<Type, PropertyDescriptorCollection> TypePropertyCollection = new Dictionary<Type, PropertyDescriptorCollection>();
 
        IDictionary<string, string> validationErrors = null;
        IDictionary<string, PropertyValueEditor> customValueEditors = null;
        FakeModelItemImpl content;
        bool isDisposed = false;
        DateTime timestamp;
        HashSet<string> changingProperties;
 
        protected DesignObjectWrapper()
        {
            throw FxTrace.Exception.AsError(new NotSupportedException(SR.InvalidConstructorCall));
        }
 
        [SuppressMessage(FxCop.Category.Usage, FxCop.Rule.DoNotCallOverridableMethodsInConstructors,
            Justification = "This class is internal with limited usage inside framework assemblies only. The code written should be safe enough to allow such usage.")]
        protected DesignObjectWrapper(ModelItem reflectedObject)
        {
            this.changingProperties = new HashSet<string>();
            this.Initialize(reflectedObject);
        }
 
        internal void Initialize(ModelItem reflectedObject)
        {
            this.isDisposed = false;
            this.changingProperties.Clear();
            this.ReflectedObject = reflectedObject;
            this.Context = ((IModelTreeItem)reflectedObject).ModelTreeManager.Context;
            this.ModelTreeManager = ((IModelTreeItem)reflectedObject).ModelTreeManager;
            this.ReflectedObject.PropertyChanged += OnReflectedObjectPropertyChanged;
            this.RaisePropertyChangedEvent("ReflectedObject");
            //update timestamp if we do reinitialize wrapper
            this.UpdateTimestamp();
            this.Content.PropertyChanged += this.OnFakeModelPropertyChanged;
        }
 
        void OnFakeModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!this.changingProperties.Contains(e.PropertyName))
            {
                this.changingProperties.Add(e.PropertyName);
                this.RaisePropertyChangedEvent(e.PropertyName);
                this.changingProperties.Remove(e.PropertyName);
            }
        }
 
        public ModelItem ReflectedObject
        {
            get;
            private set;
        }
 
        public EditingContext Context
        {
            get;
            private set;
        }
 
        protected ModelTreeManager ModelTreeManager
        {
            get;
            private set;
        }
 
        public ModelItem Content
        {
            get
            {
                if (null == this.content)
                {
                    ModelTreeManager manager = this.Context.Services.GetService<ModelTreeManager>();
                    this.content = new FakeModelItemImpl(manager, this.GetType(), this, null);
                }
                return this.content;
            }
            private set
            {
                this.content = (FakeModelItemImpl)value;
            }
        }
 
        IDictionary<string, string> ValidationErrors
        {
            get
            {
                if (null == this.validationErrors)
                {
                    this.validationErrors = new Dictionary<string, string>();
                }
                return this.validationErrors;
            }
        }
 
        protected IDictionary<string, PropertyValueEditor> CustomValueEditors
        {
            get
            {
                if (null == this.customValueEditors)
                {
                    this.customValueEditors = new Dictionary<string, PropertyValueEditor>();
                }
                return this.customValueEditors;
            }
        }
 
        public bool HasErrors
        {
            get
            {
                return null != this.validationErrors && this.validationErrors.Count != 0;
            }
        }
 
        protected abstract string AutomationId { get; }
 
        #region ICustomTypeDescriptor Members
 
        public AttributeCollection GetAttributes()
        {
            return new AttributeCollection(this.GetType().GetCustomAttributes(false).OfType<Attribute>().ToArray());
        }
 
        public string GetClassName()
        {
            return this.GetType().FullName;
        }
 
        public string GetComponentName()
        {
            return this.GetType().FullName;
        }
 
        public TypeConverter GetConverter()
        {
            object[] attributes = this.GetType().GetCustomAttributes(typeof(TypeConverterAttribute), false);
            if (attributes.Length != 0)
            {
                TypeConverterAttribute attribute = (TypeConverterAttribute)attributes[0];
                return (TypeConverter)Activator.CreateInstance(Type.GetType(attribute.ConverterTypeName));
            }
            return null;
        }
 
        public EventDescriptor GetDefaultEvent()
        {
            return null;
        }
 
        public PropertyDescriptor GetDefaultProperty()
        {
            return null;
        }
 
        public string GetValidationErrors(IList<string> invalidProperties)
        {
            var result = string.Empty;
            if (this.HasErrors)
            {
                var content = new StringBuilder();
                bool newRowRequired = false;
                foreach (var entry in this.validationErrors)
                {
                    if (newRowRequired)
                    {
                        content.AppendLine();
                    }
                    content.Append(entry.Key);
                    content.AppendLine(":");
                    content.Append(entry.Value);
                    newRowRequired = true;
                    if (null != invalidProperties)
                    {
                        invalidProperties.Add(entry.Key);
                    }
                }
                result = content.ToString();
            }
            return result;
        }
 
        public string GetValidationErrors()
        {
            return this.GetValidationErrors(null);
        }
 
        public void ClearValidationErrors()
        {
            this.ClearValidationErrors(null);
        }
 
        public void ClearValidationErrors(IEnumerable<string> properties)
        {
            if (null != this.validationErrors)
            {
                if (null != properties)
                {
                    foreach (var propertyName in properties)
                    {
                        if (this.validationErrors.ContainsKey(propertyName))
                        {
                            this.validationErrors.Remove(propertyName);
                        }
                    }
                }
                else
                {
                    this.validationErrors.Clear();
                }
            }
        }
 
        public object GetEditor(Type editorBaseType)
        {
            object[] attributes = this.GetType().GetCustomAttributes(typeof(EditorAttribute), false);
            if (attributes.Length != 0)
            {
                EditorAttribute attribute = (EditorAttribute)attributes[0];
                return Activator.CreateInstance(Type.GetType(attribute.EditorTypeName));
            }
            return null;
        }
 
        public EventDescriptorCollection GetEvents(Attribute[] attributes)
        {
            return null;
        }
 
        public EventDescriptorCollection GetEvents()
        {
            return null;
        }
 
        public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            return ((ICustomTypeDescriptor)this).GetProperties();
        }
 
        public PropertyDescriptorCollection GetProperties()
        {
            Type type = this.GetType();
            if (!DesignObjectWrapper.TypePropertyCollection.ContainsKey(type))
            {
                MethodInfo initMethod = type.GetMethod("InitializeTypeProperties", BindingFlags.Static | BindingFlags.Public);
                PropertyDescriptorData[] properties = (PropertyDescriptorData[])initMethod.Invoke(null, null);
                List<DesignObjectPropertyDescriptor> descriptors = new List<DesignObjectPropertyDescriptor>(properties.Length);
                for (int i = 0; i < properties.Length; ++i)
                {
                    properties[i].OwnerType = type;
                    DesignObjectPropertyDescriptor descriptor = new DesignObjectPropertyDescriptor(properties[i]);
                    if (null != properties[i].PropertyValidator)
                    {
                        string localPropertyName = properties[i].PropertyName;
                        PropertyDescriptorData data = new PropertyDescriptorData()
                        {
                            OwnerType = type,
                            PropertyAttributes = new Attribute[] { BrowsableAttribute.No },
                            PropertyValidator = null,
                            PropertySetter = null,
                            PropertyType = typeof(string),
                            PropertyName = string.Format(CultureInfo.InvariantCulture, "{0}{1}", localPropertyName, ValidationErrorSuffix),
                            PropertyGetter = (instance) => (!instance.IsPropertyValid(localPropertyName) ? instance.validationErrors[localPropertyName] : string.Empty),
                        };
                        descriptors.Add(new DesignObjectPropertyDescriptor(data));
                    }
                    descriptors.Add(descriptor);
                }
                for (int i = 0; i < DesignObjectWrapper.DefaultProperties.Length; ++i)
                {
                    descriptors.Add(this.ConstructDefaultPropertyPropertyDescriptor(DesignObjectWrapper.DefaultProperties[i]));
                }
                DesignObjectWrapper.TypePropertyCollection[type] = new PropertyDescriptorCollection(descriptors.ToArray(), true);
            }
 
            return DesignObjectWrapper.TypePropertyCollection[type];
        }
 
        public object GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }
 
        #endregion
 
        #region INotifyPropertyChanged Members
 
        public event PropertyChangedEventHandler PropertyChanged;
 
        #endregion
 
        public bool IsPropertyValid(string propertyName)
        {
            return this.validationErrors == null || !this.validationErrors.ContainsKey(propertyName);
        }
 
        public virtual void Dispose()
        {
            if (null != this.ReflectedObject && !this.isDisposed)
            {
                this.isDisposed = true;
                this.ReflectedObject.PropertyChanged -= this.OnReflectedObjectPropertyChanged;
                this.Content.PropertyChanged -= this.OnFakeModelPropertyChanged;
                if (null != this.customValueEditors)
                {
                    this.customValueEditors.Clear();
                }
                this.RaisePropertyChangedEvent("ReflectedObject");
            }
        }
 
        //GetDynamicPropertyValueEditor - if user marks one of the properties with DesignObjectWrapperDynamicPropertyEditor attribte,
        //it is expected that valid property value editor for each instance of design object wrapper will be provided
        internal PropertyValueEditor GetDynamicPropertyValueEditor(string propertyName)
        {
            PropertyValueEditor result = null;
            //look in the value editor cache - perhaps there is one available for given object
            if (this.CustomValueEditors.ContainsKey(propertyName))
            {
                result = this.CustomValueEditors[propertyName];
            }
            else
            {
                //no, get type of the editor
                Type editorType = this.GetDynamicPropertyValueEditorType(propertyName);
                if (null == editorType)
                {
                    throw FxTrace.Exception.AsError(new ArgumentException("GetDynamicPropertyValueEditorType() returned null for propertyName."));
                }
                //create one
                result = (PropertyValueEditor)Activator.CreateInstance(editorType);
                //store it in cache
                this.CustomValueEditors[propertyName] = result;
            }
            return result;
        }
 
        internal Type GetDynamicPropertyValueEditorType(string propertyName)
        {
            //get editor type for dynamic property
            var editorType = this.OnGetDynamicPropertyValueEditorType(propertyName);
            if (null == editorType)
            {
                //there should be always be one...
                Fx.Assert(false, "PropertyValueEditor not defined for property '" + propertyName + "'");
            }
            //and it should be assignable from PropertyValueEditor
            else if (!typeof(PropertyValueEditor).IsAssignableFrom(editorType))
            {
                Fx.Assert(false, "Type '" + editorType.FullName + "' is not assignable from PropertyValueEditor");
                editorType = null;
            }
            return editorType;
        }
 
        //virtual OnGetDynamicProperyValueEditorType - if user marks property with DesignObjectWrapperDynamicPropertyEditor, 
        //this method has to be overriden
        protected virtual Type OnGetDynamicPropertyValueEditorType(string propertyName)
        {
            throw FxTrace.Exception.AsError(new NotImplementedException());
        }
 
        //bool GetPropertyChangeTriggerState(string propertyName)
        //{
        //    if (this.propertyChangeTriggerState.ContainsKey(propertyName))
        //    {
        //        return this.propertyChangeTriggerState[propertyName];
        //    }
        //    else
        //    {
        //        return false;
        //    }
        //}
 
        //void SetPropertyChangeTriggerState(string propertyName, bool state)
        //{
        //    if (!this.propertyChangeTriggerState.ContainsKey(propertyName))
        //    {
        //        this.propertyChangeTriggerState.Add(propertyName, state);
        //    }
        //    else
        //    {
        //        this.propertyChangeTriggerState[propertyName] = state;
        //    }
        //}
 
        internal void NotifyPropertyChanged(string propertyName)
        {
            if (!this.isDisposed)
            {
                (this.Content as IModelTreeItem).OnPropertyChanged(propertyName);
            }
        }
 
        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.UseEventsWhereAppropriate, Justification = "Procedure to raise the event")]
        protected void RaisePropertyChangedEvent(string propertyName)
        {
            //don't raise property changed events if object is disposed 
            //- the underlying ModelItem might not be valid, doesn't make sense to do anything on it.
            if (!this.isDisposed)
            {
                //let the implementation react on property change
                this.OnPropertyChanged(propertyName);
 
                if (null != this.PropertyChanged)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
                if (!this.changingProperties.Contains(propertyName))
                {
                    if (this.Content.Properties[propertyName] != null)
                    {
                        this.changingProperties.Add(propertyName);
                        (this.Content as IModelTreeItem).OnPropertyChanged(propertyName);
                        this.changingProperties.Remove(propertyName);
                    }
                }
            }
        }
 
        protected virtual void OnPropertyChanged(string propertyName)
        {
        }
 
        DesignObjectPropertyDescriptor ConstructDefaultPropertyPropertyDescriptor(string propertyName)
        {
            DesignObjectPropertyDescriptor result = null;
            if (string.Equals(propertyName, HasErrorsProperty))
            {
                PropertyDescriptorData data = new PropertyDescriptorData()
                {
                    OwnerType = this.GetType(),
                    PropertyName = propertyName,
                    PropertyAttributes = new Attribute[] { BrowsableAttribute.No },
                    PropertySetter = null,
                    PropertyType = typeof(bool),
                    PropertyValidator = null,
                    PropertyGetter = (instance) => (instance.HasErrors)
                };
                result = new DesignObjectPropertyDescriptor(data);
            }
            else if (string.Equals(propertyName, ContentProperty))
            {
                PropertyDescriptorData data = new PropertyDescriptorData()
                {
                    OwnerType = this.GetType(),
                    PropertyName = propertyName,
                    PropertyAttributes = new Attribute[] { BrowsableAttribute.No },
                    PropertySetter = (instance, value) => { instance.Content = (ModelItem)value; },
                    PropertyType = typeof(ModelItem),
                    PropertyValidator = null,
                    PropertyGetter = (instance) => (instance.content)
                };
                result = new DesignObjectPropertyDescriptor(data);
            }
            else if (string.Equals(propertyName, AutomationIdProperty))
            {
                PropertyDescriptorData data = new PropertyDescriptorData()
                {
                    OwnerType = this.GetType(),
                    PropertyName = propertyName,
                    PropertyAttributes = new Attribute[] { BrowsableAttribute.No },
                    PropertySetter = null,
                    PropertyType = typeof(string),
                    PropertyValidator = null,
                    PropertyGetter = (instance) => (instance.AutomationId)
                };
                result = new DesignObjectPropertyDescriptor(data);
            }
            else if (string.Equals(propertyName, TimestampProperty))
            {
                PropertyDescriptorData data = new PropertyDescriptorData()
                {
                    OwnerType = this.GetType(),
                    PropertyName = propertyName,
                    PropertyType = typeof(DateTime),
                    PropertyValidator = null,
                    PropertyAttributes = new Attribute[] { BrowsableAttribute.No },
                    PropertySetter = (instance, value) => { instance.UpdateTimestamp(); },
                    PropertyGetter = (instance) => (instance.GetTimestamp())
                };
                result = new DesignObjectPropertyDescriptor(data);
            }
            return result;
        }
 
        protected bool IsUndoRedoInProgress
        {
            get
            {
                return null != this.Context && this.Context.Services.GetService<UndoEngine>().IsUndoRedoInProgress;
            }
        }
 
        void UpdateTimestamp()
        {
            this.timestamp = DateTime.Now;
            this.RaisePropertyChangedEvent(TimestampProperty);
        }
 
        protected DateTime GetTimestamp()
        {
            return this.timestamp;
        }
 
        void OnReflectedObjectPropertyChanged(object s, PropertyChangedEventArgs e)
        {
            if (this.IsUndoRedoInProgress)
            {
                this.OnReflectedObjectPropertyChanged(e.PropertyName);
                Type type = this.GetType();
                if (DesignObjectWrapper.TypePropertyCollection.ContainsKey(type))
                {
                    PropertyDescriptorCollection properties = DesignObjectWrapper.TypePropertyCollection[type];
                    for (int i = 0; i < properties.Count; ++i)
                    {
                        if (string.Equals(properties[i].Name, e.PropertyName))
                        {
                            this.RaisePropertyChangedEvent(e.PropertyName);
                            break;
                        }
                    }
                }
            }
 
            //whenever data within reflected object changes, we do update timestamp property on wrapped object - 
            //this allows to create triggers and bindings which do need to be reevaluated whenever overal state of the object changes
            this.UpdateTimestamp();
        }
 
        protected virtual void OnReflectedObjectPropertyChanged(string propertyName)
        {
        }
 
        sealed class DesignObjectPropertyDescriptor : PropertyDescriptor
        {
            PropertyDescriptorData descriptorData;
            string validationErrorPropertyName;
 
            public DesignObjectPropertyDescriptor(PropertyDescriptorData descriptorData)
                : base(descriptorData.PropertyName, descriptorData.PropertyAttributes)
            {
                this.descriptorData = descriptorData;
                this.validationErrorPropertyName = (null != this.descriptorData.PropertyValidator ?
                    string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.descriptorData.PropertyName, DesignObjectWrapper.ValidationErrorSuffix) :
                    null);
            }
 
            public override bool CanResetValue(object component)
            {
                return null != this.descriptorData.PropertySetter;
            }
 
            public override Type ComponentType
            {
                get { return this.descriptorData.OwnerType; }
            }
 
            public override object GetValue(object component)
            {
                DesignObjectWrapper instance = (DesignObjectWrapper)component;
                return !instance.isDisposed ? this.descriptorData.PropertyGetter(instance) : null;
            }
 
            public override bool IsReadOnly
            {
                get { return null == this.descriptorData.PropertySetter; }
            }
 
            public override Type PropertyType
            {
                get { return this.descriptorData.PropertyType; }
            }
 
            public override void ResetValue(object component)
            {
                DesignObjectWrapper instance = (DesignObjectWrapper)component;
                this.descriptorData.PropertySetter(instance, null);
            }
 
            [SuppressMessage("Reliability", "Reliability108",
                Justification = "Exception not eaten away. If its a fatal exception we rethrow, else we wrap in another exception and throw.")]
            public override void SetValue(object component, object value)
            {
                DesignObjectWrapper instance = (DesignObjectWrapper)component;
                if (!instance.IsUndoRedoInProgress)
                {
                    if (null != this.descriptorData.PropertyValidator)
                    {
                        string error = null;
                        ValidationException exception = null;
                        try
                        {
                            List<string> errors = new List<string>();
                            if (!this.descriptorData.PropertyValidator(instance, value, errors))
                            {
                                StringBuilder sb = new StringBuilder();
                                errors.ForEach((errMessage) =>
                                    {
                                        sb.AppendLine(errMessage);
                                    });
                                error = sb.ToString();
                                exception = new ValidationException(error);
                            }
                        }
                        catch (Exception err)
                        {
                            if (Fx.IsFatal(err))
                            {
                                throw FxTrace.Exception.AsError(err);
                            }
                            else
                            {
                                exception = new ValidationException(err.Message, err);
                            }
                        }
 
                        if (null != exception)
                        {
                            instance.ValidationErrors[this.Name] = exception.Message;
                            instance.RaisePropertyChangedEvent(this.validationErrorPropertyName);
                            instance.RaisePropertyChangedEvent(DesignObjectWrapper.HasErrorsProperty);
                            throw FxTrace.Exception.AsError(exception);
                        }
                        else if (null != instance.validationErrors && instance.validationErrors.ContainsKey(this.Name))
                        {
                            instance.validationErrors.Remove(this.Name);
                            if (0 == instance.validationErrors.Count)
                            {
                                instance.validationErrors = null;
                            }
                            instance.RaisePropertyChangedEvent(this.validationErrorPropertyName);
                            instance.RaisePropertyChangedEvent(DesignObjectWrapper.HasErrorsProperty);
                        }
                    }
 
                    this.descriptorData.PropertySetter(instance, value);
 
                    (instance.Content as IModelTreeItem).ModelTreeManager.AddToCurrentEditingScope(new FakeModelNotifyPropertyChange(instance.Content as IModelTreeItem, this.Name));
                }
            }
 
            public override bool ShouldSerializeValue(object component)
            {
                return false;
            }
        }
    }
 
    sealed class PropertyDescriptorData
    {
        public Type OwnerType { get; set; }
        public string PropertyName { get; set; }
        public Type PropertyType { get; set; }
        public Func<DesignObjectWrapper, object> PropertyGetter { get; set; }
        public Action<DesignObjectWrapper, object> PropertySetter { get; set; }
        public Func<DesignObjectWrapper, object, List<string>, bool> PropertyValidator { get; set; }
        [SuppressMessage(FxCop.Category.Performance, "CA1819:PropertiesShouldNotReturnArrays",
            Justification = "Array type property does not clone the array in the getter. It references the same array instance.")]
        public Attribute[] PropertyAttributes { get; set; }
 
    }
 
    //DesignObjectWrapperDynamicPropertyEditor - this class is used to allow defining different value editors for given set of DesignObjectWrappers.
    //i.e. for generic Variable<T>, user may want to provide different editors for variable's value, depending on generic type placeholder -
    // Variable<string> - would use default editor, but Variable<CustomType> can provide different editing expirience
    sealed class DesignObjectWrapperDynamicPropertyEditor : DialogPropertyValueEditor
    {
        static DataTemplate dynamicExpressionTemplate;
 
        //DynamicExpressionTemplate - this template defines a content presenter, which will be filled with default or custom type editor
        static DataTemplate DynamicExpressionTemplate
        {
            get
            {
                if (null == dynamicExpressionTemplate)
                {
                    dynamicExpressionTemplate = new DataTemplate();
                    var contentPresenterFactory = new FrameworkElementFactory(typeof(ContentPresenter));
                    contentPresenterFactory.SetBinding(ContentPresenter.ContentProperty, new Binding());
                    contentPresenterFactory.SetBinding(ContentPresenter.TagProperty, new Binding() { Converter = ModelPropertyEntryToOwnerActivityConverter, ConverterParameter = false, Path = new PropertyPath("ParentProperty") });
                    MultiBinding binding = new MultiBinding() { Converter = new TemplateConverter() };
                    binding.Bindings.Add(new Binding());
                    binding.Bindings.Add(new Binding() { Path = new PropertyPath("Tag.Timestamp"), Mode = BindingMode.OneWay, RelativeSource = RelativeSource.Self });
                    contentPresenterFactory.SetBinding(ContentPresenter.ContentTemplateProperty, binding);
                    dynamicExpressionTemplate.VisualTree = contentPresenterFactory;
                    dynamicExpressionTemplate.Seal();
                }
                //dynamicExpressionTemplate = (DataTemplate)EditorResources.GetResources()["dynamicExpressionTemplate"];
                return dynamicExpressionTemplate;
            }
        }
 
        static IValueConverter ModelPropertyEntryToModelItemConverter
        {
            get
            {
                return (ModelPropertyEntryToModelItemConverter)EditorResources.GetResources()["ModelPropertyEntryToContainerConverter"];
            }
        }
 
        static IValueConverter ModelPropertyEntryToOwnerActivityConverter
        {
            get
            {
                return (ModelPropertyEntryToOwnerActivityConverter)EditorResources.GetResources()["ModelPropertyEntryToOwnerActivityConverter"];
            }
        }
 
        //helper method - gets property value editor for property marked with DesignObjectWrapperDynamicPropertyEditor 
        static PropertyValueEditor GetEditor(PropertyValue propertyValue)
        {
            //convert property value to set of { ModelItem, Context, PropertyValue } 
            var content = (ModelPropertyEntryToModelItemConverter.Container)
                DesignObjectWrapperDynamicPropertyEditor.ModelPropertyEntryToModelItemConverter.Convert(propertyValue, null, null, null);
 
            //get current instance of design object wrapper
            var wrapper = (DesignObjectWrapper)content.ModelItem.GetCurrentValue();
 
            //query it for actual value editor
            var editor = wrapper.GetDynamicPropertyValueEditor(propertyValue.ParentProperty.PropertyName);
 
            if (null == editor)
            {
                Fx.Assert(false, "PropertyValue editor not found for '" + propertyValue.ParentProperty.PropertyName + "'");
            }
            return editor;
        }
 
        public DesignObjectWrapperDynamicPropertyEditor()
        {
            this.InlineEditorTemplate = DesignObjectWrapperDynamicPropertyEditor.DynamicExpressionTemplate;
        }
 
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "Propagating exceptions might lead to VS crash.")]
        [SuppressMessage("Reliability", "Reliability108:IsFatalRule",
            Justification = "Propagating exceptions might lead to VS crash.")]
        public override void ShowDialog(PropertyValue propertyValue, IInputElement commandSource)
        {
            //get actual value editor
            var editor = DesignObjectWrapperDynamicPropertyEditor.GetEditor(propertyValue);
 
            Fx.Assert(editor is DialogPropertyValueEditor, "PropertyValueEditor is not assigned or is not derived from DialogPropertyValueEditor");
 
            //if we are here, the editor must derive from DialogPropertyEditor, if it doesn't user provided wrong template
            if (editor is DialogPropertyValueEditor)
            {
                try
                {
                    ((DialogPropertyValueEditor)editor).ShowDialog(propertyValue, commandSource);
                }
                catch (Exception err)
                {
                    ErrorReporting.ShowErrorMessage(err.ToString());
                }
            }
        }
 
        //helper class - allows pulling template definition for dynamic property value
        private sealed class TemplateConverter : IMultiValueConverter
        {
            public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
            {
                object template = Binding.DoNothing;
                if (null != values[0])
                {
                    var editor = DesignObjectWrapperDynamicPropertyEditor.GetEditor((PropertyValue)values[0]);
                    if (null != editor)
                    {
                        template = editor.InlineEditorTemplate;
                    }
                }
                return template;
            }
 
            public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture)
            {
                throw FxTrace.Exception.AsError(new NotSupportedException());
            }
        }
    }
 
}