Jurioli / Blazor.WebForm.Components

ASP.NET Web Forms System.Web.UI.WebControls Razor Components For Blazor WebAssembly, Blazor Hybrid, Blazor Server.
MIT License
44 stars 9 forks source link

Getting a reference to a control inside gridview row in OnRowDataBound #12

Closed MG1376 closed 5 months ago

MG1376 commented 5 months ago

I have this gridview:

<asp.GridView AllowPaging="true" AllowSorting="true" AllowCustomPaging="true" AutoGenerateColumns="false" CellPadding="4"
              DataKeyNames="Id" @ref="gv" DataSourceID="freesrc" ID="grdview"
              PageSize="5" ShowHeaderWhenEmpty="true" CssClass="w-100 table-responsive"
              OnRowUpdating="GVUpdating" OnRowUpdated="GVUpdated" OnRowEditing="GVRowEditing"
              OnRowDataBound="GVRowBound" OnDataBinding="GVBinding" OnDataBound="GVBound">
    <HeaderStyle CssClass="bg-primary text-start small" ForeColor="White">

    </HeaderStyle>
    <EmptyDataRowStyle CssClass="text-center small">

    </EmptyDataRowStyle>
    <EditItemStyle CssClass="text-start" HorizontalAlign="UI.HorizontalAlign.Center">

    </EditItemStyle>
    <ItemStyle CssClass="text-start"></ItemStyle>
    <Columns>
        <asp.TemplateField>
            <EditItemTemplate TItem="GreenPaperItem">
                <asp.Button Text="Update" CommandName="Update" CssClass="btn btn-success">

                </asp.Button>
                <asp.Button Text="Cancel" CommandName="Cancel" CssClass="btn btn-warning">

                </asp.Button>
            </EditItemTemplate>
            <ItemTemplate TItem="GreenPaperItem">
                <asp.Button Text="Delete" CommandName="Delete" CssClass="btn btn-danger">

                </asp.Button>
                <asp.Button Text="Edit" CommandName="Edit" CssClass="btn btn-primary">

                </asp.Button>
            </ItemTemplate>
        </asp.TemplateField>
        <asp.TemplateField HeaderText="Date">
            <EditItemTemplate TItem="GreenPaperItem">
                <asp.TextBox ID="txtTime" TextMode="UI.TextBoxMode.Time" 
                             CssClass="form-control"
                             OnTextChanged="((s, e) =>
                               {
                                   var timeText = ((UI.TextBox)s).Text;
                                  var arr = timeText.Split(':');
                                   var h = Convert.ToInt32(arr[0]);
                                   var m = Convert.ToInt32(arr[1]);
                     AddingTime = new DateTime(2000, 1, 1, h, m, 0);
                                   StateHasChanged();
                               })">
                </asp.TextBox>
            </EditItemTemplate>
            <ItemTemplate TItem="GreenPaperItem">
                <asp.Label ID="lblDate" Text="@($"{context.Date.Hour}:{context.Date.Minute}")"></asp.Label>
            </ItemTemplate>
        </asp.TemplateField>
        <asp.TemplateField HeaderText="Title">
            <EditItemTemplate TItem="GreenPaperItem">
                <asp.TextBox ID="txtTitle" @bind-Text="@context.Title" CssClass="form-control"></asp.TextBox>
            </EditItemTemplate>
            <ItemTemplate TItem="GreenPaperItem">
                <asp.Label ID="lblTitle" Text="@context.Title"></asp.Label>
            </ItemTemplate>
        </asp.TemplateField>
    </Columns>
</asp.GridView>

<asp.FreeDataSource ID="freesrc" OnExecuteSelected="this.Selected"
                    OnExecuteUpdated="this.Updated"
                    @ref="fds">

</asp.FreeDataSource>

<br />
<h1>@msg</h1>
<br />

