动态控件,复合控件,相信大家都已经很熟悉了。只要达到同样的展示及功能,用什么样的方式实现应该取决于实际情况。我在一个项目中遇到的情况是:已经有了很多页面,但每个页面都没有用Validation控件来验证输入的有效性。
为了达到相同的展示效果:
常规办法:是为每个页面的每个TextBox有选择的生成RequiredFieldValidator、RangeValidator、 CompareValidator、RegularExpressionValidator、FilteredTextBoxExtender。但这个方法,需要每个页面都去添加,并且统一调整起效果来,工作量太大。
复合控件:是写一个复合控件,依参数来添加验证控件。这个也需要在页面中统一添加一次(工作量约为第一个办法的1/3),还需要更改一下后台的控件类型声明(我的后台代码在另一个程序集中)。
动态控件:写了个直接继承自TextBox的控件,在适当的地方,依参数来添加验证控件。这个也需要在页面中统一改动一下(工作量和复合控件的方法相当),不过,后台的代码不用做发改
动态控件,复合控件,相信大家都已经很熟悉了。只要达到同样的展示及功能,用什么样的方式实现应该取决于实际情况。我在一个项目中遇到的情况是:已经有了很多页面,但每个页面都没有用Validation控件来验证输入的有效性。
为了达到相同的展示效果:
常规办法:是为每个页面的每个TextBox有选择的生成RequiredFieldValidator、RangeValidator、CompareValidator、RegularExpressionValidator、FilteredTextBoxExtender。但这个方法,需要每个页面都去添加,并且统一调整起效果来,工作量太大。
复合控件:是写一个复合控件,依参数来添加验证控件。这个也需要在页面中统一添加一次(工作量约为第一个办法的1/3),还需要更改一下后台的控件类型声明(我的后台代码在另一个程序集中)。
动态控件:写了个直接继承自TextBox的控件,在适当的地方,依参数来添加验证控件。这个也需要在页面中统一改动一下(工作量和复合控件的方法相当),不过,后台的代码不用做发改动。
前两种,大家不陌生,我就不啰嗦了。这里,只说一下用动态控件实现上面功能的方法。
先展望一下使用方法:
<!-- 必须输入 -->
<facade:StringTextBox Options="required" HelpMessage="请输入用户名" DisplayName="用户名"
runat="server" ID="StringTextBox1" MaxLength = "30" /><br />
<!-- 必须输入,只能输入数字 -->
<facade:StringTextBox Options="required,Filtered" HelpMessage="请输入用户名"
FilterType="Numbers" DisplayName="用户名" runat="server" ID="StringTextBox2" MaxLength = "30" /><br />
<!-- 必须输入,只能输入数字,数值范围是0-400 -->
<facade:NumberTextBox runat="server" ID = "tbCount" MinValue="0" MaxValue="400" DisplayName="数量"
HelpMessage="请输入数量" />
<!-- 必须输入,只能输入数字,数值范围是0-400 -->
<facade:MoneyTextBox runat="server" ID = "NumberTextBox1" MinValue="0" MaxValue="400"
DisplayName="金额" HelpMessage="请输入金额" />
<!-- 必须输入,输入的字符串必须以ABC开头 -->
<facade:RegexTextBox runat="server" Options="Required" ValidationExpression="^abc.+$"
ID="tbRegex" DisplayName="正则测试" MaxLength="120" Example="abc123,abceee" HelpMessage="请输入正则测试" />
<!-- 输入的必须是手机号 -->
<facade:PhoneTextBox runat="server" ID="tbMobile" PhoneTypes="Mobile" DisplayName="手机号"
MaxLength="20" Example="13988888888" HelpMessage="请输入手机号" />
<!-- 输入的必须是手机号,或者电话 -->
<facade:PhoneTextBox runat="server" ID="tbPhoneMobile" PhoneTypes="Phone,Mobile" DisplayName="手机号"
MaxLength="20" Example="13988888888,0311-88886666" HelpMessage="请输入手机号" />
动态控件的添加,应该在Page.PreInit中添加,因为此时控件还没有加载ViewState。但控件中得不到Page.PreInit事件,我们遇到了第一个难题;
如果TextBox控件中模板中,则只有在Page.PreRender时,我们才能得到控件实例,所以只在Page.PreInit中添加控件不行,我们还必须在PreRender时,看看这个TextBox有没有添加动态控件,如果添加了,再添加就要报错了,在Page.PreRender时添加控件,是我们的第二个难题 。
还好,这两个问题是一类问题,我们通过写一个HttpModuler,来帮助控件得到Page.PreInit和Page.PreRender事件,还要有一个HttpModuler和控件都能访问的地方:BaseTextBoxContext,来把所有的控件事件放在这里。代码如下:
![](/Images/OutliningIndicators/ContractedBlock.gif)
BaseTextBoxContext
/// <summary>
/// HttpModuler和控件的交互对象
/// </summary>
public class BaseTextBoxContext
{
private static readonly string item_key = "::grain.basetextbox.context";
private static LocalDataStoreSlot GetSlot()
{
return Thread.GetNamedDataSlot(item_key);
}
/// <summary>
/// 当前线程上下文的对象
/// </summary>
public static BaseTextBoxContext Current
{
get
{
BaseTextBoxContext current = Thread.GetData(GetSlot()) as BaseTextBoxContext;
if (current == null)
{
current = new BaseTextBoxContext();
Thread.SetData(GetSlot(), current);
}
return current;
}
}
/// <summary>
/// 移除线程上下文
/// </summary>
public void RemoveContext()
{
Thread.FreeNamedDataSlot(item_key);
}
private List<EventHandler> preInitHandlers = new List<EventHandler>();
/// <summary>
/// 控件的PreInit事件响应函数列表
/// </summary>
public List<EventHandler> PreInitHandlers
{
get { return preInitHandlers; }
}
/// <summary>
/// 添加一个PreInit时的响应函数
/// </summary>
/// <param name="eventHandler"></param>
public void AddPagePreInit(EventHandler eventHandler)
{
PreInitHandlers.Add(eventHandler);
}
private List<EventHandler> preRenderHandlers = new List<EventHandler>();
/// <summary>
/// 控件的PreRender事件响应函数列表
/// </summary>
public List<EventHandler> PreRenderHandlers
{
get { return preRenderHandlers; }
}
/// <summary>
/// 添加一个PreRender时的响应函数
/// </summary>
/// <param name="eventHandler"></param>
public void AddPagePreRender(EventHandler eventHandler)
{
preRenderHandlers.Add(eventHandler);
}
}
![](/Images/OutliningIndicators/ContractedBlock.gif)
BaseTextBoxModule
/// <summary>
/// 帮助BaseTextBox响应Page.PreInit和Page.PreRender类
/// </summary>
public class BaseTextBoxModule : IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
}
void context_PostRequestHandlerExecute(object sender, EventArgs e)
{
//页面执行结束,清理
BaseTextBoxContext.Current.RemoveContext();
}
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
Page page = HttpContext.Current.CurrentHandler as Page;
if (page != null)
{
//注册页面的PreInit和PreRender事件
page.PreInit += new EventHandler(Page_PreInit);
page.PreRenderComplete += new EventHandler(page_PreRenderComplete);
}
}
void page_PreRenderComplete(object sender, EventArgs e)
{
try
{
List<EventHandler> list = new List<EventHandler>(BaseTextBoxContext.Current.PreRenderHandlers);
foreach (EventHandler handler in list)
{
//执行控件要求要PreRender时的事件函数
handler.Invoke(sender, e);
}
}
finally
{
BaseTextBoxContext.Current.PreRenderHandlers.Clear();
}
}
void Page_PreInit(object sender, EventArgs e)
{
try
{
List<EventHandler> list = new List<EventHandler>(BaseTextBoxContext.Current.PreInitHandlers);
foreach (EventHandler handler in list)
{
//执行控件要求要PreInit时的事件函数
handler.Invoke(sender, e);
}
}
finally
{
BaseTextBoxContext.Current.PreInitHandlers.Clear();
}
}
#endregion
}
现在第一个问题和第二个问题的一部分解决了。为了在Page.PreInit之前,让用户控件自己把自己的事件添加到BaseTextBoxContext中,我在BaseTextBox的构造函数中,调用
![](/Images/OutliningIndicators/ContractedBlock.gif)
BaseTextBox 构造
public BaseTextBox()
{
BaseTextBoxContext.Current.AddPagePreInit(new EventHandler(Page_PreInit));
}
把自己要在Page.PreInit中执行的函数添加进去。在控件的Page_PreInit时, 我们要添加动态控件,还可以把自己放在另一个动态控件的内部哟,比如把TextBox放在Panel中,生成类似:
![](/Images/OutliningIndicators/ContractedBlock.gif)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
<div class="input_wrapper">
<input type="text" />
<span class="helpmessage">help message here</span>
<!--这里放置动态验证控件-->
</div>
下面是我的实现:
![](/Images/OutliningIndicators/ContractedBlock.gif)
BaseTextBox
[Flags]
public enum TextBoxOptions
{
None = 0,
Required = 1,
Filtered = 2
}
public class BaseTextBox : TextBox
{
private bool preRendered = false;
private bool buildValidatorControls = false;
public BaseTextBox()
{
BaseTextBoxContext.Current.AddPagePreInit(new EventHandler(Page_PreInit));
}
void Page_PreInit(object sender, EventArgs e)
{
if (!buildValidatorControls)
{
buildValidatorControls = true;
BuildValidatorControls();
}
}
void EnableValidatorControls(object sender, EventArgs e)
{
if (preRendered)
{
if (!buildValidatorControls)
{
buildValidatorControls = true;
BuildValidatorControls();
}
}
}
private Panel parentWrap;
public Panel ParentWrap
{
get {
if (parentWrap == null)
{
parentWrap = new Panel() { CssClass = "input_wrapper" };
this.Parent.Controls.AddAt(this.Parent.Controls.IndexOf(this), parentWrap);
this.Parent.Controls.Remove(this);
parentWrap.Controls.Add(this);
}
return parentWrap;
}
}
private ValidatorDisplay display = ValidatorDisplay.Dynamic;
/// <summary>
/// 验证控件显示方式
/// </summary>
public ValidatorDisplay ValidatorDisplay
{
get { return display; }
set { display = value; }
}
private string validatorGroup = string.Empty;
/// <summary>
/// 验证分组
/// </summary>
public string ValidatorGroup
{
get { return validatorGroup; }
set { validatorGroup = value; }
}
private FilterTypes filterType;
/// <summary>
/// 过滤类型
/// </summary>
public FilterTypes FilterType
{
get { return filterType; }
set { filterType = value; }
}
private string invalidChars = string.Empty;
/// <summary>
/// 过滤时的无效字符,这些字符不能输入
/// </summary>
public string InvalidChars
{
get { return invalidChars; }
set { invalidChars = value; }
}
private string validChars = string.Empty;
/// <summary>
/// 过滤时的有效字符,除这些字符外其它字符不能输入
/// </summary>
public string ValidChars
{
get { return validChars; }
set { validChars = value; }
}
private String helpMessage = string.Empty;
/// <summary>
/// 当获得焦点时,提示的文本
/// </summary>
public String HelpMessage
{
get { return helpMessage; }
set { helpMessage = value; }
}
/// <summary>
/// 如果有示例,在提示文本中加上
/// </summary>
/// <returns></returns>
private string GetHelpMessage()
{
return string.Format("{0} {1}",HelpMessage , this.GetExampleString());
}
private String displayName = string.Empty;
/// <summary>
/// 出错提示时使用的控件ID的中文名字
/// </summary>
public String DisplayName
{
get {
if (string.IsNullOrEmpty(this.displayName))
return this.ID;
return displayName;
}
set { displayName = value; }
}
private string example = string.Empty;
/// <summary>
/// 示例文本
/// </summary>
public string Example
{
get { return example; }
set { example = value; }
}
protected string GetExampleString()
{
return string.IsNullOrEmpty(this.Example) ? string.Empty : string.Format("示例:{0}", this.Example);
}
private TextBoxOptions options = TextBoxOptions.None;
/// <summary>
/// 文本框选项
/// </summary>
public TextBoxOptions Options
{
get { return options; }
set {options =value;}
}
protected void AddValidator(BaseValidator validator)
{
//注册
validator.CssClass = "errormessage";
validator.Display = this.ValidatorDisplay;
validator.ValidationGroup = this.ValidatorGroup;
this.OnAddValidator(this.ParentWrap, validator);
ParentWrap.Controls.Add(validator);
}
protected void AddValidator(ExtenderControlBase validator)
{
this.OnAddValidator(this.ParentWrap, validator);
ParentWrap.Controls.Add(validator);
}
protected virtual void OnAddValidator(Panel panelParent, BaseValidator validator)
{
}
protected virtual void OnAddValidator(Panel panelParent, ExtenderControlBase validator)
{
}
private void AddHelpMessage()
{
string helpMessage = this.GetHelpMessage();
if (!string.IsNullOrEmpty(helpMessage.Trim()))
{
Label msg = new Label() { EnableViewState = false, Text = this.GetHelpMessage(), CssClass = "helpmessage" };
msg.ID = "help_" + this.ID;
//msg.Style.Add("display","none");
ParentWrap.Controls.Add(msg);
ScriptManager.RegisterStartupScript(this, this.GetType(), this.ClientID + "_help_message", string.Format("evlon.facade.textbox.help_message.add('{0}','{1}');", this.ClientID, msg.ClientID), true);
ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "error_message", "evlon.facade.textbox.clearErrorMessageStyle();", true);
}
}
protected virtual void BuildValidatorControls()
{
if ((this.Options & TextBoxOptions.Required) == TextBoxOptions.Required)
{
RequiredFieldValidator rfv = new RequiredFieldValidator();
rfv.ID = "rfv_" + this.ID;
rfv.ControlToValidate = this.ID;
rfv.ErrorMessage = "*";// = string.Format("应该输入{0}", this.DisplayName);
//添加到上下文
this.AddValidator(rfv);
}
this.AddHelpMessage();
if ((this.Options & TextBoxOptions.Filtered) == TextBoxOptions.Filtered)
{
FilteredTextBoxExtender fte = new FilteredTextBoxExtender();
fte.ID = "fte_" + this.ID;
fte.TargetControlID = this.ID;
fte.ValidChars = this.ValidChars;
fte.InvalidChars = this.InvalidChars;
fte.FilterType = this.FilterType;
//添加控件
this.AddValidator(fte);
}
}
private void Page_LoadComplete(object sender, EventArgs e)
{
this.BuildValidatorControls();
}
private void AppendCssClass(string className)
{
this.CssClass = (this.CssClass + " " + className).TrimStart(' ');
}
protected override void OnPreRender(EventArgs e)
{
BaseTextBoxContext.Current.AddPagePreRender(new EventHandler(EnableValidatorControls));
preRendered = true;
base.OnPreRender(e);
if (this.TextMode == TextBoxMode.MultiLine)
{
this.AppendCssClass("textarea");
}
else
{
if (this.MaxLength > 50)
{
this.AppendCssClass("string_long");
}
else
{
this.AppendCssClass("string_short");
}
}
}
}
![](/Images/OutliningIndicators/ContractedBlock.gif)
NumberTextBox
public abstract class NumberTextBox<T> : BaseTextBox where T : IComparable<T>
{
private T minValue = default(T);
public virtual T MinValue
{
get { return minValue; }
set { minValue = value; }
}
private T maxValue = default(T);
public virtual T MaxValue
{
get { return maxValue; }
set { maxValue = value; }
}
protected abstract T DefaultMinValue { get; }
protected abstract T DefaultMaxValue { get; }
protected ValidationDataType type;
public virtual ValidationDataType Type
{
get { return type; }
set { type = value; }
}
public NumberTextBox()
{
this.FilterType = FilterTypes.Numbers;
this.Options = TextBoxOptions.Required | TextBoxOptions.Filtered;
this.MaxValue = this.DefaultMaxValue;
this.MinValue = this.DefaultMinValue;
}
protected override void BuildValidatorControls()
{
base.BuildValidatorControls();
//添加额外的控件
if ((this.MinValue.CompareTo(this.DefaultMinValue) != 0) && (this.MaxValue.CompareTo(this.DefaultMaxValue) != 0))
{
RangeValidator rv = new RangeValidator();
rv.ID = "rv_" + this.ID;
rv.ControlToValidate = this.ID;
rv.MaximumValue = this.MaxValue.ToString();
rv.MinimumValue = this.MinValue.ToString();
rv.SetFocusOnError = true;
rv.Type = this.Type;
rv.ErrorMessage = string.Format("{0}的值应该位于{1}和{2}之间", this.DisplayName, this.MinValue, this.MaxValue);
this.AddValidator(rv);
}
else if (this.MinValue.CompareTo(this.DefaultMinValue) != 0)
{
CompareValidator cv = new CompareValidator();
cv.ID = "rv_" + this.ID;
cv.ControlToValidate = this.ID;
cv.SetFocusOnError = true;
cv.Type = this.Type;
cv.Operator = ValidationCompareOperator.GreaterThanEqual;
cv.ValueToCompare = this.MinValue.ToString();
cv.ErrorMessage = string.Format("{0}的值应该大于{1}", this.DisplayName, this.MinValue);
this.AddValidator(cv);
}
else if ((this.MaxValue.CompareTo(this.DefaultMaxValue) != 0))
{
CompareValidator cv = new CompareValidator();
cv.ID = "rv_" + this.ID;
cv.ControlToValidate = this.ID;
cv.SetFocusOnError = true;
cv.Type = this.Type;
cv.Operator = ValidationCompareOperator.LessThanEqual;
cv.ValueToCompare = this.MaxValue.ToString();
cv.ErrorMessage = string.Format("{0}的值应该小于{1}", this.DisplayName, this.MaxValue);
this.AddValidator(cv);
}
}
}
public class NumberTextBox : NumberTextBox<int>
{
public NumberTextBox()
{
this.Type = ValidationDataType.Integer;
}
protected override int DefaultMinValue
{
get { return int.MinValue; }
}
protected override int DefaultMaxValue
{
get { return int.MaxValue; }
}
}
public class MoneyTextBox : NumberTextBox<decimal>
{
public MoneyTextBox()
{
this.Type = ValidationDataType.Currency;
}
protected override decimal DefaultMinValue
{
get { return decimal.MinValue; }
}
protected override decimal DefaultMaxValue
{
get { return decimal.MaxValue; }
}
}
![](/Images/OutliningIndicators/ContractedBlock.gif)
RegexTextBox ,PhoneTextBox ,EMailTextBox,StringTextBox
public class RegexTextBox : BaseTextBox
{
private string regexMatch = string.Empty;
public virtual string ValidationExpression
{
get { return regexMatch; }
set { regexMatch = value; }
}
protected override void BuildValidatorControls()
{
base.BuildValidatorControls();
//添加额外的控件
if (!string.IsNullOrEmpty(this.ValidationExpression))
{
RegularExpressionValidator rev = new RegularExpressionValidator();
rev.ID = "rv_" + this.ID;
rev.ControlToValidate = this.ID;
rev.ValidationExpression = this.ValidationExpression;
rev.SetFocusOnError = true;
rev.ErrorMessage = string.Format("{0}的值格式不正确!{1}",this.DisplayName, GetExampleString());
this.AddValidator(rev);
}
}
}
public class StringTextBox : RegexTextBox
{
}
public class EMailTextBox : RegexTextBox
{
public EMailTextBox()
{
this.ValidationExpression = @"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$";
}
}
[Flags]
public enum PhoneTypes
{
/// <summary>
/// 手机
/// </summary>
Mobile = 1,
/// <summary>
/// 小灵通
/// </summary>
Unicom = 2,
/// <summary>
/// 电话
/// </summary>
Phone = 4
}
public class PhoneTextBox : RegexTextBox
{
private PhoneTypes phoneTypes = PhoneTypes.Phone;
/// <summary>
/// 电话类型
/// </summary>
public PhoneTypes PhoneTypes
{
get { return phoneTypes; }
set { phoneTypes = value; }
}
public override string ValidationExpression
{
get
{
if (!string.IsNullOrEmpty(base.ValidationExpression))
{
return base.ValidationExpression;
}
else
{
StringBuilder sb = new StringBuilder();
if ((this.PhoneTypes & PhoneTypes.Phone) == PhoneTypes.Phone)
{
if (sb.Length > 0)
sb.Append("|");
sb.Append(@"(^\d{3,4}-\d{7,8}$)");
}
if ((this.PhoneTypes & PhoneTypes.Mobile) == PhoneTypes.Mobile)
{
if (sb.Length > 0)
sb.Append("|");
sb.Append(@"(^1[35]\d{9}$)");
}
if ((this.PhoneTypes & PhoneTypes.Unicom) == PhoneTypes.Unicom)
{
if (sb.Length > 0)
sb.Append("|");
sb.Append(@"(^\d{3,4}-\d{7,8}$)");
}
return sb.ToString();
}
}
set
{
base.ValidationExpression = value;
}
}
}
差不多就这样了,如果有问题请留言。