自定义gridview控件的怪异问题

自定义gridview控件的怪异问题

问题描述:

我有一个可能比较复杂的问题.我发誓我曾经一直使用扩展的gridview控件,但是我离开了一段时间,回来了,但是它不再起作用了(我是唯一的程序员).

I have what may be a rather complicated issue. I have an extended gridview control that I swear used to work all the time, but I went away for a while, came back, and it doesn't work anymore (I'm the sole programmer).

扩展的gridview的设计使其始终显示页脚行(用于插入新行).它会正确加载并显示现有数据.如果没有行,则添加数据可以正常进行.但是,如果将新行添加到已经具有现有行的gridview中,则会出现gvPhones.FooterRow为null的问题,因此找不到我要引用的控件.

The extended gridview is designed so that it always shows a footer row (for inserting new rows). It loads and displays existing data correctly. If there are no rows, then adding the data works fine. But if I'm adding a new row to a gridview that already has existing rows, I get an issue where gvPhones.FooterRow is null, so it can't find the control I'm referencing.

这是扩展的gridview类(从stackoverflow页面获得):

Here's the extended gridview class (gotten from a stackoverflow page):

using System.Linq;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

//https://stackoverflow.com/questions/994895/always-show-footertemplate-even-no-data/10891744#10891744
namespace WebForms.LocalCodeLibrary.Controls
{
//modified from https://stackoverflow.com/questions/3437581/show-gridview-footer-on-empty-grid
public class GridViewExtended : GridView
{

    private GridViewRow _footerRow;
    [DefaultValue(false), Category("Appearance"), Description("Include the footer when the table is empty")]
    public bool ShowFooterWhenEmpty { get; set; }

    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), Browsable(false)]
    public override GridViewRow FooterRow
    {
        get
        {
            if ((this._footerRow == null))
            {
                this.EnsureChildControls();
            }
            return this._footerRow;
        }
    }

    protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
    {
        //creates all the rows that would normally be created when instantiating the grid
        int returnVal = base.CreateChildControls(dataSource, dataBinding);
        //if no rows were created (i.e. returnVal == 0), and we need to show the footer row, then we need to create and bind the footer row.
        if (returnVal == 0 && this.ShowFooterWhenEmpty)
        {
            Table table = this.Controls.OfType<Table>().First<Table>();
            DataControlField[] dcf = new DataControlField[this.Columns.Count];
            this.Columns.CopyTo(dcf, 0);
            //creates the footer row
            this._footerRow = this.CreateRow(-1, -1, DataControlRowType.Footer, DataControlRowState.Normal, dataBinding, null, dcf, table.Rows, null);
            if (!this.ShowFooter)
            {
                _footerRow.Visible = false;
            }
        }
        return returnVal;
    }

    private GridViewRow CreateRow(int rowIndex, int dataSourceIndex, DataControlRowType rowType, DataControlRowState rowState, bool dataBind, object dataItem, DataControlField[] fields, TableRowCollection rows, PagedDataSource pagedDataSource)
    {
        GridViewRow row = this.CreateRow(rowIndex, dataSourceIndex, rowType, rowState);
        GridViewRowEventArgs e = new GridViewRowEventArgs(row);
        if ((rowType != DataControlRowType.Pager))
        {
            this.InitializeRow(row, fields);
        }
        else
        {
            this.InitializePager(row, fields.Length, pagedDataSource);
        }
        //if the row has data, sets the data item
        if (dataBind)
        {
            row.DataItem = dataItem;
        }
        //Raises the RowCreated event
        this.OnRowCreated(e);
        //adds the row to the gridview's row collection
        rows.Add(row);
        //explicitly binds the data item to the row, including the footer row and raises the RowDataBound event.
        if (dataBind)
        {
            row.DataBind();
            this.OnRowDataBound(e);
            row.DataItem = null;
        }
        return row;
    }

}

}

以下是ASPX页面中的相关内容:

