Search code examples
asp.netcustom-controlsdesigner

VS 2008, ASP.NET: Generate Local Resources


I've build an simple control called Menu:

namespace MyControls
{
    public class MenuItem
    {
        public MenuItem()
        {
            Visible = true;
        }

        [Localizable(true)]
        public string Text { get; set; }
        [Localizable(false)]
        public string Link { get; set; }
        [DefaultValue(true)]
        public bool Visible { get; set; }
    }

    public class MenuDesigner : System.Web.UI.Design.ControlDesigner
    {
        ...
    }

    [ParseChildren(true, "Items")]
    [PersistChildren(false)]
    [Designer(typeof(MenuDesigner))]
    public class Menu : Control
    {
        ...

        public Menu()
        {
        }

        ...

        private List<MenuItem> _items = new List<MenuItem>();
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public List<MenuItem> Items
        {
            get
            {
                return _items;
            }
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e); 

            ... // More Controls.

            list = new BulletedList();
            list.DisplayMode = BulletedListDisplayMode.HyperLink;
            this.Controls.Add(list);

            foreach (var mi in _items)
            {
                list.Items.Add(new ListItem(mi.Text, Page.Request.CreateUrl(mi.Link)));
            }
        }
    }
}

I use it in my Page this way:

    <my:Menu ID="menu" runat="server" Text="MenuTitle">
        <my:MenuItem Text="text" Link="link1.aspx">
        </my:MenuItem>
        <my:MenuItem Text="text2" Link="link2.aspx">
        </my:MenuItem>
    </my:Menu>

This works. No Problems. When I switch to the Designer View I see the Control in the way my MenuDesigner renders it. Reformating with CTRL-K, CTRL-D works. Running my WebPage renders to Menu the way I expected it.

But: when I hit in the DesingView the Menu Item "Tools" -> "Generate Local Resource" I get this result:

<my:Menu ID="menu" runat="server" Text="MenuTitle" 
            meta:resourcekey="menuResource9">
            <my:MenuItem Text="text" Link="link1.aspx">
            </my:MenuItem>
            <my:MenuItem Text="text2" Link="link2.aspx">
            </my:MenuItem>
            <Items>
<my:MenuItem Text="text" Link="link1.aspx" meta:resourcekey="MenuItemResource10"></my:MenuItem>
<my:MenuItem Text="text2" Link="link2.aspx" meta:resourcekey="MenuItemResource11"></my:MenuItem>
</Items>
        </my:Menu>

Which Attributes are missing/wrong? I've looked into ListBox, which also parses child items an I have the feelding that my control is doing the same.

The only differences I've found:

  • I am using a generic List, ListBox has it's own collection type for ListItems
  • I have no Editor or ControlBuilder or TypeConverter for my MenuItem, ListBox does.

This is not a control I'm willing to sell, it's only for me. I don't need any Editor or Desinger, I'm writing HTML/ASP.NET markup by hand.

I am using Visual Studio 2008, .NET 3.5.

Thanks for any Hints, Help or Sulutions!


Solution

  • IMO, your code is incorrect. Because, you didn't implement IStateManager in the MenuItem. Also generic list is not a valid type in this case. You have to write a custom collection that implements IStateManager or use StateManagedCollection.

    MenuItem

    public class MenuItem : IStateManager
    {
    
        private StateBag _viewState;
        private bool _isTrackingViewState;
    
        public string Text
        {
            get { return (string)ViewState["Text"] ?? string.Empty; }
            set { ViewState["Text"] = value; }
        }
    
        public void SetDirty()
        {
            if (this._viewState != null)
                this._viewState.SetDirty(true);
        }
    
        protected virtual StateBag ViewState
        {
            get
            {
                if (this._viewState == null)
                {
                    this._viewState = new StateBag(true);
                    if (this._isTrackingViewState)
                        ((IStateManager)this._viewState).TrackViewState();
                }
                return this._viewState;
            }
        }
    
        bool IStateManager.IsTrackingViewState
        {
            get { return this._isTrackingViewState; }
        }
    
        void IStateManager.LoadViewState(object state)
        {
            if (state != null)
                ((IStateManager)this.ViewState).LoadViewState(state);
        }
    
        object IStateManager.SaveViewState()
        {
            if (this._viewState != null)
                return ((IStateManager)this._viewState).SaveViewState();
            return null;
        }
    
        void IStateManager.TrackViewState()
        {
            this._isTrackingViewState = true;
    
            if (this._viewState != null)
                ((IStateManager)this._viewState).TrackViewState();
        }
    
    }
    

    MenuItemCollection

    public class MenuItemCollection : StateManagedCollection
    {
    
        public MenuItem this[int index]
        {
            get { return (MenuItem)((IList)this)[index]; }
        }
    
        public int Add(MenuItem item)
        {
            return ((IList)this).Add(item);
        }
    
        public void Remove(MenuItem item)
        {
            ((IList)this).Remove(item);
        }
    
        // Write Insert and RemoveAt methods
    
        protected override void SetDirtyObject(object o)
        {
            ((MenuItem)o).SetDirty();
        }
    
    }
    

    Menu

    [ParseChildren(true, "Items"), PersistChildren(false)]
    public class Menu : Control
    {
    
        private MenuItemCollection _items;
    
        [PersistenceMode(PersistenceMode.InnerDefaultProperty), MergableProperty(false)]
        public MenuItemCollection Items
        {
            get
            {
                if (this._items == null)
                {
                    this._items = new MenuItemCollection();
                    if (base.IsTrackingViewState)
                        ((IStateManager)this._items).TrackViewState();
                }
                return this._items;
            }
        }
    
        protected override void TrackViewState()
        {
            base.TrackViewState();
    
            if (this._items != null)
                ((IStateManager)this._items).TrackViewState();
        }
    
        protected override void LoadViewState(object savedState)
        {
            Pair states = (Pair)savedState;
    
            base.LoadViewState(states.First);
    
            if (states.Second != null)
                ((IStateManager)this.Items).LoadViewState(states.Second);
        }
    
        protected override object SaveViewState()
        {
            Pair states = new Pair();
    
            states.First = base.SaveViewState();
    
            if (this._items != null)
                states.Second = ((IStateManager)this._items).SaveViewState();
    
            return states;
        }
    
    }
    

    Note: I didn't test the above code.

    Anyway, there was another answer before mine that was correct in my opition, but has been deleted. You have set the PersistenceMode of Items property to InnerProperty. Thus, you have to write markup as follows.

    <my:Menu ID="menu" runat="server" Text="MenuTitle">
        <Items>
            <my:MenuItem Text="text" Link="link1.aspx" />
            <my:MenuItem Text="text2" Link="link2.aspx" />
        </Items>
    </my:Menu>