views:

371

answers:

1

Hi!

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!

+1  A: 

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>
Mehdi Golchin
Yes! PersistenceMode.InnerDefaultProperty did it! Thank you! BTW: I've tried to implement a StateManagedCollection but with no luck. OK - I stopped implementing StateManagedCollection when I read your Edit.
Arthur
Oh man, I'm so sorry. This point is not mine. As far as I said other user answer your question. But, he deleted his post. Anyway, I'll write a simple example for you to fix the ViewState problem.
Mehdi Golchin
Thanks! In that case I do not need to fix the viewstate issue (not using viewstate) but I interesting in it! So: No stress!
Arthur
@another users answer: Well, you saw that this might be the right direction, you saw that the post was deleted, you repeated it because you thought it may be right. So I think the points are yours :-)
Arthur
@Arthur, thanks. I have updated my post.
Mehdi Golchin
Thanks! Works great!
Arthur