Shift Focus to the Next Control during a PostBack using ASP.NET and LINQ

Posted by: Suprotim Agarwal , on 1/5/2009, in Category ASP.NET
Views: 99748
Abstract: We at times come across a requirement where a feature in a web application is supposed to behave similar to that of its windows counterpart. One such requirement is to either maintain the focus on the control that caused a postback or to shift focus to the next control after a postback. In this article, we will use ASP.NET and LINQ to do so.
Shift Focus to the Next Control during a PostBack using ASP.NET and LINQ
 
We at times come across a requirement where a feature in a web application is supposed to behave similar to that of its windows counterpart. One such requirement is to either maintain the focus on the control that caused a postback or to shift focus to the next control after a postback.
Ryan Farley has two cool articles on Determining the Control that Caused a PostBack  and Set Focus to an ASP.NET Control. We will make use of his code to determine the control that caused a postback. We will then build on that code and use LINQ to loop through the controls, find the TabIndex of the control that caused postback and then shift focus to the control having the next TabIndex. I got this idea of using LINQ while reading a forum post and thought that this solution would be worth sharing with others.
In an application where a lot of text fields are involved, we generally see that when a user tabs out of a textbox, some calculation is performed and the calculated value is then displayed back in the textbox. On a page not powered with ASP.NET AJAX, the desired behavior is to display the calculated value in the textbox and shift the focus to the next one.
In an ASP.NET page, the __doPostBack is used by server controls to cause a postback. If you observe the html markup after a postback, ASP.NET automatically adds two hidden fields (“__EVENTTARGET” and “__EVENTARGUMENT”) and a client-side script method (“__doPostBack”) to the page. The EVENTTARGET is the ID of the control that caused the postback and the EVENTARGUMENT contains any arguments passed that can be accessed on the server. The __doPostBack method sets the values of the hidden fields and causes the form to be submitted to the server. Ryan makes use of the __EVENTTARGET to find the control that caused postback.
Note: Remember that Button and the ImageButton do not use the __doPostBack unless the UseSubmitBehaviour property is set explicitly.
In this sample of ours, I am using a couple of controls on the form to test out our logic. There are a few TextBoxes with AutoPostBack = true which will cause a postback whenever the control looses focus. I also have a couple of Buttons and ImageButton on the page. We will set the TabIndex of each of these controls as shown below:
The markup looks similar to the following:
   <form id="form1" runat="server">
    <div>
        <asp:TextBox ID="TextBox1" runat="server" AutoPostBack="True" TabIndex="1">
        </asp:TextBox>
        <br />
        <asp:TextBox ID="TextBox2" runat="server" AutoPostBack="True" TabIndex="2">
        </asp:TextBox>
        <br />
        <asp:Button ID="Button1" runat="server" Text="Button" TabIndex="3" />
        <br />
        <asp:Button ID="Button2" runat="server" Text="Button" TabIndex="4" />       
        <br />
    </div>
    </form>
The code to set focus to the next control after a postback is given below:
C#
    protected void Page_Load(object sender, EventArgs e)
    {
        if (Page.IsPostBack)
        {
            WebControl wcICausedPostBack = (WebControl)GetControlThatCausedPostBack(sender as Page); 
            int indx = wcICausedPostBack.TabIndex;                      
            var ctrl = from control in wcICausedPostBack.Parent.Controls.OfType<WebControl>()
                       where control.TabIndex > indx
                       select control;
            ctrl.DefaultIfEmpty(wcICausedPostBack).First().Focus();
        }
    }
 
    protected Control GetControlThatCausedPostBack(Page page)
    {
        Control control = null;
 
        string ctrlname = page.Request.Params.Get("__EVENTTARGET");
        if (ctrlname != null && ctrlname != string.Empty)
        {
            control = page.FindControl(ctrlname);
        }
        else
        {
            foreach (string ctl in page.Request.Form)
            {
                Control c = page.FindControl(ctl);
                if (c is System.Web.UI.WebControls.Button || c is System.Web.UI.WebControls.ImageButton)
                {
                    control = c;
                    break;
                }
            }
        }
        return control;
 
    }  

