Windows Forms 自定义控件的自白书 [ComboView] – 第一部分。






3.28/5 (25投票s)
2003年12月28日
5分钟阅读

97112

343
我是谁?我为什么在这里 CodeProject?嗯,我会自己回答所有这些问题以及其他问题。
引言
我是谁?我为什么在这里 CodeProject?嗯,我会自己回答所有这些问题以及其他问题。
我是一个 ComboView
控件。你可能会想,那是什么?嗯,我是一个 ComboBox
,能够在一个 ListView
中显示列式项。嗯,你可能会问,有很多其他类似的控件可用,那么我有什么特别之处呢?没什么,唯一不同的是,我将要向你讲述我是如何被创造出来的,以及我的创造者在这段创造之旅中学到的经验。
起初
就在几天前,一位疯狂的开发者想到了创造我。这不是出于对我的爱,而是出于贪婪。对学习某事物的贪婪,对理解控件开发的复杂性的贪婪等等。所以,你看到了,一个人是很贪婪的。他做的一切都是为了满足自己的需求。
好吧,让我们看看他在计划创造我的时候,脑子里都有什么。
- 他需要学习在 .NET 中创建控件(特别是用 C#)。
- 他需要理解如何创建一个复合控件。
- 他需要理解
ListView
的工作方式。 - 他需要理解如何提供自定义数据绑定的细节。
- 他需要理解如何处理自定义事件。
- 等等……
好吧,我需要承认一件事。我不是最好的控件之一,甚至不是最接近的。但我是快乐的,因为在一定程度上,正是通过我,我的创造者理解了上述所有目标(或者我假设他理解了)。
让我们来解剖我自己,在这段旅程中,你和我都可以学到一些东西。
解剖我自己
首先。为了让我诞生,我需要有一个父控件。开发者决定 UserControl
应该是我的父控件。
所以,我继承自 System.Windows.Forms.Usercontrol
。这意味着我将拥有我的父控件(UserControl
)的所有属性和行为。
public class MyCombo : System.Windows.Forms.UserControl
{
因为我是一个 ComboView
,为了让我诞生,我需要一些其他控件的支持,特别是 Button
、ListView
和 TextBox
。
private System.Windows.Forms.ListView lvObject;
private System.Windows.Forms.TextBox txtObject;
private System.Windows.Forms.Button btnDropDown;
好的,我将解释为什么需要以上三个控件。
ListView
将用于以多列形式显示数据。TextBox
将用于显示选定的值。Button
将用于实现我的展开和收起功能。
然后,开发者需要一些变量来控制我的行为。所以,他将它们声明在自己的区域里。
#region MyCombo Variables
private object _dataSource;
private string _dataMember;
private string _displayMember;
private string _valueMember;
protected ArrayList _columnHeader = new ArrayList();
private object _selectedValue;
private object _selectedText;
//Size
const int MIN_WIDTH = 140;
const int MIN_HEIGHT = 22;
#endregion
让我告诉你其中一些变量对我意味着什么,其余的都很明显。(稍后,我们将为上述变量添加相应的 set
/get
属性。)
_dataSource
我需要能够用来自不同来源的数据填充我自己,例如自定义类、数据库、XML 文件等。使用此属性来分配数据源。
_dataMember
一个
_dataSource
可以包含多个表。通过此属性指定我应该从哪个表获取数据。_displayMember
由于我将以多列格式显示自己,请通过此属性指定要在
TextBox
中显示的列。_valueMember
这应该是每行数据中每个数据元素的唯一 ID。您在查询、保存等方面需要此值。
_selectedValue
此属性返回
_valueMember
变量的值。_selectedText
此属性返回
_displayMember
变量的值。_columnHeader
这是一个
ArrayList
。它将存储列标题。
现在轮到事件部分了。事件是为了让控件的用户知道何时发生某些事情而需要的。目前,开发者只提供了一个自定义事件。
#region MyCombo Events
public delegate void
SelectedItemChangedEventHandler(object sender, EventArgs e);
public event SelectedItemChangedEventHandler
OnSelectedItemChangedHandler;
#endregion
当用户从 ListView
中选择任何列表项时,将引发 OnSelectedItemChangedHandler
。
正如我所承诺的,这是我相应的 get
/set
访问器。我的用户将看到这些属性,而不是我们上面看到的变量。
public string DisplayMember
{
get
{
return _displayMember;
}
set
{
_displayMember = value;
}
}
public string ValueMember
{
get
{
return _valueMember;
}
set
{
_valueMember = value;
}
}
public string DataMember
{
get
{
return _dataMember;
}
set
{
_dataMember = value;
}
}
public object DataSource
{
get
{
return _dataSource;
}
set
{
_dataSource = value;
OnDataBind();
}
}
public object SelectedText
{
get
{
return _selectedText;
}
set
{
_selectedText=value;
}
}
public object SelectedValue
{
get
{
return _selectedValue;
}
set
{
_selectedValue = value;
}
}
在 DataSource
属性中,您会看到一个 OnDataBind()
方法。这将在我们继续前进时进行解释。
现在,应该有一个机制,让控件的用户能够分配列给我。这由以下函数处理。该函数接受两个参数,列名和列的宽度。此函数仅将列名添加到我们上面看到的 ArrayList
中。
public void Columns(string columnName, int colWidth)
{
_columnHeader.Add(columnName);
OnHeaderBind(columnName,colWidth);
}
现在,让我们看看 OnHeaderBind()
函数。这个函数将 columnName
添加到实际的 ListView
对象中,并设置其宽度。
protected void OnHeaderBind(object v, int colWidth)
{
lvObject.Columns.Add(v.ToString(), colWidth, HorizontalAlignment.Left);
}
还应该提供一种手动添加数据给我的方法,而不是使用数据源和数据成员。这可以通过以下函数实现。该函数接受一个 ID 和一个要添加到 ListView
的项目数组。所以,我的用户可以像这样添加一个项目给我:
<instance of me>.Add (“101”,”Rajesh”,”23)
public void Add(string id, params string [] items)
{
ListViewItem lvi = new ListViewItem(id);
foreach(string s in items)
{
lvi.SubItems.Add(s);
}
lvObject.Items.Add(lvi);
}
现在,正如之前所承诺的,让我们来谈谈 OnDataBind()
方法。每当您将数据源分配给我时,都会调用此方法。此方法只会用数据源中的信息填充 ListView
。
protected void OnDataBind()
{
if (_dataSource == null)
return;
IList iList = InnerDataSource();
Type type = iList.GetType();
string s;
for (int i = 0; i < iList.Count ; i++)
{
s= GetField(RuntimeHelpers.GetObjectValue(iList[i]),_valueMember);
ListViewItem lvi = new ListViewItem(s);
for (int j = 1; j < _columnHeader.Count; j++)
{
lvi.SubItems.Add(GetField(RuntimeHelpers.GetObjectValue(iList[i]),
_columnHeader[j].ToString()));
}
lvObject.Items.Add(lvi);
}
}
RuntimeHelpers.GetObjectValue(obj)
做什么?如果 obj
是值类型,它会返回 obj
的装箱副本;否则,它会返回 obj
本身。
InnerDataSource()
函数返回与数据源关联的 IList
对象。稍后,我们可以枚举 IList
对象来获取它包含的每个列表值。
private IList InnerDataSource()
{
IList iList;
if (_dataSource is DataSet)
{
if (_dataMember.Length > 0)
{
iList = ((IListSource)
((DataSet)_dataSource).Tables[_dataMember]).GetList();
}
else
{
iList = ((IListSource)((DataSet)_dataSource).Tables[0]).GetList();
}
}
else if (_dataSource is IListSource)
{
iList = ((IListSource)_dataSource).GetList();
}
else if (_dataSource is ICollection)
{
object[] objs = new object[((ICollection)_dataSource).Count];
((ICollection)_dataSource).CopyTo(objs,0);
iList = objs;
}
else
{
iList = (IList)_dataSource;
}
return iList;
}
IListSource
:此接口提供对象返回可绑定到数据源的列表的功能。ICollection
:此接口定义所有集合的大小、枚举器和同步方法。
编译我,添加一个测试项目并引用我,我就准备好面对世界了。
现在就到这里了。有很多事情需要在我身上完成。也许,那会在以后。
我希望您喜欢阅读我。任何建议、批评、想法都应该直接发送给我的创造者,以便他可以改进我或多学一点。
有关完整的源代码,请查看本文随附的示例项目。
暂别,
ComboView
致谢
InnerDataSource
及相关函数 -> 这些函数是从 The Code Project/或互联网上的某个地方获取的。如果有人知道原作者,请给我留言,我很乐意给他署名。
修订历史
- 2003-12-28:原文。