Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Dynamically Created ButtonColumn Not firing event

Status
Not open for further replies.
Oct 15, 2003
145
US
I am creating a datagrid dynamically,I have 3 columns -
1 - data bound column from a dataset that is generated based on my parenty key
2 - Input text box
3 - button that will be used to save the data entered in the text box


Problem:
My button does nothing. It's like it doesn't fire any event.

Code:
I am trying to create a child datagrid ( a datagrid that is displayed in the row beneath a "parent" row that dispalys many rows from another table) - so I call this function within the parent datagrids ItemDataBound function.

' code to create my child dataset: oBillDS
' code to create the datagrid
NewDg = New DataGrid
NewDg.AutoGenerateColumns = False
Dim cDgCol As DataColumn

For Each cDgCol In oBillDS.Tables(0).Columns
If cDgCol.ColumnName = "Pnum" Then
NewDg.Columns.Add(CreateBoundControl(cDgCol))
End If

Next
NewDg.Width = Unit.Percentage(100)

Dim tc1 As New TemplateColumn
tc1.HeaderTemplate = New DataGridTemplate(ListItemType.Header, "Amount Paid")
tc1.ItemTemplate = New DataGridTemplate(ListItemType.Item, "From DB Amt Paid")
tc1.EditItemTemplate = New DataGridTemplate(ListItemType.EditItem, "AmtPaid")
tc1.FooterTemplate = New DataGridTemplate(ListItemType.Footer, "NoFooter")
NewDg.Columns.Add(tc1)

Dim txtBoxColumn As New ButtonColumn

txtBoxColumn.CommandName = "MyCommmand"
txtBoxColumn.Text = "Update"
txtBoxColumn.ButtonType = ButtonColumnType.PushButton
' my attempt to add a handler for my datagrid
AddHandler NewDg.ItemCommand, AddressOf NewDG_EditCommand
NewDg.Columns.Add(txtBoxColumn)
NewDg.EnableViewState = True

NewDg.EditItemIndex = 0

NewDg.DataSource = oBillDS
NewDg.DataBind()

Dim sw As New System.IO.StringWriter
Dim htw As New System.Web.UI.HtmlTextWriter(sw)
NewDg.RenderControl(htw)

Dim DivStart As String = "<DIV id=uniquename" + e.Item.ItemIndex.ToString("' style='DISPLAY: none>'")
Dim DivBody As String = sw.ToString()
Dim DivEnd As String = "</DIV>"
Dim FullDIV As String = DivStart + DivBody + DivEnd

Dim LastCellPosition As Integer = e.Item.Cells.Count - 1
Dim NewCellPosition As Integer = e.Item.Cells.Count - 2

e.Item.Cells(0).ID = "CellInfo" + e.Item.ItemIndex.ToString()

If e.Item.ItemType = ListItemType.Item Then
e.Item.Cells(LastCellPosition).Controls.Add(New LiteralControl("</td><tr><td bgcolor='f5f5f5'></td><td colspan='" + NewCellPosition.ToString + "'>" + FullDIV))
Else
e.Item.Cells(LastCellPosition).Controls.Add(New LiteralControl("</td><tr><td bgcolor='d3d3d3'></td><td colspan='" + NewCellPosition.ToString + "'>" + FullDIV))

End If
e.Item.Cells(0).Attributes("onmouseover") = "this.style.cursor='pointer'"
e.Item.Cells(0).Attributes("onmouseout") = "this.style.cursor='pointer'"


' Sub that I thought would fire - but doesn't

