Search code examples
asp.netdetailsview

DetailsView: How to set a HiddenField value using the CommandArgument on a New command?


I have inherited some code that has a GridView and a DetailsView in a webpart control.

The DetailsView is able to create two different kinds of an object e.g. TypeA and TypeB.

There's a dropdown list that filters the GridView by object type and the DetailsView has an automatically generated Insert button.

<asp:DetailsView ID="myDetailsView"  
    AutoGenerateInsertButton="True"
    AutoGenerateEditButton="True" 
    AutoGenerateRows="false" 
    OnItemUpdating="OnItemUpdating"
    DefaultMode="ReadOnly" 
    OnDataBound="OnDetailsViewBound"
    OnItemInserting="OnItemInserting" 
    OnModeChanging="OnDetailsViewModeChanging"        
    runat="server">

I have been asked to:

  1. remove the filter on the GridView; and
  2. split the New buttons/links into two so there's a separate button for creating each type of object.

Removing the filter means that I need some other way to track what kind of object we're creating. I have split the New links by changing the above to:

<asp:DetailsView ID="myDetailsView"  
    AutoGenerateInsertButton="False"
    AutoGenerateEditButton="True" 
    AutoGenerateRows="false" 
    OnItemUpdating="OnItemUpdating"
    DefaultMode="ReadOnly" 
    OnDataBound="OnDetailsViewBound"
    OnItemInserting="OnItemInserting" 
    OnModeChanging="OnDetailsViewModeChanging"        
    runat="server">

and adding

 <FooterTemplate>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:LinkButton runat="server" ID="lnkCreateNewTypeA" CommandName="New" CommandArgument="TypeA" CssClass="buttonlink">New Type A</asp:LinkButton>
                <asp:LinkButton runat="server" ID="lnkCreateNewTypeB" CommandName="New" CommandArgument="TypeB" CssClass="buttonlink">New Type B</asp:LinkButton>
            </ItemTemplate>
        </asp:TemplateField>
 </FooterTemplate>

I haven't yet removed the filter so the changes currently function the same as before, as I'm using the New command. What I was hoping to be able to do is somehow capture the New event so I can put the CommandArgument value into a hidden field, that the DetailsView would then use to determine which type of object it's creating and also to show/hide fields. When I put breakpoints in all of the event handlers in my code, the first one to break is OnDetailsViewModeChanging, which doesn't have access to the CommandArgument.

