基于另一个控件的ControlId设置WebControls TabIndex

时间:2010-04-01 00:21:13

标签: asp.net html webforms extension-methods tabindex

我有一个ASP.NET Webforms网站,它经常添加功能。 大多数时候,新的WebControl被添加到页面中,我需要将TabIndex增加到页面上的所有后续控件。

我更喜欢比在初始分配的标签索引之间选择任意间隙更强大的解决方案。使用设计器选项卡顺序功能设置选项卡索引是一种选择,但我更喜欢保留在源视图中。

理想情况下,如果我有三个复选框,我希望能够根据之前的控制tabindex定义tabindex。然后我只需要插入新控件并更改一个现有控件。

例如,将新属性TabIndexAfterControlId添加到WebControl:

<asp:CheckBox ID="checkBoxA" runat="server" TabIndex="1"/>
<asp:CheckBox ID="checkBoxB" runat="server" TabIndexAfterControlId="checkBoxA"/>
<asp:CheckBox ID="checkBoxC" runat="server" TabIndexAfterControlId="checkBoxB"/>

我的第一个想法是使用新属性扩展System.Web.UI.WebControls.WebControl,但extension properties aren't supported

2 个答案:

答案 0 :(得分:0)

注意:此方法适用于某些webcontrols(DropDownLists),但不适用于所有这些(CheckBoxes)。我把它留在这里作为参考。

我最终得到了一个使用代码隐藏方法捕获控件之间关系的解决方案。

<asp:CheckBox ID="checkBoxA" runat="server" TabIndex="1"/>
<asp:CheckBox ID="checkBoxB" runat="server" TabIndex='<%# TabIndexAfter(checkBoxB, checkBoxA) %>'/>
<asp:CheckBox ID="checkBoxC" runat="server" TabIndex='<%# TabIndexAfter(checkBoxC, checkBoxB) %>'/>

代码隐藏方法最初将执行基本的TabIndex分配,当Tab键顺序遵循页面上控件的顺序时,该分配很有效。然后在PreRender事件期间,将再次检查选项卡索引顺序。如果Tab键顺序不符合页面的自然流程,这一点很重要。

    private LinkedList<WebControl> _webControlTabOrder;

    /// <summary>
    /// Assign the current WebControl TabIndex a value greater than the prior WebControl.
    /// </summary>
    /// <param name="currentWebControl">The current WebControl to set the TabIndex for</param>
    /// <param name="priorWebControl">The prior WebControl to get the previous TabIndex from.</param>
    /// <returns>The new TabIndex for the control</returns>
    public int TabIndexAfter(WebControl currentWebControl, WebControl priorWebControl)
    {
        if (_webControlTabOrder == null)
        {
            _webControlTabOrder = new LinkedList<WebControl>();
            this.PreRender += new EventHandler(UserControlBase_PreRender);
        }

        LinkedListNode<WebControl> priorNode = _webControlTabOrder.Find(currentWebControl);

        if (priorNode == null)
        {
            priorNode = _webControlTabOrder.AddLast(priorWebControl);
        }
        _webControlTabOrder.AddAfter(priorNode, currentWebControl);

        return priorWebControl.TabIndex + 1;
    }

    void UserControlBase_PreRender(object sender, EventArgs e)
    {
        LinkedListNode<WebControl> currentNode = _webControlTabOrder.First;

        while(currentNode.Next != null)
        {
            LinkedListNode<WebControl> nextNode = currentNode.Next;

            if (nextNode.Value.TabIndex <= currentNode.Value.TabIndex)
            {
                nextNode.Value.TabIndex = (short)(currentNode.Value.TabIndex + 1);
            }

            currentNode = nextNode;
        }

    }

答案 1 :(得分:0)

我之前尝试使用数据绑定语法(&lt;%#...%&gt;)来设置Web控件的TabIndex失败,因为某些控件不会绑定TabIndex(CheckBox)。它也不理想,因为我需要将对当前控件的引用传递给后面的代码方法。

这一次,我选择了一个自定义ExpressionBuilder,它接受​​当前控件应按Tab键顺序执行的Web控件的名称。

TabIndexAfterExpressionBuilder最初返回short -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;
            }

        }
    }
}

<强>的web.config

<system.web>
    <compilation debug="true" targetFramework="4.0">
        <expressionBuilders>
            <add expressionPrefix="TabIndex" type="ExpressionBuilders.TabIndexExpressionBuilder, ExpressionBuilders"/>
        </expressionBuilders>
    </compilation>
</system.web>