locked
Trying to make a controldesigner with editable regions RRS feed

  • Question

  • User-1827453801 posted

    Hi folks,

    I'm having a real pain of a time trying to get editable regions to function correctly. The main problem as I see it so far is that "GetEditableDesignerRegionContent" is being called for the same region. I add n number of regions, one for each field, and then GetEditableDesignerRegionContent is being called for the same region over and over. This results in the same template being added over and over and I end up with errors in the designer complaining of duplicate control ids. Wtf!?

    The MS example uses CompositeControlDesigner which I am not using. As there is no CreateChildControls method for ControlDesigner my implementation is different.

    Please help!

     

    /// <summary>
        /// ShivamForm controls the layout of a form and it's fields. It provides a facility to manage various field states such as read only or not shown.
        /// </summary>
        /// <example>
        /// 
        /// </example>
        [System.Web.UI.ParseChildren(true, DefaultProperty = "FieldSets")]
        [System.ComponentModel.Designer(typeof(ShivamFormDesigner))]
        [ToolboxData("&lt;{0}:ShivamForm Runat=\"server\"&gt;</{0}:ShivamForm>")]
        public class ShivamForm : WebControl, INamingContainer
        {
            public ShivamForm()
            {
            }
    
            string validationGroup;
            string tableCssClass, formContainerCssClass;
            List<FIELDSET> fieldSets = new List<FIELDSET>();
    
            protected override void OnInit(EventArgs e)
            {
                Page.RegisterRequiresControlState(this);
                //if (!Page.IsPostBack)
                    EnsureChildControls();
                base.OnInit(e);
            }
    
            /// <summary>
            /// This is the default validation group of the form. If no validation group is explicitly provided for a field then this validation group is used.
            /// </summary>
            [PersistenceMode(PersistenceMode.Attribute)]
            [Localizable(false)]
            [Description("This is the default validation group of the form. If no validation group is explicitly provided for a field then this validation group is used.")]
            public string ValidationGroup
            {
                get { return validationGroup; }
                set { validationGroup = value; }
            }
    
            /// <summary>
            /// This is the css class that is applied to each fieldsets table.
            /// </summary>
            [PersistenceMode(PersistenceMode.Attribute)]
            [Localizable(false)]
            [Description("This is the css class that is applied to each fieldsets table.")]
            public string TableCssClass
            {
                get { return tableCssClass; }
                set { tableCssClass = value; }
            }
    
            /// <summary>
            /// This is the css that is applied to the outer table that lays out the individual fieldsets.
            /// </summary>
            [PersistenceMode(PersistenceMode.Attribute)]
            [Localizable(false)]
            [Description("This is the css that is applied to the outer table that lays out the individual fieldsets.")]
            public string FormContainerCssClass
            {
                get { return formContainerCssClass; }
                set { formContainerCssClass = value; }
            }        
    
            /// <summary>
            /// The width of the field label column.
            /// </summary>
            [PersistenceMode(PersistenceMode.Attribute)]
            [Localizable(false)]
            [Description("The width of the field label column.")]
            public int? LabelWidth
            {
                get { return (int?)ViewState["LabelWidth"]; }
                set { ViewState["LabelWidth"] = value; }
            }
    
            /// <summary>
            /// The list of fieldsets to display in the form.
            /// </summary>
            [System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.InnerProperty),
            System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)]
            [Description("The list of fieldsets to display in the form.")]
            public List<FIELDSET> FieldSets
            {
                get { return fieldSets; }
            }
    
            protected override void CreateChildControls()
            {
                HtmlGenericControl lastRow = null;
                HtmlGenericControl formContainer = new HtmlGenericControl("table");
                formContainer.Attributes["class"] = FormContainerCssClass;
    
                Controls.Add(formContainer);
    
                foreach (FieldSet fieldSet in fieldSets)
                {
                    fieldSet.form = this;
                    HtmlGenericControl fieldsetContainer;
    
                    //Determine whether to append the fieldset as a new row or new column.
                    switch (fieldSet.AppendMethod)
                    {
                        case FieldSetAppendMethod.NewRow :
                            lastRow = new HtmlGenericControl("tr");
                            fieldsetContainer = new HtmlGenericControl("td");
                            lastRow.Controls.Add(fieldsetContainer);
                            formContainer.Controls.Add(lastRow);
                            break;
                        case FieldSetAppendMethod.NewColumn :
                            if (lastRow == null)
                            {
                                lastRow = new HtmlGenericControl("tr");
                                formContainer.Controls.Add(lastRow);
                            }
    
                            fieldsetContainer = new HtmlGenericControl("td");
                            lastRow.Controls.Add(fieldsetContainer);
                            break;
                        default :
                            return;
                    }
    
                    fieldsetContainer.Attributes["class"] = "fieldsetContainer";
    
                    HtmlGenericControl control = new HtmlGenericControl("fieldset");
                    fieldsetContainer.Controls.Add(control);
    
                    HtmlGenericControl table = new HtmlGenericControl("table");
                    control.Controls.Add(table);
    
                    if (LabelWidth != null)
                    {
                        HtmlGenericControl col = new HtmlGenericControl("col");
                        col.Attributes["width"] = LabelWidth.ToString();
                        table.Controls.Add(col);
                    }                    
    
                    if (tableCssClass != null)
                        table.Attributes["class"] = tableCssClass;
    
                    foreach (Field field in fieldSet.Fields)
                    {
                        HtmlGenericControl tr = new HtmlGenericControl("tr");
                        table.Controls.Add(tr);
    
                        tr.Visible = field.Visible;
    
                        HtmlGenericControl labelCell = new HtmlGenericControl("td");
                        tr.Controls.Add(labelCell);
    
                        if (field.LabelHorizontalAlign != HorizontalAlign.NotSet)
                            labelCell.Attributes.Add("align", field.LabelHorizontalAlign.ToString().ToLower());
    
                        if (field.LabelVerticalAlign != VerticalAlign.NotSet)
                            labelCell.Attributes.Add("valign", field.LabelVerticalAlign.ToString().ToLower());
    
                        if (!string.IsNullOrEmpty(field.Label))
                        {
                            Label label = new Label();
                            label.AssociatedControlId = field.ControlName;
                            label.Text = field.Label;
                            label.AccessKey = label.Text.Substring(0, 1);
    
                            labelCell.Controls.Add(label);
                        }
                        else
                            labelCell.Controls.Add(new LiteralControl("&nbsp;"));
    
                        if (field.ValidatorGroup.ValidationGroup == null)
                            field.ValidatorGroup.ValidationGroup = ValidationGroup;
    
                        if (field.ValidatorGroup.ControlToValidate == null)
                            field.ValidatorGroup.ControlToValidate = field.ControlName;
    
                        HtmlGenericControl fieldCell = new HtmlGenericControl("td");
                        tr.Controls.Add(fieldCell);
    
                        if (field.FieldControlInstance == null)
                        {
                            field.FieldControl.InstantiateIn(fieldCell);
                            field.FieldControlInstance = fieldCell.Controls[0];
                        }
                        else
                            fieldCell.Controls.Add(field.FieldControlInstance);
    
                        fieldCell.Controls.Add(field.ValidatorGroup);
    
                        field.row = tr;
                    }
                }
                
                base.CreateChildControls();
            }
    
            protected override void LoadControlState(object savedState)
            {
                StateBag stateInfo = new StateBag();
                
                if (savedState != null)
                {
                    ((IStateManager)stateInfo).LoadViewState(savedState);
                    
                    foreach (FieldSet fieldSet in FieldSets)
                    {
                        if (stateInfo["FieldSet_" + FieldSets.IndexOf(fieldSet)] != null)
                        {
                            ((IStateManager)fieldSet).LoadViewState(stateInfo["FieldSet_" + FieldSets.IndexOf(fieldSet)]);
                        }
                    }
                }
            }
    
            protected override object SaveControlState()
            {
                StateBag stateInfo = new StateBag();
                ((IStateManager)stateInfo).TrackViewState();
    
                foreach (FieldSet fieldSet in FieldSets)
                {
                    stateInfo["FieldSet_" + FieldSets.IndexOf(fieldSet)] = ((IStateManager)fieldSet).SaveViewState();
                }
    
                //var x = ((IStateManager)stateInfo).SaveViewState();
                return ((IStateManager)stateInfo).SaveViewState();
            }
        }
    
        public class ShivamFormDesigner : ControlDesigner
        {
            ShivamForm form;
    
            public override void Initialize(IComponent component)
            {
                base.Initialize(component);
                form = component as ShivamForm;
    
                base.SetViewFlags(ViewFlags.TemplateEditing, true);
            }
    
            public override string GetEditableDesignerRegionContent(EditableDesignerRegion region)
            {
                IDesignerHost host = (IDesignerHost)Component.Site.GetService(typeof(IDesignerHost));
                if (host != null)
                {
                    string regionName = region.Name;
                    int fieldsetIndex = Convert.ToInt32(regionName.Substring(0, regionName.IndexOf('_')));
                    string fieldName = regionName.Substring(regionName.IndexOf('_') + 1);
    
                    ITemplate template = form.FieldSets[fieldsetIndex].Fields[fieldName].FieldControl;
                    return ControlPersister.PersistTemplate(template, host);
                }
    
                return string.Empty;
            }
    
            public override void SetEditableDesignerRegionContent(EditableDesignerRegion region, string content)
            {
                if (content == null)
                    return;
    
                IDesignerHost host = (IDesignerHost)Component.Site.GetService(typeof(IDesignerHost));
                if (host != null)
                {
                    ITemplate template = ControlParser.ParseTemplate(host, content);
    
                    if (template != null)
                    {
                        string regionName = region.Name;
                        int fieldsetIndex = Convert.ToInt32(regionName.Substring(0, regionName.IndexOf('_')));
                        string fieldName = regionName.Substring(regionName.IndexOf('_') + 1);
                        form.FieldSets[fieldsetIndex].Fields[fieldName].FieldControl = template;
                    }
                }            
            }
    
            protected override void OnClick(DesignerRegionMouseEventArgs e)
            {
                if (e.Region == null)
                    return;
    
                e.Region.Highlight = true;
                base.UpdateDesignTimeHtml();
    
                base.OnClick(e);
            }
    
            public override string GetDesignTimeHtml(DesignerRegionCollection regions)
            {
                try
                {
                    StringWriter sw = new StringWriter();
    
                    HtmlGenericControl lastRow = null;
                    HtmlGenericControl formContainer = new HtmlGenericControl("table");
                    formContainer.Attributes["class"] = form.FormContainerCssClass;
    
                    foreach (FieldSet fieldSet in form.FieldSets)
                    {
                        fieldSet.form = form;
                        HtmlGenericControl fieldsetContainer;
    
                        //Determine whether to append the fieldset as a new row or new column.
                        switch (fieldSet.AppendMethod)
                        {
                            case FieldSetAppendMethod.NewRow:
                                lastRow = new HtmlGenericControl("tr");
                                fieldsetContainer = new HtmlGenericControl("td");
                                lastRow.Controls.Add(fieldsetContainer);
                                formContainer.Controls.Add(lastRow);
                                break;
                            case FieldSetAppendMethod.NewColumn:
                                if (lastRow == null)
                                {
                                    lastRow = new HtmlGenericControl("tr");
                                    formContainer.Controls.Add(lastRow);
                                }
    
                                fieldsetContainer = new HtmlGenericControl("td");
                                lastRow.Controls.Add(fieldsetContainer);
                                break;
                            default:
                                continue;
                        }
    
                        fieldsetContainer.Attributes["class"] = "fieldsetContainer";
    
                        HtmlGenericControl control = new HtmlGenericControl("fieldset");
                        fieldsetContainer.Controls.Add(control);
    
                        HtmlGenericControl table = new HtmlGenericControl("table");
                        control.Controls.Add(table);
    
                        if (form.LabelWidth != null)
                        {
                            HtmlGenericControl col = new HtmlGenericControl("col");
                            col.Attributes["width"] = form.LabelWidth.ToString();
                            table.Controls.Add(col);
                        }
    
                        if (form.TableCssClass != null)
                            table.Attributes["class"] = form.TableCssClass;
    
                        foreach (Field field in fieldSet.Fields)
                        {
                            HtmlGenericControl tr = new HtmlGenericControl("tr");
                            table.Controls.Add(tr);
    
                            tr.Visible = field.Visible;
    
                            HtmlGenericControl labelCell = new HtmlGenericControl("td");
                            tr.Controls.Add(labelCell);
    
                            if (field.LabelHorizontalAlign != HorizontalAlign.NotSet)
                                labelCell.Attributes.Add("align", field.LabelHorizontalAlign.ToString().ToLower());
    
                            if (field.LabelVerticalAlign != VerticalAlign.NotSet)
                                labelCell.Attributes.Add("valign", field.LabelVerticalAlign.ToString().ToLower());
    
                            if (!string.IsNullOrEmpty(field.Label))
                            {
                                Label label = new Label();
                                label.AssociatedControlId = field.ControlName;
                                label.Text = field.Label;
                                label.AccessKey = label.Text.Substring(0, 1);
    
                                labelCell.Controls.Add(label);
                            }
                            else
                                labelCell.Controls.Add(new LiteralControl("&nbsp;"));
    
                            HtmlGenericControl fieldCell = new HtmlGenericControl("td");
                            tr.Controls.Add(fieldCell);
    
                            field.row = tr;
    
                            string regionName = form.FieldSets.IndexOf(fieldSet) + "_" + field.ControlName;
                            fieldCell.Attributes[DesignerRegion.DesignerRegionAttributeName] = regionName;
                            regions.Add(new EditableDesignerRegion(this, regionName, false));
    
                            if (regions.Count == 0)
                                regions[0].Highlight = true;                        
                        }
                    }
    
                    HtmlTextWriter writer = new HtmlTextWriter(sw);
                    formContainer.RenderControl(writer);
    
                    return sw.GetStringBuilder().ToString();
                }
                catch (Exception e)
                {
                    return e.Message + "\n" + e.StackTrace;
                }
            }
        }
    
        public class FieldDictionary : List<FIELD>
        {
            public Field this[string name]
            {
                get
                {
                    return base.Find(new Predicate<FIELD>(delegate(Field field) { return field.ControlName == name; }));
                }
            }
        }
    
        [ParseChildren(DefaultProperty="Fields")]
        public class FieldSet : IStateManager
        {
            FieldSetAppendMethod appendMethod = FieldSetAppendMethod.NewRow;
            FieldDictionary fields = new FieldDictionary();
            bool readOnly;
            internal ShivamForm form;
            StateBag stateBag = new StateBag();
    
            public FieldSet()
            {
                ((IStateManager)stateBag).TrackViewState();
            }
    
            [Browsable(false)]
            public ShivamForm Form
            {
                get { 
                    return form;
                }
            }
    
            /// <summary>
            /// Determines the way in which the fieldset is appended to the form. Either as a new column or new row.
            /// </summary>
            [Localizable(false)]
            public FieldSetAppendMethod AppendMethod
            {
                get { return appendMethod; }
                set { appendMethod = value; }
            }
    
            /// <summary>
            /// Gets or sets the read only value. If true then the forms normal field controls are not shown. Instead the field value is displayed as text only.
            /// </summary>
            [Description("Gets or sets the read only value. If true then the forms normal field controls are not shown. Instead the field value is displayed as text only.")]
            [DefaultValue(false)]
            [Localizable(false)]
            public bool ReadOnly
            {
                get { return readOnly; }
                set {
                    readOnly = value;
                    stateBag.SetItemDirty("ReadOnly", true);
    
                    RefreshReadOnly();
                }
            }
    
            /// <summary>
            /// This is the collection of fields defined for the fieldset instance.
            /// </summary>
            [System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.InnerProperty),
            System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)]
            [Description("This is the collection of fields defined for the fieldset instance.")]
            public FieldDictionary Fields
            {
                get { return fields; }
            }
    
            void RefreshReadOnly()
            {
                foreach (Field field in fields)
                {
                    IFieldController controller = null;
                    controller = FieldControllerFactory.GetController(field.FieldControlInstance);
    
                    if (controller != null)
                        controller.SetReadOnly(form, field.FieldControlInstance, ReadOnly);
                }
    
    
            }
    
            #region IStateManager Members
    
            bool IStateManager.IsTrackingViewState
            {
                get { return ((IStateManager)stateBag).IsTrackingViewState; }
            }
    
            void IStateManager.LoadViewState(object state)
            {
                if (state != null)
                    ((IStateManager)stateBag).LoadViewState(state);
    
                readOnly = (bool)(stateBag["ReadOnly"] ?? false);
                form.Load += delegate(object sender, EventArgs e)
                {
                    RefreshReadOnly();
                };
            }
    
            object IStateManager.SaveViewState()
            {
                if (!((IDictionary)stateBag).Contains("ReadOnly") || stateBag.IsItemDirty("ReadOnly"))
                {
                    stateBag.Clear();
                    // Add the _message property to the StateBag.
                    stateBag.Add("ReadOnly", readOnly);
                }
    
                return ((IStateManager)stateBag).SaveViewState();
            }
    
            void IStateManager.TrackViewState()
            {
                ((IStateManager)stateBag).TrackViewState();
            }
    
            #endregion
        }
    
        public enum FieldSetAppendMethod
        {
            NewRow,
            NewColumn
        }    
        
        [DefaultProperty("Label")]
        public class Field
        {
            ValidatorGroup validatorGroup = new ValidatorGroup();
            ITemplate fieldControl;
            string controlName, label;
            HorizontalAlign labelHorizontalAlign = HorizontalAlign.Left;
            VerticalAlign labelVerticalAlign = VerticalAlign.Middle;
            bool visible = true;
            bool readOnly = false;
            Control fieldControlInstance;
    
            internal HtmlGenericControl row;
    
            [System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.InnerProperty),
            System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)]
            public ValidatorGroup ValidatorGroup
            {
                get { return validatorGroup; }
            }
    
            [System.Web.UI.PersistenceMode(System.Web.UI.PersistenceMode.InnerProperty),
            System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content),
            TemplateInstance(TemplateInstance.Single)]
            public ITemplate FieldControl
            {
                get { return fieldControl; }
                set { fieldControl = value; }
            }
    
            [Browsable(false)]
            public Control FieldControlInstance
            {
                get { return fieldControlInstance; }
                set { fieldControlInstance = value; }
            }
    
            /// <summary>
            /// This is the Id of the control this field definition references.
            /// </summary>
            [Description("This is the Id of the control this field definition references.")]
            public string ControlName
            {
                get { return controlName; }
                set { 
                    controlName = value;
                    validatorGroup.ID = value + "_ValidatorGroup";
                }
            }
    
            /// <summary>
            /// This is the label the will be displayed adjacent to the field.
            /// </summary>
            [Description("This is the label the will be displayed adjacent to the field.")]
            public string Label
            {
                get { return label; }
                set{ label = value; }
            }
    
            /// <summary>
            /// Controls the horizontal alignment applied to the field label.
            /// </summary>
            [Description("Controls the horizontal alignment applied to the field label.")]
            public HorizontalAlign LabelHorizontalAlign
            {
                get { return labelHorizontalAlign; }
                set { labelHorizontalAlign = value; }
            }
    
    
            /// <summary>
            /// Controls the vertical alignment applied to the field label.
            /// </summary>
            [Description("Controls the vertical alignment applied to the field label.")]
            public VerticalAlign LabelVerticalAlign
            {
                get { return labelVerticalAlign; }
                set { labelVerticalAlign = value; }
            }
    
            /// <summary>
            /// Indicates if the field should be displayed in the form or not.
            /// </summary>
            [Description("Indicates if the field should be displayed in the form or not.")]
            public bool Visible
            {
                get { return visible; }
                set { 
                    visible = value;
                    if (row != null)
                        row.Visible = value;
                }
            }
    
            public bool ReadOnly
            {
                get { return readOnly; }
                set { readOnly = value; }
            }
        }</FIELDSET></FIELDSET></FIELDSET>
     
    Sunday, January 6, 2008 11:56 PM

