I am getting [MethodError 500] when I use cascading drop down. below is my code
<tr>
<td >
Select a Hoster:
</td>
<td>
<asp:DropDownList ID="ddlFeaturedHoster" runat="server" ></asp:DropDownList>
</td>
</tr>
<ajaxToolkit:CascadingDropDown ID="cddHoster" runat="server" TargetControlID="ddlFeaturedHoster"
PromptText="Select a Hoster" LoadingText="Loading ..." Category="ActiveHoster"
ServiceMethod="GetDropDownContents" ServicePath="~/Hosting/HostingService.asmx"/>
Service Code:
[WebMethod]
[ScriptMethod]
public CascadingDropDownNameValue[] GetActiveHosters()
{
List<CascadingDropDownNameValue> returnList = new List<CascadingDropDownNameValue>();
HostersManager hosterManager = new HostersManager();
List<Hosters_HostingProviderDetail> hosters = hosterManager.GetAllHosters();
returnList.Add(new CascadingDropDownNameValue("--Please Select One--","0",true));
foreach (Hosters_HostingProviderDetail item in hosters)
{
returnList.Add(new CascadingDropDownNameValue() { name=item.HostingProviderName, value= item.HosterID.ToString()});
}
return returnList.ToArray() ;
}
[WebMethod]
[ScriptMethod]
public CascadingDropDownNameValue[] GetDropDownContents(string knownCategoryValues, string category)
{
knownCategoryValues = FormatCategoryWord(knownCategoryValues);
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
HostersManager hosterManager = new HostersManager();
switch (category)
{
case "ActiveHoster":
values.AddRange(GetActiveHosters());
break;
case "ActiveOffer":
values.AddRange(GetActiveOffers(1));
break;
}
return values.ToArray<CascadingDropDownNameValue>();
}
/// <summary>
/// Formats the category word
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private string FormatCategoryWord(string value)
{
if (string.IsNullOrEmpty(value)) return value;
if (value.LastIndexOf(":") > 0) value = value.Substring(value.LastIndexOf(":") + 1);
if (value.LastIndexOf(";") > 0) value = value.Substring(0, value.LastIndexOf(";"));
return value;
}
}
How about some try catch blocks within your webMethods with some exception logging?
I kept [ScriptService] attribute on top of service class its working now.
Related
So I created an ASP.NET user control that is a simple wrapper around the bootstrap slider control. Now I want my control to have a public property named Value that I could bind to a property of my model, just like I do with asp:TextBox:
<asp:TextBox runat="server" Text="<%# BindItem.Name %>" />
Can I do the same with my user control?
<uc1:Slider runat="server" Value="<%# BindItem.Age %>" />
So for any future reader, here's a working version of bootstrap-slider's server-side control. You can drag-n-drop it onto your ASPX page. It has two properties Value and Value2 (for ranges). There is an event ValueChanged that will fire when user moves the position of slider. You can attach an event handler to this even just like you do for Button control's Click event. Last but not the least, I ensured that the control works within UpdatePanel too:
N.B. This control doesn't try to include the require JS or CSS file, because if user drops multiple instances of the Slider on the page, the output will probably include a copy of CSS and JS file for each instance, which is undesirable. I couldn't figure out how to link a CSS/JS file only if it hasn't already been included in the current page. So just make sure you include those two files in the parent page once for all.
Markup
<div id="<%= this.UniqueID + "_Div" %>">
<input runat="server" ID="_TextBox"
EnableTheming="false"
class="slider form-control"
style="vertical-align: middle; width: 100%;"
data-slider-min="<%# Min %>"
data-slider-max="<%# Max %>"
data-slider-step="<%# Step %>"
data-slider-value="<%# Value %>"
data-slider-precision="<%# Precision %>"
data-slider-orientation="horizontal"
data-slider-selection="after"
data-slider-tooltip="show"
data-slider-range="<%# IsRange.ToString().ToLower() %>" />
</div>
Code-behind
using System;
using System.ComponentModel;
using System.Web.UI;
namespace YourProjectNameSpace
{
public partial class Slider : System.Web.UI.UserControl
{
public bool IsRange { get; set; }
public float Min { get; set; }
public float Max { get; set; }
public float Step { get; set; }
public int Precision { get; set; }
public event Action ValueChanged;
[Bindable(true, BindingDirection.TwoWay)]
public float Value
{
get
{
if (IsRange)
{
string[] Vals = (this._TextBox.Attributes["value"] ?? "0,0").Split(',');
return float.Parse(Vals[0]);
}
else
return float.Parse((this._TextBox.Attributes["value"] ?? "0"));
}
set
{
if (IsRange)
{
string[] CurVals = (this._TextBox.Attributes["value"] ?? "0,0").Split(',');
this._TextBox.Attributes["value"] = value.ToString() + ',' + CurVals[1];
}
else
this._TextBox.Attributes["value"] = value.ToString();
}
}
[Bindable(true, BindingDirection.TwoWay)]
public float? Value2
{
get
{
if (IsRange)
{
string[] Vals = (this._TextBox.Attributes["value"] ?? "0,0").Split(',');
return float.Parse(Vals[1]);
}
else
return null;
}
set
{
if (IsRange)
{
string[] CurVals = (this._TextBox.Attributes["value"] ?? "0,0").Split(',');
this._TextBox.Attributes["value"] = CurVals[0] + ',' + value.ToString();
}
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
if (Request["__EVENTTARGET"] == "_TextBox")
{
if (ValueChanged != null)
ValueChanged();
string MyDivName = this.UniqueID.Replace("$", #"\\$") + "_Div";
string SliderVal = Request["__EVENTARGUMENT"];
if (IsRange) SliderVal = '[' + SliderVal.Split(',')[0] + ',' + SliderVal.Split(',')[1] + ']';
Page.ClientScript.RegisterStartupScript(this.GetType(), "CreateSliderFor_" + MyDivName,
"$('#" + MyDivName + " > input').slider().slider('setValue', " + SliderVal +
").on('slideStop', function(slideEvt) { __doPostBack('_TextBox', slideEvt.value); });", true);
}
else
{
string MyDivName = this.UniqueID.Replace("$", #"\\$") + "_Div";
Page.ClientScript.RegisterStartupScript(this.GetType(), "CreateSliderFor_" + MyDivName, "$('#" + MyDivName + " > input').slider().on('slideStop', function(slideEvt) { __doPostBack('_TextBox', slideEvt.value); });", true);
}
}
else
{
string MyDivName = this.UniqueID.Replace("$", #"\\$") + "_Div";
Page.ClientScript.RegisterStartupScript(this.GetType(), "CreateSliderFor_" + MyDivName, "$('#" + MyDivName + " > input').slider().on('slideStop', function(slideEvt) { __doPostBack('_TextBox', slideEvt.value); });", true);
}
}
}
}
Usage
Put this at the top of the page, after #Page line:
<%# Register Src="~/Slider.ascx" TagPrefix="uc1" TagName="Slider" %>
Now place Slider control anywhere in the page:
<uc1:Slider runat="server" ID="Slider1" Min="10" Max="24" Value="<%# BindItem.Age %>" Step="0.1" Precision="1" IsRange="true" OnValueChanged="Slider1_ValueChanged" />
The event handler looks like this:
protected void Slider1_ValueChanged()
{
//do whatever you want with Slider1.Value or Slider1.Value2
}
I have 2 cascading drop downs, 1 is a parent and the other/child populates once you select a value in the first/parent drop down.
The first one populates on page load and I can set the selected value for this one based off database value that was saved on previous page.
For the second cascading drop down it populates for me when I set the selected value for the parent ID on page load (based off the database value).
But since the second/child drop down is not set/loaded with values until the page loads I can not set the selected value (I tried on Page_Load_Complete but it does not work there either).
I need to know how once I set the selected value in the parent drop down (this works fine), to populate the second drop down based off the value in the first one.
Here is my code for the aspx page. I can set the first selected value, and it populates the second select box (but I can not set the selected value because it is not populated at the time I set the first selected value.
aspx page
<asp:Label ID="lblAffPartCat" Text="<%$ Resources:share,lblAffPartCat %>" runat="server"></asp:Label>
<asp:DropDownList ID="ddlPartCat" runat="server"></asp:DropDownList>
<ajaxToolkit:CascadingDropDown ID="CascadingDropDown2" runat="server" TargetControlID="ddlPartCat"
Category="BasePart" PromptText="<%$ Resources:share,lblSelPartCat %>" LoadingText="[Loading Part Cat...]"
ServicePath="PAIntExtPart.asmx" ServiceMethod="BindPartCat"
ContextKey="" UseContextKey="True"/>
<asp:Label ID="lblAffBasePart" Text="<%$ Resources:share,lblAffBasePart %>" runat="server"></asp:Label>
<asp:DropDownList ID="ddlBasePart" runat="server" ></asp:DropDownList>
<ajaxToolkit:CascadingDropDown ID="ddlBasePart_CascadingDropDown" runat="server" Category="BasePart"
TargetControlID="ddlBasePart" ParentControlID= "ddlPartCat" PromptText="<%$ Resources:share,lblSelBasePart %>"
LoadingText="Loading Base Parts.."
ServicePath="PAIntExtPart.asmx"
ServiceMethod="BindBasePart"
ContextKey="" UseContextKey="True" />
asmx.cs page that populates the drop downs:
using System;
using System.Collections.Generic;
using System.Web.Services;
using System.Data;
using System.Collections.Specialized;
using AjaxControlToolkit;
using Hotline.DataAccess;
/// <summary>
/// Summary description for PAIntExtPart
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService()]
public class PAIntExtPart : System.Web.Services.WebService
{
string _SiteLocation = MiscFunctions.getCurrentSiteLocation();
/// <summary>
/// WebMethod to Populate Part Category Dropdown
/// </summary>
[WebMethod]
public CascadingDropDownNameValue[] BindPartCat(string knownCategoryValues, string category, string contextKey)
{
DataTable dsPartCat = null;
// string passed for contextKey is FormType and Language split by ":"
string[] arrcontextKey = contextKey.Split(':');
string FormType = arrcontextKey[0].ToString();
int LanguageID = Int32.Parse(arrcontextKey[1].ToString());
string PartCatValue = arrcontextKey[2].ToString();
try
{
dsPartCat = HarDB.getPartCat(_SiteLocation, LanguageID, FormType);
//create list and add items in it by looping through dataset table
List<CascadingDropDownNameValue> PartCatdetails = new List<CascadingDropDownNameValue>();
foreach (DataRow dtrow in dsPartCat.Rows)
{
string PartCatID = dtrow["PartCatID"].ToString();
string PartCat = dtrow["PartCat"].ToString();
PartCatdetails.Add(new CascadingDropDownNameValue(PartCat, PartCatID));
}
if (PartCatValue.Trim() != "")
{
//SelectedValue = PartCatValue;
}
return PartCatdetails.ToArray();
}
catch (Exception ex)
{
Server.Transfer("Errorpage.aspx?function=getAttachInfo+Error=" + Server.UrlEncode(ex.Message));
return null;
}
}
/// <summary>
/// WebMethod to Populate Base Part Dropdown
/// </summary>
[WebMethod]
public CascadingDropDownNameValue[] BindBasePart(string knownCategoryValues, string category, string contextKey)
{
string PartCatID;
//int LanguageID = Int32.Parse(contextKey);
string[] arrcontextKey = contextKey.Split(':');
string FormType = arrcontextKey[0].ToString();
int LanguageID = Int32.Parse(arrcontextKey[1].ToString());
string BasePartValue = arrcontextKey[2].ToString();
//This method will return a StringDictionary containing the name/value pairs of the currently selected values
StringDictionary PartCatdetails = AjaxControlToolkit.CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
PartCatID = PartCatdetails["BasePart"];
DataTable dsBasePart = null;
try
{
dsBasePart = HarDB.getBasePart(_SiteLocation, LanguageID, PartCatID, FormType);
//create list and add items in it by looping through dataset table
List<CascadingDropDownNameValue> BasePartdetails = new List<CascadingDropDownNameValue>();
foreach (DataRow dtrow in dsBasePart.Rows)
{
string BasePartID = dtrow["BasePartNumID"].ToString();
string BasePart = dtrow["BasePartNum"].ToString();
BasePartdetails.Add(new CascadingDropDownNameValue(BasePart, BasePartID));
}
if (BasePartValue.Trim() != "")
{
//SelectedValue = PartCatValue;
}
return BasePartdetails.ToArray();
}
catch (Exception ex)
{
Server.Transfer("Errorpage.aspx?function=getAttachInfo+Error=" + Server.UrlEncode(ex.Message));
return null;
}
}
}
I found out my issue, I was not using the correct "Selected Value" when trying to populate the second drop down.
I am having some trouble getting CascadingDropDown lists to work, its displaying the Method 500 error inside the list. I have tried all the common solutions to this and still no luck.
I was originally getting the following error:
System.ArgumentException: Invalid
method name 'getcategories',
method names are case sensitive. The
method name 'GetCategories'
with the same name but different
casing was found. Parameter name:
methodName
Which is odd because I am definately setting the method name in the correct case but it was sending it in lowercase (even though chrome showed the page as sending it in the correct case). Anyhow I worked around this by changing the method name itself to lowercase. This now brings up a new error:
System.InvalidOperationException:
Missing parameter:
knownCategoryValues.
If anyone could shed any light on either of these problems that would be a great help, I've spent way too long on this problem.
Thanks.
UPDATED Code:
<ajaxToolkit:ToolkitScriptManager EnablePageMethods="true" ID="ToolkitScriptManager1" runat="server">
</ajaxToolkit:ToolkitScriptManager>
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown1"
runat="server"
TargetControlID="mmCategory"
Category="Category"
PromptText="Select a category"
ServicePath="~/DropDownLists.asmx"
ServiceMethod="GetCategories" />
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown2"
runat="server"
TargetControlID="mmTemplate"
ParentControlID="mmCategory"
PromptText="Select a template"
ServiceMethod="GetTemplates"
ServicePath="~/DropDownLists.asmx"
Category="Template" />
Category: <asp:DropDownList ID="mmCategory" runat="server"/><br/>
Template: <asp:DropDownList ID="mmTemplate" runat="server"/><br/>
Web Service File:
using System;
using System.Web;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.Services;
using System.Web.Services.Protocols;
using AjaxControlToolkit;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Linq;
/// <summary>
/// Summary description for TemplateData
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService()]
public class DropDownLists : System.Web.Services.WebService
{
public DropDownLists()
{
//Uncomment the following line if using designed components
//InitializeComponent();
}
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] getcategories(string knownCategoryValues, string category)
{
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
values.Add(new CascadingDropDownNameValue("test 1", "1"));
values.Add(new CascadingDropDownNameValue("test 2", "2"));
return values.ToArray();
/*using (MiscDataContext dc = new MiscDataContext())
{
var CatQuery = from D in dc.Templates
select new { D.Category }
into CatQueryResults
select CatQueryResults;
foreach (var CatResult in CatQuery)
{
string CatReturn = CatResult.Category;
values.Add(new CascadingDropDownNameValue(CatReturn, CatReturn));
}
return values.ToArray();
}*/
}
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] GetTemplates(string knownCategoryValues, string category)
{
StringDictionary kv = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
string varCat;
varCat = kv["Category"];
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
values.Add(new CascadingDropDownNameValue("test 3", "3"));
values.Add(new CascadingDropDownNameValue("test 4", "4"));
return values.ToArray();
/*using (MiscDataContext dc = new MiscDataContext())
{
var CatQuery = from D in dc.Templates
where(D.Category == varCat)
select new { D.ID, D.Name }
into CatResult
select CatResult;
foreach (var CatResult in CatQuery)
{
int ID = (int)CatResult.ID;
string Name = CatResult.Name;
values.Add(new CascadingDropDownNameValue(Name, Convert.ToString(ID)));
}
return values.ToArray();
}*/
}
}
In order to be usable with a CascadingDropDown, your web method's prototype must be:
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] GetCategories(
string knownCategoryValues, string category)
{
}
If you assign something to the ContextKey property of the extender, that prototype becomes:
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] GetCategories(
string knownCategoryValues, string category, string contextKey)
{
}
The knownCategoryValues parameter represents the current selection in the master dropdown. You should pass it to CascadingDropDown.ParseKnownCategoryValuesString() to obtain its actual value.
I currently have a string that I want to limit to 200 characters.
I don't know how to format it so if it's less, it wont change, but if its more, it will trim it.
This is in a ListView Control, NOT a Repeater. Sorry for that, my mistake.
<ItemTemplate>
<div class="portfolio_title">
<div class="custom_title">
<%# DataBinder.Eval(Container.DataItem, "Title")%></div>
</div>
<asp:Literal ID="LiteralArticle" runat="server"></asp:Literal>
<%# DataBinder.Eval(Container.DataItem, "Article")%><br />
Read Full Article...
<div class="page_line">
</div>
</ItemTemplate>
Here is some code that I use for this sort of thing. Attach this to the OnRowDataBound event. This truncates to 50 characters and adds elipses "...".
protected void CommentGridViewRowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
TableCell cell = e.Row.Cells[0];
if (!string.IsNullOrEmpty(cell.Text) && cell.Text.Length > 50)
{
cell.Text = cell.Text.Substring(0, 50) + "…";
}
}
}
Do you mean like...
int maxLength = 200;
string trimmed = (trimmed.length > maxLength) ? trimmed.Substring(0,maxlength) : trimmed ;
I assume this is going in a grid or something... I'd call a function and pass your Eval as an argument:
My example:
<asp:Image ID="imgTopLevelTickCross" runat="server" ImageUrl='<%# "/images/" & getImage(Eval("DrwgID").toString()) & ".gif" %> ' />
The ImageURL calls getImage and passes the value of Eval("DrwgID") to it to form the src path
Public Function getImage(ByVal drwgID As Integer) As String
If TopLevelDrwgID = drwgID Then
Return "True"
Else
Return "blank"
End If
End Function
It's maybe a litte more than you need, but works well for me in most cases. It preserves the file ending, if you deal with files and adds "..." at the end of the shortend string if you want.
/// <summary>
/// Shortens a long string. Optionally keeps the file ending and adds a placeholder at the end.
/// </summary>
/// <example>
/// Input: ThisIsAVeryLongFilenameForThisTest.doc (length=10, placeholder='...', saveFileEnding=true)
/// Output: ThisIsAVeryLong
/// </example>
/// <param name="value"></param>
/// <param name="length"></param>
/// <param name="placeHolder"></param>
/// <param name="saveFileEnding"></param>
/// <returns></returns>
public static string ShowSummary(string value, int length, string placeHolder, bool saveFileEnding)
{
int lengthNew = length;
string fileEnding = "";
//nothing to do if the string is short enough
if (length > value.Length)
{
return value;
}
if (saveFileEnding)
{
int index = value.LastIndexOf(".");
if (index != -1)
{
fileEnding = value.Substring(index);
lengthNew = length - fileEnding.Length;
}
}
//substract the length of the placeholder
lengthNew = lengthNew - placeHolder.Length;
if (lengthNew > 0)
{
return value.Substring(0, lengthNew) + placeHolder + fileEnding;
}
else
{
//something is weird, maybe a really long filending or a '.' in the filename, so just cut it down
return value.Substring(0, length);
}
}//ShowSummary
I have an ASP.NET Webforms site that is regularly having features added.
The majority of time a new WebControl is added to the page and I need to increment the TabIndex to all subsequent controls on the page.
I'd prefer a more robust solution than choosing an arbitrary gap between the initial assigned tab indexes. Setting the tab indexes using the designer tab order functionality is one option but I'd prefer to stay in the source view.
Ideally, if I had, for example, three check boxes I'd like to be able to define the tabindex based off the previous controls tabindex. Then I'd only need to insert the new control and change one existing control.
For example, add a new property TabIndexAfterControlId to 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"/>
My first thought was to extend System.Web.UI.WebControls.WebControl with a new property, but extension properties aren't supported.
Note: This approach worked for some webcontrols (DropDownLists) but not all of them (CheckBoxes). I've left it here for reference.
I've ended up with a solution that uses a code behind method to capture the relationship between controls.
<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) %>'/>
The code behind method will initially do a basic TabIndex assignment, which works well when the tab order follows the order of the controls on a page. Then during the PreRender event the tab index order will be checked again. This is important if the tab order doesn't not follow the natural flow of the page.
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;
}
}
My prior attempt using the data-binding syntax (<%# ... %>) to set the TabIndex for web controls failed when some controls wouldn't bind the TabIndex (CheckBox). It also wasn't ideal as I needed to pass a reference to the current control into the code behind method.
This time around I went with a custom ExpressionBuilder that accepts the name of the web control that the current control should follow in the tab order.
The TabIndexAfterExpressionBuilder initially returns the short -1 as the value. At the same time it registers with the LoadComplete event of the current Page. When this event fires both controls are found and the tab indexes set according to their relative positions.
Example WebControls using the TabIndex Expression Builder
<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>