Here's the relevant stuff in the ASPX page:

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ContactEdit.aspx.cs" Inherits="WebForms.Directory.ContactEdit" %>
<%@ Register TagPrefix="gcctl" Namespace="WebForms.LocalCodeLibrary.Controls" Assembly="WebForms" %>
<asp:Content ID="Content1" ContentPlaceHolderID="Head" runat="server">
    <style>
    </style>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<div id="bodycontent" class="body-content">
    <h2><asp:Literal ID="formheader" runat="server" /></h2>

    <!-- Start: Main Customer section -->
    <asp:Panel ID="mainformcontent" runat="server" CssClass="formsection">
        <section id="mainform" class="simplelayoutform">
            <asp:TextBox id="customerid" type="hidden" runat="server" />
            <asp:TextBox id="contactid" type="hidden" runat="server" />
        </section>
    </asp:Panel>
    <!-- End: Main Customer section -->

    <!-- Start: Phones section -->
    <asp:SqlDataSource ID="gvPhonesDataSource" runat="server" OnInserted="gvPhonesDataSource_Inserted"
        ConnectionString="<%$ ConnectionStrings:ConnString %>" 
        SelectCommand="SELECT p.[CustomerPhoneID]
                    ,p.[CustomerID]
                    ,LTRIM(COALESCE(cc.FirstName,'') + ' ' + COALESCE(cc.LastName,'')) AS ContactFullName
                    ,p.CustomerContactID
                    ,p.PhoneTypeID
                    ,lp.PhoneType
                    ,p.[PhoneNumber]
                    ,p.[Extension]
                    ,p.[FormattedPhone]
                    ,p.[IsActive]
                    ,CASE WHEN p.LocationID IS NULL THEN CASE WHEN p.CustomerContactID IS NULL THEN 0 ELSE 1 END ELSE 2 END AS SortOrder
                FROM [dbo].[Phones] p
                LEFT JOIN dbo.Contacts cc ON p.CustomerContactID = cc.CustomerContactID
                LEFT JOIN list.PhoneTypes lp ON p.PhoneTypeID = lp.PhoneTypeID
                WHERE p.CustomerContactID = @CustomerContactID"
        DeleteCommand="DELETE FROM [dbo].[Phones] WHERE [CustomerPhoneID] = @CustomerPhoneID" 
        InsertCommand="INSERT INTO [dbo].[Phones] ([CustomerID]
                    , [CustomerContactID]
                    , [PhoneNumber]
                    , [Extension]
                    , [PhoneTypeID]
                    , LastModifiedByStaffID) 
                VALUES (@CustomerID
                    , @CustomerContactID
                    , CASE WHEN COALESCE(@FormattedPhone, '')='' THEN NULL ELSE LTRIM(RTRIM(LEFT(dbo.RemoveNonNumeric(@FormattedPhone),10))) END
                    , CASE WHEN COALESCE(@FormattedPhone, '')='' THEN NULL ELSE CASE WHEN LTRIM(RTRIM(SUBSTRING(dbo.RemoveNonNumeric(@FormattedPhone),11,1000))) = '' THEN NULL ELSE LTRIM(RTRIM(SUBSTRING(dbo.RemoveNonNumeric(@FormattedPhone),11,1000))) END END
                    , @PhoneTypeID
                    , @StaffID)" 
        UpdateCommand="UPDATE [dbo].[CustomerPhones] 
                    SET [CustomerContactID] = @CustomerContactID
                    , [PhoneNumber] = CASE WHEN COALESCE(@FormattedPhone, '')='' THEN NULL ELSE LTRIM(RTRIM(LEFT(dbo.RemoveNonNumeric(@FormattedPhone),10))) END
                    , [Extension] = CASE WHEN COALESCE(@FormattedPhone, '')='' THEN NULL ELSE CASE WHEN LTRIM(RTRIM(SUBSTRING(dbo.RemoveNonNumeric(@FormattedPhone),11,1000))) = '' THEN NULL ELSE LTRIM(RTRIM(SUBSTRING(dbo.RemoveNonNumeric(@FormattedPhone),11,1000))) END END
                    , [PhoneTypeID] = @PhoneTypeID
                    , [IsActive] = @IsActive
                    , [DateModified] = getdate()
                    , [LastModifiedByStaffID] = @StaffID
                WHERE [CustomerPhoneID] = @CustomerPhoneID">
        <SelectParameters>
            <asp:ControlParameter Name="CustomerContactID" Type="Int32" ControlID="contactid" PropertyName="Text" />
        </SelectParameters>
        <DeleteParameters>
            <asp:Parameter Name="CustomerPhoneID" Type="Int32" />
        </DeleteParameters>
        <UpdateParameters>
            <asp:ControlParameter Name="CustomerContactID" Type="Int32" ControlID="contactid" PropertyName="Text" />
            <asp:Parameter Name="FormattedPhone" Type="String" />
            <asp:Parameter Name="PhoneTypeID" Type="Int32" />
            <asp:Parameter Name="IsActive" Type="Boolean" />
            <asp:SessionParameter Name="StaffID" Type="Int32" SessionField="StaffID" />
            <asp:Parameter Name="CustomerPhoneID" Type="Int32" />
        </UpdateParameters>
        <InsertParameters>
            <asp:ControlParameter Name="CustomerID" Type="Int32" ControlID="customerid" PropertyName="Text" />
            <asp:ControlParameter Name="CustomerContactID" Type="Int32" ControlID="contactid" PropertyName="Text" />
            <asp:Parameter Name="PhoneTypeID" Type="Int32" />
            <asp:Parameter Name="FormattedPhone" Type="String" />
            <asp:SessionParameter Name="StaffID" Type="Int32" SessionField="StaffID" />
        </InsertParameters>
    </asp:SqlDataSource>

    <asp:Panel ID="phonesformcontent" runat="server" CssClass="formsection separate">
        <section id="phonesform" class="simplelayoutform">
            <h3>All Phones</h3>
            <gcctl:MyCheckBox ID="chkPhoneShowInactive" Text="Show Inactive?" Checked="false" AutoPostBack="true" OnCheckedChanged="chkPhoneShowInactive_CheckedChanged" runat="server" />
            <asp:label id="lblPhoneMessage" CssClass="responsemsg" runat="server" enableviewstate="False" />
            <gcctl:gridviewextended ID="gvPhones" runat="server" DataSourceID="gvPhonesDataSource"
                AutoGenerateColumns="False" DataKeyNames="CustomerPhoneID" EmptyDataText="No phones on record."
                CssClass="searchresultsgrid" ShowFooter="True" OnRowCommand="gvPhones_RowCommand" AllowSorting="True"
                ShowFooterWhenEmpty="true" OnRowDataBound="gvPhones_RowDataBound">
                <Columns>
                    <asp:BoundField DataField="CustomerPhoneID" InsertVisible="false" ReadOnly="true" Visible="False" />

                    <asp:TemplateField HeaderText="Phone Type" SortExpression="PhoneType">
                        <FooterTemplate>
                            <asp:DropDownList ID="cboPhoneTypeID" runat="server"
                                DataSourceID="DataSourcePhoneTypes" DataTextField="PhoneType" DataValueField="PhoneTypeID"
                                SelectedValue='<%# Bind("PhoneTypeID") %>'>
                            </asp:DropDownList>
                        </FooterTemplate>
                        <EditItemTemplate>
                            <asp:DropDownList ID="cboPhoneTypeID" runat="server"
                                DataSourceID="DataSourcePhoneTypes" DataTextField="PhoneType" DataValueField="PhoneTypeID"
                                SelectedValue='<%# Bind("PhoneTypeID") %>'>
                            </asp:DropDownList>
                        </EditItemTemplate>
                        <ItemTemplate>
                            <asp:Label ID="lblPhoneTypeID" runat="server" Text='<%# Bind("PhoneType") %>'></asp:Label>
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:TemplateField HeaderText="Phone" SortExpression="PhoneNumber">
                        <FooterTemplate>
                            <asp:TextBox runat="server" Text='<%# Bind("FormattedPhone") %>' ID="txtPhone"></asp:TextBox>
                        </FooterTemplate>
                        <EditItemTemplate>
                            <asp:TextBox runat="server" Text='<%# Bind("FormattedPhone") %>' ID="txtPhone"></asp:TextBox>
                        </EditItemTemplate>
                        <ItemTemplate>
                            <asp:Label runat="server" Text='<%# Bind("FormattedPhone") %>' ID="lblPhone"></asp:Label>
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:TemplateField HeaderText="Active?" SortExpression="IsActive">
                        <FooterTemplate>
                            <asp:CheckBox runat="server" Checked='<%# Bind("IsActive") %>' ID="chkPhoneIsActive"></asp:CheckBox>
                        </FooterTemplate>
                        <EditItemTemplate>
                            <asp:CheckBox runat="server" Checked='<%# Bind("IsActive") %>' ID="chkPhoneIsActive"></asp:CheckBox>
                        </EditItemTemplate>
                        <ItemTemplate>
                            <asp:Label runat="server" Text='<%# Bind("IsActive") %>' ID="lblPhoneIsActive"></asp:Label>
                        </ItemTemplate>
                    </asp:TemplateField>

                    <asp:TemplateField ShowHeader="False">
                        <EditItemTemplate>
                            <asp:LinkButton runat="server" Text="Update" CommandName="Update" CausesValidation="True" ID="PhoneUpdate"></asp:LinkButton>&nbsp;<asp:LinkButton runat="server" Text="Cancel" CommandName="Cancel" CausesValidation="False" ID="PhoneEditCancel"></asp:LinkButton>
                        </EditItemTemplate>
                        <ItemTemplate>
                            <asp:LinkButton runat="server" Text="Edit" CommandName="Edit" CausesValidation="False" ID="PhoneEdit"></asp:LinkButton>&nbsp;<asp:LinkButton runat="server" Text="Delete" CommandName="Delete" CausesValidation="False" ID="PhoneDelete"></asp:LinkButton>
                        </ItemTemplate>
                        <FooterTemplate>
                            <asp:LinkButton runat="server" Text="Save New Phone" CommandName="FooterInsert" CausesValidation="True" ID="PhoneInsert"></asp:LinkButton>
                        </FooterTemplate>
                    </asp:TemplateField>
                </Columns>
            </gcctl:gridviewextended>
            <div id="phonenotes" class="tip">
                <div>NUMBERS ONLY - NO LETTER CODES IN THE PHONE FIELD!</div>
                <div>Be sure to always enter the area code, especially if you're also adding an extension.</div>
                <div>Note that only numbers will stay in the "Phone" field. Anything else you enter will disappear once it goes behind the scenes. The first 10 digits will become the phone number, and any remaining digits will become the extension.</div> 
            </div>
        </section>
    </asp:Panel>
    <!-- End: Phones section -->

    <div id="responsetextdiv" class="error"><asp:Literal ID="responsetext" runat="server"></asp:Literal></div>