All replies

  • User1327479286 posted

    I have the same problem, and it's driving me nuts as well. Somehow I expect it to be a VS.NET bug. (I use MS Visual C# Express Edition 2008 version 9.0.21022.8 RTM.)

    Each time GetEditableDesignerRegionContent() is called, it returns the same region name. Even though I have defined two EditableDesignerRegion objects with different names to the regions argument of the GetDesignTimeHtml() method. I double-confirmed that by looping the DesignerRegionCollection after adding the EditableDesignerRegion objects.

    Something I found out: the last region added to the regions argument of the GetDesignTimeHtml() method of the ControlDesigner class is the one used every time in the GetEditableDesignerRegionContent() and SetEditableDesignerRegionContent() methods.

    Anybody experience on creating a server control with two (or more) EditableDesignerRegion objects?

     

    Thursday, April 3, 2008 3:01 AM
  • User1327479286 posted

    I found out what that little problem causer was, and luckily, it's no bug! :-)

    It's all about the designer region attribute that you embed in the design-time HTML. As assumed in this thread, these attribute should be filled with the accompanying name of each EditableDesignerRegion. But this doesn't work. What the attribute should define, is the index of the region in the regions collection. Then it works all fine...

     

    Sunday, April 13, 2008 10:19 PM
  • User-1827453801 posted

    Sounds reasonable...

    But how do you get the index. The region collection seems to only be available in GetDesignTimeHtml(DesignerRegionCollection regions). Do I need to store a private reference to the region collection (or region)...?

    When I need the "index" in CreateChildControls I seem to be unable to determine the index of the region. 

    Monday, April 14, 2008 6:51 PM
  • User-1827453801 posted

    Got it working, Thanks Bart.

    I'm getting null from e.Region every time I click on something though (in OnClick overload). Any ideas on that? Is there some way to make the designer clearly show the boundaries for each region?

    Monday, April 14, 2008 10:11 PM