locked
GridView would not allow user to add row RRS feed

  • Question

  • User1216627406 posted

    Greetings everyone.

    Hope everyone is staying safe.

    I have several GridView control IDs, GridView1 through Gridview8.

    Users can enter data into any of the GridView controls and if needed, click button to add a new row.

    This works fine.

    The issue is that assume that a user enters data into three rows of GridView1 but no data entered for the rest of the GridView controls, when rendered, the form shows three rows for the rest of the controls.

    In other words, if any control has one row of data, the rest of the controls show one row of data. If any control has two or more rows of data, the rest of the controls, even though have no data, will show as many rows as the row of the control that has data.

    To keep things short, I am posting the following screenshot that shows three control IDs.

    GridView Controls

    As you can see from the above screenshot, GridView2 has three rows of data and GridView1 and GridView3 also shows three rows of data even though the rows are empty.

    We need only one row of data for any GridView control that is empty.

    To fix this, I added some code to RowDataBound. as shown below:

    '//Markup:

        <asp:GridView ID="Gridview1" runat="server" AutoGenerateColumns="false" OnRowDataBound="Gridview1_RowDataBound">
            <Columns>
                <asp:BoundField DataField="RowNumber" Visible="false" HeaderText="Row Number" />
                <asp:TemplateField HeaderText="Name">
                    <HeaderStyle HorizontalAlign="Left" />
                    <ItemTemplate>
                        <asp:TextBox ID="txtsourcename" Text='<%# Eval("sourcename") %>' placeholder="Name...(e.g, Jane Doe)" runat="server" Style="width: 375px;" AutoPostBack="true" class="form-control textClass"></asp:TextBox><br />
                        <asp:CheckBox ID="grid1Details" ClientIDMode="Static" runat="server" Checked="false" AutoPostBack="true" /><span style="color: #ff0000">*Check this box if N/A</span>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="Address">
                    <ItemStyle HorizontalAlign="Left"></ItemStyle>
                    <ItemTemplate>
                        <asp:TextBox ID="txtsourceaddress" Text='<%# Eval("sourceaddress") %>' placeholder="Address..." runat="server" Style="width: 375px;" class="form-control textClass"></asp:TextBox><br />
                        <br />
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="">
                    <ItemTemplate>
                        <asp:Button ID="ButtonAdd" runat="server" Text="Add another row if needed" CssClass="grvAddButton" /><br />
                        <br />
                        <br>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:TemplateField HeaderText="">
                    <ItemTemplate>
                        <asp:Button ID="sourceDelete" runat="server" Text="Delete" CommandName="Delete"
                            CssClass="grvDelButton" OnClientClick="return confirm('Are you sure you want to remove this row?')" />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
    </asp:GridView>

    '//VB

        Protected Sub Gridview1_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles Gridview1.RowDataBound
            If e.Row.RowType = DataControlRowType.DataRow Then
                'BEGIN: Show only one row of data if row is empty
                If e.Row.RowIndex > 0 Then
                    Dim sourceName As String = (TryCast(e.Row.FindControl("txtsourcename"), TextBox)).Text
                    Dim txtSourceAddress As String = (TryCast(e.Row.FindControl("txtsourceaddress"), TextBox)).Text
                    If String.IsNullOrEmpty(sourceName) AndAlso String.IsNullOrEmpty(txtSourceAddress) Then
                        e.Row.Visible = False
                    Else
                        e.Row.Visible = True
                    End If
                End If
                'END: Show only one row of data if row is empty
                If e.Row.RowIndex = -1 Then
                    DirectCast(e.Row.FindControl("sourceDelete"), Button).Visible = True
                Else
                    If Convert.ToInt32(ViewState("rowIndex" & 0)) = e.Row.RowIndex Then
                        DirectCast(e.Row.FindControl("sourceDelete"), Button).Visible = False
                    End If
                End If
            End If
        End Sub

    This works except that now, if user needs to add an additional row and clicks the 'Add another if needed' button, the additional row is not being added.

    Any ideas how to fix this?

    Many thanks in advance

    Wednesday, May 6, 2020 1:52 PM

