Updating a Hybrid DetailsView (with static and dynamic fields) - asp.net

I've got a DetailsView which has some static fields defined in my markup and adds some extra fields dynamically in the VB codebehind. It should be noted that the DetailsView lives in an UpdatePanel.
Assume that I would like to get inventory information for a number of warehouses, but I only want to see those which have stock on hand. In addition I have some static information such as product, sku, etc.
The problem I was having was that every time I clicked the "Details" button it would just keep appending the dynamic fields to the end and never re-rendering the table. I figure I either need a conditional or to tell it to re-render somehow. I suspect this problem arises from my use of the UpdatePanel but it's important for the user experience of my customer to keep this.
'Warehouse Stock by Location
Dim stockDT As New DataTable
For Each row As DataRow In stockDT.Rows 'Add column for each Warehouse that has stock
'Adds the fields to the DetailsView dynamically for Warehouses with stock.
Dim col As New DataColumn(WarehouseLocID + row(WarehouseLocID).ToString)
Dim bf As New BoundField
bf.HeaderText = "Warehouse " + row(WarehouseLocID)
bf.DataField = col.ColumnName
'NEED HELP HERE! HOW TO UPDATE OR RE-CREATE THE DETAILSVIEW TO REFLECT NEW INFO?
If Not SAPDetailsView.HasControls Then 'BROKEN
SAPDetailsView.Fields.Add(bf)
Else
SAPDetailsView.Fields.Item(0).HeaderText = bf.DataField 'UPDATES THE WRONG "ITEM" FIELD VALUE!!
End If
Next
Here's the very basic markup.
<asp:UpdatePanel ID="statusUpdatePanel" runat="server">
<ContentTemplate>
<asp:GridView ID="ProductsTable" DataSourceID="ProductsSDS">
<Columns>
<asp:ButtonField ButtonType="Button" Text="Details" CommandName="Select" />
</Columns>
</asp:GridView>
<asp:DetailsView ID="WarehouseStock" runat="server" AutoGenerateRows="False">
<Fields>
<asp:BoundField HeaderText="Product" DataField="Product" />
<asp:BoundField HeaderText="Unit Price" DataField="UnitPrice" />
<asp:BoundField HeaderText="SKU" DataField="SKU" />
</Fields>
</asp:DetailsView>
<!-- The Dynamic Fields are appended in the codebehind -->
</ContentTemplate>
</asp:UpdatePanel>

Based on your logic, my guess is that you are not clearing SAPDetailsView prior to updating it with new data. Also note that dynamically added fields usually need to be added in the page_init event in order to survive postback.
If you are just sending the fields to the user and not expecting any data back, then this won't matter, but you also won't get any fields posted back.

Related

How do I make a read only cell editable in a gridview control in asp using vb?

