【问题标题】:Bizarre issue with customized gridview control自定义gridview控件的奇怪问题
【发布时间】:2018-03-14 18:02:13
【问题描述】:

我有一个可能相当复杂的问题。我有一个扩展的 gridview 控件,我发誓过去一直在工作,但我离开了一段时间,回来了,它不再工作了(我是唯一的程序员)。

扩展的 gridview 被设计成总是显示一个页脚行(用于插入新行)。它正确加载和显示现有数据。如果没有行,则添加数据可以正常工作。但是,如果我向已有行的 gridview 添加新行,我会遇到 gvPhones.FooterRow 为空的问题,因此它找不到我正在引用的控件。

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

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 页面中的相关内容:

<%@ 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>

这是我得到错误的代码:

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 中有数据时才会发生。如果数据表为空,则页脚插入代码可以顺利运行。

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

编辑:在 Page_Load 后面添加相关代码。我只是添加了 DataBind() 语句,但没有任何区别。

    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:

    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;
        }
    }

【问题讨论】:

    标签: c# asp.net gridview


    【解决方案1】:

    尝试在您的代码中使用发件人,如下所示:

    替换这一行:

    DropDownList PhoneTypeID = (DropDownList)gvPhones.FooterRow.FindControl("cboPhoneTypeID");
    

    为此:

    DropDownList PhoneTypeID = (DropDownList)((GridView)sender).FooterRow.FindControl("cboPhoneTypeID");
    

    另外,如果问题不在于回发,请检查页面加载。

    我的回答是基于这个问题: Unable to get gridview footer values in RowCommand

    更新:

    更改您的 GridViewExtended 类,

    ShowFooterWhenEmpty 属性:

        [Category("Behavior")]
        [Themeable(true)]
        [Bindable(BindableSupport.No)]
        public bool ShowFooterWhenEmpty
        {
            get
            {
                if (this.ViewState["ShowFooterWhenEmpty"] == null)
                {
                    this.ViewState["ShowFooterWhenEmpty"] = false;
                }
    
                return (bool)this.ViewState["ShowFooterWhenEmpty"];
            }
            set
            {
                this.ViewState["ShowFooterWhenEmpty"] = value;
            }
        }
    

    GridViewRow

        private GridViewRow _footerRow;
        public override GridViewRow FooterRow
        {
            get
            {
                GridViewRow f = base.FooterRow;
                if (f != null)
                    return f;
                else
                    return _footerRow;
            }
        }
    

    我的更改基于此链接: Always show FooterTemplate, even no data

    【讨论】:

    • 谢谢。 :) 不幸的是,代码更改不起作用,并且您提供的链接具有以下说明“如果您在页面加载时绑定您的网格视图,那么您必须检查页面回发。所以如果页面没有回发然后只绑定网格。可能也是这种情况。” ---当gridview绑定到aspx页面本身而不是PageLoad代码中的sqldatasource控件时,我不知道该怎么做。有什么指示吗?
    • 我已经添加了 Page_Load 代码。我已经尝试更新代码行以使用 (Gridview)sender,但它没有产生任何影响。还有其他建议吗?
    • 抱歉耽搁了,我正在尝试做一些测试来重现问题。我认为问题出在以下两种方法之一:CreateChildControls 或 EnsureChildControls。
    • 尝试添加“gvPhones.DataBind();”在 (!IsPostBack) 块内。
    • 非常感谢您的帮助。 :) 回复:您的第二条评论:那是它最初的位置(就在“gvPhones.Sort”行之前),但我昨天移动了它,因为我认为它可能会有所帮助(没有任何区别)。问:Sort 命令中的一些东西已经过时了……这可能是相关的吗?
    【解决方案2】:

    我最终放弃了整个课程。相反,我制作了常规的 asp:gridviews,它基于具有联合选择的数据源,其中一行在键列中带有 -1(因为我的所有表都有单个自动增量 PK,所以没有行在键列中合法地具有 -1) ,然后将以下内容放入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 中始终至少存在一行(即使该行被隐藏),并且始终显示页脚行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-11
      • 2021-07-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多