NCName に使用可能な文字しか入力出来ない TextBox を作ろうと思っていろいろ調べてみました。
特定の文字しか入力出来ないようにするには、じゃんぬさんの数字または一部の文字しか入力できないようにするのソースを改造すればいけそうです。
ある文字列が NCName かどうかを判断するには System.Xml.XmlConvert.VerifyNCName() を使えば可能です。
VerifyNCName() は、与えられた文字列が NCName でなかった場合は例外を投げます。
この2つを使えば実装することが出来そうです。
ただし、NCName は1文字目と2文字目以降で使用できる文字が違うので注意する必要があります。
こんな感じになります(変更したところだけ抜粋)。
#region OnChar メソッド (virtual)
protected virtual void OnChar(KeyPressEventArgs e)
{
if (char.IsControl(e.KeyChar))
{
return;
}
if (!HasPermitChars(e.KeyChar, this.SelectionStart))
{
e.Handled = true;
}
}
#endregion
#region OnPaste メソッド (virtual)
protected virtual void OnPaste(System.EventArgs e)
{
string stString = Clipboard.GetDataObject().GetData(DataFormats.Text).ToString();
if (stString != null)
{
this.SelectedText = GetPermitedString(stString, this.SelectionStart);
}
}
#endregion
#region HasPermitChars メソッド
private bool HasPermitChars(char chTarget, int index)
{
if (index == 0)
{
return IsStartNCNameChar(chTarget);
}
else
{
return IsNCNameChar(chTarget);
}
}
#endregion
#region GetPermitedString メソッド
private string GetPermitedString(string stTarget, int index)
{
string stReturn = string.Empty;
foreach (char chTarget in stTarget)
{
if (HasPermitChars(chTarget, index))
{
stReturn += chTarget;
index++;
}
}
return stReturn;
}
#endregion
public bool IsStartNCNameChar(char ch)
{
try
{
XmlConvert.VerifyNCName(ch.ToString());
return true;
}
catch (XmlException e)
{
return false;
}
}
public bool IsNCNameChar(char ch)
{
try
{
XmlConvert.VerifyNCName("a" + ch.ToString());
return true;
}
catch (XmlException e)
{
return false;
}
}
一応これで完成になるのですが、ある文字が NCName かどうか何てちょっとした if 文を書けば終わりじゃないの?と思っている自分にとっては IsStartNCNameChar() と IsNCNameChar() で例外を投げまくってたり、IsNCNameChar() で文字列の連結したりといった無駄な処理をしているのが非常に気になります。
ということで、もっとカッコイイ実装方法は無いかといろいろ調べてみました。
結果、.NET Framework では文字を検証するためのフラグを 65536 個全ての文字に対してテーブルとして持っていて、ある文字が NCName かどうかをフラグが立っているかどうかで判断しているようです。
そして、そのテーブルは System.Xml アセンブリのリソースに XmlCharType.bin という名前で置いてあります。
これを使えばもっとスマートに書くことが出来そうです。
private static readonly byte[] s_charProperties;
#region コンストラクタ
public NCNameTextBox()
{
if (s_charProperties == null)
{
Stream stream =
Assembly.GetAssembly(typeof(XmlElement)).GetManifestResourceStream("XmlCharType.bin");
byte[] v = new byte[stream.Length];
stream.Read(v, 0, v.Length);
s_charProperties = v;
}
}
#endregion
...
public bool IsStartNCNameChar(char ch)
{
return (s_charProperties[ch] & 4) != 0;
}
public bool IsNCNameChar(char ch)
{
return (s_charProperties[ch] & 8) != 0;
}
これですっきりしました。
ただ、これは unofficial な方法なので、今後の .NET Framework で動くかどうかは定かではありません。