基于文本的姓名列表 + 查找控件






3.64/5 (7投票s)
2004 年 1 月 21 日
4分钟阅读

72445

706
一个可用的示例,
引言
您是否曾经想过创建一个类似 Outlook 的文本框,该文本框会解析用户输入的内容并根据某些数据源进行验证?嗯,这是我为此类控件实现的一个版本。
步骤 A - 所需的内部对象
我试图使对象模型尽可能通用,以适应大多数需求。作为基础,我使用了 RichTextBox
控件,并继承了其中的编辑和格式化文本显示功能。
该控件的功能:我允许使用标准分隔符“;”和“,” - 但由于这是一个数组,您可以根据需要添加或更改它们。现在,由于我不想编写文本编辑器,而是专注于查找功能,因此我创建了一个类 (RichTextList
),该类继承自 System.Windows.Forms.RichTextBox
,并封装了一些用于在其中选择和单击的逻辑。这样,我也有一种本机方法可以使用格式化(颜色、粗体、下划线等)打印列表。当然,RTF 控件并不是真正轻量级的,所以我们可能会考虑放弃内联渲染,但另一方面,它包含在 .NET Framework 中,因此无论如何都不包含在二进制文件中。
//
// This class is only used internally -
// out control will not expose any of it directly
//
class RichTextList : System.Windows.Forms.RichTextBox {
...
public char[] SeparationChars = new char[] {';',','};
// which chars are interpreted as object-separators
...
我们重写了一些继承的事件,以便我们可以捕获用户尝试单击或选择某项内容。无论用户单击何处,我们都尝试选择用户单击的项。我们不希望用户能够更改已验证项的输入。
protected override void OnSelectionChanged(EventArgs e) {
if (this.NoSelChangeEvent) return;
//at end of list
if (base.SelectionStart == base.Text.Length) return;
//selected whole list
if (base.SelectionLength == base.Text.Length) return;
if (base.SelectionLength == 0 //at sep-char
&& base.Text.IndexOfAny(this.SeparationChars,
base.SelectionStart,1) > -1) return;
this.MarkItem(base.SelectionStart); //within an item >> select it!
base.OnSelectionChanged(e);
if (base.SelectedText.Length > 0 && this.ItemSelected != null)
this.ItemSelected(base.SelectedText);
}
这是我们的选择逻辑,它确定当前单击/选择的项的开始和结束位置。也许,我有些老套,应该使用 RegExp - 不确定哪种方式性能更好。
// this function actually marks the item in the textbox
private void MarkItem(int Pos) {
this.NoSelChangeEvent = true;
/* Find first pos */
if (Pos == base.Text.Length) Pos--;
int x1 = base.Text.LastIndexOfAny(this.SeparationChars, Pos, Pos)+1;
base.SelectionStart = x1;
/* Find last pos */
int x2 = base.Text.IndexOfAny(this.SeparationChars, Pos+1);
base.SelectionLength = (x2<0?base.Text.Length:x2)-base.SelectionStart;
this.NoSelChangeEvent = false;
}
现在,我们创建一个轻量级对象来表示列表中的一个项。该对象将用于/实例化用户输入的每个解析出的项。基本上,它允许开发人员定义已验证项的显示颜色。当然,为了通用性,该控件本身无法验证任何输入。它通过 ValidateItems
事件通知容器,只有当列表中有任何未经验证的项时才会引发该事件。
我们进行验证的时间点是 OnValidate
事件,该事件在失去焦点或当父窗体/窗体请求验证时自动引发。
//
// a low-weight Class to hold all parsed elements
//
public class ObjectItem {
...
public System.Drawing.Color TextColor = System.Drawing.Color.Blue;
// the default color of a validated item
当引发 ValidateItems
事件时,开发人员会遍历集合(见下文)来确定未经验证的项。为了验证它们,他只需处理输入的文本并根据某些后端逻辑进行验证,后端逻辑通常会返回某个对象或该对象的唯一标识符(例如数据库 ID、用户对象、DataRow
、GUID
或其他任何内容)。无论返回什么,都可以通过将其分配给 ObjectRef
来将其挂接到 ObjectItem
。如果 ObjectRef
不为 null
,则这些项将被视为已经验证 - 非常基础,非常简单,非常有效:)。
// wether this item has been validated or not
public bool Validated {
get { return (this.ObjectRef != null); }
}
// a reference to the validated source.
// can be an ID value, an object-reference or any
// other means of locating the resource. If this object is null,
// then Validated returns false, else it returns true.
public object ObjectRef;
但是,由于列表中很可能不止一项,我们需要一个集合来容纳所有项。我们将此对象称为 ObjectItemCollection
- 因为它是 ObjectItem
的集合。
让我重点介绍这些重要的实现
每当从列表中移除一项时,我们都想知道!通常,开发人员也希望从后端资源(例如数据库或业务对象)中移除该项,因此当这种情况发生时会引发一个事件。现在,由于所有目前和之前解释的对象都不包含我们正在构建的主要控件,您将在下面看到它们之间的关系。
//
// The collection which holds all entered elements
//
public class ObjectItemCollection : CollectionBase {
...
// we have the UI control raise an event, if an item
// has been removed from the collection
protected override void OnRemoveComplete(int index, object value) {
base.OnRemoveComplete (index, value);
this.Textbox.OnRemoveItem((ObjectItem)value);
}
当然,开发人员可以随时向我们的控件添加项 - 这些项通常已经验证,因此他也可以在此处提供 ObjectRef
。以编程方式添加未经验证的项通常没有意义。即使如此,您只需为 objRef
提供 NULL
。
// implementing code can add items to the Text/Listbox using this
// add method
public ObjectItem Add(string itemName, object objRef) {
ObjectItem it = new ObjectItem(itemName, objRef);
it.isNew = true;
List.Add(it);
return it;
}
步骤 B - 最后,'TextObjectList' UserControl 本身
接下来,我们构建控件本身,它将引发事件并管理列表的解析和构建。我将此类命名为 TextObjectList
,它将是控件的类(因此,它是 public
),它必须继承自 System.Windows.Forms.UserControl
。
此处声明的事件是您将绑定到的事件。上述子对象只会向该控件报告其操作 - 它决定如何进行并发出指令。
// our event delegates
public delegate void ObjectItemRemovedEvent(TextObjectList list,
ObjectItem item);
public delegate void ObjectItemClickedEvent(TextObjectList list,
ObjectItem item, MouseEventArgs ev);
public delegate void ValidateObjectItemsEvent(ObjectItemCollection col);
public event ObjectItemClickedEvent ObjectItemClicked;
public event ObjectItemRemovedEvent ObjectItemRemoved;
public event ValidateObjectItemsEvent ValidateItems;
// this collection holds all entered items - validated and not
public ObjectItemCollection ObjectItems;
我们重写了 Validate
事件,以便我们可以响应用户输入(实际上,响应失去焦点或窗体手动请求 Validate
)。
// we create our own validation code
public override bool Validate() {
base.Validate();
bool AllValid = true;
string txtEntered = this.NList.Text;
string intSep = "";
foreach (char sepChar in this.intSepChar) {
intSep += sepChar.ToString();
}
/* Replace all allowed Sep-Chars with our internal one
* so we can split the input */
foreach (char sepChar in this.SeparationChars) {
txtEntered = txtEntered.Replace(sepChar.ToString(), intSep);
}
/* Now split the input */
string[] txtItems = txtEntered.Split(this.intSepChar);
/* Then parse each item */
ArrayList idxs = new ArrayList();
foreach (string txtItem in txtItems) {
if (txtItem.Trim() == string.Empty) continue;
Debug.WriteLine(" .. parsing txtItem " + txtItem.Trim(),
"TextObjectList.Validate");
if (this.ObjectItems.Contains(txtItem.Trim())) {
idxs.Add( this.ObjectItems.IndexOf(
this.ObjectItems.FindByName(txtItem.Trim())
));
continue;
}
//not in collection yet, add it!
ObjectItem it = new ObjectItem(txtItem.Trim());
this.ObjectItems.Add(it);
idxs.Add( this.ObjectItems.IndexOf(it) );
}
/* Now remove all items not in array */
for (int i = this.ObjectItems.Count-1; i >= 0; i--) {
if (idxs.Contains(i)) continue;
if (this.ObjectItems.Item(i).isNew) continue;
this.ObjectItems.RemoveAt(i);
}
/* Something to validate by host? */
AllValid = true;
foreach (ObjectItem it in this.ObjectItems) {
if (!it.Validated) AllValid = false;
}
/* Now have the host validate all new items */
if (!AllValid && this.ValidateItems != null)
this.ValidateItems(this.ObjectItems);
/* Finally visually display all items */
AllValid = true;
string newRtf = "";
string colTbl = BuildColorTable();
foreach (ObjectItem it in this.ObjectItems) {
it.isNew = false;
if (it.Validated) {
newRtf += @"\cf" + this.colors[it.TextColor.ToArgb()]
+ @"\ul\b " + it.ItemName + @"\b0\ulnone\cf0";
} else {
newRtf += @"\cf1 " + it.ItemName + @"\cf0";
AllValid = false;
}
newRtf += " " + this.SeparationChars[0].ToString();
}
this.NList.Rtf = @"{\rtf1\ansi\ansicpg1252\deff0\deflang3079" +
@"{\fonttbl{\f0\fswiss\fcharset0 Arial;}}" +
@"{\colortbl ;\red255\green0\blue0;" + colTbl + "}" +
@"{\*\generator TextObjectList.NET;}\viewkind4\uc1\pard\f0\fs20 "
+ newRtf + @"\par}";
return AllValid;
}
以下是从下方对象引发的事件 - 我们会相应地捕获和处理它们。
// ah, an item in the textbox has been clicked,
// we check which one it is in our
// collection and raise the appropriate event
protected void NList_ItemClicked(string ItemName, MouseEventArgs e) {
if (this.ObjectItemClicked == null) return;
if (!this.ObjectItems.Contains(ItemName)) return;
this.ObjectItemClicked(this, this.ObjectItems.FindByName(ItemName), e);
}
// our UI textbox wants to validate -
// so we check all items and don't let the textbox
// loose focus if an item in it could not be validated
protected void NList_Validating(object sender,
System.ComponentModel.CancelEventArgs e) {
e.Cancel = (!this.Validate());
}
// fire the event, if an item has been removed from the collection
internal void OnRemoveItem(ObjectItem value) {
if (this.ObjectItemRemoved != null)
this.ObjectItemRemoved(this, value);
}
关注点
此示例实际上为您提供了以下主题和技术的快速介绍:继承、委托和事件、控件构建、集合构建。由于我使用 ObjectItem
对象来处理对象,您可以直接增强它,或者从它继承您自己的扩展对象,以为其添加更多功能或根据您的需求进行调整。
希望这些代码对您有所帮助。由于这里只展示了部分代码,请使用上面的链接下载源代码。如果您有任何疑问,请随时与我联系。