none
WF4.0 Custom Activity - Property Editor or TypeConverter

    Question

  • Hi

    As part of my custom activity development, I have a requirement to change the value of one property acording to another property's value.

    For example, I have two properties- Position and Qualification in my custom activity. Once I select a position, the qualification should change according to the selected position value. Both, Position and Qualification  are string properties and the value of Position is populated with a TypeConverter. 

    I tried with TypeConverter for Qualification property too. Issue is how to refresh the Qualification values on change of Position property?

    I tried with PropertyEditor also. But not sure how to pass the data from my ActivityDesigner to PropertyEditor. I am handling the ModelItem.PropertyChanged event and getting the Qualification values. How to pass this values either to my TypeConverter or to My PropertyEditor?

    PropertyEditor Code is as follows

      public class DropDownEditor : PropertyValueEditor
      {    
        public DropDownEditor()
        {
          FrameworkElementFactory comboBox = new FrameworkElementFactory(typeof(ComboBox));
          
          Binding binding = new Binding();      
          binding.Path = new PropertyPath("Value");
          binding.Mode = BindingMode.TwoWay;      
          comboBox.SetBinding(ComboBox.ItemsSourceProperty, binding);
          
          DataTemplate dt = new DataTemplate();
          dt.VisualTree = comboBox;
    
          InlineEditorTemplate = dt;
    
        }
    }
    

     

    Thanks in Advance

    Ambily

     

     

     


    Assistant Consultant
    • Changed type Ambily Monday, October 04, 2010 4:52 AM
    Monday, October 04, 2010 4:49 AM