Public Sub NewDG_EditCommand(ByVal sender As Object, ByVal e As DataGridCommandEventArgs)
Response.Redirect(" End Sub

Does anyone have any suggestions as to why my event is never fired. I thought the buttonColumn would fire the ItemCommand event of the datagrid --- if anyone has any thoughts - I'd greatly appreciate them.
 
Thanks KDavie - I read that - I may be doing something like what he is saying - however I don't see it. I haven't been able to figure this out.
 
Maybe it's not even possible what I want to do....

I basically want a master/detail datagrid that is editable.

So it would like this ---- if this were a datagrid and not just text:


Master row:
Account Account Name Total
+ 505 Checking 800
Child Rows(displayed when plus button is pressed)
Bill Amount To Apply
Electric 500
Sewer 300
BUTTON:Save Child
BUTTON: Save Master


I have my datagrid displaying kind of like I want but I can NOT get the Save Child Button to fire the event that I added to it. I do not have each of my children to be displayed in text boxes -- only one row - but I think that has something to do with the EditItemIndex of the row or something - not sure.

Does anyone have any thoughts as to what I am doing wrong? I've changed my code to no longer do a buttom column but rather create a button at the end of the datagrid:

Code:
        ' code to create my child dataset: oBillDS
        ' code to create the datagrid
        NewDg = New DataGrid
        NewDg.AutoGenerateColumns = False
        Dim cDgCol As DataColumn

        For Each cDgCol In oBillDS.Tables(0).Columns
            If cDgCol.ColumnName = "Pnum" Then
                NewDg.Columns.Add(CreateBoundControl(cDgCol))
            End If

        Next
        NewDg.Width = Unit.Percentage(100)

        Dim tc1 As New TemplateColumn
        tc1.HeaderTemplate = New DataGridTemplate(ListItemType.Header, "Amount Paid")
        tc1.ItemTemplate = New DataGridTemplate(ListItemType.Item, "From DB Amt Paid")
        tc1.EditItemTemplate = New DataGridTemplate(ListItemType.EditItem, "AmtPaid")
        tc1.FooterTemplate = New DataGridTemplate(ListItemType.Footer, "NoFooter")
        NewDg.Columns.Add(tc1)


        NewDg.EnableViewState = True
        NewDg.EditItemIndex = 0
        NewDg.DataSource = oBillDS
        NewDg.DataBind()

' change of code for button:
Page.RegisterHiddenField(Me.UniqueID & "_buttonClicked", "")
        Dim objButton As New Button
        objButton.Text = "Update ME"
objButton.Attributes.Add("onClick", "btn_click()")
NewDg.Controls.Add(objButton)


        Dim sw As New System.IO.StringWriter
        Dim htw As New System.Web.UI.HtmlTextWriter(sw)
        NewDg.RenderControl(htw)

        Dim DivStart As String = "<DIV id=uniquename" + e.Item.ItemIndex.ToString("' style='DISPLAY: none>'")
        Dim DivBody As String = sw.ToString()
        Dim DivEnd As String = "</DIV>"
        Dim FullDIV As String = DivStart + DivBody + DivEnd

        Dim LastCellPosition As Integer = e.Item.Cells.Count - 1
        Dim NewCellPosition As Integer = e.Item.Cells.Count - 2

        e.Item.Cells(0).ID = "CellInfo" + e.Item.ItemIndex.ToString()

        If e.Item.ItemType = ListItemType.Item Then
            e.Item.Cells(LastCellPosition).Controls.Add(New LiteralControl("</td><tr><td bgcolor='f5f5f5'></td><td colspan='" + NewCellPosition.ToString + "'>" + FullDIV))
        Else
            e.Item.Cells(LastCellPosition).Controls.Add(New LiteralControl("</td><tr><td bgcolor='d3d3d3'></td><td colspan='" + NewCellPosition.ToString + "'>" + FullDIV))

        End If
        e.Item.Cells(0).Attributes("onmouseover") = "this.style.cursor='pointer'"
        e.Item.Cells(0).Attributes("onmouseout") = "this.style.cursor='pointer'"
 
do you need to create this dynamically?
why not use the markup and WebUserControls to piece the grid together? this way you don't need to worry about dynamic controls and postbacks.

Jason Meckley
Programmer
Specialty Bakers, Inc.
 
I agree with jmeckley. Post back if you need help accomplishing this.

 
I don't *have* to do it dynamically I spose - I just can't seem to get it to work any other way as far as displaying the data - obviously this way isn't working either to do what I want.

I don't know exactly what you mean by WebUserControls and markup - can you give me an example and I'll see if I can get it to work.

I'm using Visual Studio 2003 .net 1.1

Thanks for your help!
 
webusercontrols (ascx) objects as opposed to
webforms (aspx)
global handlers (asax)
generic handlers (ashx)
web services (asmx)

msdn and google have plenty of info and examples.

another issue is your webform has way to many responsibilities.
1. display data
2. change master data
3. save master data
4. change child data
5. save child data

instead of have 1 page do everything. Send the user to another form to edit the information.

Jason Meckley
Programmer
Specialty Bakers, Inc.
 
Thanks Jmeckley - Sending the user to another page is not what the client wants - they want it all on one page so the user doesn't have to go back and forth between pages.

I'll continue my research to see if I can get this to work...

Thanks
 
you could use ajax do divide and conquer. This would allow the user to experience everything on one page (no postbacks/redirects). the page is actually calling a variety of generic handlers to update/ fetch data.

jQuery could make this very simple using the ajax plugin.

this is a departure from the postback model which you are currently working with. The idea of server controls also disappears as you are working with the html more than the client code.

Jason Meckley
Programmer
Specialty Bakers, Inc.
 
you can use the MultiView control to show and hide portions of the page. I usually use this when I need to display a form, then the user submits, and a thank you message is displayed. With a multiview control, you stay on the same page.
 
there is no practical difference between MultiView and redirection. My guess is the user wants all the data visible when editing.

WyldGynjer is using 1.1 where Multiview doesn't exist.

Jason Meckley
Programmer
Specialty Bakers, Inc.
 
Sorry WyldGynjer, I missed that you were using 1.1, there is no MultiView control available.

there is no practical difference between MultiView and redirection.
True but:
Thanks Jmeckley - Sending the user to another page is not what the client wants -

My understanding was they only wanted on "Physical Page" perhaps for tracking etc. If it's just a matter of keeping all the data on screen during an edit, then my suggestion is not an option.
 
Thanks jbenson001 I appreciate any thoughts...I wasn't sure about the multiView - but figured I'd look it up when I tried a few other things - glad to know I don't need to since it doesn't exist for 1.1 :)