@code {

    string msg = "";

    protected void GVRowBound(object sender, UI.GridViewRowEventArgs e)
    {
        msg += "GV Row Bound, ";
        if (e.Row.RowType == UI.DataControlRowType.DataRow)
        {
            if (e.Row.RowState == UI.DataControlRowState.Edit)
            {
                var item = (GreenPaperItem)e.Row.DataItem;              
                var txtbox = e.Row.FindControl("txtTime") as UI.TextBox; // or e.Row.Cells[1].FindControl("txtTime") as UI.TextBox;
                txtbox.Text = $"{item.Date.Hour}:{item.Date.Minute}"; // txtBox is null 
                //AddingTime = null;
            }
        }
    }
}

and I removed all non asp.net components from this grid, I expected to get a reference to the txtTime control inside gridview but it is null. is this possible?

Jurioli commented 5 months ago

EditItemTemplate will not add ChildControl to GridViewRow until its RenderFragment\<TItem> is actually executed. FindControl must wait until the addition is completed before it can be used.

    protected void GVRowBound(object sender, UI.GridViewRowEventArgs e)
    {
        msg += "GV Row Bound, ";
        if (e.Row.RowType == UI.DataControlRowType.DataRow)
        {
            if ((e.Row.RowState & UI.DataControlRowState.Edit) != 0)
            {
                var item = (GreenPaperItem)e.Row.DataItem;
                this.InvokeWaitTask(() =>
                {
                    var txtbox = e.Row.FindControl("txtTime") as UI.TextBox;
                    if (txtbox != null)
                    {
                        txtbox.Text = $"{item.Date.Hour}:{item.Date.Minute}";
                        this.StateHasChanged();
                    }
                    return Task.CompletedTask;
                });
                //AddingTime = null;
            }
        }
    }

But you should be able to use context directly in EditItemTemplate.

            <EditItemTemplate TItem="GreenPaperItem">
                <asp.TextBox ID="txtTime" TextMode="UI.TextBoxMode.Time" 
                             CssClass="form-control" Text="@($"{context.Date.Hour}:{context.Date.Minute}")"
                             OnTextChanged="((s, e) =>
                               {
                                   var timeText = ((UI.TextBox)s).Text;
                                  var arr = timeText.Split(':');
                                   var h = Convert.ToInt32(arr[0]);
                                   var m = Convert.ToInt32(arr[1]);
                     AddingTime = new DateTime(2000, 1, 1, h, m, 0);
                                   StateHasChanged();
                               })">
                </asp.TextBox>
            </EditItemTemplate>
MG1376 commented 5 months ago

With this way:

 protected void GVRowBound(object sender, UI.GridViewRowEventArgs e)
    {
        msg += "GV Row Bound, ";
        if (e.Row.RowType == UI.DataControlRowType.DataRow)
        {
            if ((e.Row.RowState & UI.DataControlRowState.Edit) != 0)
            {
                var item = (GreenPaperItem)e.Row.DataItem;
                this.InvokeWaitTask(() =>
                {
                    var txtbox = e.Row.FindControl("txtTime") as UI.TextBox;
                    if (txtbox != null) // is null
                    {
                        txtbox.Text = $"{item.Date.Hour}:{item.Date.Minute}";
                        this.StateHasChanged();
                    }
                    return Task.CompletedTask;
                });
                //AddingTime = null;
            }
        }
    }

the txtbox is still null

Jurioli commented 5 months ago

Blazor server seems to be slower to complete addition, then there's no way.

MG1376 commented 5 months ago

Fortunately there was a simple way, I found the solution to be:

protected void GVRowBound(object sender, UI.GridViewRowEventArgs e)
{
    msg += "GV Row Bound, ";
    if (e.Row.RowType == UI.DataControlRowType.DataRow)
    {
        if ((e.Row.RowState & UI.DataControlRowState.Edit)
           == UI.DataControlRowState.Edit)
        {
            var item = (GreenPaperItem)e.Row.DataItem;          
            InvokeAsync(() => this.StateHasChanged());
            var txtbox = e.Row.FindControl("txtTime") as UI.TextBox;
            if (txtbox != null) // is null
            {
                var zero = item.Date.Hour < 10 ? "0" : "";
                txtbox.Text = $"{zero}{item.Date.Hour}:{zero}{item.Date.Minute}";
            }
        }
    }
}
Jurioli commented 5 months ago

Awesome!