Answers

  • Hi Zafar

    We received one suggestion from Mr.Morgan Skinner and implemented the same. Now, we are keeping both the properties on the activity designer instead of PropertyGrid.

    Adding the suggestion from Mr. Morgan Skinner for furthur reference. We implemented it till the IMetadata interface. We didn't used the MetaDataService in our implementation.

    =================================================================================================== 

    You would typically do this inside a custom designer for your activity, and use standard WPF bindings & property change notifications to update the second list (state) when an item in the first list (country) is selected.

     

    The Entity Name and Entity Field combo boxes are related – a change to the Entity Name refreshes the list of values in the Entity Field combo.

     

    My activity exposes two lists (Entities & Fields) and two discrete values (EntityName and FieldName). The Entity Name combo is bound to the Entities list (which is just a list of strings), and the Entity Field combo is bound to the Fields list which again is just a list of strings. The pertinent parts of the XAML for the designer are as follows…

     

      <TextBlock Grid.Row="0" Text="Entity Name" VerticalAlignment="Center" />

      <ComboBox ItemsSource="{Binding Entities}" Width="200" Grid.Column="1" SelectedValue="{Binding EntityName}" Margin="4,0,0,0"/>

     

      <TextBlock Grid.Row="1" Text="Entity Field" VerticalAlignment="Center"/>

      <ComboBox ItemsSource="{Binding Fields}" Grid.Row="1" Grid.Column="1" SelectedValue="{Binding FieldName}" Margin="4,4,0,0"/>

     

    Now you’ll notice here that I’m binding to designer properties (Entities/EntityName/Fields/FieldName) and not ModelItem.{whatever}. This is critical in this instance as I don’t want to pollute the activity with any knowledge about the lists that are shown in the designer, I just want to have 2 fields on the activity, one for EntityName and another for FieldName (or Country/State in your example). So far so good.

     

    Now, the code in the designer needs to do a couple of things. As I’m binding to (effectively) Designer.EntityName/FieldName, I need to ensure that the getters and setters on these two properties feed values from/to the underlying model item (i.e. the activity being designed). So, these properties end up being defined as follows…

     

            public string EntityName

            {

                get { return this.GetModelItemProperty("EntityName") as string; }

                set

                {

                    this.SetModelItemProperty("EntityName", value);

                    this.OnPropertyChanged(this, "EntityName");

                    this.OnPropertyChanged(this, "Fields");

                }

            }

     

            public string FieldName

            {

                get { return this.GetModelItemProperty("FieldName") as string; }

                set

                {

                    this.SetModelItemProperty("FieldName", value);

                    this.OnPropertyChanged(this, "FieldName");

                }

     

            }

     

    Each getter uses a method I’ve called GetModelItemProperty which goes to the ModelItem (i.e. activity) and gets the current value…

     

            private object GetModelItemProperty(string propertyName)

            {

                PropertyDescriptor pd = TypeDescriptor.GetProperties(this.ModelItem)[propertyName];

     

                return pd.GetValue(this.ModelItem);

            }

     

    Similarly the setters use the SetModelItemProperty method which does the reverse…

     

            private void SetModelItemProperty(string propertyName, object value)

            {

                PropertyDescriptor pd = TypeDescriptor.GetProperties(this.ModelItem)[propertyName];

     

                pd.SetValue(this.ModelItem, value);

            }

     

    You’ll also see in the setter for EntityName that I mark the Fields property as having changed. Thus, WPF will refresh the list of fields and hence when the user chooses a different entity, the fields for that entity will then be displayed.


    One more thing about my solution here – the Entities/Fields are not hardcoded, they are actually read from a database at runtime. So, I needed a way to populate these lists dynamically and for that I used a metadata interface (that I created myself for this example) which has a couple of simple methods…

     

        public interface IMetadata

        {

            /// <summary>

            /// Return the list of entities that are available for the user to browse from

            /// </summary>

            /// <returns>A list of entity names</returns>

            List<string> GetEntities();

     

            /// <summary>

            /// Get the list of fields that are available for the user to browse from, given the name of the entity

            /// </summary>

            /// <param name="entityName">The name of the entity to lookup the fields from</param>

            /// <returns>A list of field names</returns>

            List<string> GetFields(string entityName);

        }

     

    My designer then uses this metadata service in order to populate the list of entities and fields, using standard WPF databinding to these lists…

     

            public List<string> Entities

            {

                get { return GetMetadataService().GetEntities(); }

            }

     

            public List<string> Fields

            {

                get

                {

                    if (string.IsNullOrEmpty(EntityName))

                        return new List<string>();

                    else

                        return GetMetadataService().GetFields(EntityName);

                }

            }

     

    I’ve created a simple GetMetadataService method on my designer…

     

            private IMetadata GetMetadataService()

            {

                IMetadata metadata = null;

     

                if (null != this.Context)

                {

                    if (null != this.Context.Services)

                    {

                        metadata = this.Context.Services.GetService<IMetadata>();

                    }

                }

     

                // Manufacture one for use within Visual Studio just so I can show something on the UI when developing

                if (null == metadata)

                    metadata = new DebugMetadataService();

     

                return metadata;

            }

     

    This uses the Context.Services property of the designer to access the real implementation of the metadata service. This is ‘fed’ into the designer (I was rehosting the workflow designer) by registering an implementation of the IMetadata service…

     

                WorkflowDesigner designer = new WorkflowDesigner();

     

                designer.Context.Services.Publish(typeof(IMetadata), new MetadataService());

     

    At design time (in the rehosted designer) I can then feed in a real metadata implementation which is going against the customers database. For debugging purposes inside Visual Studio I also created a default implementation of this metadata service which just returned enough data for my activities to be usable on screen – you might wish to avoid this step.

     

    Let me know if you need any more assistance with this – it’s fairly fresh in my mind as I only put this together a couple of weeks back.

     

     

    ====================================================================================================

    Regards

    Ambily

     


    Assistant Consultant
    Thursday, October 21, 2010 4:12 AM