They want to be able to display the child data - and update it - then collapse it so you can't see it - then change other data...theres alot to think about - like when they collapse the datagrid -- I need to make sure it saves right...etc...but right now - i just want a button to work!! It seems like this would be a common thing that they want...I'll keep on trekin' - my attempt to apply what was in that article from MS didn't work....so must try again...
 
There are probably 3rd party objects out there that you could buy that would you life easier. It's just a matter of budge I suppose.
 
Have you tried putting a datagrid in one of your datagrid rows and populating it that way?

I've implemented what you want before, using datagrids. It is possible.

Perhaps when they collapse the detail datagrid, you use an image button with the + and - sign and on the click event do the updating. attach some javascript that switches the style to display:none or display:block accordingly.

Hope this helps somewhat.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

- The Complete IT Community
 
I've put together the following example to help you conceptualize one way you can achieve your goal. There are probably better ways you can do this, but for sake of a simple example this is what I've put together. I've written the example using C#, so you'll have to convert it if you want to use vb. There are many online utilities you can use if you need help converting it.

Markup:
Code:
<asp:DataGrid id="dgParent" runat="server" AutoGenerateColumns="False" DataKeyField="id">
	<Columns>
		<asp:TemplateColumn>
			<HeaderTemplate>
				<table id="tblHeader" runat="server">
					<tr>
						<td></td>
						<td>
							<asp:Label ID="lblAccountHeader" Runat="server">Account</asp:Label>
						</td>
						<td>
							<asp:Label ID="lblAccountNameHeader" Runat="server">AccountName</asp:Label>
						</td>
						<td>
							<asp:Label ID="lblTotalHeader" Runat="server">Total</asp:Label>
						</td>
					</tr>
				</table>
			</HeaderTemplate>
			<ItemTemplate>
				<table id="tblParent" runat="server">
					<tr>
						<td>
							<asp:LinkButton ID="lbExpand" Runat="server" style="text-decoration:none" CommandArgument="Expand">+</asp:LinkButton>
						</td>
						<td>
							<asp:Label ID="lblAccount" Runat="server">
								<%# DataBinder.Eval(Container.DataItem, "Account") %>
							</asp:Label>
						</td>
						<td>
							<asp:Label ID="lblAccountName" Runat="server">
								<%# DataBinder.Eval(Container.DataItem, "AccountName") %>
							</asp:Label>
						</td>
						<td>
							<asp:Label ID="lblTotal" Runat="server">
								<%# DataBinder.Eval(Container.DataItem, "Total") %>
							</asp:Label>
						</td>
					</tr>
					<tr>
						<td></td>
						<td colspan="3">
							<asp:DataGrid id="dgChild" runat="server" AutoGenerateColumns="False" DataKeyField="id" OnEditCommand="dgChild_EditCommand" OnCancelCommand="dgChild_CancelCommand" OnUpdateCommand="dgChild_UpdateCommand" >
								<Columns>
									<asp:BoundColumn HeaderText="Bill" DataField="Bill"></asp:BoundColumn>
									<asp:BoundColumn HeaderText="Amount to Apply" DataField="Amount"></asp:BoundColumn>
									<asp:EditCommandColumn EditText="Edit" CancelText="Cancel" UpdateText="Update"></asp:EditCommandColumn>
								</Columns>
							</asp:DataGrid>
						</td>
					</tr>
				</table>
			</ItemTemplate>
		</asp:TemplateColumn>
	</Columns>
