[C#.NET][Winform][User Control] 使用 IExtenderProvider 擴充 Textbox 控制項 限制輸入字元 功能
相信這樣的功能我們常在寫,限制文字方塊輸入某些字元,比如下段程式碼只允許輸入數字鍵
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
//法一
if (Char.IsNumber(e.KeyChar))
{
e.Handled = false;
}
else
{
e.Handled = true;
}
//法二
int key = (int)e.KeyChar;
if (key >= 48 && key <= 57)
{
e.Handled = false;
}
else
{
e.Handled = true;
}
}
這樣的寫法應該很常出現在您我的專案,而且每次寫完都要重新測試過一遍,不然就是還要再查ASCII表,索性將這樣的功能包起來,
在此,我想要實現兩種模式,一種是內建的限制對應表,一種是可以由使用者自行加入ASCII Code,並且查詢當下按下的鍵盤屬於什麼ASCII Code
下圖是我心中內建定義表的操作方式
下圖是我心中自定定義表的查詢功能
很好!如果你跟我想的一樣,就可以繼續往下看,如果你想學怎麼完成這樣的功能(雖然功能很鳥),但實作是很殘酷的,這個功能也花掉了我一天的時間。
外殼的作法我就不多講了跟前面幾篇一樣
[ProvideProperty("Filter", typeof(Control))]
public partial class FilterCharacterContainer : Component, IExtenderProvider
{
public FilterCharacterContainer()
{
InitializeComponent();
}
public FilterCharacterContainer(IContainer container)
{
container.Add(this);
InitializeComponent();
}
#region 實作 IExtenderProvider 成员
public bool CanExtend(object target)
{
if (target is TextBox)
return true;
else
return false;
}
#endregion
Dictionary<Control, Filter> _Filters = new Dictionary<Control, Filter>();
public void SetFilter(Control Ctrl, Filter Property)
{
if (Ctrl == null || Ctrl is FilterCharacterContainer || Ctrl is Form)
{
return;
}
if (this._Filters.ContainsKey(Ctrl))
{
this._Filters[Ctrl] = Property;
}
else
{
this._Filters.Add(Ctrl, Property);
}
if (Property.IsFilter)
{
Ctrl.KeyPress += new KeyPressEventHandler(Ctrl_KeyPress);
}
else
{
Ctrl.KeyPress -= new KeyPressEventHandler(Ctrl_KeyPress);
}
}
public Filter GetFilter(Control Ctrl)
{
if (this._Filters.ContainsKey(Ctrl))
{
return this._Filters[Ctrl];
}
else
{
Filter property = new Filter()
{
IsFilter = false,
DefaultASCIIs = new DefaultASCIIs(),
CustomASCIIs = new CustomASCIIs(),
};
this._Filters.Add(Ctrl, property);
return property;
}
}
}
我把對應表存放在Setting檔裡,本來想利用反射Char來處理,不過我一直沒辦法用反射取得IsNumber方法,只好用笨方法,建檔!
對應這些設定檔的列舉
[Serializable]
public enum ASCIIType
{
Number,
Lower,
Upper,
ESC,
Backspace,
Spack,
}
Set/Get要回傳的Filter類別以及相關類別
[Serializable, TypeConverter(typeof(ExpandableObjectConverter))]
public class DefaultASCII
{
private ASCIIType _FilterType;
public ASCIIType FilterType
{
get { return _FilterType; }
set { _FilterType = value; }
}
private List<string> _FilterASCIIs;
public ReadOnlyCollection<string> FilterASCIIs
{
get
{
switch (this.FilterType)
{
case ASCIIType.Number:
this._FilterASCIIs = Properties.Settings.Default.Numbers.Cast<string>().ToList();
break;
case ASCIIType.Lower:
this._FilterASCIIs = Properties.Settings.Default.Lowers.Cast<string>().ToList();
break;
case ASCIIType.Upper:
this._FilterASCIIs = Properties.Settings.Default.Uppers.Cast<string>().ToList();
break;
case ASCIIType.ESC:
this._FilterASCIIs = Properties.Settings.Default.ESC.Cast<string>().ToList();
break;
case ASCIIType.Backspace:
this._FilterASCIIs = Properties.Settings.Default.Backspace.Cast<string>().ToList();
break;
case ASCIIType.Spack:
this._FilterASCIIs = Properties.Settings.Default.Spack.Cast<string>().ToList();
break;
}
return new ReadOnlyCollection<string>(this._FilterASCIIs);
}
}
}
[Serializable, TypeConverter(typeof(ExpandableObjectConverter))]
public class CustomASCII
{
public string ASCII { get; internal set; }
public string Char { get; internal set; }
public override string ToString()
{
if (string.IsNullOrEmpty(this.Char) || string.IsNullOrEmpty(this.ASCII))
return string.Format("");
else
return string.Format("Ascii:{0},Char:{1}", this.ASCII, this.Char);
}
}
[Serializable, EditorAttribute(typeof(CollectionEditor), typeof(UITypeEditor))]
public class CustomASCIIs : List<CustomASCII>
{
}
[Serializable, EditorAttribute(typeof(CollectionEditor), typeof(UITypeEditor))]
public class DefaultASCIIs : List<DefaultASCII>
{
}
[Serializable, TypeConverter(typeof(ExpandableObjectConverter))]
public class Filter
{
[DefaultValue(false)]
public bool IsFilter { get; set; }
private DefaultASCIIs _DefaultASCIIs=new DefaultASCIIs();
public DefaultASCIIs DefaultASCIIs
{
get { return _DefaultASCIIs; }
set { _DefaultASCIIs = value; }
}
private CustomASCIIs _CustomASCIIs = new CustomASCIIs();
public CustomASCIIs CustomASCIIs
{
get { return _CustomASCIIs; }
set { _CustomASCIIs = value; }
}
public override string ToString()
{
return string.Format("Is Filter:{0}", this.IsFilter);
}
}
還有還有~回過頭去在外殼裡加上KeyPress事件
void Ctrl_KeyPress(object sender, KeyPressEventArgs e)
{
TextBox ctrl = null;
if (sender is Control)
ctrl = sender as TextBox;
if (ctrl == null || !this._Filters.ContainsKey(ctrl))
{
return;
}
bool isFilter = this._Filters[ctrl].IsFilter;
if (!isFilter)
{
return;
}
DefaultASCIIs defaultList = this._Filters[ctrl].DefaultASCIIs;
CustomASCIIs customList = this._Filters[ctrl].CustomASCIIs;
int key = (int)e.KeyChar;
List<string> ASCIIs = new List<string>();
foreach (var item in defaultList)
{
foreach (var ascii in item.FilterASCIIs)
{
if (!ASCIIs.Contains(ascii))
{
ASCIIs.Add(ascii);
}
}
}
foreach (var item in customList)
{
if (!ASCIIs.Contains(item.ASCII))
{
ASCIIs.Add(item.ASCII);
}
}
if (ASCIIs.Count == 0)
{
return;
}
if (ASCIIs.Contains(key.ToString()))
{
e.Handled = false;
}
else
{
e.Handled = true;
}
}
到目前為止我們已經完成了預設定義功能,將IsFilter設為true,按下F5,就能依你想要的規則限制鍵盤輸入
喘口氣,喝杯茶,接下來要處理自定義的部份!
我們需要一個UserControl專案,並且規劃以下UI
FilterScannerEditor 的程式碼長這樣,主要就是按下鍵盤後自動幫我們轉成ASCII Code,這樣一來就不用查表了,馬上按馬上加入限制規則
internal partial class FilterScannerEditor : UserControl
{
public FilterScannerEditor()
{
InitializeComponent();
}
private CustomASCII _CustomASCII = new CustomASCII();
public CustomASCII CustomASCII
{
get { return _CustomASCII; }
set { _CustomASCII = value; }
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
TextBox textBox = sender as TextBox;
int key = (int)e.KeyChar;
this.CustomASCII.ASCII = key.ToString();
this.CustomASCII.Char = e.KeyChar.ToString();
this.labelChar.Text = this.CustomASCII.ASCII;
this.labelAscii.Text = this.CustomASCII.Char;
textBox.Clear();
}
private void FilterScannerEditor_Load(object sender, EventArgs e)
{
this.labelChar.Text = "";
this.labelAscii.Text = "";
}
}
再來定義我們FilterASCIIScannerUITypeEditor類別,它繼承了UITypeEditor,詳細做法請參考http://www.dotblogs.com.tw/yc421206/archive/2010/07/04/16351.aspx
internal class FilterASCIIScannerUITypeEditor : UITypeEditor
{
private CustomASCII _custom=new CustomASCII();
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
IWindowsFormsEditorService editorService = null;
if (context != null && context.Instance != null && provider != null)
{
editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
FilterScannerEditor editor = new FilterScannerEditor();
editorService.DropDownControl(editor);
this._custom = editor.CustomASCII;
return this._custom;
}
else
{
return null;
}
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
if (context != null && context.Instance != null)
{
//return UITypeEditorEditStyle.Modal;
return UITypeEditorEditStyle.DropDown;
//return UITypeEditorEditStyle.None;
}
else
{
return base.GetEditStyle(context);
}
}
}
然後再回過頭去把CustomASCII類別的Attribute重新定義,加上我們剛寫好的FilterASCIIScannerUITypeEditor類別
[Serializable, TypeConverter(typeof(ExpandableObjectConverter))]
[EditorAttribute(typeof(FilterASCIIScannerUITypeEditor), typeof(UITypeEditor))]
public class CustomASCII
{
public string ASCII { get; internal set; }
public string Char { get; internal set; }
public override string ToString()
{
if (string.IsNullOrEmpty(this.Char) || string.IsNullOrEmpty(this.ASCII))
return string.Format("");
else
return string.Format("Ascii:{0},Char:{1}", this.ASCII, this.Char);
}
}
如此一來便可以看到彈跳視窗,並實現我們想要的功能
@故障排除
若你在開發時用來測試的Winform專案,會出錯,那是因為UserControl有修改並編譯,原本VS幫我們產生的Code並存放在Winform裡的.resx內容反射失敗,你可以把resx刪掉
然後,再刪掉VS幫我們產生的Code
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET