0

Having researched similar questions, none seem to deal with my specific issue. I have a simple case of an aspx DataList with text boxes where the user is to input data and then submit the page. I simply want to read the data the user has input into each item's text boxes. I know how to reference the item as the following example:

sFile = ((Label)row.FindControl("lblArtFile")).Text;    
sItem = ((TextBox)row.FindControl("tbItemNo")).Text;

The label is populated on page creation and I can retrieve that data. But the user input into the text box is not found and the string comes back always empty.

I also know that I can add a command button to the item line, but I don't want to do that because it's not elegant/intuitive. Rather, I want the user to only have to click the page submit button and then have the code read all the item lines at once. We can easily get the user input from a "normal" text box, but how to do so from one embedded in a datalist control?

Thank you, James

2
  • Is this a WebForms application? Commented Oct 11, 2021 at 15:18
  • Yes it is a Web Forms application. Commented Oct 15, 2021 at 17:00

1 Answer 1

1

Ok, I never used a datalist, but it sure looks like a NICE alternative to the repeater.

So, say we have this markup:

<asp:DataList ID="DataList1" runat="server" DataKeyField="ID" >
    <ItemTemplate>
    <div style="border-style:solid;color:black;width:300px;margin-left:25px">

        <div style="padding:5px;text-align:right">

            <p>Hotel Name: <asp:TextBox ID="HotelName" runat="server" Text ='<%# Eval("HotelName") %>' /></p>
            <p>First Name: <asp:TextBox ID="FirstName" runat="server" Text ='<%# Eval("FirstName") %>' /></p>
            <p>Last Name: <asp:TextBox ID="LastName" runat="server" Text ='<%# Eval("LastName") %>'    /></p>
            <p>City: <asp:TextBox ID="City" runat="server" Text ='<%# Eval("City") %>'  /></p>
            <p>Province: <asp:TextBox ID="Province" runat="server" Text ='<%# Eval("Province") %>'  /></p>
            Active: <asp:CheckBox ID="Active" runat="server" Checked = '<%# Eval("Active") %>'/>
            <br />
            <asp:Button ID="cmdThisRow" runat="server" Text="Row click This item"
                OnClick="cmdThisRow_Click"/>
        </div>
    </div>
    <div style="height:15px"></div>
</ItemTemplate>
</asp:DataList>

Ok, and then our code to fill:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadGrid();
    }

    void LoadGrid()
    {
        DataList1.DataSource = MyRst("SELECT TOP 3 * from tblHotels ORDER BY HotelName");
        DataList1.DataBind();
    }


    public DataTable MyRst(string strSQL)
    {
        DataTable rstData = new DataTable();
        using (SqlConnection con = new SqlConnection(Properties.Settings.Default.TEST3))
        using (SqlCommand cmdSQL = new SqlCommand(strSQL, con))
        {
            con.Open();
            rstData.Load(cmdSQL.ExecuteReader());
        }
        return rstData;
    }

Ok, and now we have this:

enter image description here

So we have two use cases. A button outside of the control - process ALL records.

That code/loop would be like this:

    protected void Button1_Click(object sender, EventArgs e)
    {
        foreach (DataListItem gRow in DataList1.Items)
        {
            Debug.Print("Row index = " + gRow.ItemIndex.ToString());
            int pkID = (int)(DataList1.DataKeys[gRow.ItemIndex]);
            Debug.Print("Data base PK id = " + pkID.ToString());

            Debug.Print("Hotel name = " + ((TextBox)(gRow.FindControl("HotelName"))).Text);
            Debug.Print("City       = " + ((TextBox)(gRow.FindControl("City"))).Text);
            Debug.Print("--------------------");
        }
    }

OutPut:

Row index = 0
Data base PK id = 20187
Hotel name = Albert Royal Hotel
City       = Edmotnon
--------------------
Row index = 1
Data base PK id = 78
Hotel name = Athabasca Hotel
City       = Kelowna
--------------------
Row index = 2
Data base PK id = 99
Hotel name = Banff Airporter
City       = Kimberley
--------------------

Ok, now lets wire up the button inside of the datalist.

we can't double click on the button to create the event, but we can do this: We use intel-sense, and when you in markup type in OnClick= then we see this:

enter image description here

so, we choose create new event. Does not seem like anything occurred, but flip to code behind, and you see the code stub:

The code to get/use the click on the one row is this:

    protected void cmdThisRow_Click1(object sender, EventArgs e)
    {
        Button btn = (Button)sender;
        DataListItem gRow = (DataListItem)btn.Parent ;
        Debug.Print("Row index = " + gRow.ItemIndex.ToString());
        int pkID = (int)(DataList1.DataKeys[gRow.ItemIndex]);
        Debug.Print("Data base PK id = " + pkID.ToString());

        Debug.Print("Hotel name = " + ((TextBox)(gRow.FindControl("HotelName"))).Text);
        Debug.Print("City       = " + ((TextBox)(gRow.FindControl("City"))).Text);
        Debug.Print("--------------------");


    }

thus, we now have both a loop for a button outside of the Datalist to process each row.

And we have a button inside of the Datalist to get/process the one row we clicked on.