VB.NET
   Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Page.IsPostBack Then
            Dim wcICausedPostBack As WebControl = CType(GetControlThatCausedPostBack(TryCast(sender, Page)), WebControl)
            Dim indx As Integer = wcICausedPostBack.TabIndex
            Dim ctrl = _
             From control In wcICausedPostBack.Parent.Controls.OfType(Of WebControl)() _
             Where control.TabIndex > indx _
             Select control
            ctrl.DefaultIfEmpty(wcICausedPostBack).First().Focus()
        End If
    End Sub
 
    Protected Function GetControlThatCausedPostBack(ByVal page As Page) As Control
        Dim control As Control = Nothing
 
        Dim ctrlname As String = page.Request.Params.Get("__EVENTTARGET")
        If ctrlname IsNot Nothing AndAlso ctrlname <> String.Empty Then
            control = page.FindControl(ctrlname)
        Else
            For Each ctl As String In page.Request.Form
                Dim c As Control = page.FindControl(ctl)
                If TypeOf c Is System.Web.UI.WebControls.Button OrElse TypeOf c Is System.Web.UI.WebControls.ImageButton Then
                    control = c
                    Exit For
                End If
            Next ctl
        End If
        Return control
 
    End Function
The code makes use of the GetControlThatCausedPostBack function written by Ryan to find the control that caused the postback. We then determine the TabIndex of the control and use LINQ to select the control with the next tabindex and set focus to it .I have used LINQ as I find it very useful when I loop over collections. It gives me all the control I need over the code, keeping it tight and without much effort.
Just run the application and test out the functionality. When you tab out of a TextBox or click on the Button control, the focus shifts to the next control after a postback. That’s it for now. I hope you liked the article and I thank you for viewing it. 
 If you liked the article,  Subscribe to my RSS Feed or Subscribe Via Email

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of DotNetCurry, DNC Magazine for Developers, SQLServerCurry and DevCurry. He has also authored a couple of books 51 Recipes using jQuery with ASP.NET Controls and The Absolutely Awesome jQuery CookBook.

Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.

Get in touch with him on Twitter @suprotimagarwal or at LinkedIn



Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!
Comment posted by swarna on Monday, January 12, 2009 11:10 PM
hello
Comment posted by Kirti Darji on Tuesday, April 28, 2009 6:18 AM
when i use in gridview foter after added two row focus are Maintain on foter Textbox but after that not Focus on Foter Text Box Please Provide help
Comment posted by Suprotim Agarwal on Wednesday, April 29, 2009 8:03 AM
Kirti: Can you mail me the code using the Contact link at the top of this page.
Comment posted by Jonathan Small on Thursday, March 25, 2010 9:11 AM
I have an asp.net page where I dynamically generate a series of text boxes.  When generated, I generate an ID of txtNewElection_NN where NN is a number from 01 through some other number.  I also generate a tab index starting at 1 and bumping by 1 for each dynamically generated text box.  All text boxes have autopostback set to true.  When I populate txtNewElection_02 and press the tab key, a postback is executed.  I execute your code.  In the function GetControlThatCausedPostBack, the variable ctrlname gets the value of txtNewElection_02 correctly.  However, the line control = page.FindControl(ctrlname) results in control being nothing.  Could this be because my textbox fields are being generated dynamically?  The only thing I could think of would be that the dynamically generated textboxes don't have runat="server" (or do they?).  Any help would be greatly appreciated.
Comment posted by Kasper on Friday, March 26, 2010 6:21 AM
Jonathan, if you build the controls before you execute the code, you should be able to find them using findcontrol.
Comment posted by Steve Nixey on Saturday, August 14, 2010 11:14 AM
hi, thanks, dont take this the wrong way but I love you :-) nice, very nice and simple :-)
Comment posted by Suprotim Agarwal on Monday, August 16, 2010 3:21 AM
Steven: LOL..thanks!
Comment posted by Kristoffer on Tuesday, October 12, 2010 2:59 PM
Thank you alot, worked perfect!
Just copy/paste and it worked flawless!

Comment posted by Keith Thompson on Tuesday, November 23, 2010 9:31 AM
Hi, great article, very clear and simple, thanks.  I was wondering if there was a way to adapt this to set focus to "control n", so to speak?  E.g. if you have many textboxes on screen, the user may have filled textbox(1), then clicked on textbox(5).  The focus is still lost, but after the postback, you want to set focus to textbox(5), NOT textbox(2) (i.e. the textbox with the next tabindex)...  I'd be grateful for any pointers!
Comment posted by karam on Monday, October 24, 2011 3:13 AM
dharam
Comment posted by Steve on Friday, January 13, 2012 12:09 PM
I'm having problems with the cursor control in an updatepanel (it works in a regular panel).  Any help you can provide is appreciated.

