关于asp.net:基于另一个控件的ControlId设置WebControls TabIndex

Setting a WebControls TabIndex based on the ControlId of another control

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

比起在最初分配的标签索引之间选择一个任意的间隙,我更喜欢一个更健壮的解决方案。使用设计器选项卡顺序功能设置选项卡索引是一个选项,但我更喜欢留在源代码视图中。

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

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

1
 

我的第一个想法是使用新属性扩展System.Web.UI.WebControls.WebControl,但不支持扩展属性。


我先前尝试使用数据绑定语法(<%…%>)当某些控件无法绑定TabIndex(复选框)时,为Web控件设置TabIndex失败。它也不理想,因为我需要将对当前控件的引用传递到代码隐藏方法中。

这一次,我使用了一个自定义的ExpressionBuilder,它接受当前控件应按选项卡顺序跟随的Web控件的名称。

TabIndexAfterExpressionBuilder最初返回short-1作为值。同时,它向当前页面的LoadComplete事件注册。当此事件激发时,将找到两个控件,并根据它们的相对位置设置选项卡索引。

使用TabIndex表达式生成器的WebControls示例

1
2
3
<br />
" /><br />
" />

tabIndexExpressionBuilder.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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(禁用或取消控制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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

1
2
3
4
5
6
7
<system.web>
    <compilation debug="true" targetFramework="4.0">
        <expressionBuilders>
           
        </expressionBuilders>
    </compilation>
</system.web>

注意:这种方法适用于某些Web控件(DropDownList),但并非所有控件(复选框)。我把它留在这里作为参考。

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

1
2
'/>
'/>

代码隐藏方法最初将执行基本的tabindex分配,当选项卡顺序遵循页面上控件的顺序时,该方法工作良好。然后在预渲染事件期间,将再次检查选项卡索引顺序。如果标签顺序不遵循页面的自然流动,这一点很重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    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;
        }

    }