OnItemCommand (if it's hooked up) is triggered when any button within the DetailsView is pressed and does give me access to the CommandArgument but I'm not sure what exactly needs to be done within this method to mimic the chain of events that occurs when you use automatically generated buttons.

Is my only option for retrieving the CommandArgument to capture it in the OnItemCommand event handler or is there some other event that is triggered on the New command?

Can anyone explain to me the sequence of events that occurs when the New command is triggered?

I read somewhere that it changes the mode to Insert but I don't know what else it does. I believe the OnItemInserting method isn't called until the "Insert" link is clicked.

Any help would be gratefully received!!


Edit:

I have found this link on DetailsView events but it hasn't answered my question. http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.detailsview_events.aspx


Edit:

I have tried adding the following:

in ascx:

<asp:DetailsView ID="myDetailsView"
    ...
    OnItemCommand="OnItemCommand"
    ...
    runat="server">

    ...

    <asp:TemplateField HeaderText="Object Type" HeaderStyle-CssClass="columnHeader">
        <ItemTemplate>
            <asp:HiddenField runat="server" ID="hidObjectType" Value=""/>
            <asp:Label runat="server" ID="lblObjectType"></asp:Label>
        </ItemTemplate>
    </asp:TemplateField>

in code behind:

protected void OnItemCommand(object sender, DetailsViewCommandEventArgs e)
{
    if (e.CommandName.Equals("New"))
    {
        var objectType = e.CommandArgument.ToString();

        HiddenField typeHidden = this.myDetailsView.FindControl("hidObjectType") as HiddenField;
        if (typeHidden != null)
        {
            typeHidden.Value = objectType;
        }

        Label typeLabel = this.myDetailsView.FindControl("lblObjectType") as Label;
        if (typeLabel != null)
        {
            typeLabel.Text = objectType;
        }
    }
}

I found that I didn't need to set the mode (this.myDetailsView.ChangeMode(DetailsViewMode.Insert);) in this method, as the OnDetailsViewModeChanging event handler still triggered. This finds the controls and sets the values on them correctly. If I check the values again in OnDetailsViewModeChanging, their values are still set but as part of the logic in this method, there is a call to

this.myDetailsView.DataBind()

which causes a postback and at this point, the values are lost. I tried adding

EnableViewState="True"

but this made no difference. I've reviewed the page lifecycle (http://spazzarama.files.wordpress.com/2009/02/aspnet_page-control-life-cycle.pdf) and thought that maybe this.EnsureChildControls() would help but it's also made no difference.

An alternative would be to store the value in the session but I'd rather not.


Solution

  • As far as I can tell there is no event for capturing the "New" command aside from OnItemCommand, which captures all commands. (NOTE: You will need to make sure that CausesValidation="False" is set on your LinkButton or the code won't break into OnItemCommand).

    When stepping through the code, the following occurred:

    1. After the linkbutton was pressed, OnItemCommand is triggered. CommandName = "New" and here I could retrieve the CommandArgument
    2. Next OnModeChanging is triggered. e.NewMode = "Insert". From all examples I've seen, here you call ChangeMode on the DetailsView and then call Databind() on it
    3. Next OnDataBound is triggered as a result of calling Databind()

    I didn't find a way to retain the value of the hidden variable between the various events so I ended up using a session variable. Code is below in case anyone wants it.

    DetailsView declaration in the ASCX:

        <asp:DetailsView ID="myDetailsView"  
            AutoGenerateInsertButton="False"
            AutoGenerateEditButton="True" 
            AutoGenerateRows="false" 
            OnItemInserting="OnItemInserting" 
            OnItemUpdating="OnItemUpdating"
            OnItemCommand="OnItemCommand"
            DefaultMode="ReadOnly" 
            OnDataBound="OnDetailsViewBound"
            OnModeChanging="OnDetailsViewModeChanging"        
            runat="server">
    

    In the code-behind:

    constant declarations...

        private const string SESSIONKEY_MYVALUE = "MyValue";
        private const string DEFAULT_OBJECTTYPE = "TypeA";
    

    OnItemCommand event handler...

        protected void OnItemCommand(object sender, DetailsViewCommandEventArgs e)
        {
            if (e.CommandName.Equals("New", StringComparison.InvariantCultureIgnoreCase))
            {
                var objectType = e.CommandArgument.ToString();
    
                HiddenField typeHidden = this.myDetailsView.FindControl("hidObjectType") as HiddenField;
                if (typeHidden != null)
                {
                    typeHidden.Value = objectType;
                }
    
                HttpContext.Current.Session[SESSIONKEY_MYVALUE] = objectType;
            }
        }
    

    OnModeChanging event handler....

        protected void OnDetailsViewModeChanging(Object sender, DetailsViewModeEventArgs e)
        {
            if (e.NewMode == DetailsViewMode.Insert)
            {
                this.myDetailsView.ChangeMode(DetailsViewMode.Insert);
                this.myDetailsView.DataBind();
            }
        }
    

    OnDataBound event handler...

        protected void OnDetailsViewBound(object sender, EventArgs e)
        {
            if (this.myDetailsView.CurrentMode == DetailsViewMode.Insert)
            {
                var sessionVar = HttpContext.Current.Session[SESSIONKEY_MYVALUE];
                var objectType = sessionVar == null ? 
                    DEFAULT_OBJECTTYPE : 
                    sessionVar.ToString();
    
                var typeHidden = this.myDetailsView.FindControl("hidObjectType") as HiddenField;
    
                if (typeHidden != null)
                {
                    typeHidden.Value = objectType;
                }
            }
        }