我之前尝试使用数据绑定语法 (<%# ... %>) 为 Web 控件设置 TabIndex 失败,因为某些控件不会绑定 TabIndex (CheckBox)。这也不是很理想,因为我需要将对当前控件的引用传递到代码隐藏方法中。
这一次,我使用了一个自定义ExpressionBuilder,它接受当前控件应按 Tab 键顺序跟随的 Web 控件的名称。
TabIndexAfterExpressionBuilder 最初返回短 -1 作为值。同时它注册到当前 Page 的 LoadComplete 事件。当此事件触发时,会找到两个控件并根据它们的相对位置设置选项卡索引。
使用 TabIndex 表达式生成器的示例 WebControl
<asp:TextBox ID="txtTextBox0" runat="server" TabIndex="1" /><br />
<asp:TextBox ID="txtTextBox1" runat="server" TabIndex="<%$ TabIndex:txtTextBox0 %>" /><br />
<asp:TextBox ID="txtTextBox2" runat="server" TabIndex="<%$ TabIndex:txtTextBox1 %>" />
TabIndexExpressionBuilder.cs
namespace ExpressionBuilders
{
    public class TabIndexExpressionBuilder : ExpressionBuilder
    {
        public override System.CodeDom.CodeExpression GetCodeExpression(System.Web.UI.BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
        {
            string priorControlId = entry.Expression.Trim();
            string currentControlId = entry.ControlID;
            CodeExpression[] inputParams = new CodeExpression[] { new CodePrimitiveExpression(priorControlId),
                                                       new CodePrimitiveExpression(currentControlId),
                                                       new CodeTypeOfExpression(entry.DeclaringType),
                                                       new CodePrimitiveExpression(entry.PropertyInfo.Name) };
            // Return a CodeMethodInvokeExpression that will invoke the GetRequestedValue method using the specified input parameters
            return new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(this.GetType()),
                                        "GetRequestedValue",
                                        inputParams);
        }
        public static object GetRequestedValue(string priorControlId, string currentControlId, Type targetType, string propertyName)
        {
            if (HttpContext.Current == null)
            {
                return null;
            }
            Page page = HttpContext.Current.Handler as Page;
            if (page != null)
            {
                page.LoadComplete += delegate(object sender, EventArgs e)
                {
                    WebControl currentWebControl = FindControlRecursive(page, currentControlId);
                    WebControl priorWebControl = FindControlRecursive(page, priorControlId);
                    if (currentWebControl != null && priorWebControl != null)
                    {
                        TabIndexAfter(page, currentWebControl, priorWebControl);
                    }
                };
            }
            // Default TabIndex
            short value = (short)-1;
            return value;
        }
        private static WebControl FindControlRecursive(Control rootControl, string controlID)
        {
            if (rootControl.ID == controlID) { return rootControl as WebControl; }
            foreach (Control controlToSearch in rootControl.Controls)
            {
                Control controlToReturn = FindControlRecursive(controlToSearch, controlID);
                if (controlToReturn != null)
                {
                    return controlToReturn as WebControl;
                }
            }
            return null;
        }
        #region Tabbing
        /// <summary>
        /// Assign the current WebControl TabIndex a value greater than the prior WebControl.
        /// </summary>
        /// <param name="currentWebControl">The current Control to set the TabIndex for</param>
        /// <param name="priorWebControl">The prior Control to get the previous TabIndex from.</param>
        /// <returns>The new TabIndex for the current control</returns>
        private static short TabIndexAfter(Page page, WebControl currentWebControl, object prior)
        {
            TabOrderWebControl tabOrderWebControl = page.FindControl("TabOrderWebControl") as TabOrderWebControl;
            if (tabOrderWebControl == null)
            {
                tabOrderWebControl = new TabOrderWebControl();
                page.Controls.Add(tabOrderWebControl);
            }
            WebControl priorWebControl = prior as WebControl;
            if (priorWebControl == null)
            {
                string priorWebControlId = prior as string;
                priorWebControl = page.FindControl(priorWebControlId) as WebControl;
            }
            if (currentWebControl == null) { throw new ArgumentNullException("currentWebControl"); }
            if (priorWebControl == null) { throw new ArgumentNullException("priorWebControl"); }
            if (currentWebControl == priorWebControl) { throw new ArgumentException("priorWebControl is the same as the currentWebControl", "priorWebControl"); }
            tabOrderWebControl.TabIndexAfter(currentWebControl, priorWebControl);
            return currentWebControl.TabIndex;
        }
        #endregion
    }
}
TabOrderWebControl.cs
namespace ExpressionBuilders
{
    public class TabOrderWebControl :
        WebControl
    {
        LinkedList<WebControl> _webControlTabOrder;
        internal void TabIndexAfter(System.Web.UI.WebControls.WebControl currentWebControl, System.Web.UI.WebControls.WebControl priorWebControl)
        {
            if (_webControlTabOrder == null)
            {
                _webControlTabOrder = new LinkedList<WebControl>();
                this.Page.PreRender += new EventHandler(PageBase_PreRender);
            }
            LinkedListNode<WebControl> priorNode = _webControlTabOrder.Find(priorWebControl);
            LinkedListNode<WebControl> currentNode = _webControlTabOrder.Find(currentWebControl);
            if (currentNode != null)
            {
                //The current node is already in the list (it must preceed some other control)
                //Add the prior node before it.
                if (priorNode == null)
                {
                    priorNode = _webControlTabOrder.AddBefore(currentNode, priorWebControl);
                }
                else
                {
                    //Both nodes are already in the list. Ensure the ordering is correct.
                    bool foundPriorNode = false;
                    foreach (WebControl controlNode in _webControlTabOrder)
                    {
                        if (controlNode == priorWebControl)
                        {
                            foundPriorNode = true;
                        }
                        else if (controlNode == currentWebControl)
                        {
                            if (foundPriorNode)
                            {
                                //Ordering is correct
                                break;
                            }
                            else
                            {
                                throw new ApplicationException(string.Format("WebControl ordering is incorrect. Found {1} before {0}", currentWebControl.ID, priorWebControl.ID));
                            }
                        }
                    }
                }
            }
            else if (priorNode == null)
            {
                //Neither control is in the list yet.
                priorNode = _webControlTabOrder.AddLast(priorWebControl);
                currentNode = _webControlTabOrder.AddAfter(priorNode, currentWebControl);
            }
            else
            {
                //Prior node is already in the list but the current node isn't
                currentNode = _webControlTabOrder.AddAfter(priorNode, currentWebControl);
            }
        }
        /// <summary>
        /// Once all the controls have been added to the linked list ensure the tab ordering is correct.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void PageBase_PreRender(object sender, EventArgs e)
        {
            AssignTabIndexes();
        }
        /// <summary>
        /// Reassign tab indexes for all known controls.
        /// </summary>
        protected void AssignTabIndexes()
        {
            LinkedListNode<WebControl> currentNode = _webControlTabOrder.First;
            while (currentNode.Next != null)
            {
                LinkedListNode<WebControl> nextNode = currentNode.Next;
                WebControl currentControl = currentNode.Value;
                WebControl nextControl = nextNode.Value;
                if (currentControl == nextControl)
                {
                    throw new ApplicationException("Control added twice");
                }
                short currentTabIndex = currentControl.TabIndex;
                short nextTabIndex = nextControl.TabIndex;
                if (nextTabIndex <= currentTabIndex)
                {
                    nextControl.TabIndex = (short)(currentTabIndex + 1);
                }
                currentNode = nextNode;
            }
        }
    }
}
网络配置
<system.web>
    <compilation debug="true" targetFramework="4.0">
        <expressionBuilders>
            <add expressionPrefix="TabIndex" type="ExpressionBuilders.TabIndexExpressionBuilder, ExpressionBuilders"/>
        </expressionBuilders>
    </compilation>
</system.web>