</div>

<asp:XmlDataSource ID="DataSourcePhoneTypes" runat="server" DataFile="~/XML/PhoneTypes.xml" EnableCaching="true">
</asp:XmlDataSource>

</asp:Content>

这是我得到错误的代码:

Here's the code where I get the error:

protected void gvPhones_RowCommand(object sender, GridViewCommandEventArgs e)
{
    // Insert data if the CommandName == "Insert" 
    // and the validation controls indicate valid data...
    if (e.CommandName == "FooterInsert" && Page.IsValid)
    {
//ERROR HAPPENS ON THE FOLLOWING LINE:
        DropDownList PhoneTypeID = (DropDownList)gvPhones.FooterRow.FindControl("cboPhoneTypeID");
        TextBox FormattedPhone = (TextBox)gvPhones.FooterRow.FindControl("txtPhone");

        gvPhonesDataSource.InsertParameters["PhoneTypeID"].DefaultValue = PhoneTypeID.SelectedValue.ToString();

        string sFormattedPhone = null;
        if (!string.IsNullOrEmpty(FormattedPhone.Text))
            sFormattedPhone = FormattedPhone.Text;
        gvPhonesDataSource.InsertParameters["FormattedPhone"].DefaultValue = sFormattedPhone;

        gvPhonesDataSource.InsertParameters["CustomerID"].DefaultValue = customerid.Text.ToString();
       gvPhonesDataSource.InsertParameters["CustomerContactID"].DefaultValue = contactid.Text.ToString();
        gvPhonesDataSource.InsertParameters["StaffID"].DefaultValue = System.Web.HttpContext.Current.Session["StaffID"].ToString();

        // Insert new record
        gvPhonesDataSource.Insert();
    }
}