</asp:DataGrid>

code-behind
Code:
private void Page_Load(object sender, System.EventArgs e)
{
	// Put user code to initialize the page here
	if(!IsPostBack)
	{
		//Create fake DataSource for parent grid and bind
		DataTable dt = CreateDataSource();
		dgParent.DataSource = dt;
		dgParent.DataBind();
		//Create a collection in session to hold child grid data sources
		Session["ChildDataSourceCollection"] = new Hashtable();
	}
}
private void dgParent_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
	HtmlTable table = new HtmlTable();
	
	//Find table container so we can set cell widths later
	if(e.Item.ItemType == ListItemType.Header)
		table = (HtmlTable)e.Item.FindControl("tblHeader");

	if(e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
	{
		//Find table container so we can set cell widths later
		table = (HtmlTable)e.Item.FindControl("tblParent");
		//Create reference to DataRow corresponding to current index
		DataTable dt = (DataTable)dgParent.DataSource;
		DataRow dr = dt.Rows[e.Item.DataSetIndex];
		//Check value of HasChildren column to determine if the lbExpand should be visible
		bool hasChildren = bool.Parse(dr["HasChildren"].ToString());
		LinkButton lbExpand = (LinkButton)e.Item.FindControl("lbExpand");
		//Set visibility for lbExpand
		lbExpand.Visible = hasChildren;
	}

	//Set widths of header and item cells so they line up
	if(e.Item.ItemType != ListItemType.Footer)
	{
		int cells = table.Rows[0].Cells.Count;
		for(int i = 0; i<cells;i++)
		{
			string width = i == 0 ? "20px" : "100px";
			table.Rows[0].Cells[i].Width = width;
		}
	}
}
private void dgParent_ItemCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
{
	//Find child grid
	DataGrid dgChild = (DataGrid)e.Item.FindControl("dgChild");
	//Find lbExpand
	LinkButton lb = (LinkButton)e.Item.FindControl("lbExpand");

	if(lb.CommandArgument == "Expand")
	{
		//Check child data source collection to see if we already retrieved data
		Hashtable hashTable = (Hashtable)Session["ChildDataSourceCollection"];
		DataTable dt;
		//If there isn't a table in the collection then retrieve data
		if(!hashTable.Contains(e.Item.ItemIndex))
		{
			//In this case we're creating another fake data source 
			int key = int.Parse(dgParent.DataKeys[e.Item.ItemIndex].ToString());
			dt = CreateChildDataSource(key);
			//Add the DataTable to the child data source collection
			hashTable.Add(e.Item.ItemIndex,dt);
		}
		else
		{
			//There's already a table in the child collection so grab it
			dt = (DataTable)hashTable[e.Item.ItemIndex];
		}
		//Bind data to child grid and make sure header displays
		dgChild.DataSource = dt;
		dgChild.DataBind();
		dgChild.ShowHeader = true;
		//Change the command argument and text of lbExpand so we can collapse it next time
		lb.CommandArgument = "Collapse";
		lb.Text = "-";
	}
	else
	{
		//Collapse the child grid
		if(lb.CommandArgument == "Collapse")
		{
			//Set the DataSource to nothing, re-bind it, and make sure header doesn't display
			dgChild.DataSource = "";
			dgChild.DataBind();
			dgChild.ShowHeader = false;
			//Change the command argument and text of lbExpand so we can expand it next time
			lb.CommandArgument = "Expand";
			lb.Text = "+";
		}
	}
	
	
}
//Returns the RowIndex of the child grid's parent
private int GetParentIndex(DataGrid dgChild)
{
	DataGridItem parentRow = (DataGridItem)dgChild.Parent.BindingContainer;
	return parentRow.ItemIndex;
}
protected void dgChild_EditCommand(object source, DataGridCommandEventArgs e)
{
	DataGrid dgChild = (DataGrid)source;
	//Set the edit index
	dgChild.EditItemIndex = e.Item.ItemIndex;
	//Grab the data source from the child data source collection
	Hashtable hashTable = (Hashtable)Session["ChildDataSourceCollection"];
	int parentIndex = GetParentIndex(dgChild);
	DataTable dt = (DataTable)hashTable[parentIndex];
	//Re-bind the child grid so we can edit the row
	dgChild.DataSource = dt;
	dgChild.DataBind();
}