Code is:

   <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
    <asp:UpdatePanel runat="server" ID="UpdatePanel">
      <ContentTemplate>
         <asp:Panel runat="server" ID="InnerPanel" BorderStyle="Solid">
            <asp:Table runat="server">
               <asp:TableRow runat="server">
                  <asp:TableCell runat="server">
                     <asp:Label ID="Demographics_Label" runat="server">Demographics</asp:Label>
                  </asp:TableCell>
               </asp:TableRow>
               <asp:TableRow runat="server">
                  <asp:TableCell runat="server">
                     <asp:TextBox ID="TextBox1" runat="server" AutoPostBack="True" TabIndex="1"></asp:TextBox>
                  </asp:TableCell>
               </asp:TableRow>
               <asp:TableRow runat="server">
                  <asp:TableCell runat="server">
                     <asp:TextBox ID="TextBox2" runat="server" AutoPostBack="True" TabIndex="2"></asp:TextBox>
                  </asp:TableCell>
               </asp:TableRow>
               <asp:TableRow runat="server">
                  <asp:TableCell runat="server">
                     <asp:Button ID="Button1" runat="server" Text="Button" TabIndex="3" />
                  </asp:TableCell>
               </asp:TableRow>
               <asp:TableRow runat="server">
                  <asp:TableCell runat="server">
                     <asp:Button ID="Button2" runat="server" Text="Button" TabIndex="4" />
                  </asp:TableCell>    
               </asp:TableRow>
            </asp:Table>
         </asp:Panel>
      </ContentTemplate>
    </asp:UpdatePanel>

  Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Page.IsPostBack Then
            Dim wcICausedPostBack As WebControl = CType(GetControlThatCausedPostBack(TryCast(sender, Page)), WebControl)
            Dim indx As Integer = wcICausedPostBack.TabIndex
            Dim ctrl = _
             From control In wcICausedPostBack.Parent.Controls.OfType(Of WebControl)() _
             Where control.TabIndex > indx _
             Select control
            ctrl.DefaultIfEmpty(wcICausedPostBack).First().Focus()
         Session("TextBox1") = TextBox1.Text
         Session("TextBox2") = TextBox2.Text
      Else
         If Not Session("TextBox1") Is Nothing
            TextBox1.Text = Session("TextBox1").ToString()         
         End If

         If Not Session("TextBox1") Is Nothing
            TextBox2.Text = Session("TextBox2").ToString()
         End If
         
        End If
    End Sub

    Protected Function GetControlThatCausedPostBack(ByVal page As Page) As Control
        Dim control As Control = Nothing

        Dim ctrlname As String = page.Request.Params.Get("__EVENTTARGET")
        If ctrlname IsNot Nothing AndAlso ctrlname <> String.Empty Then
            control = page.FindControl(ctrlname)
        Else
            For Each ctl As String In page.Request.Form
                Dim c As Control = page.FindControl(ctl)
                If TypeOf c Is System.Web.UI.WebControls.Button OrElse TypeOf c Is System.Web.UI.WebControls.ImageButton Then
                    control = c
                    Exit For
                End If
            Next ctl
        End If
        Return control

    End Function
End Class
Comment posted by Steve on Friday, January 13, 2012 1:24 PM
Forgot to include what the problem is:

The cursor goes down to the next field but then goes back to the field where the postback occurred.  

Thanks
Comment posted by Steve on Friday, January 13, 2012 2:56 PM
Forgot to include what the problem is:

The cursor goes down to the next field but then goes back to the field where the postback occurred.  