我得到的全部错误是:

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.

Source Error: 


Line 276:            if (e.CommandName == "FooterInsert" && Page.IsValid)
Line 277:            {
Line 278:                DropDownList PhoneTypeID = (DropDownList)gvPhones.FooterRow.FindControl("cboPhoneTypeID");
Line 279:                TextBox FormattedPhone = (TextBox)gvPhones.FooterRow.FindControl("txtPhone");
Line 280:

Source File: <snip>    Line: 278 

Stack Trace: 


[NullReferenceException: Object reference not set to an instance of an object.]
   GCWebForms.Directory.ContactEdit.gvPhones_RowCommand(Object sender, GridViewCommandEventArgs e) in <snip>ContactEdit.aspx.cs:278
   System.Web.UI.WebControls.GridView.OnRowCommand(GridViewCommandEventArgs e) +137
   System.Web.UI.WebControls.GridView.HandleEvent(EventArgs e, Boolean causesValidation, String validationGroup) +95
   System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +49
   System.Web.UI.WebControls.GridViewRow.OnBubbleEvent(Object source, EventArgs e) +146
   System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +49
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5450


单步执行时(尝试向其中已经有数据的gridview添加新行时),我发现gvPhones.FooterRow说它为空.同样,只有在gvPhones中有数据时,才会发生这种情况.如果数据表为空,则页脚插入代码可以顺利运行.


When stepping through (when trying to add a new row to a gridview that already has data in it), I found that gvPhones.FooterRow says that it's null. Again, this only happens if there is data in gvPhones. If the datatable is empty, then the footerrow insert code works without a hitch.

任何帮助将不胜感激! :-)