protected void dgChild_CancelCommand(object source, DataGridCommandEventArgs e)
{
	DataGrid dgChild = (DataGrid)source;
	//Remove the edit index
	dgChild.EditItemIndex =  -1;
	//Grab the data source from the child data source collection
	int parentIndex = GetParentIndex(dgChild);
	Hashtable hashTable = (Hashtable)Session["ChildDataSourceCollection"];
	DataTable dt = (DataTable)hashTable[parentIndex];
	//Re-bind the child grid so we can't edit the row
	dgChild.DataSource = dt;
	dgChild.DataBind();
}
protected void dgChild_UpdateCommand(object source, DataGridCommandEventArgs e)
{
	DataGrid dgChild = (DataGrid)source;
	//Grab the data source from the child data source collection
	int parentIndex = GetParentIndex(dgChild);
	Hashtable hashTable = (Hashtable)Session["ChildDataSourceCollection"];
	DataTable dtOld = (DataTable)hashTable[parentIndex];
	
	//Get the new text
	TextBox txtBill = (TextBox)e.Item.Cells[0].Controls[0];
	TextBox txtAmount = (TextBox)e.Item.Cells[1].Controls[0];
	string bill = txtBill.Text;
	string amount = txtAmount.Text;

	//Normally we would save back to the database here, but since our data source
	//is in memory we'll copy it, delete the old row from the copy, and add a new one to it

	//Gets the row index for the data source
	int dataIndex = e.Item.DataSetIndex; 
	//Copies the DataTable so we can we can control where the new row is added
	DataTable dt = dtOld.Copy(); 
	//Removes old row
	dt.Rows.RemoveAt(dataIndex); 
	//Create new row with updated values
	DataRow dr = dt.NewRow(); 
	dr["id"] = dgChild.DataKeys[e.Item.ItemIndex].ToString();
	dr["Bill"] = bill;
	dr["Amount"] = amount;
	//Insert new row in same place we deleted the old one from
	dt.Rows.InsertAt(dr,dataIndex); 

	//Remove the edit index and re-bind the child grid to the copied DataTable
	dgChild.EditItemIndex = -1;
	dgChild.DataSource = dt;
	dgChild.DataBind();

	//Save the copied table back to the collection
	hashTable[parentIndex] = dt;
}
private DataTable CreateDataSource()
{
	DataTable dt = new DataTable();
	dt.Columns.Add("id");
	dt.Columns.Add("Account");
	dt.Columns.Add("AccountName");
	dt.Columns.Add("Total");
	dt.Columns.Add("HasChildren");
	for(int i = 0;i<5;i++)
	{
		DataRow dr = dt.NewRow();
		dr["id"] = i.ToString();
		dr["Account"] = i.ToString();
		dr["AccountName"] = "Name" + i.ToString();
		dr["Total"] = i.ToString() + ".00";
		dr["HasChildren"] = i % 2 == 0 ? true : false;
		dt.Rows.Add(dr);
	}
	return dt;

}
private DataTable CreateChildDataSource(int parentKey)
{
	//Were not actually doing anything with the parent key
	//but if you were retrieving from the database you would use it
	DataTable dt = new DataTable();
	dt.Columns.Add("id");
	dt.Columns.Add("Bill");
	dt.Columns.Add("Amount");
	for(int i = 0;i<2;i++)
	{
		DataRow dr = dt.NewRow();
		dr["id"] = i.ToString();
		dr["Bill"] = "Bill" + i.ToString();
		dr["Amount"] = i.ToString() + ".00";
		dt.Rows.Add(dr);
	}
	return dt;
}

Let me know how you fare out or if you have any questions.

Good luck!

 
KDavie - Thank you for that example. However I still have the same problem - when I press the + all it does it reload the page - and it doesn't expand a column or create a child...I would have assumed it would have triggered dgParent_ItemCommand right? what version of .net/VS are you using?

I used what I read in the microsoft help that I posted the other day - and I'm able to finally get my grid to fire an event --- however now the child grid is in a column not in a separate row....I'm going to see if that applies to your example

Thank you again - that does help me as I know I'm on the right track and it's encouraging to know it really is possible - I just haven't got it yet :)
 
I wrote this example using VS 2003 (1.1 framework) and it works as expecting for me.

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top