All replies

  • Hi Ambily,

    Are you still seeking help for this issue?

    Thanks,

    Zafar

    Wednesday, October 20, 2010 9:55 PM
    Moderator
  • Hi Zafar

    We received one suggestion from Mr.Morgan Skinner and implemented the same. Now, we are keeping both the properties on the activity designer instead of PropertyGrid.

    Adding the suggestion from Mr. Morgan Skinner for furthur reference. We implemented it till the IMetadata interface. We didn't used the MetaDataService in our implementation.

    =================================================================================================== 

    You would typically do this inside a custom designer for your activity, and use standard WPF bindings & property change notifications to update the second list (state) when an item in the first list (country) is selected.

     

    The Entity Name and Entity Field combo boxes are related – a change to the Entity Name refreshes the list of values in the Entity Field combo.

     

    My activity exposes two lists (Entities & Fields) and two discrete values (EntityName and FieldName). The Entity Name combo is bound to the Entities list (which is just a list of strings), and the Entity Field combo is bound to the Fields list which again is just a list of strings. The pertinent parts of the XAML for the designer are as follows…

     

      <TextBlock Grid.Row="0" Text="Entity Name" VerticalAlignment="Center" />

      <ComboBox ItemsSource="{Binding Entities}" Width="200" Grid.Column="1" SelectedValue="{Binding EntityName}" Margin="4,0,0,0"/>

     

      <TextBlock Grid.Row="1" Text="Entity Field" VerticalAlignment="Center"/>

      <ComboBox ItemsSource="{Binding Fields}" Grid.Row="1" Grid.Column="1" SelectedValue="{Binding FieldName}" Margin="4,4,0,0"/>

     

    Now you’ll notice here that I’m binding to designer properties (Entities/EntityName/Fields/FieldName) and not ModelItem.{whatever}. This is critical in this instance as I don’t want to pollute the activity with any knowledge about the lists that are shown in the designer, I just want to have 2 fields on the activity, one for EntityName and another for FieldName (or Country/State in your example). So far so good.

     

    Now, the code in the designer needs to do a couple of things. As I’m binding to (effectively) Designer.EntityName/FieldName, I need to ensure that the getters and setters on these two properties feed values from/to the underlying model item (i.e. the activity being designed). So, these properties end up being defined as follows…

     

            public string EntityName

            {

                get { return this.GetModelItemProperty("EntityName") as string; }

                set

                {

                    this.SetModelItemProperty("EntityName", value);

                    this.OnPropertyChanged(this, "EntityName");

                    this.OnPropertyChanged(this, "Fields");

                }

            }

     

            public string FieldName

            {

                get { return this.GetModelItemProperty("FieldName") as string; }

                set

                {

                    this.SetModelItemProperty("FieldName", value);

                    this.OnPropertyChanged(this, "FieldName");

                }

     

            }

     

    Each getter uses a method I’ve called GetModelItemProperty which goes to the ModelItem (i.e. activity) and gets the current value…

     

            private object GetModelItemProperty(string propertyName)

            {

                PropertyDescriptor pd = TypeDescriptor.GetProperties(this.ModelItem)[propertyName];

     

                return pd.GetValue(this.ModelItem);

            }

     

    Similarly the setters use the SetModelItemProperty method which does the reverse…

     

            private void SetModelItemProperty(string propertyName, object value)

            {

                PropertyDescriptor pd = TypeDescriptor.GetProperties(this.ModelItem)[propertyName];

     

                pd.SetValue(this.ModelItem, value);

            }

     

    You’ll also see in the setter for EntityName that I mark the Fields property as having changed. Thus, WPF will refresh the list of fields and hence when the user chooses a different entity, the fields for that entity will then be displayed.


    One more thing about my solution here – the Entities/Fields are not hardcoded, they are actually read from a database at runtime. So, I needed a way to populate these lists dynamically and for that I used a metadata interface (that I created myself for this example) which has a couple of simple methods…

     

        public interface IMetadata

        {

            /// <summary>

            /// Return the list of entities that are available for the user to browse from

            /// </summary>

            /// <returns>A list of entity names</returns>

            List<string> GetEntities();

     

            /// <summary>

            /// Get the list of fields that are available for the user to browse from, given the name of the entity

            /// </summary>

            /// <param name="entityName">The name of the entity to lookup the fields from</param>

            /// <returns>A list of field names</returns>

            List<string> GetFields(string entityName);

        }

     

    My designer then uses this metadata service in order to populate the list of entities and fields, using standard WPF databinding to these lists…

     

            public List<string> Entities

            {

                get { return GetMetadataService().GetEntities(); }

            }

     

            public List<string> Fields

            {

                get

                {

                    if (string.IsNullOrEmpty(EntityName))

                        return new List<string>();

                    else

                        return GetMetadataService().GetFields(EntityName);

                }

            }

     

    I’ve created a simple GetMetadataService method on my designer…

     

            private IMetadata GetMetadataService()

            {

                IMetadata metadata = null;

     

                if (null != this.Context)

                {

                    if (null != this.Context.Services)

                    {

                        metadata = this.Context.Services.GetService<IMetadata>();

                    }

                }

     

                // Manufacture one for use within Visual Studio just so I can show something on the UI when developing

                if (null == metadata)

                    metadata = new DebugMetadataService();

     

                return metadata;

            }

     

    This uses the Context.Services property of the designer to access the real implementation of the metadata service. This is ‘fed’ into the designer (I was rehosting the workflow designer) by registering an implementation of the IMetadata service…

     

                WorkflowDesigner designer = new WorkflowDesigner();

     

                designer.Context.Services.Publish(typeof(IMetadata), new MetadataService());

     

    At design time (in the rehosted designer) I can then feed in a real metadata implementation which is going against the customers database. For debugging purposes inside Visual Studio I also created a default implementation of this metadata service which just returned enough data for my activities to be usable on screen – you might wish to avoid this step.

     

    Let me know if you need any more assistance with this – it’s fairly fresh in my mind as I only put this together a couple of weeks back.

     

     

    ====================================================================================================

    Regards

    Ambily

     


    Assistant Consultant
    Thursday, October 21, 2010 4:12 AM
  • I found this posting and it is close to exactly what I am trying to do as well.   I do not need to control one combo box from anothers entered value.  I am simple trying to alter the Property Editor to display a combo box for the user to choose options from.    I am trying to create a custom editor to allow multiple properties to use the base editor and the combo box with the appropriate list.   The following code does cause a blank combo box to be displayed in the editor, however I do not know how to populate the combo box with values.  Any help would be greatly appreciated.

    CustomActivity code

      public string Field { get; set; }
    
      static CustomActivity()
      {
         AttributeTableBuilder builder = new AttributeTableBuilder();
    
         EditorAttribute fieldDDEditor = new EditorAttribute(typeof(DropDownEditor), typeof(PropertyValueEditor));
         
         builder.AddCustomAttributes(typeof(CustomActivity), "Field", new EditorAttribute(typeof(DropDownEditor), typeof(PropertyValueEditor)));
          
         MetadataStore.AddAttributeTable(builder.CreateTable());
       }

     

    Custom editor

    class DropDownEditor : PropertyValueEditor
    {
       public DropDownEditor()
      {
         this.InlineEditorTemplate = new DataTemplate();
    
         FrameworkElementFactory stack = new FrameworkElementFactory(typeof(StackPanel));
    
         FrameworkElementFactory dropDown = new FrameworkElementFactory(typeof(ComboBox));
    
         Binding dropDownBinding = new Binding("Value");
    
         dropDownBinding.Mode = BindingMode.TwoWay;
    
         stack.AppendChild(dropDown);
    
         this.InlineEditorTemplate.VisualTree = stack;
    
      }
    
    }
    
    
    Friday, December 17, 2010 1:32 AM
  • Hi

    We implemented this requirement using TypeProviders; not with PropertyEditor.

    Our CustomActivity have the following field

     /// <summary>
      /// LogicalView
      /// </summary>
      [TypeConverterAttribute(typeof(LogicalViewConverter))]    
      [BrowsableAttribute(true)]  
      public string LogicalView
      {
       get
       {
        return this.logicalView;
       }
       set
       {
        this.logicalView = value;
       }
      }
    

    LogicalViewConverter is defined as

     public class LogicalViewConverter : StringConverter
     {  
      private static StandardValuesCollection svc;
      
      public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
      {
       return true;
      }
      
      public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
      {
       return true;
      }
    
      public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
      {
       IJournal journal = null;
    
       try
       {
        
        ArrayList logicalViews = new ArrayList();
        //Populate the ArrayList with required field values
        svc = new TypeConverter.StandardValuesCollection(logicalViews);
       }
       catch (System.Exception ex)
       { -------- }
       finally
       {
        ------
       }
       return svc;
      }
     }
    

     

    If you are looking for a editable comboBox, don't override the GetStandardValuesExclusive method.

    Thanks

    Ambily


    Assistant Consultant
    Monday, December 20, 2010 6:16 AM
  • Hi,

    How to make this converter value as required argument?

    In the designer, it should show the error when no value is provided.

    Please help.

    Monday, June 06, 2011 9:18 AM
  • Hi

     

    Please refer the PropertyValidation section under http://www.dotnetfunda.com/articles/article1149-workflow-foundation-40-validation-.aspx

     

    Regards

    Ambily


    Assistant Consultant
    Monday, June 06, 2011 9:57 AM
  • Thanks.

    It helped me.

    I've used CodeActivityMetaData.

    Tuesday, June 07, 2011 6:32 AM