Thanks
Comment posted by Steve on Friday, January 13, 2012 3:37 PM
Figured out the problem has to do with having each TextBox in it's own TableCell.   Not sure the solution yet.   But if I put all TextBoxes in the same TableCell - each seperated by break, the cursor movement works correctly...
Comment posted by Steve on Tuesday, January 17, 2012 12:02 PM
Figured out the problem has to do with having each TextBox in it's own TableCell.   Not sure the solution yet.   But if I put all TextBoxes in the same TableCell - each seperated by break, the cursor movement works correctly...
Comment posted by Angela on Wednesday, February 1, 2012 1:11 PM
Hi Steve...I have the same problem.  I have to double-tab to get it to move to the next control when they are in their own cells.  Have you come up with a solution yet?
Comment posted by Angela on Wednesday, February 1, 2012 1:33 PM
It's an issue with the use of the update panel, not the cells.  It stills has the same problem if you put two droplists within the same cell of an update panel.
Comment posted by Angela on Wednesday, February 1, 2012 2:00 PM
Woohoo!  Ok...the fix for this problem:  where you have to tab twice when using cascading drop lists inside an update panel in Internet Explorer:  Use the code above AND set the tab index for each control on the page.  i.e. TabIndex ="1", TabIndex ="2".  
Comment posted by gspeedtech on Tuesday, February 7, 2012 3:09 PM
This is awesome!
However I am having trouble converting it to VB.Net 2.0 because of(.OfType and .DefaultIfEmpty)
Can you tell me how to translate so it will work in 2.0?
Comment posted by Steve on Monday, February 13, 2012 1:26 PM
Angela - I fixed my problem by using html table instead of asp table.  Also fixed a problem with UpdatePanel by adding Updatemode = "Conditional" ChildrenAsTriggers="false" to the updatepanel and EnablePartialRendering="true" to ToolScriptManager.  Only problem I'm currently having is going from textbox to image control via tabindex.  Blows up on me.
Comment posted by Steve on Tuesday, February 14, 2012 1:26 PM
Angela - I fixed my problem by using html table instead of asp table.  Also fixed a problem with UpdatePanel by adding Updatemode = "Conditional" ChildrenAsTriggers="false" to the updatepanel and EnablePartialRendering="true" to ToolScriptManager.  Only problem I'm currently having is going from textbox to image control via tabindex.  Blows up on me.
Comment posted by vipul chotaliya on Wednesday, June 6, 2012 4:49 AM
System.NullReferenceException: Object reference not set to an instance of an object.
Comment posted by vipul chotaliya on Wednesday, June 6, 2012 4:50 AM
I get this error:

Object reference not set to an instance of an object.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Comment posted by Daniel Powell on Tuesday, June 12, 2012 12:56 PM
If you use special tabindex to control how a form is filled out then the original code has a problem.  The following is an example table, brackets indicate cells, numbers indicate a control with a tabindex is inside the cell:

Col1  Col2  Col3
[ ]   [ ]   [7]
[1]   [2]   [8]
[3]   [4]   [9]
[5]   [6]   [10]

If you change control with tabindex 1 through 6, the next control to get focused is control with tabindex 7 because it is the first query result.  Even if you delete the first row, if you changed control with tabindex 3, then control with tabindex 8 would be selected.  You have to order the results like so:

            Dim ctrl = _
             From control In wcICausedPostBack.Parent.Controls.OfType(Of WebControl)() _
             Where control.TabIndex > indx _
             Order By control.TabIndex
             Select control
Comment posted by Daniel Powell on Tuesday, June 12, 2012 1:03 PM
Forgot to add underscore after "Order By control.TabIndex"

            Dim ctrl = _
             From control In wcICausedPostBack.Parent.Controls.OfType(Of WebControl)() _
             Where control.TabIndex > indx _
             Order By control.TabIndex _
             Select control
Comment posted by Daniel Powell on Tuesday, June 12, 2012 1:20 PM
Here's the original code with the fix and another feature to select all the text if the control to get focus is a textbox:

        If Page.IsPostBack Then ' Code from blog: http://www.dotnetcurry.com/ShowArticle.aspx?ID=253 with my fix to add "Order by" clause
            Dim wcICausedPostBack As WebControl = CType(GetControlThatCausedPostBack(TryCast(sender, Page)), WebControl)
            Dim indx As Integer = wcICausedPostBack.TabIndex
            Dim ctrl = _
                 From control In wcICausedPostBack.Parent.Controls.OfType(Of WebControl)() _
                 Where control.TabIndex > indx
                 Order By control.TabIndex _
                 Select control
            With ctrl.DefaultIfEmpty(wcICausedPostBack)
                .First().Focus()
                If TypeOf (.First()) Is TextBox Then
                    Dim strScript As String = "document.getElementById('" & .First().ClientID & "').select();"
                    ScriptManager.RegisterStartupScript(.First(), .First().Page.GetType, (New Guid).ToString, strScript, True)
                End If
            End With
        End If