Any help would be greatly appreciated! :-)

在Page_Load之后添加相关代码.我只是添加了DataBind()语句,但并没有什么不同.

adding the relevant code behind Page_Load. I just added the DataBind() statement, but it didn't make a difference.

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            bool bolNewRec = (this.iContactID == null);
            phonesformcontent.Visible = (!bolNewRec);

            if (bolNewRec)
            { //snipping unrelated code
            }
            else
            {
                //snipping code that loads the data into the page
                gvPhones.Sort("SortOrder, PhoneType", SortDirection.Ascending);
            }
        }

        if (phonesformcontent.Visible)
            gvPhones.DataBind();
    }

...,以防万一,这里是RowDataBound:

...and, just in case, here's RowDataBound:

    protected void gvPhones_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            DataRowView rowView = (DataRowView)e.Row.DataItem;
            bool bolShowInactive = chkPhoneShowInactive.Checked;
            if (!bolShowInactive && (Convert.ToBoolean(rowView["IsActive"]) == false))
                e.Row.Visible = false;
            else
                e.Row.Visible = true;
            rowView = null;
        }

        if (e.Row.RowType == DataControlRowType.Footer)
        {
            CheckBox chkIsActive = (CheckBox)e.Row.FindControl("chkPhoneIsActive");
            chkIsActive.Checked = true;
            chkIsActive = null;
        }
    }

我结束了整个课程的学习.取而代之的是,我基于基于数据源的常规asp:gridviews,这些数据源具有在键列中带有-1的行进行联合选择(因为我的所有表都具有单个自动增量PK,因此在键列中没有行合法地具有-1) ,然后将以下内容放入RowDataBound:

I wound up scrapping this entire class. Instead, I made regular asp:gridviews that are based on datasources that have union selects with one row with -1 in the key column (since all of my tables have single autoincrement PKs, no row will legitimately have -1 in the key column), and then put the following in RowDataBound:

if (e.Row.RowType == DataControlRowType.DataRow)
{
    DataRowView rowView = (DataRowView)e.Row.DataItem;
    string sKeyName = gvPhones.DataKeyNames[0].ToString();
    if ((rowView[sKeyName].ToString() == "-1"))
        e.Row.Visible = false;
    else
        e.Row.Visible = true;
    rowView = null;
}

这将隐藏键列中任何带有-1的行.因此,gridview中总是至少有一行(即使该行是隐藏的),并且总是显示页脚行.

This hides any row with -1 in the key column. So there's always at least one row in the gridview (even if that one row is hidden), and the footer row always shows.