I have a user control comprised of two nested accordions with a repeater inside, all of which are pulled from a database. The repeater has CheckBoxes with MutuallyExclusiveCheckBoxExtenders inside. When the page posts back I am attempting to use LINQ along with a recursive FindControl function to gather all of the CheckBoxes. The issue is that the CheckBoxes are not being located.
Basic Structure, cleaned up for brevity:
<ajaxToolkit:Accordion ID="acc1">
<ContentTemplate>
<ajaxToolkit:Accordion ID="acc2">
<ContentTemplate>
<asp:Repeater ID="r1">
<ItemTemplate>
<asp:CheckBox ID="cb1" />
<asp:CheckBox ID="cb2" />
<ajaxToolkit:MutuallyExclusiveCheckBoxExtender ID="mece1" TargetControlID="cb1" />
<ajaxToolkit:MutuallyExclusiveCheckBoxExtender ID="meceMine2" TargetControlID="cb2" />
</ItemTemplate>
</asp:Repeater>
</ContentTemplate>
</ajaxToolkit:Accordion>
</ContentTemplate>
</ajaxToolkit:Accordion>
Everything displays correctly. On button click, elsewhere on the page, I am attempting to gather all of the controls and only the accordion controls, some literals, and the hidden fields I am using are being displayed.
Here is the recursive FindControls function I am using:
public static void FindControls(Control start, List<Control> list)
{
list.Add(start);
foreach (Control c in start.Controls)
{
FindControls(c, list);
}
}
This function is never finding the CheckBoxes in order to return them even though it displays correctly.
Related
I'm working on an ASP.NET Web Forms project. I have Update Panel and within this panel I have Repeater with several nested repeaters inside it. So my markup looks like this:
<asp:UpdatePanel ID="UpProducts"
UpdateMode="Always"
runat="server">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btnAllocate"/>
</Triggers>
<ContentTemplate>
<asp:Repeater ID="RpUserNameAndProductName"
runat="server">
<ItemTemplate>
..
<asp:Repeater ID="RpProductDescription"
runat="server">
<HeaderTemplate>
//sonme data
</HeaderTemplate>
<ItemTemplate>
//some data
</ItemTemplate>
<asp:Repeater ID="AnotherRepeater"
runat="server">
<HeaderTemplate>
//sonme data
</HeaderTemplate>
<ItemTemplate>
//some data
</ItemTemplate>
</ItemTemplate>
</ContentTemplate>
</asp:UpdatePanel>
<asp:button id="btnAllocate"
text="Allocate"
usesubmitbehavior="false"
onclick="btnAllocate_Click"
runat="server"/>
A little confusing markup maybe but the idea is that when I click btnAllocate I want to get the values from some textboxes of the nested repeater which I populate at the beginning from the database and then based on their value calculate and set the value of another textbox which initially is empty untill the user decides that he wants the system to do the allocation for him.
So when I enter in the btnAllocate_Click event in code behind I have :
try
{
foreach (RepeaterItem item in RpUserNameAndProductName.Items)
{
Repeater innerRepeater = (Repeater)item.FindControl("RpProductDescription")
foreach (RepeaterItem innerItem in innerRepeater.Items)
{
//here I collect the values and calculate/set the empty text box value
TextBox txtSomeCalculatedBox = (TextBox)innerItem.FindControl("txtCalculated");
txtSomeCalculatedBox.Text = //here I execute DB query
}
Repeater anotherInnerRepeater = (Repeater)item.FindControl("AnotherRepeater")
foreach (RepeaterItem aInnerItem in anotherInnerRepeater .Items)
{
//here I collect the values and calculate/set the empty text box value
}
}
}
catch (Exception ex)
{
//How to stop the values from being updated?
}
So basically this is where I'm confused. In order to calculate the new value I need to execute DB queries based on the information that I fetch from the Repeaters so there's room for errors. How ever If the first foreach goes well and the error is thrown from the second or third and so on the repeaters are updated with the newly calculated values. It's logical since I set the value inside the foreach where I get the control.
so what I want to do is if I get in the catch block (an excpetion is thrown) just to stop the update panel from partially update panel/the repeaters from updating the information.
I have added a checkbox to the rows of a gridview that when clicked will expand a panel in the row. My gridview needs to use paging, so I am saving in a session variable the state of the current page before the page change. When the user clicks back to a page I am repopulating the checkboxes, but this does not expand the panels. Is there away to expand the panels from the code behind?
<asp:CheckBox runat="server" Text="Order Updated Records" ID="cbUR" Visible='<%# !DBNull.Value.Equals(Eval("AnyBox"))%>' />
<asp:CollapsiblePanelExtender ID="cInst" runat="server" TargetControlID="inst" Collapsed="true" AutoExpand="true" AutoCollapse="false" ExpandControlID="cbUR" CollapseControlID="cbUR" />
<asp:Panel ID="Inst" runat="server">
<asp:TextBox runat="server" ID="txtInst" TextMode="MultiLine" Width="200" />
</asp:panel>
I tried adding the panel, textbox and panel extender from the code, but could not get it to work. I read on a different post that the whole gridview would need to load from the code for this to work.
I would love to use something like
<asp:CollapsiblePanelExtender ID="cInst" runat="server" TargetControlID="inst" Collapsed='<%#!Convert.ToBoolean(rowItems[index].ToString()) %>' />
Would it be better not use the CollapsiblePanelExtender and find a different way to show the panels?
You could try adding a public/protected method:
public bool IsCollapsed(object rowId) {
//get row by ID here and return true if collapsed
return ....
}
that would return a value for a row that needs to be expanded or collapsed.
and use it like:
Collapsed='<%# IsCollapsed(Eval("RowId")) %>'
where RowId is a property that represents the ID of the item.
I am trying to have a collapsible panel inside of a listview item. In the item template, I have a panel, and a collapsible panel extender. In order to set the attributes TargetControlID, CollapseControlID, etc., I need the ClientIDs that are generated after databinding for each of the listview items. Does anyone know how I can set those attributes client-side?
I've tried various things along the lines of the following:
<ItemTemplate>
<asp:Panel ID="ManagingPanel" runat="server">
</asp:Panel>
<asp:CollapsiblePanelExtender runat="server" TargetControlID='<%="ManagingPanel.ClientID" %>' />
</ItemTemplate>
SOLUTION - Turns out you do not need to use the ClientID. The Extender will recognize that its target is inside the same listview item.
<asp:CollapsiblePanelExtender runat="server" TargetControlID="ManagingPanel" />
I have create a custom user control that includes the CollapsiblePanelExtender and every other think that I like to show, a complex html struct, and then I have include this control in the repeater.
The repeater pass the data that I need to render my custom control, and then the custom control render its self in every line of the repeater, and all is working fine.
something like
<asp:Repeater ID="myRepeater" runat="server">
<ItemTemplate>
<uc1:MyCustonControl ID="lPro" runat="server" data="<%#PassData%>" />
</ItemTemplate>
</asp:Repeater>
Turns out you do not need to use the ClientID. The Extender will recognize that its target is inside the same listview item.
<asp:CollapsiblePanelExtender runat="server" TargetControlID="ManagingPanel" />
Is it possible to use an UpdatePanel that has like a few text boxes, and a search button, and then perhaps another UpdatePanel that has a gridview in it to return the results of what was searched. When the user clicks search it hides the boxes, and displays the gridview. Can I do this with UpdatePanels? I am using c# for my coding. Or should I be doing this another way?
You only need one UpdatePanel in that case and setup a Trigger to your search Button.
Put only the controls that will be refreshed in your UpdatePanel.
Example:
<asp:TextBox ID="txtSearchCriteria" runat="server" />
<asp:Button ID="btnSearch" runat="server" OnClick="btnSearch_Click" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btnSearch" EventName="Click" />
</Triggers>
<ContentTemplate>
<asp:GridView ID="grdSearchResults" runat="server">
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
Implement the btnSearch_Click function to execute your search and bind the results to the GridView. The UpdatePanel will handle the ajax call and replacing of the HTML that the GridView will produce.
You want to keep as much out of the UpdatePanel as possible and only include what will actually change because it is transmitting that HTML with each update so it's a waste of resources if you are not actually doing anything to those controls with each action. That is why a trigger is best to be used in this case which will hook the UpdatePanel to the Click event outside of the UpdatePanel scope.
Read up more on UpdatePanel and how triggers work on MSDN.
If I understand the question correctly, you can do that with two <asp:Panel> controls inside the <UpdatePanel>. One panel for the textboxes, and the other for the gridview. You set which panel to show in the codebehind, depending on whether you're expecting your user to enter search criteria, or review the search results.
Yes you can.
You also could use just one update panel. Since you the Search Form (could be in a Panel) and the GridView inside the UpdatePanel.
To make certain form creation easier, we use a modified Formview control that is inside a User Control. This User Control is for a grid and a FormView, you can choose an item in the grid, and a FormView is presented in a modal for viewing/editing:
<I2CL:Grid runat="server" ID="Grid" OnSelecting="Selecting" ShowCreate="true" />
<I2:Modal ID="SFModal" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<I2:FormView runat="server" ID="FVSubForm" DefaultMode="Edit" DataSourceID="DSSubForm" />
<I2:ILDataSource ID="DSSubForm" runat="server" />
</ContentTemplate>
</I2:Modal>
In a page, the control looks like this:
<I2C:TabGrid ID="TG" runat="server" Property="ParentProperty">
<Columns>
<I2:Column Header="Column 1" DataSource="Column1" />
<I2:Column Header="Column 2" DataSource="Column2" />
</Columns>
<EditItemTemplate>
<I2Form:Dropdown ID="Col1" runat="server" SelectedValue='<%# Bind("Column1") %>' List="Column1Options" />
<I2Form:Textbox ID="Col2" runat="server" Text='<%# Bind("Column2") %>' />
</EditItemTemplate>
</I2C:TabGrid>
The problem is the EditItemTemplate we use. The only way I can figure out how to hook it up is to have an ITemplate in the TabGrid control and apply the reference in OnInit:
[PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(FormView))]
public ITemplate EditItemTemplate { get; set; }
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
FVSubForm.EditItemTemplate = EditItemTemplate;
}
The problem with this is that because the reference is to an object in the user control, the EditItemTemplate reference that ties to the dictionary entries in FormView for changes are destroyed, so when you get the dictionary of changes sent to the datasource, they're empty on every postback.
The I2:ILDataSource used here is a custom implementation closest to ObjectDataSource. Instead of a generic object call, it directly calls a GetEntity() in the page (or user control in this case) and a UpdateEntity(obj Entity) to save. Since it's a very specific scenario, we can eliminate 90% of the code in ObjectDataSource.
What I want to be able to do is point the <EditItemTemplate> in the <I2C:TabGrid> directly to the <EditItemTemplate> of the <I2:FormView> inside. Is this possible, or anyone have suggestions of another route to go?
Note: I tried exposing the EditItemTemplate on FVSubForm as a proxy property, but this didn't work because the property is set on the user control before the child control is created, so FVSubForm is null. If this can be worked around, I'm certainly all ears.
One thing you need to do is mark your ITemplate property as supporting two-way databinding:
[TemplateContainer(typeof(FormView), System.ComponentModel.BindingDirection.TwoWay)]
Without this, ASP.NET will not generate the proper code for the page that allows Bind() expressions to work.
I'm not sure if that's all you need, but that's something to try.
David