Comment posted by Daniel Powell on Tuesday, June 12, 2012 1:59 PM
Bug fix with my code:

Replace: (New Guid).ToString
With: (New Guid()).NewGuid().ToString
Comment posted by Steve on Thursday, June 14, 2012 6:04 PM
This doesn't appear to work for webcontrols that are in nested updatepanels.  For example, I have a checkbox control in a parent updatepanel.  When I do the postback, I would want to see the control collection loop thru the controls in the nested updatepanel.  And then move the focus to the first textbox in the nested updatepanel.  But it only sees the checkbox control in the parent updatepanel.  And focus stays with that control.
Comment posted by Steve on Monday, July 16, 2012 12:28 PM
How would you get this to work controls in a repeater.  For instance, you have a textbox and a repeater in an updatepanel.  When a value is entered into textbox control and the tab key is hit, causing the postback to occur, I want the cursor to move to the first textbox in the repeater control.  But the parent of the repeater control is the repeater, not the panel.  How can this be modified to recognize the child controls in the repeater?

Thanks
Comment posted by Steve on Monday, July 16, 2012 4:37 PM
How would you get this to work controls in a repeater.  For instance, you have a textbox and a repeater in an updatepanel.  When a value is entered into textbox control and the tab key is hit, causing the postback to occur, I want the cursor to move to the first textbox in the repeater control.  But the parent of the repeater control is the repeater, not the panel.  How can this be modified to recognize the child controls in the repeater?

Thanks
Comment posted by Steve on Tuesday, July 17, 2012 2:32 PM
How would you get this to work controls in a repeater.  For instance, you have a textbox and a repeater in an updatepanel.  When a value is entered into textbox control and the tab key is hit, causing the postback to occur, I want the cursor to move to the first textbox in the repeater control.  But the parent of the repeater control is the repeater, not the panel.  How can this be modified to recognize the child controls in the repeater?

Thanks
Comment posted by Steve on Tuesday, July 17, 2012 6:22 PM
How would you get this to work controls in a repeater.  For instance, you have a textbox and a repeater in an updatepanel.  When a value is entered into textbox control and the tab key is hit, causing the postback to occur, I want the cursor to move to the first textbox in the repeater control.  But the parent of the repeater control is the repeater, not the panel.  How can this be modified to recognize the child controls in the repeater?

Thanks
Comment posted by Steve on Thursday, July 19, 2012 12:09 PM
How would you get this to work controls in a repeater.  For instance, you have a textbox and a repeater in an updatepanel.  When a value is entered into textbox control and the tab key is hit, causing the postback to occur, I want the cursor to move to the first textbox in the repeater control.  But the parent of the repeater control is the repeater, not the panel.  How can this be modified to recognize the child controls in the repeater?

Thanks
Comment posted by Steve on Thursday, July 19, 2012 4:39 PM
How would you get this to work controls in a repeater.  For instance, you have a textbox and a repeater in an updatepanel.  When a value is entered into textbox control and the tab key is hit, causing the postback to occur, I want the cursor to move to the first textbox in the repeater control.  But the parent of the repeater control is the repeater, not the panel.  How can this be modified to recognize the child controls in the repeater?

Thanks
Comment posted by shankar on Thursday, October 18, 2012 6:27 AM
my problem is that i am having some 12 textboxes.i am using asp .net technology.when i change value in any textbox one event is called and postback happens and focus goes to the first textbox.however i want to set focus on my desired location where ever i click.if you guys have any solution for this please let me know............Thanks in advance
Comment posted by Sandip Paul on Saturday, June 14, 2014 12:36 PM
I have a gridview which have dropdowllist,textbox as itemfield.I can add dynamically rows to gridview.I want to move the cursor to next cell after postback from dropdown list and when i add a new row then cursor will move to the new rows first cell that is dropdownlist.The gridview is in tabpanel.
I need your help
Comment posted by Arunmozhidevan on Friday, September 26, 2014 6:07 AM
I got the error like this,
'Object reference not set to an instance of an object.'
in the following line
int indx = wcICausedPostBack.TabIndex;

Please guide me what is my mistake?
Advance Thanks.
Comment posted by pavan on Wednesday, October 29, 2014 7:08 AM
The cursor goes down to the next field but then goes back to the field where the postback occurred.