Hi me again with another question.
I'm using VS2019 and VB on an aspx page. I have a gridview that has Edit enabled.
enter image description here
When a user clicks the EDIT link Progress % and Comments become editable.
What I want to do is make the Goal be editable if Approved is set to No.
How can I do this?
<asp:GridView ID="grdgoals" runat="server" AutoGenerateColumns="False" DataSourceID="DS1" Height="225px" Width="1001px" BorderColor="#003960" BorderStyle="Solid" BorderWidth="1px" DataKeyNames="goalid" EmptyDataText="No goals found." Font-Bold="True" Font-Names="Calibri" Font-Overline="False" Font-Size="Medium" Font-Strikeout="False" ForeColor="#00AD86" ShowHeaderWhenEmpty="True" AllowSorting="True" style="margin-right: 21px">
<Columns>
<asp:BoundField DataField="goalid" HeaderText="goalid" ReadOnly="True" SortExpression="goalid" Visible="False" />
<asp:CommandField EditText="EDIT" ShowEditButton="True" ShowHeader="True">
<HeaderStyle BorderColor="#003960" />
<ItemStyle Font-Names="Calibri" Font-Underline="True" ForeColor="#006EAA" HorizontalAlign="Center" BorderColor="#003960" Width="20px" />
</asp:CommandField>
<asp:BoundField DataField="goaltext" HeaderText="Goal" SortExpression="goaltext" ReadOnly="True" >
<HeaderStyle BorderColor="#003960" />
<ItemStyle BorderColor="#003960" Width="250px" />
</asp:BoundField>
<asp:BoundField ConvertEmptyStringToNull="True" DataField="type" HeaderText="Type" ReadOnly="True" >
<ItemStyle Width="70px" />
</asp:BoundField>
<asp:BoundField DataField="progress" HeaderText="Progress %" SortExpression="progress" >
<HeaderStyle BorderColor="#003960" />
<ItemStyle HorizontalAlign="Center" BorderColor="#003960" VerticalAlign="Middle" Width="20px" />
</asp:BoundField>
<asp:BoundField DataField="comments" HeaderText="Comments" SortExpression="comments" ItemStyle-Wrap="true">
<ControlStyle Height="400px" Width="240px" />
<HeaderStyle BorderColor="#003960" />
<ItemStyle BorderColor="#003960" Width="250px" HorizontalAlign="Left" VerticalAlign="Middle" />
</asp:BoundField>
<asp:BoundField DataField="approved" HeaderText="Approved" ReadOnly="True" SortExpression="approved" >
<ItemStyle HorizontalAlign="Center" Width="40px" />
</asp:BoundField>
</Columns>
<EditRowStyle HorizontalAlign="Left" VerticalAlign="Top" />
<HeaderStyle ForeColor="#006EAA" />
</asp:GridView>
I have searched online looking for answer but haven't found anything.
Ok, so how much extra code is it to simple drop in a "plain jane" button to click on and edit?
It not much!!!
You do have to bear the cost/time of building a form layout for the one row, but at the end of the day:
this tends to be "easy" for the users.
You can also then move the row delete button to that "form layout" area for editing one record. (this means you don't need a delete record button on the GV - not a huge deal, but that means users have to make a "bit more" effort to delete a row of data if that option is desired).
However, the "why" of having a detail edit layout?
You can now have MUCH more control over your logic (such as the rule to allow what to edit, change or even "enable" based on other values. And such code is clean easy server side code.
And this allows far more code logic into the editing of that data. (required fields, formatting, all that stuff.
Best bonus feature?
Such a approach now ALSO allows adding of new records in a MUCH more user friendly way, since now the "edit" area you build ALSO can be used as the "add new" record area. (so, you save having to write separate code for edit and adding - the one solution does both). And this also means the end user has the "same" experience when editing existing, or adding new - it shortens the user learning curve.
Now, about the only downside?
Well, when starting out, those wizards can be a real gift horse. And I still after all these years often use the wizard(s) to create the GV.
however, I THEN clean it up a bit, and remove things from the page.
(such as removing the datasource on the page).
And the other tip?
If possbile, build a few "helper" routines. They can be used over and over.
For example, I became VERY tired of having to type in code with a row of data, and then "please fill out" those controls on the page with that data.
Then after done, we have to take that those controls, and send back to the database. We do this "over and over". And the code to do this is darn near the "same" over and over. So, then, why not write one sub routine that can do this for ANY page we work on!!!
So, here is a working approach.
for the GV, then the markup becomes rather clean - just some fields.
Say, like this markup:
<asp:GridView ID="GridView1" runat="server" Width="40%"
AutoGenerateColumns="False" DataKeyNames="ID"
CssClass="table table-hover table-striped" >
<Columns>
<asp:BoundField DataField="FirstName" HeaderText="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:BoundField DataField="Description" HeaderText="Description" />
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="cmdEdit" runat="server" Text="Edit"
CssClass="btn myshadow"
OnClick="cmdEdit_Click"
/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Note a few things in above:
If we don't use width, or use 100%? The GV will expand to the WHOLE page - it is what we call "responsive".
I assume that you have bootstrap installed, and most projects do (by default).
The result is a "very" nice grid, and the fonts + spacing and layout is "tweaked" by bootstrap. The result is quite nice.
Code to load the GV is this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
LoadData()
End If
End Sub
Sub LoadData()
Dim strSQL = "SELECT * FROM tblHotelsA ORDER BY HotelName"
Dim cmdSQL As New SqlCommand(strSQL)
GridView1.DataSource = MyrstP(cmdSQL)
GridView1.DataBind()
End Sub
Note the "helper" routine MyrstP. I use this "over and over" everywhere.
(I'll post that at end of this post).
So, I removed the Sqldatasourc1 from the page. I find that you obtain/enjoy MUCH more control over the code. (but, if you don't have a helper routine, then setting up a query + connection and all that jazz? Then you might as well just stick with the wizard generated SqlDataSource on the page. But, you find over time that using code behind is a "less effort" choice, and one that gives you far more control (say to filter, or do other things).
And note the table hover (you get a "nice" row hover effect), and note the table-striped. That gives you a nice alternating row shade - and you do not need a messy "alternating" template.
So far? We have a VERY clean markup, and so far VERY little code has been written.
Now, not the button we dropped into the GV. That is a plan jane asp.net button. We don't need to use "speical" commands, "speical" code or anything. Just a good old fashioned button and button click.
However, do note that we have a click event for the button. Normally, I often just double click on a button, and then a code behind stub is created, and we are jumped to the code behind editor.
however, since the button is "nested" in a GV, then we can't double click on that button to create a event.
So, flip to markup. And in markup type in
OnClick=
WHEN you hit the "=", then intel-sense should pop up and gives you the option to create the event. (since we can't use double click on button to do this).
So, it looks like this:
And thus the code is for button click:
Protected Sub cmdEdit_Click(sender As Object, e As EventArgs)
Dim btn As Button = sender
Dim gRow As GridViewRow = btn.NamingContainer
Dim intPK As Integer = GridView1.DataKeys(gRow.RowIndex).Item("ID")
Dim cmdSQL = New SqlCommand("SELECT * FROM tblHotelsA WHERE ID = #ID")
cmdSQL.Parameters.Add("#ID", SqlDbType.Int).Value = intPK
Dim rstHotel As DataTable = MyrstP(cmdSQL)
ViewState("PKID") = intPK
fLoader(EditRecord, rstHotel.Rows(0))
GridView1.Visible = False ' Hide GV
EditRecord.Visible = True ' show edit area
End Sub
Again, note the small amount of code.
The button click thus:
we get GV row
we get GV row PK database ID (using datakeys feature)
we pull that data row into data table
we call the floader() routine that takes ONE row, fills out controls.
the resulting effect is thus this:
And our helper MyRstP routine is this:
Public Function MyrstP(cmdSQL As SqlCommand,
Optional cmdOnly As Boolean = False) As DataTable
Dim rstData As New DataTable
Using mycon As New SqlConnection(GetConstr)
Using (cmdSQL)
cmdSQL.Connection = mycon
mycon.Open()
If cmdOnly Then
cmdSQL.ExecuteNonQuery()
Else
rstData.Load(cmdSQL.ExecuteReader)
End If
End Using
End Using
Return rstData
End Function
I'll post more code from above (later this evening)., and say have some "conditional" code. Say if the hotel not active, we can't edit description.

Dynamically created Labels and TextBoxes are unable to be positioned

I am building a dynamically created popup window that contains a dynamically created grid and controls (Labels and TextBoxes).
The grid part is working correctly, and not an issue. However, the controls are giving me issues.
The idea is to create a Label and a TextBox for each of the columns in the Grid. I am able to create them fine, but am unable to position them at all. label.Location, label.Top, label.point, etc. return an error saying that it is not part of Label. The same thing happens with the TextBox.
When they do display, they appear in a horizontal row with the TextBox overlapping the Label unless the width is set sufficiently large enough (which of course leave a bunch of empty space).
I would like the to be vertically aligned with the TextBox immediately after the Label.
Something like:
labelText: TextBox
These controls are being added into an asp:Panel (pnlFields)
For Each col As DataColumn In dataTable.Columns
Dim label As New Label()
label.Text = col.ColumnName & ": "
label.Height = 24
label.Width = label.Text.Length()
pnlFields.Controls.Add(label)
Dim textBox As New TextBox()
textBox.Height = 24
textBox.Width = 100
pnlFields.Controls.Add(textBox)
Next
I would like to try to have the Label width be as long as the text it contains instead of static, that way I would (hopefully) be able to set the TextBox location to be right after the Label.
In any event, I can't seem to specify a position at all in order to do this.
Any ideas?
Thanks!
Edit: Markup
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<asp:Panel runat="server" ID="pnlFields" CssClass="FormStyle" Height="600px" />
<asp:Panel runat="server" ID="pnlFinder" CssClass="FormStyle">
<telerik:RadGrid ID="batchRecords" runat="server" Skin="Windows7" ShowHeader="true" AutoGenerateColumns="true" Width="600px" AutoPostBack="true">
<MasterTableView CommandItemDisplay="Bottom" AllowPaging="true">
<CommandItemSettings ShowAddNewRecordButton="false" />
<Columns>
<telerik:GridButtonColumn UniqueName="selectRecord" HeaderText="Edit" CommandName="Select" Text="Edit Record" ButtonType="ImageButton" ImageUrl="~/images/icons/pencil.png">
</telerik:GridButtonColumn>
</Columns>
</MasterTableView>
</telerik:RadGrid>
</asp:Panel>
</asp:Content>
I really, but really don't see the need for creating those text boxes. there is a LARGE number of built in repeating controls. If you create those controls on the fly, then you can NOT wire up the code behind events - so you quite much give up the WHOLE reason for using asp.net and ALL of those great built in controls.
I see no reason why you can inject and include some extra "divs" and apply style to those divs. But, before you go down that road?
You should attempt to make a VERY STRONG case as to why you going down this road as opposed to using a listview, gridview, or a repeater control. They are designed for repeating data over and over - and do so WITH VERY LITTLE code and VERY LITTLE markup.
and where does the data come from that going to fill out these controls? Or even the reverse - where is the reulsts of the controls going to be placed? You see, by using say a repeater or whatever? They are data bound. So, a person can say add 2 or 15 rows of data, and then in ONE sql update operations you can write out ALL of that data! - in other words, you are repeating some controls - but based on what and how is the big question here?
And as noted, if you create + inject such controls, then you can't have nor wire up events for these controls. So, think very long and hard as to the final goal here. You might fix the layout issues, but then your next issue/problem will be event code, and then after that, will be the complex code required to take that input and somehow push it back out to some database. As a result, I really don't recommend this road.
Further more, using one of those built in "repeating" objects allows you with relative ease to enable editing of that data on screen, and as noted, writing back one row, or 15 rows of data can be done with the same code - and with far greater ease then trying to inject dynamic controls into that form. Furthermore, what "id" are you going to use for referencing those controls and once you managed to inject these controls?
The end goal here is the key concept.
I find that gridview is nice - often less mark up. However WHEN you start to have say 3 or more custom controls in that grid? Then I like listview over that of gridview (because listview allows you to drap + drop and use standard asp.net controls WITHOUT having to put that control inside of a template block. But, your case? Only two extra controls for each row of the grid? then sure, stick with grid view.
So, to add those two extra columns to that grid? You can do this:
First, I will use the wizard to create this gridview. I THEN blow out the data sourceID setting and also chop out the datasource control on the form.
So, I count less then 1 minute - drop gridview. Click on the configure data source from the form. This one:
So I configure the data source, generate the gv, AND THEN remove the data source ID and also remove the data source control from the markup. So, it takes 30 seconds of time, and I wind up with this grid:
So, we get this:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
<asp:BoundField DataField="City" HeaderText="City" SortExpression="City" />
</Columns>
</asp:GridView>
And the code to load this grid up is this:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
LoadGrid()
End If
End Sub
Sub LoadGrid()
Using cmdSQL As New SqlCommand("SELECT ID, FirstName, LastName, HotelName, City from tblHotels",
New SqlConnection(My.Settings.TEST3))
cmdSQL.Connection.Open()
GridView1.DataSource = cmdSQL.ExecuteReader
GridView1.DataBind()
End Using
End Sub
And now we have this:
Ok, not bad for less time then it took me to WRITE this post!!!
And note how we only up to about what - 3 lines of code to load up the grid?
Super easy.
Ok, so now lets add a label, and a text box to the grid.
Well, just do this for the gv template:
So, note how we just added two extra columns. The markup is like this:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
<asp:BoundField DataField="City" HeaderText="City" SortExpression="City" />
<asp:TemplateField HeaderText = "My label">
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText = "My Text Box">
<ItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
Now, when we run the above, we get this:
I mean was that easy or what? And I could have placed that lable + text box in the SAME TEMPLATE column. So you quite much free to add those extra columns to that grid, and we did not have to write code - and in fact it was VERY easy, and the result is we now have that extra two columns - and achieve this WITH GREAT ease.
And we can also with relative ease set/change the values of the label and text box - even based on other colum data from that row, and can do that again with great ease.
So, at the end of the day? I don't suggest you try and manual add two columns when we have such a great approach as outlined above. and even better, is the values the user types into each row and text box can via code be pulled and refreenced from that gv - and done so with great ease for 2 rows, or 20 rows of data.
Edit:
Since the controls and addtions to the gv are to be at run time (dynamic), then you STILL WANT to use the gv object model - fighting against that model is a REALLY bad idea - go with the flow.
So, you can add extra bound field columns to the gv this way:
Sub LoadGrid()
Using cmdSQL As New SqlCommand("SELECT ID, FirstName, LastName, HotelName, City from tblHotels",
New SqlConnection(My.Settings.TEST3))
cmdSQL.Connection.Open()
GridView1.DataSource = cmdSQL.ExecuteReader
AddColumns()
GridView1.DataBind()
End Using
End Sub
Sub AddColumns()
Dim lblfield As New BoundField()
lblfield.HeaderText = "Label1"
GridView1.Columns.Add(lblfield)
Dim txtfield As New BoundField()
txtfield.HeaderText = "TextBox"
GridView1.Columns.Add(txtfield)
End Sub
And then on item bound event, you can also then inject the controls of your choosing like this:
Protected Sub GridView1_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles GridView1.RowDataBound
If e.Row.RowType = DataControlRowType.DataRow Then
Dim MyLabel As New Label
MyLabel.ID = "Label1"
MyLabel.Text = "Label Text"
e.Row.Cells(5).Controls.Add(MyLabel)
Dim MyTextBox As New TextBox
MyTextBox.ID = "Textbox1"
MyTextBox.Text = "Text box value"
e.Row.Cells(6).Controls.Add(MyTextBox)
End If
End Sub
so, now with this simple markup:
<link href="Content/cuscosky.css" rel="stylesheet" />
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
<asp:BoundField DataField="City" HeaderText="City" SortExpression="City" />
</Columns>
</asp:GridView>
So, with above code? We get the SAME results, and this output:
Edit:
I solved this by creating an HTML Table in the markup, then programmatically adding Rows and Cells with controls (Labels and TextBoxes) and unique IDs. I was then able to find and populate those controls with the correct data.
Worked MUCH easier than trying to manually position each control.
I was able to solve the issue of positioning the controls.
I needed to use label.Style.Add() and put in the values needeed.
It seems to be working correctly now.
Thanks!

How to use Session to save value on current row of GridView, to be copied into TextBox? - VB.Net

I'm trying to use Session to record the data of the 1st column of current row to be used in another Web From which will be opened using a LinkButton that is in the GridView.
Basically, if I click the LinkButton on the 1st row, the 1st column data of the 1st row will be copied to the next Web Form. But before I do that, I want to do a smaller scale experiment to test it. So instead for now I want the Session to copy the data into a TextBox in the same form.
For reference, here is the design of the GridView, most rows removed since they're not relevant:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
BorderColor="Black" BorderStyle="Solid" BorderWidth="1px" Font-Names="Arial">
<AlternatingRowStyle BackColor="#B7DBFF" />
<Columns>
<asp:BoundField DataField="caseticket" HeaderText="Ticket #" >
<HeaderStyle BackColor="#000066" ForeColor="White" Wrap="False" width="10%"/>
<ItemStyle Wrap="False" />
</asp:BoundField>
<asp:TemplateField ShowHeader="False">
<HeaderStyle BackColor="#000066" ForeColor="White" Wrap="False" width="10%"/>
<ItemTemplate>
<asp:linkbutton ID="newLog" runat="server" onclick = "CaseLog_click" >Add Log </asp:linkbutton>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<HeaderStyle BackColor="#000066" />
<RowStyle HorizontalAlign="Center" />
</asp:GridView>
For the TemplateField is a LinkButton with a onclick property. With it, I created the sub:
Sub CaseLog_click(ByVal sender As Object, ByVal e As EventArgs)
Session("ticket") = GridView1.SelectedRow.Cells(1).Text
'Response.Redirect("~/CaseLog.aspx") ==> will be using this to proceed to next Web Form
TextBox1.Text = Session("ticket") '==> For test use only.
End Sub
If I only kept the Response.Redirect("~/CaseLog.aspx") in the sub, the LinkButton can direct me to the next Web Form. But as it is now, during testing when I use the LinkButton I get an error on the session line of the sub.
Object reference not set to an instance of an object.
Is the code salvageable, or do I need to redo this?
Thanks.
It looks like the button event to select the row is not wired up.
I would use say this:
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
<asp:ButtonField CommandName="Select" HeaderText="Select" ShowHeader="True" Text="Button" />
Note CAREFULL how we put the CommandName="Select" in above. If you don't do this, then the selected row does not come though correctly to the click event you have.
You could try the select command as per above on your link button, but I would just use the above. Now, hightlight the grid in the form desinger. On the properity sheet, go to events, and double click on the SelectedIndex Change event. So, your buttion is not changing the selected index correctly.
The code stub will look like this:
Protected Sub GridView1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles GridView1.SelectedIndexChanged
Dim lgridrow As GridViewRow = Me.GridView1.SelectedRow
Debug.Print("<" & lgridrow.Cells(0).Text & ">")
Debug.Print("<" & lgridrow.Cells(3).Text & ">")
Debug.Print("<" & Me.GridView1.SelectedRow.Cells(3).Text & ">")
End Sub
Note VERY careful how the event code stub is setup - the event args are different then yours.
So, you can try the CommandName="Select" in your existing code, but if not, then try the above button field as opposed to your custom asp.net button you have. As it stands, it don't look like your asp.net button is firing the row-changed event.
Edit and follow up:
Can I have extra buttons - run their own code?
Yes, you can. You can do this several ways (one is to pick up which button was clicked in the SelectedIndex change event.
Or, you can drop in extra buttons and use that event code stub.
So, in my example, lets add an extra button.
we now have this:
<Columns>
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
<asp:ButtonField CommandName="Select" HeaderText="Select" ShowHeader="True" Text="Button" />
<asp:TemplateField>
<ItemTemplate>
<asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click"
CommandName="MySelect" CommandArgument ="<%# Container.DisplayIndex %>"
style="background-color:gray;color:white;text-decoration:none;padding-left:6px;padding-right:6px"
text="Mybutton"></asp:LinkButton>
</ItemTemplate>
</asp:TemplateField>
</Columns>
We thus have this:
Now, we can attach/have button code for the extra button. But NOTE careful we did NOT use the built in SELECT for the command argument. The REASON is that if we have Command=select, then the selected index WILL fire, but AFTER our button code stub. That means we cannot use the selectedrow (too early).
So, what we do/did in above was have the CommandArguemnt PASS the selected row value - that value will pass ok, and thus we don't care that the selected index event does not fire (and by CHANGING our command argument to NOT "select", then in fact the selectedindexchange event DOES NOT fire.
As a result, we use the passed row in the command argument, and we have this for the button code:
Protected Sub LinkButton1_Click(sender As Object, e As EventArgs)
Dim ixrow As Integer = sender.CommandArgument
Debug.Print(Me.GridView1.Rows(ixrow).Cells(0).Text)
End Sub
And note while we are editing the markup, intel-sense will give a list of options when editing. Eg this:
So, that gives us a chance to wire up (add) a standard click event). No selected index code stub is required (since the button would fire before selected index anyway). So we are now manually wiring up this event. We are NOT thus using the selectedindex change event - we don't even need it.
So, now in our button stub, we are free to do anything we want - including jumping to another page
eg:
Protected Sub LinkButton1_Click(sender As Object, e As EventArgs)
Dim ixrow As Integer = sender.CommandArgument
Debug.Print(Me.GridView1.Rows(ixrow).Cells(0).Text)
Session("HotelName") = Me.GridView1.Rows(ixrow).Cells(3)
Response.Redirect("~/ShowHotelDetails.aspx")
End Sub
So, to add separate code buttons:
Don't use selected index change event - you MIGHT still want it to run but it will run/fire AFTER your button code (so can't use selectedrow - too early).
But, you do need Command="myjunk" because without a command, then the command argument does not work. By passing the row index in commandargument, then we are free to grab data from the gridview as per above code via row index.
So, you can well dump the selected index change event. You just have to pass the row index, and work from that. The code stub can thus walk the dog, setup values in session, or even pass/make the url with parameters.

Filter gridview with OnChecked Event

I have a gridview loaded from a database with a checkbox in the first column. What I want is that when clicking on any row the grid will be filtered to show only the rows in which the value of the second cell is the same as the one I clicked on (having the option to select more rows with that value). Removing the checks he would reload all the information again. I tried doing it using the OnChecked event, but due to AutoPostBack it filters but loses the value of the checkbox checked. What is the best way to do what I want? I hope I have been explicit. Thank you
asp:GridView ID="gridview" runat="server" AutoGenerateColumns="false" DataKeyNames="Id">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox ID="chkRow" OnCheckedChanged="chkRow_CheckedChanged" AutoPostBack="true" runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Document" ItemStyle-Width="10%" HeaderText="Documento" />
<asp:BoundField DataField="Entidad" ItemStyle-Width="55%" HeaderText="Entidad" />
</Columns>
For Each row As GridViewRow In gvEncomendasPendentes.Rows
If row.RowType = DataControlRowType.DataRow Then
Dim chkRow As CheckBox = TryCast(row.Cells(0).FindControl("chkRow"), CheckBox)
If chkRow.Checked Then
'Bind with filter
End If
End If
Next
Does the table display the entire dataset at all times? If so, I'd check into client-side filtering (JavaScript). Not having the round trip to the server/database would make your page a lot more responsive.
But assuming that's not the case, and to actually answer the posted question, here's how I'd approach it in the VB.Net Code. Try changing the Change Event like this:
Protected Sub chkRow_CheckedChanged (sender as object, e As EventArgs)
Dim chk As CheckBox = DirectCast(sender, CheckBox)
dim grv As GridRowView = DirectCast(chk.NamingContainer, GridRowView)
dim hdn AS HiddenField = grv.FindControl("hdnFieldWithValue")
If chk.Checked then
AddValueToFilerList(hdn.Value)
Else
RemoveValueFromFilterList(hdn.Value)
End If
BindGridView()
End Sub
Your way does work, but that has a Log(N) time on processing, as it needs to look at every row in the grid. The above way will KNOW which checkbox was checked, and deal with it appropriately.
And this small change to the markup:
<asp:hiddenField runat='server' id='hdnFilterList' />
<asp:GridView ID="gridview" runat="server" AutoGenerateColumns="false" DataKeyNames="Id">
<Columns>
<asp:TemplateField>
<ItemTemplate>
<asp:CheckBox ID="chkRow" OnCheckedChanged="chkRow_CheckedChanged" AutoPostBack="true" runat="server" checked='<%# CheckValueInFilterList(Eval("Entidad")) %>' />
<asp:hiddenField id="hdnFieldWithValue" runat="server" value='<%# Eval("Entidad")' />
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="Document" ItemStyle-Width="10%" HeaderText="Documento" />
<asp:BoundField DataField="Entidad" ItemStyle-Width="55%" HeaderText="Entidad" />
</Columns>
You'll also a function something like this:
Protected Function CheckValueInFilterList(input as string) as boolean
Return MyListOfFilterItems.Contains(input)
End Function
The purpose of that would be to see if the input is contained in your list of filters you're applying to your query and returning true/false depending on that.
Which is directly related to why you're not retaining a check in the checkboxes between postbacks. Since you're rebinding the grid, the existing form state is thrown away in favor of the values from the bind. Since your markup lacked a setter for the checkbox's checked state in the databind, it always resorted to false.
I also added a hidden field to the Markup which would contain the filter list as some type of serialized string (JSON, XML, etc). I'd read it from that field on the Page Load where IsPostBack is True, and always write the list to it in the PreRender event. The point is to just have that passed back and forth through the posted form state so that you can remember them between postbacks.
Maintaining that list of filters in a control outside of the gridview as described does add a bit of overhead in the data that is sent to/from the server on roundtrips. But I wouldn't expect that to a large amount, and would expect a preformance increase due to not having to iterate through the entire gridview item list on every postback.

Dynamic Data - Selecting a table from a DropDownList to scaffold in a GridView

I have been struggling with this ASP.NET Dynamic Data problem for days now... I have a DropDownList containing the table names of all the tables in my data context (.dbml) file. When I select the DropDownList, it needs to scaffold the selected table in a GridView. My code works 100% in scaffolding the MetaTable in the GridView (it implements all the rules that I applied in my Meta Classes).
However, filtering only seems to work if I explicitly add the DynamicExpression in the declaration of the QueryExtender:
<asp:QueryExtender ID="GridQueryExtender" TargetControlID="GridDataSource" runat="server">
<asp:DynamicFilterExpression ControlID="FilterRepeater" />
</asp:QueryExtender>
This in turn requires me to specify the MetaTable explicitly in the LinqDataSource (linqdsData), either programmatically in the Page_Load or in the ASP.NET syntax.
Since the GridView gets scaffolded in the Page_Load part of the life-cycle, the above approach does not work for me, since it takes place in the Page_Init part of the life-cycle.
So my requirement is that as soon as I select another table to populate the GridView with from the DropDownList, the FilterRepeater needs to reflect the filters of the newly selected MetaTable.
Is there any way for me to programmatically update the FilterRepeater in the Page_Load so that it will contain the filters of the MetaTable that I selected in the DropDownList.
The following is some of my code:
ASP.NET Page Code-Behind:
protected void Page_Load(object sender, EventArgs e)
{
if (ddlTable.SelectedIndex > 0)
{
string tableName = ddlDataType.SelectedValue;
linqdsData.TableName = tableName;
MetaTable mt = ASP.global_asax.DefaultModel.GetTable(tableName);
GridViewData.SetMetaTable(mt, mt.GetColumnValuesFromRoute(Context));
GridViewData.EnableDynamicData(mt.EntityType);
GridViewData.DataSourceID = linqdsData.ID;
}
}
ASP.NET Page:
<asp:Panel runat="server" ID="pnlFilters" CssClass="gridFilterCon" EnableTheming="True">
<div class="filterGridHeading">
Filter the grid by:</div>
<asp:QueryableFilterRepeater runat="server" ID="FilterRepeater">
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Eval("DisplayName") %>' OnPreRender="Label_PreRender"
CssClass="gridFilterLbl" />
<asp:DynamicFilter runat="server" ID="DynamicFilter" />
<br />
</ItemTemplate>
</asp:QueryableFilterRepeater>
<asp:Button ID="btnFilter" runat="server" Text="OK"
EnableTheming="False" UseSubmitBehavior="False" OnClick="btnFilter_Click" />
</asp:Panel>
<asp:GridView ID="GridViewData" runat="server" OnSelectedIndexChanged="GridViewData_SelectedIndexChanged"
OnPreRender="GridViewData_PreRender" OnRowDataBound="GridViewData_RowDataBound"
OnPageIndexChanged="GridViewData_PageIndexChanged" AllowPaging="True" PageSize="50" OnInit="GridViewData_Init">
<Columns>
...
</Columns>
<PagerTemplate>
<asp:GridViewPager ID="GridViewPager1" runat="server" />
</PagerTemplate>
<PagerSettings Mode="NumericFirstLast" NextPageText="Next" />
</asp:GridView>
<asp:LinqDataSource ID="linqdsData" runat="server" ContextTypeName="pdcDataContext"
OnSelected="linqdsData_Selected" OnSelecting="linqdsData_Selecting" EnableUpdate="True">
</asp:LinqDataSource>
<asp:QueryExtender ID="GridQueryExtender" TargetControlID="linqdsData" runat="server">
</asp:QueryExtender>
Your help will be greatly appreciated.
It sounds like you are trying to do a lot on one web page. This creates complications of the type you are experiencing: each table requires distinct filters and MetaTables. Trying to keep each item straight requires a bunch of switch and/or if...then statements. I recommend an alternate approach. Instead of doing all of this on one page:
Create a web page for each table
Setup the appropriate filters and MetaTables
Copy the DropDownList containing the table names of all the tables to each web page, and use it to redirect to the appropriate web page.
ASP.net Dynamic Data makes it easy to create web pages for each table. That is what scaffolding is all about. With the approach above, each web page will handle its own set of concerns that are focused on the particular table.
Hope this helps.

Resources