do keep in mind that you can use the built in events of this control. They quite much follow listview/gridview etc in which you can have the indexchanged and other events fire - but often the above trick to just drop in a button, use btn.parent to get the row and work from that is less work and effort.

However, even if you decide to use the built in events for the DataList, the use of findcontrol as per above is how you pluck out the controls, and then ultimatly the values from those controls.

So, you can't reference the controls directly, since the whole "thing" is being repeated over and over - hence you get the row, and THEN get the values out of that row.

Edit: =================================================

So, the user is wondering how to send the edits back to the database? Well, as noted, we dumped the world poverty mess of using extra templates.

so, if we wanted to be able to freely edit each of the repeated rows?

Then do this, as then ONE SINGLE update command to the database will update all changes we have.

so right below the data list lets add 2 buttons (save, and un-do).

</ItemTemplate>
</asp:DataList>
        <asp:Button ID="cmdSave" runat="server" Text="Save" CssClass="btn" Width="120px" />
        <asp:Button ID="cmdUnDo" runat="server" Text="Undo edits" CssClass="btn" Width="120px"
            style="padding-left:25px"/>
        <br />
        <br />

So, now our load code becomes this (we persit the data table).

    DataTable rstData;
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            LoadGrid();
            ViewState["MyData"] = rstData;
        }
        else
            rstData = (DataTable)ViewState["MyData"];
    }

    void LoadGrid()
    {
        string strSQL;
        strSQL = "SELECT TOP 3 *  FROM tblHotels ORDER BY HotelName";

        rstData = MyRst(strSQL);
        DataList1.DataSource = rstData;
        DataList1.DataBind();
    }

Note HOW we are persiting the data table here!!!

Now, to save ALL edits in one shot? We send the data list control BACK to table, and THEN execute a single database update on that table like this:

void GridToTable()
{
    // pull datalist rows back to table.
    foreach (DataListItem rRow in DataList1.Items)
    {
        int RecordPtr = rRow.ItemIndex;
        DataRow OneDataRow;
        OneDataRow = rstData.Rows[RecordPtr];
        OneDataRow["FirstName"] = ((TextBox)rRow.FindControl("FirstName")).Text;
        OneDataRow["LastName"] = ((TextBox)rRow.FindControl("LastName")).Text;
        OneDataRow["City"] = ((TextBox)rRow.FindControl("City")).Text;
        OneDataRow["Province"] = ((TextBox)rRow.FindControl("Province")).Text;
        OneDataRow["HotelName"] = ((TextBox)rRow.FindControl("HotelName")).Text;
        OneDataRow["Active"] = ((CheckBox)rRow.FindControl("Active")).Checked;
    }
}

protected void cmdSave_Click(object sender, EventArgs e)
{
    GridToTable();
    // now send table back to database with updates
    string strSQL;
    strSQL = "SELECT * FROM tblHotels WHERE ID = 0";

    using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
    {
        using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
        {
            conn.Open();
            SqlDataAdapter daupdate = new SqlDataAdapter(cmdSQL);
            SqlCommandBuilder cmdBuild = new SqlCommandBuilder(daupdate);
            daupdate.Update(rstData);
        }
    }

So we go: Controls back to table,

Then table back to database. Now I did not wire up the un-do button, but it would simple call LoadGrid again (and hence a un-do all edits).

Ok, so now we have this:

enter image description here

So you are free to tab around - change any of the above displayed.

Sign up to request clarification or add additional context in comments.

5 Comments

I don't see any markup for Button1. But assuming it's just an ordinary asp button run at server, then yes, the event handler for the button click can retrieve the datalist contents. BUT, if the user edits the content, that will not, in my testing, be reflected. The datalist control does not incorporate user edits without handling a row-by-row button event to update source data, and then "re-binding" the updated data to the datalist..
Sure the row click button is is the markup - called cmdThisRow. The example for the button outside of the code - no, I did not include that markup - but it just a pane jane button on the page outside of the datalist. It just standard button click, and thus you can loop all rows of the datalist. And yes, if the user edits values, then you see the updated values - (I don't use the horrible mess of edit template, and you don't have to - just the one template with text box controls in place of labels. but yes, any changes to the text, or check box will and should be seen by BOTH routines posted.
See my new code I added. I how how to set ALL edits back to the database in ONE simple update command. We do have to send controls back to table, but that setup allows easy edit, or delete or even add a new items (just use 3 lines of code to add a row to the table, and then re-bind ).
I think this answer is missing my point, which is, if the user edits changing "Albert Royal Hall" to "Taj Mahal" then clicks "Save", that button handler will not see the change to "Taj Mahal". The only way, apparently to see the change is to have something like an "Update" button on each item row, and that's precisely what I want to avoid.
Yes, the button click DOES and WILL most certainly see the changes in my above sample post. I don't bother with edit templates - since we avoiding boatloads of work and world poverty by using them. So the above simple clean code works, and yes a simple ONE save button will save any and all edits in all 3 of the displayed in this example. As noted, if your button click is not seeing the changes (it should) then something is wrong here that we are missing. So again, yes the above sees the changes, and the posted code works fine and yes the single final bottom button click does see changes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.