All replies

  • User475983607 posted

    Data bound controls reflect the number of rows found in the bound data source.   It's not clear how the 3 data sources are generated but most likely finding where the data sources are populated is the key to fixing this issue.   Fix the data source and the GridViews will follow.

    You approach toggles the visibility of the GridView rows.  Basically you're the UI not the business logic behind generating the data source.  That's why the code does not function as expected.

    I recommend simplifying the design.  Stop caching data in ViewState and use the DB tables.  Move the user inputs outside the GridViews.  When the user clicks "Add another row if needed", save the inputs into a table.  Bind the table results to a GridView.  No you can use all the of the standard GridView features; delete, edit, update.  Plus you'll have less code to maintain and you are not burdened with managing ViewState.  

    Wednesday, May 6, 2020 2:42 PM
  • User1216627406 posted

    I think you are oversimplifying the problem, unfortunately.

    https://www.c-sharpcorner.com/UploadFile/8c19e8/dynamically-adding-and-deleting-rows-in-gridview-and-saving/

    Although, I did not use this link to build it but it follows the same design and logic.

    I am using the DB tables as well as ViewState to store the loops.

    The controls I alluded to are populated from the database but like I said in my original post, because not all controls are populated with data (from the database), they are obviously empty as they should be except that they are mimicking the behavior of controls with data.

    The controls with empty rows display however many rows that the controls with data display.

    Ok, thanks for your help.

    Wednesday, May 6, 2020 4:24 PM
  • User475983607 posted

    I would approach the problem as shown below.  The concept is pretty simple.  Two input fields and a button go with a GridView.  Clicking the button inserts the row into the  in the GridView.  From here you can add the delete, edit, update to GridView.  Also this can be turned into a UserControl rather easily. 

    Public Class IncomeSource
        Public Property IncomeSourceId As Integer
        Public Property Name As String
    End Class
    
    Public Enum IncomeSourceType
        Employee = 1
        Spouse = 2
        IntersetDividens = 3
    End Enum
    <%@ Page Title="" Language="vb" AutoEventWireup="false" MasterPageFile="~/Site.Master" CodeBehind="default.aspx.vb" Inherits="WebFormsVBDemo._default1" %>
    <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
        <div>
            <asp:TextBox ID="EmployeeIncomeName" runat="server"></asp:TextBox>
            <asp:TextBox ID="EmployeeIncomeAddress" runat="server"></asp:TextBox>
            <asp:Button ID="InsertEmployeeIncome" 
                runat="server" 
                Text="Add another row if needed"
                OnClick="InsertEmployeeIncome_Click" />
        </div>
        <div>
            <asp:GridView ID="EmployeeIncomeGridView" runat="server"></asp:GridView>
        </div>
    </asp:Content>
    Imports System.Data.SqlClient
    
    Public Class _default1
        Inherits System.Web.UI.Page
    
        Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            If Not Page.IsPostBack Then
                BindEmployeeIncomeGridView()
            End If
        End Sub
    
        Protected Sub InsertEmployeeIncome_Click(sender As Object, e As EventArgs)
            InsertIncomeType(EmployeeIncomeName.Text, EmployeeIncomeAddress.Text, IncomeSourceType.Employee)
            BindEmployeeIncomeGridView()
            EmployeeIncomeName.Text = String.Empty
            EmployeeIncomeAddress.Text = String.Empty
        End Sub
    
    
        Protected Sub BindEmployeeIncomeGridView()
            EmployeeIncomeGridView.DataSource = GetIncomeByType(IncomeSourceType.Employee)
            EmployeeIncomeGridView.DataBind()
        End Sub
    
        Protected Function GetIncomeByType(incomeTypeId As Integer) As List(Of IncomeSource)
    
            Dim results As List(Of IncomeSource) = New List(Of IncomeSource)
    
            Dim connectionString As String = ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString
            Dim queryString As String = "SELECT IncomeSourceId, [Name], [Address], IncomeTypeId 
                                    FROM dbo.IncomeSource 
                                    WHERE IncomeTypeId = @IncomeTypeId"
    
            Using connection As SqlConnection = New SqlConnection(connectionString)
    
                Dim Command As SqlCommand = New SqlCommand(queryString, connection)
                Command.Parameters.AddWithValue("@IncomeTypeId", incomeTypeId)
    
                connection.Open()
                Dim reader As SqlDataReader = Command.ExecuteReader()
                While reader.Read()
                    Dim item = New IncomeSource
                    item.IncomeSourceId = reader.GetInt32(0)
                    item.Name = reader.GetString(1)
                    results.Add(item)
                End While
                reader.Close()
            End Using
            Return results
        End Function
    
        Protected Sub InsertIncomeType(Name As String, Address As String, IncomeType As Integer)
            Dim connectionString As String = ConfigurationManager.ConnectionStrings("DefaultConnection").ConnectionString
    
            Dim queryString As String = "INSERT INTO dbo.IncomeSource ([Name], [Address], IncomeTypeId)
                                            VALUES(@Name, @Address, @IncomeType)"
            Using connection As SqlConnection = New SqlConnection(connectionString)
                Dim Command As SqlCommand = New SqlCommand(queryString, connection)
                Command.Parameters.AddWithValue("@Name", Name)
                Command.Parameters.AddWithValue("@Address", Address)
                Command.Parameters.AddWithValue("@IncomeType", IncomeType)
                connection.Open()
                Command.ExecuteNonQuery()
                connection.Close()
            End Using
        End Sub
    
    
    End Class
    Wednesday, May 6, 2020 7:25 PM
  • User-1330468790 posted

    Hi simflex,

      

    I think the problem might be located in the button "Add another row if needed" since you use this button to add new row.

    According to the URL you posted, this button is associated with an event handler "AddNewRowToGrid" which will add a new row to the GridView when a Button is clickedThe point is that your problem is related to the new added row.

    Could you please share the more codes to allow me to find more details?

      

    I understand that the reason why you have this design is that you want to update the database using these grid view controls. The values will be temporarily stored in the ViewState and will be stored in the database when you submit all of the grid views. (There might be a "submit-change" button or similar stuff in the page).

    If so, this design is reasonable as long as you keep the values correctly and don't lose them.

      

    Best regards,

    Sean

    Thursday, May 7, 2020 8:42 AM
  • User1216627406 posted

    Thank you very much Sean.

    I was confident the code was designed correctly as is. No change is necessary.

    Everything was working great until I modified the RowDataBound method.

    With this code below, everything was working great:

        Protected Sub Gridview1_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles Gridview1.RowDataBound
            If e.Row.RowType = DataControlRowType.DataRow Then
                If e.Row.RowIndex = -1 Then
                    DirectCast(e.Row.FindControl("sourceDelete"), Button).Visible = True
                Else
                    If Convert.ToInt32(ViewState("rowIndex" & 0)) = e.Row.RowIndex Then
                        DirectCast(e.Row.FindControl("sourceDelete"), Button).Visible = False
                    End If
                End If
            End If
        End Sub

    It was after I added the below code just to ensure empty rows don't display that I started having problem adding new row:

                If e.Row.RowIndex > 0 Then
                    Dim sourceName As String = (TryCast(e.Row.FindControl("txtsourcename"), TextBox)).Text
                    Dim txtSourceAddress As String = (TryCast(e.Row.FindControl("txtsourceaddress"), TextBox)).Text
                    If String.IsNullOrEmpty(sourceName) AndAlso String.IsNullOrEmpty(txtSourceAddress) Then
                        e.Row.Visible = False
                    Else
                        e.Row.Visible = True
                    End If
                End If

    And per your request, here is the AddNewRow method:

        Protected Sub ButtonAdd_Click(sender As Object, e As EventArgs)
            AddNewRowToGrid()
        End Sub
        
        
            Private Sub AddNewRowToGrid()
                Dim rowIndex As Integer = 0
        
                If ViewState("CurrentTable") IsNot Nothing Then
                    Dim dtCurrentTable As DataTable = DirectCast(ViewState("CurrentTable"), DataTable)
                    Dim drCurrentRow As DataRow = Nothing
                    If dtCurrentTable.Rows.Count > 0 Then
                        For i As Integer = 1 To dtCurrentTable.Rows.Count
                            'extract the TextBox values
                            Dim txtsoname As TextBox = DirectCast(Gridview1.Rows(rowIndex).Cells(1).FindControl("txtsourcename"), TextBox)
                            Dim txtsoaddress As TextBox = DirectCast(Gridview1.Rows(rowIndex).Cells(2).FindControl("txtsourceaddress"), TextBox)
        
                            drCurrentRow = dtCurrentTable.NewRow()
                            drCurrentRow("RowNumber") = i + 1
        
                            dtCurrentTable.Rows(i - 1)("sourcename") = txtsoname.Text
                            dtCurrentTable.Rows(i - 1)("sourceaddress") = txtsoaddress.Text
        
                            rowIndex += 1
                        Next
                        dtCurrentTable.Rows.Add(drCurrentRow)
                        ViewState("CurrentTable") = dtCurrentTable
        
                        Gridview1.DataSource = dtCurrentTable
                        Gridview1.DataBind()
                    End If
                Else
                    Response.Write("ViewState is null")
                End If
        
                'Set Previous Data on Postbacks
                SetPreviousData()
        End Sub

    Thank you very much.

    Thursday, May 7, 2020 1:21 PM
  • User475983607 posted

    simflex

    Everything was working great until I modified the RowDataBound method.

    Can you clarify?  I though the changes were made to RowDataBound event because the design was not working as expected.  I thought the problem was when one GridView contained 3 rows, the other GridViews show 3 empty rows.  The fix sets the GridView row visibility to false when the inputs are empty but fix has side affects too.

    Data bound controls like the GridView display the same number of records as the data source.  The fact the GridViews share the same number of rows indicates a bug somewhere in the data source logic.   I assume each grid should have a variable number of rows independent of the other grids.  Setting the visibility seems more like a hack than fixing the problem.

    The AddNewRwoToGrid() method shows the GridView as a data source a DataTable..   That's not a good idea.  A GridView is a UI element not a data source.  The DataTable(s) should be the data source and drive the GridView. 

    I think the code changes to toggle row visibility compounds the problem.  The Web Forms framework does not render server controls when Visibility attribute is set to false.  This updates ViewState which might cause downstream issues.  For example, a hypothetical.  Let say a GridView is populated with 5 records during the Page_load event.  Every post back from that point on will show 5 GridView records.  This behavior is out-of-the-box Web Forms state management.  Let's say another event sets the visibility on the last 2 GridView rows to false.  Now every post back will have 3 GridView records. If there is a dependency on ViewState, then this can be a problem.

    What could be happening is there is bug elsewhere in the code related to the GridView data sources.  Toggling visibility makes everything worse because the the system of record is the GridView.  It's like a circular update reference.

    One thing you can try is setting the row CSS display to none rather than setting visibility.  The row still exists in the markup but it's not displayed.  I'm not convinced {display:none;}  this will fix the actual problem. I think you'll continue to run into issues until you figure why the GridViews share the same number of rows.  Fix that and you can move on.  

    Thursday, May 7, 2020 7:52 PM
  • User1216627406 posted

    mgebhard, can I clarify?

    Sure of course.

    But let start with the premise that One thing you can try is setting the row CSS display to none rather than setting visibility. 

    I think this holds true and would work if we are dealing with just one GridView control like GridView1.

    In my original post, I indicated that I was working with there are 7 GridView controls, GridView1 through GridView7.

    Each gives the user the ability to add new rows dynamically.

    So, I do not believe there is a bug in the original code. There maybe one since the modification.

    Anyway, the app has been working now since 2016.

    Currently, the way it works is that you enter your userID and the system checks whether that was first time you are accessing the system.

    If it is, then it shows blank form so you can complete entire.

    If you had used the form the previous year(s), it populates your personal information like name, title, email, and that userID you just entered, asks you to verify the accuracy of those personal info and then complete rest of form.

    If you completed the form this year, it tells you that you that you had already submitted the form and  can only submit the form once a year.

    This year, they are about to start completing the form again for 2020 and I have been asked to modify the app so that once you enter your ID, and you have completed this form the previous year, to populate all the information you entered from previous year so you don't have to enter them again unless you wish to make changes to them.

    That's what led to the changes I made and that's why I am having problems now.

    To answer your question that only rows will data can only be displayed, yes this is true if you are dealing with just one GridView control eg GridView1 and since I have 7 of them, and to populate the entire form, I have to do joins, that's what is causing the empty rows to display on controls where there is no data on the database.

    I have run the stored proc over to several people and I was told it was done correctly.

    I can post it here again because if there is to be any bug, it has to come from it.

    Thursday, May 7, 2020 8:46 PM
  • User475983607 posted

    But let start with the premise that One thing you can try is setting the row CSS display to none rather than setting visibility. 

    I think this holds true and would work if we are dealing with just one GridView control like GridView1.

    In my original post, I indicated that I was working with there are 7 GridView controls, GridView1 through GridView7.

    I don't understand your response.  Why can't you replace the row visibility logic with setting the CSS?   If the Visibility logic works then I do not understand why setting the display property is any different.

    Secondly, the empty GridView rows are expected and this page has behaved this way since 2016?  I though the empty row were unwanted?  

    I have run the stored proc over to several people and I was told it was done correctly.

    I can post it here again because if there is to be any bug, it has to come from it.

    What proc?  Can you share the code?

    Thursday, May 7, 2020 10:01 PM
  • User1216627406 posted

    I don't understand your response.  Why can't you replace the row visibility logic with setting the CSS?   If the Visibility logic works then I do not understand why setting the display property is any different.

    Yea, you have a good point there. This issue though is not the visibility is not being set to hidden. The issue is that when I click to add a new row, it does not add one/

    In other words, even though setting the visibility to none works, when attempting to click the button to add a new row, it is not doing that.

    So, I can try your suggestion but not sure if that solves the click button problem.

    Secondly, the empty GridView rows are expected and this page has behaved this way since 2016?  I though the empty row were unwanted? 

    No, what I meant was that the page was working perfectly until I made the change.

    In other words, normally, you get one empty row by default and you could add additional rows if needed and that row would be added. It has been working that way until I made the change in the event click method that I posted today. Then it stopped adding the additional rows.

    I will post the stored proc shortly. It is on my other laptop.

    Here is the stored procedure:

    I am sure you understand what it is doing but for vanity, it checks three things:

    1, when a user attempts to submit a form, has this user submitted the form this year already? 0 for no, 1 yes

    2, did this user submit this form last year? 0 for no, 1 for yes

    3, did this year submit this form previous years (not this year, not last year but any year before last year? 0 for no, 1 for years.

    On my VB, if thisYear = 1, then the user submitted this form already; cannot submit again. Let the user know this with a message.

    This works great.

    If user submitted this form last year or year before last, grab the record s/he submitted and populate the gridview form.

    This is where the issue comes in.

    If the user did the form last year or the year before and his/her record populates the gridview and one of the controls has more than one row, then the rest have more than one row whether empty or not.

    ALTER PROCEDURE [dbo].[uspGetRecs] 
        @empID nvarchar(50)
    AS
    begin
        SELECT s.sourcename, s.sourceaddress,d.dateCreated,sp.spousename, sp.spouseaddress,
    	 r.reimbursementName,r.reimbursementAddress, h.HonorariaName,h.HonorariaAddress, 
    	 h.HonorariaName as honoraria,h.HonorariaAddress,h.HonorariaIncome,g.giftName,g.giftAddress,dv.dividentName,dv.dividentAddress,
    	 o.orgName,o.orgAddress,cr.creditorName, cr.creditorAddress,
         CASE WHEN d.dateCreated < prev_yr_jan01 THEN 1 ELSE 0 END AS pastYears,
         CASE WHEN d.dateCreated >= prev_yr_jan01 AND d.dateCreated < curr_yr_jan01
              THEN 1 ELSE 0 END as previousYear, 
         CASE WHEN d.dateCreated >= curr_yr_jan01 THEN 1 ELSE 0 END as thisYear 
         FROM Employees e 
         CROSS APPLY (
             SELECT DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0) AS curr_yr_jan01,
                 DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0) AS prev_yr_jan01
        ) AS yr_start_dates
    	INNER JOIN  SourceDetails s ON e.EmployeeID = s.EmployeeID 
    	INNER JOIN  SpouseDetails sp ON e.EmployeeID = sp.employeeID
    	INNER JOIN  DividentDetails dv ON e.EmployeeID = dv.EmployeeID 
    	INNER JOIN  ReimbursementDetails r ON e.EmployeeID = r.employeeID
    	INNER JOIN  Honoraria h ON e.EmployeeID = h .EmployeeID 
    	INNER JOIN  GiftDetails g ON e.EmployeeID = g.employeeID
    	INNER JOIN  dateDetails d ON e.EmployeeID = d.employeeID
    	INNER JOIN  org o ON e.employeeID = o.employeeID
    	INNER JOIN  creditorDetails cr ON e.employeeID = cr.employeeID
        WHERE e.EmpID=@empID
    	ORDER By d.dateCreated DESC
    end

    As you can see, 8 tables are being joined by employeeID since that is the common key amongst them.

    Sometimes, I wonder if I just do this check one table at a time instead of joining them, would that have made any difference?

    Reason I have not tried this theory is not sure how to use the stored proc on my VB

    Thanks

    Friday, May 8, 2020 2:57 AM
  • User-1330468790 posted

    Hi simflex,

     

    Thanks for your further codes.

    However, I think the codes should be working as what you expected except one point as below codes in yellow background.

    simflex

    Protected Sub ButtonAdd_Click(sender As Object, e As EventArgs)
            AddNewRowToGrid()
        End Sub
        
        
            Private Sub AddNewRowToGrid()
                Dim rowIndex As Integer = 0
        
                If ViewState("CurrentTable") IsNot Nothing Then
                    Dim dtCurrentTable As DataTable = DirectCast(ViewState("CurrentTable"), DataTable)
                    Dim drCurrentRow As DataRow = Nothing
                    If dtCurrentTable.Rows.Count > 0 Then
                        For i As Integer = 1 To dtCurrentTable.Rows.Count
                            'extract the TextBox values
                            Dim txtsoname As TextBox = DirectCast(Gridview1.Rows(rowIndex).Cells(1).FindControl("txtsourcename"), TextBox)
                            Dim txtsoaddress As TextBox = DirectCast(Gridview1.Rows(rowIndex).Cells(2).FindControl("txtsourceaddress"), TextBox)
        
                            drCurrentRow = dtCurrentTable.NewRow()
                            drCurrentRow("RowNumber") = i + 1
        
                            dtCurrentTable.Rows(i - 1)("sourcename") = txtsoname.Text
                            dtCurrentTable.Rows(i - 1)("sourceaddress") = txtsoaddress.Text
        
                            rowIndex += 1
                        Next
                        dtCurrentTable.Rows.Add(drCurrentRow)
                        ViewState("CurrentTable") = dtCurrentTable
        
                        Gridview1.DataSource = dtCurrentTable
                        Gridview1.DataBind()
                    End If
                Else
                    Response.Write("ViewState is null")
                End If
        
                'Set Previous Data on Postbacks
                SetPreviousData()
        End Sub

    There is a method called "SetPreviousData()" which I think is to populate the values from ViewState to GridView1 so that you cannot hide the row even that text box does not contain any value. The method "SetPreviousData()" works after "RowDataBound" event handler.

    I remember that I have dealt with a similar case which uses SetPreviousData to manually set data for grid view control.

    I think if it would make problem clearer to see what exactly this method does for GridView1 control. 

      

    Another problem will be occurred if you could solve this problem.

    That is, you will never get the new row rendered in the page since every new row will have two text boxes without value inside. According to your logic, the row will be hidden if both two text box contain "string.Empty". 

      

    Suggestion:

    If you don't want to make these 8 grid view working together, you would better to let each of them having a separate in-memory data table (ViewState).

    If you add one row for one of them, it will only have extra empty row which will not bother others and you will not need to check the row's value and hide it.

      

    Hope this can help you. 

    Best regards,

    Sean

     

    Friday, May 8, 2020 9:34 AM
  • User1216627406 posted

    Thanks a lot Sean.

    First they have  a separate in-memory data table (ViewState).

    For instance, you asked for the code for setpreviousdata() method,

    Here is two (SetPreviousData() and SetPreviousData2(), etc)

    Each representing each grid control.

        Private Sub SetPreviousData()
            Dim rowIndex As Integer = 0
            If ViewState("CurrentTable") IsNot Nothing Then
                Dim dtc As DataTable = DirectCast(ViewState("CurrentTable"), DataTable)
                If dtc.Rows.Count > 0 Then
                    For i As Integer = 0 To dtc.Rows.Count - 1
                        Dim txtsoname As TextBox = DirectCast(Gridview1.Rows(rowIndex).Cells(1).FindControl("txtsourcename"), TextBox)
                        Dim txtsoaddress As TextBox = DirectCast(Gridview1.Rows(rowIndex).Cells(2).FindControl("txtsourceaddress"), TextBox)
    
                        txtsoname.Text = dtc.Rows(i)("sourcename").ToString()
                        txtsoaddress.Text = dtc.Rows(i)("sourceaddress").ToString()
    
                        rowIndex += 1
                    Next
                End If
            End If
        End Sub
        Private Sub SetPreviousData2()
            Dim rowIndex As Integer = 0
            If ViewState("SpouseTable") IsNot Nothing Then
                Dim dt As DataTable = DirectCast(ViewState("SpouseTable"), DataTable)
                If dt.Rows.Count > 0 Then
                    For i As Integer = 0 To dt.Rows.Count - 1
                        Dim txtspname As TextBox = DirectCast(grvspouse.Rows(rowIndex).Cells(1).FindControl("txtspousename"), TextBox)
                        Dim txtspaddress As TextBox = DirectCast(grvspouse.Rows(rowIndex).Cells(2).FindControl("txtspouseaddress"), TextBox)
    
                        txtspname.Text = dt.Rows(i)("spousename").ToString()
                        txtspaddress.Text = dt.Rows(i)("spouseaddress").ToString()
    
                        rowIndex += 1
                    Next
                End If
            End If
        End Sub

    Friday, May 8, 2020 12:38 PM
  • User475983607 posted

    The stored procedure clears up what's happening.  The design around the DateDetails table is not normalized which is driving the downstream complexity.  A union query can hide complexity from the code behind.   Basically we'll move the code behind visibility toggle to SQL.   We'll also convert the variable columns to variable rows.  Each row will have an identifier which allows easy filtering.  Keep in mind, I found a HonorariaIncome column which seems to be specific to the Honoraria table.  HonorariaIncome might be an outlier.  I'm not sure what it is used for.

    Anyway, the SQL is below.  I cannot test the SQL because I do not have your table structure or data.  I did my best to validate the code without being about to test.  With that being said, you might have to tweak the SQL.  You'll notice the SQL is in sections and each section queries a different income table.  The where clause filters out the empty rows.  This is the part that takes over the the code behind visibility toggle.   Another change is all the column names are the same.  That's just the way a UNION works.  Other than changing the GridView bindings it should not cause any issues because you can filter by income.

    SELECT 	s.sourcename [Name], 
    	s.sourceaddress [Address], 
    	d.dateCreated [DateCreated],
    	'Source' AS [IncomeSource],
    	0 AS HonorariaIncome
         CASE WHEN d.dateCreated < prev_yr_jan01 THEN 1 ELSE 0 END AS pastYears,
         CASE WHEN d.dateCreated >= prev_yr_jan01 AND d.dateCreated < curr_yr_jan01
              THEN 1 ELSE 0 END as previousYear, 
         CASE WHEN d.dateCreated >= curr_yr_jan01 THEN 1 ELSE 0 END as thisYear 
         FROM Employees e 
         CROSS APPLY (
             SELECT DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0) AS curr_yr_jan01,
                 DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0) AS prev_yr_jan01
        ) AS yr_start_dates
     INNER JOIN  SourceDetails s ON e.EmployeeID = s.EmployeeID
     INNER JOIN  dateDetails d ON e.EmployeeID = d.employeeID
     WHERE e.EmpID = @empID 
    	AND s.sourcename IS NOT NULL AND s.sourcename <> ''
     
     UNION
     	 
     SELECT sp.spousename AS [Name], 
    	sp.spouseaddress AS [Address],  
    	d.dateCreated AS [DateCreated],
    	'Souse' AS [IncomeSource],
    	0 AS HonorariaIncome
         CASE WHEN d.dateCreated < prev_yr_jan01 THEN 1 ELSE 0 END AS pastYears,
         CASE WHEN d.dateCreated >= prev_yr_jan01 AND d.dateCreated < curr_yr_jan01
              THEN 1 ELSE 0 END as previousYear, 
         CASE WHEN d.dateCreated >= curr_yr_jan01 THEN 1 ELSE 0 END as thisYear 
         FROM Employees e 
         CROSS APPLY (
             SELECT DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0) AS curr_yr_jan01,
                 DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0) AS prev_yr_jan01
        ) AS yr_start_dates
     INNER JOIN  SpouseDetails sp ON e.EmployeeID = sp.employeeID 
     INNER JOIN  dateDetails d ON e.EmployeeID = d.employeeID
     WHERE e.EmpID = @empID
    	AND sp.spousename IS NOT NULL AND sp.spousename <> ''
    	
     UNION
    
     SELECT h.HonorariaName AS [Name], 
    	h.HonorariaAddress AS [Address],  
    	d.dateCreated AS [DateCreated],
    	'HonorariaI' AS [IncomeSource],
    	h.HonorariaIncome AS HonorariaIncome
         CASE WHEN d.dateCreated < prev_yr_jan01 THEN 1 ELSE 0 END AS pastYears,
         CASE WHEN d.dateCreated >= prev_yr_jan01 AND d.dateCreated < curr_yr_jan01
              THEN 1 ELSE 0 END as previousYear, 
         CASE WHEN d.dateCreated >= curr_yr_jan01 THEN 1 ELSE 0 END as thisYear 
         FROM Employees e 
         CROSS APPLY (
             SELECT DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0) AS curr_yr_jan01,
                 DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0) AS prev_yr_jan01
        ) AS yr_start_dates
     INNER JOIN  Honoraria h ON e.EmployeeID = h .EmployeeID 
     INNER JOIN  dateDetails d ON e.EmployeeID = d.employeeID
     WHERE e.EmpID = @empID
    AND h.HonorariaName IS NOT NULL AND h.HonorariaName <> ''

    Friday, May 8, 2020 1:58 PM
  • User1216627406 posted

    Great work there sir.

    Sorry my response is late again...too many virtual meetings.

    I have a question. I think I know the answer to my question but I will play it safe by asking for your help in answering.

    As you can see from the query, there are 8 tables involved.

    However, your version using UNION query handles only three sections, source, spouse and honoraria.

    Is that all I need? I know said I may have to modify the query a bit. Is that what you meant?

    You made a good observation about honoraria having income.

    Yes, that's true. The rest of the tables (grid controls) require name and address but for some reason, we were told honoraria needs income as well.

    I am still trying to understand how dateDetails table is the problem.

    That table, as well as the rest of the tables are related by employeeID.

    Finally, assume that this works and I able to see now, I no longer need to additional vb code that hides the empty rows, correct?

    I didn't know you are very good with sql as well.

    Many thanks for your help.

    BTW: Is there any relevance to using 'source', 'spouse', and 'honoraria1' aliases?

    If yes, how do I incorporate them into my VB?

    Friday, May 8, 2020 8:28 PM
  • User475983607 posted

    As you can see from the query, there are 8 tables involved.

    However, your version using UNION query handles only three sections, source, spouse and honoraria.

    Is that all I need? I know said I may have to modify the query a bit. Is that what you meant?

    My intention is to provide enough code so the pattern is obvious and not overwhelming.  I thought you could add the missing tables.

    I am still trying to understand how dateDetails table is the problem.

    That table, as well as the rest of the tables are related by employeeID.

    It's a little more than just the dateDetails.  Frankly, I don't have the words to explain without sounding overly critical. 

    Finally, assume that this works and I able to see now, I no longer need to additional vb code that hides the empty rows, correct?

    Right.  The intention is a "data driven design".    I guess in other words, the data is smart and the UI does what it is told. 

    BTW: Is there any relevance to using 'source', 'spouse', and 'honoraria1' aliases?

    To filter the result set by type.  Each GridView data source is filtered by income type.  I made the type up.  I'm sure you can give the types better names.  An enum is good for this sort of deign; named constants. 

    If yes, how do I incorporate them into my VB?

    The "types" become an input parameter to filter the data along with EmpId.   The filter can be applied to the cached DataTable.

    Friday, May 8, 2020 9:25 PM
  • User1216627406 posted

    My intention is to provide enough code so the pattern is obvious and not overwhelming.  I thought you could add the missing tables.

    I think I got it. In other words, continue adding the UNION query till all tables (Grid controls) are accounted for?

    It's a little more than just the dateDetails.  Frankly, I don't have the words to explain without sounding overly critical. 

    I have no issues with constructive criticisms at all. Here, you have told me what could be wrong with my code. At the same time, you have provided useful resources include code on how to fix the problem. I am fine with that absolutely. Thank you

    The "types" become an input parameter to filter the data along with EmpId.   The filter can be applied to the cached DataTable.

    Sorry to sound too slow in the uptake but still not clear how to use "types" as input parameter.

    Since this is a stored procedure with empID as the only input parameter, do I need to add "types" as input parameter as well?

    This is the method that is calling the stored procedure:

    //FillGridView()
        Private Sub FillGridView()
            Dim control As Control = Nothing
            Dim conn_str As String = ConfigurationManager.ConnectionStrings("constr").ConnectionString
            Using conn As SqlConnection = New SqlConnection(ConfigurationManager.ConnectionStrings("ppmtest").ConnectionString)
                conn.Open()
                Using sourcecmd As SqlCommand = New SqlCommand()
                    sourcecmd.CommandText = "uspGetRecs"
                    sourcecmd.CommandType = CommandType.StoredProcedure
                    sourcecmd.Parameters.AddWithValue("@empID", txtEmpID.Text.Trim())
                    sourcecmd.Connection = conn
                    Using ad As SqlDataAdapter = New SqlDataAdapter(sourcecmd)
                        Dim ds As DataTable = New DataTable()
                        ad.Fill(ds)
                        If ds.Rows.Count > 0 Then
                            Gridview1.DataSource = ds
                            Gridview1.DataBind()
                            grvspouse.DataSource = ds
                            grvspouse.DataBind()
                            grvDiv.DataSource = ds
                            grvDiv.DataBind()
                            grvReim.DataSource = ds
                            grvReim.DataBind()
                            grvHon.DataSource = ds
                            grvHon.DataBind()
                            grvGift.DataSource = ds
                            grvGift.DataBind()
                            grvOrg.DataSource = ds
                            grvOrg.DataBind()
                            grvCred.DataSource = ds
                            grvCred.DataBind()
                        Else
                        End If
                    End Using
     
                End Using
            End Using
        End Sub

    Two things with the method, as you can see, it uses empID as parameter. So, if I understand correctly, "types" will be added as second parameter in this method.

    You can also see how it is using the 7 GridView controls.

    Thank you very much

    Friday, May 8, 2020 10:38 PM
  • User-1330468790 posted

    Hi simflex,

     

    The problem is clear now. Try remove the SetPreviousData() method which works as DataBind() for the gridview control.

    You already have done the data binding stuff by calling the method "Gridview1.DataBind()" and you don't need to manually bind the data again.

     

    Best regards,

    Sean

     

    Tuesday, May 12, 2020 1:24 AM
  • User1216627406 posted

    Hi Sean,

    If I understand you correctly sir, you are suggesting that removing SetPreviousData() will solve the problem of having several empty rows displayed when there is no data in them, no?

    If yes, well unfortunately, commenting it out or straight up remove the method didn't solve the problem.

    It certainly didn't solve it in terms of the inability to add another row when you click the "Add new row if needed" button.

    I think that mgebhard is on to something with the modification of the stored procedure.

    Issue that I have with his modification now is what to do with those aliases like 'source', spouse', 'honoraria1', etc.

    Are they variables and if yes, how do I use them with fillSource() method?

    Tuesday, May 12, 2020 10:52 PM