


























这一系列文章中,我主要讨论如何由浅入深的创建自定义控件。首先,我们需要认识什么是控件,以及自定义控件的应用如何节省时间和金钱。
第一篇中,我们将对.NET自定义控件有一个快速的认识,并在文章结束时创建一个具有一定功能的自定义控件。在开始创建你自己的控件之前,理解他们的架构是一个重要的先决条件。
控件是可重用的用户界面功能组件。在Windows的世界里,控件代表了用户与应用程序交互的方法。控件允许用户输入数据并且可以操作数据,从而使用户在应用程序上执行某些操作,如输入数据、友好的显示数据。应用程序的界面是由控件以及控件的功能组成的。控件的功能是基于控件本身与后台代码之间的互相调用而实现的。
让我们看看接下来的几张照片,你就会了解当今最流行的控件。你必须了解,使用控件进行程序设计比手动从头开始创建控件要简单的多。
每个应用程序都有不可见的另一面,但是这些不可见的部分却为可见的用户界面提供着实际的方法操作。控件完整的软件设计元素,它既用图形化的方式表现在屏幕上,而且它还包含着代码来支持界面的功能。
控件具有两个主要的功能:
除此之外,你可以使用颜色选取器来修改控件的背景色,可以通过按下一个按钮来执行具体的操作,或者你可以通过进度条来查看执行的进度状态变化。另一类控件只是作为显示用,即他们使用图形的方式显示数据内容,但是不允许客户修改显示的内容。标签控件就是最好的例子。
有趣的是,整个控件构思的发展的根源是个人计算机应用的开发。开发人员必须为应用程序提供一些良好用户体验的控件。这里让我们用一个常见的按钮为例。现实中的按钮具有不同的形状和尺寸,以及多种状态。它可以被按下或释放,或者可以当按下时发出声音。为了真实再现控件的特点,我们可以使用很多技巧。
自定义这个词非常的神奇。在你的很多开发任务中,你只可以使用开发环境中默认提供的控件,但是有些情况下你可能需要定义你自己的用户控件。
自己定义的控件,顾名思义,叫做自定义控件。自定义控件是由你自己进行设计和编程的控件,而且在设计和开发过程中,你可以利用已有的控件。有时自定义控件也可以叫做第三方控件。
自定义控件说明,.NET框架提供的默认控件基类,无法满足需要时,或者你从第三方购买的控件要么过于昂贵要么无法满足需要时,创建自定义控件就成了必然的要求。
你还可以通过实现自定义控件的方法来提高代码的效率。这样做的意义可以用一个很简单的例子说明。如果在你的应用程序中必须使用一个包含多个元素的饼状图,从而达到以友好的方式显示多个结果的目的。有两种方法可以实现。
• 你可以直接在窗体中编写代码。首先,你必须在某个特定的位置绘制一个包含不同元素的饼状图。然后,你需要重写鼠标处理事件,来获得图形事件。最后假设这个图形具有一些功能,你就必须将图形所需代码直接附加到窗体的代码上。
你要想在应用程序中操作这个饼状图,就必须按照上面三个步骤完成才可。接下来,即使进行一些简单功能的更改,如将某个操作从左键触发,转移到右键触发,都需要执行上面的三个步骤。你的代码将会包含大量重复的功能,使其可读性极差,理解、调试、扩展都非常困难。更何况你每次更改这个图表,你都需要重新生成你的整个应用程序。
• 你可以创建一个自定义控件。你可以定义一个饼状图控件,使其可以自己绘制饼状图,而且可以具有自己的事件和事件处理机制。它将想窗体抛出不同的属性和必要的方法。这个自定义控件的位置可以通过设置它的坐标,轻易的在窗体中变化。另外,一旦创建了这样的自定义控件,你就会节省宝贵的时间,因为你用在修改自定义控件、添加扩展功能以及调试自定义控件所化的时间都很短暂,而且代码的变更也只出现在一个地方——控件的代码中。
封装方法到窗体的用户控件中,带来了几个好处:
根据绘制方式的不同,自定义控件分为三类:
为了实现自定义控件,我们需要理解自定义控件及其组成部分的工作原理。我们现在需要了解控件的可见和不可见部分。自定义控件由两个主要部分组成。第一部分是黑盒。这部分保存了控件及其私有成员数据和内部的功能。第二部分是控件的公共接口。这部分接口由公共属性、事件和方法组成。这些抛出的控件的功能允许在使用控件的代码中,以编程方式操纵控件。
从技术上讲,控件就是一个继承自System.Windows.Forms.Control类的自定义类。它包含任何控件的基本方法,如响应鼠标事件、键盘事件、焦点以及绘图事件、预设样式、属性。定义一个最基本自定义控件的方法如下所示:
1: public class MyControl:Control
2: { 3: }接下来,我们会学习一个控件类的基本组成。重要的是知道并理解这些组成元素是什么,以及他们如何实现控件功能。这些组成部分组成了控件类,并通过你继承的基础控件类,将你的更改落实到你的自定义控件上。换句话说,我们从Control类继承了一些基本的功能,这些功能是所有控件都共有的,而且我们可以通过添加这些组成部分来为自己的控件构建自定义功能。我们还可以修改现有的控件来添加一些额外的功能。
私有变量,顾名思义,是一个不能被外部访问到的变量。当构建一个自定义控件时,“外部”就是指使用控件的应用程序(也可能是使用这个控件的其他自定义控件)。通常情况下,对于每个公共属性来说,都至少会有一个私有变量类存储抛出的数据。
一个良好的编程习惯是声明私有类变量,然后通过公共属性将私有变量抛出。这里我们使用的命名规则定义为,类命名使用Pascal命名(每个单词的首字母都大写,如ProgesssBar),而变量使用驼峰命名(首字母小写,其他单词首字母均大写,如myProgessBar)。
下面是一个代码段,它定义了一个名为MyControl的,具有私有变量的控件:
1: public class MyControl : Control
2: {3: private Color backgroundColor;
4: private Color foregroundColor;
5: private int intemCount;
6: private Brush backBrush;
7: }当你在Visual Studio的窗体设计器中选中一个控件后,你可以就可以在属性窗口中查看控件的属性。属性是一个类或对象相关联的特点。例如,一个常见的按钮就有很多属性:名称、文本、字体、大小等等。按钮所抛出的所有属性都可以在属性窗口中查看。
属性是任何控件向外抛出控件的设置和数据的关键特征。通过公共属性用户可以对控件的配置进行一些列的交互,而且通过这种方法,用户可以获取和设置保存配置和数据的私有变量。
属性在Get和Set访问器中定义了数据可读或可写的过滤代码。这些访问器通常用来读取或设置那些真正保存数据的私有成员的值。通过为属性只定义Get访问器,你可以控制它的只读;通过只定义Set访问器,你可以控制它只写。这里以背景色为例,正如解释私有变量时所说,数据和配置隐藏在控件内的私有变量中,这些数据和配置通过属性抛出到控件的外部。
属性的默认结构是:
1: public <type> <PropertyName>
2: { 3: get 4: {5: return <fieldName>;
6: } 7: set 8: {9: <fieldName> = value;
10: } 11: }这里,<type>表示属性的数据类型(如string),<PropertyName>是属性的名称(如BackgroundColor),而<fieldName>是存储属性值的私有变量的名称。注意,属性本身不保存任何数据,而且它可以使用Get和Set访问器,自由返回或设置私有变量的值。
属性基本上包含两种方法来读取或设置成员的值。他们还可以帮助实例化空字段,或者当成员改变时,执行某些活动。下面代码就说明了这些:
1: private Brush backBrush = null;
2: 3: public Brush BackBrush
4: { 5: get 6: {7: if(backBrush == null)
8: {9: backBrush = new SolidBrush(Color.Black);
10: }11: return backBrush;
12: } 13: set 14: {15: if(backBrush != value)
16: {17: backBrush = value;
18: Invalidate(); 19: } 20: } 21: }上面的代码中,get访问器内,首先需要获得backBrush私有变量的实例引用,属性使用默认值将其初始化,并返回它的值。如果代码尝试读取BackBrush属性的值,而BackBrush属性并未放入try/catch块儿中,那么一个空引用异常将会抛出。
查看Set访问器中的代码:当设置backBrush变量时,如果它的值与旧值相同,则什么事情也不发生。这有助于优化应用程序代码,使得不必要时,某些方法可以不调用。
假如你有一个控件,或数据类中包含一系列项的集合。与其使用共有属性抛出这个集合,还不如为控件添加索引器来抛出这个集合。索引器是一种特殊类型的属性,它可以使类以数组下标的方式访问,这样就可以通过索引值获得其内部的对象。
例如,如果一个名为list的对象,包含一个索引器,你就可以通过读取list[1],list[2]等等来获取集合中的项。如果没有索引器,你只能通过list.Items[1],list.Items[2]等来访问。索引器与属性的差别是,索引器需要传递参数。
默认声明索引器的格式如下:
1: public <type> this[int index]
2: { 3: get 4: {5: //return the object at the index;
6: } 7: set 8: {9: //set the object at the index;
10: } 11: }通常情况下,索引器返回的是一个数组中给定索引位置的成员。
让我们用一个例子来更好的理解使用索引器的好处。颜色选择器是一个控件,它可以让你选择一种颜色。作为一个额外的功能,提供通过颜色的索引器来在默认颜色中或最近使用的颜色中进行选择。颜色数组中的对象通过调用ColorPicker的索引器就可以获得。
1: public class ColorPicker : Control
2: {3: private Color[] colors;
4: public Color this[int index]
5: { 6: get 7: {8: if(index >= 0 && index < colors.Length)
9: {10: return colors[index];
11: }12: else
13: {14: return null;
15: } 16: } 17: set 18: {19: if(index >= 0 && index < colors.Length)
20: {21: colors[index] = value;
22: } 23: } 24: } 25: }如何使用索引器呢:
1: ColorPicker colorPicker1; 2: colorPicker1[0] = Color.Red;get/set语句实际上鼓励你验证你要读取或设置的值。你可以在上面的代码段中看到简单的验证结构。
一般来说,当你可以将数据以数组的结构保存时,你可以使用索引器;这就意味着数据的个数应该是可数的。例如,你可以使用属性来存储气温的温度,而你可以使用属性来存储一个星期的每一天。
事件和委托是Windows平台程序设计的核心,因为他们是应用程序与用户进行互操作的主要机制(但是它们也可以作为其他用途)。事件和委托允许一个控件(或其它类型的类),当它自身发生一些事件时,发送出信号,而且你可以编写C#方法来依据控件发送的信号,自动执行某些功能。而这个信号,就是所激发的事件本身。委托是一种对象类型,它允许你注册本地C#方法(那些我们在事件处理时需要调用的),当事件触发时执行。
若要用一个例子说明,那么假设我们有一个窗体,这个窗体中包含一个按钮。我们也许想知道当单击按钮时,我们可以执行一些C#代码作为响应。按钮与窗体之间的链接是通过事件实现的。当单击按钮时,按钮的Click事件被触发,用来表示按钮已经被单击。为了响应这个Click事件,你可以创建一个事件处理程序,它可以是一个C#方法,而且它可以在单击时激发执行。
事件定义了控件产生的活动。这些活动可以由用户对界面的互操作产生,或者通过其它实现逻辑。鼠标单击是一个事件,键盘按下也是一个事件,也可以说明当前控件下产生了一事件触发。事件由发送者激发,由接收者捕捉。委托作为一种特殊的调用形式,成为发送者和接收者之间的链接。
前面我们说到,为了响应触发的事件,我们可以注册一个本地方法让它自动执行。虽然这些事件可以帮你规划你的系统功能如何执行,但是,如果在真正的C#代码实现中,你什么代码也不写,那么没有任何功能会自动执行。当某个控件发生事件后,这个控件就知道如何执行使用这个控件的类中的方法;为了实现这种效果,对于某个方法的引用必须发送到该控件。而委托就是这样的一种方法引用。委托是一个数据类型,它定义了方法的模板,而且委托的实例是指向具体方法的引用。控件所能触发的每个事件,都有其具体执行的委托类型,而正是这些委托类型将事件需要的参数发送到控件本身执行(这些参数包含事件的细节,而这些细节都依据事件的不同而不同)。
下面我们看一下委托如何声明:
1: public delegate <return type> <delegate name> ( <parameter list> );
例如:
1: public delegate int myDelegate( int intValue );
这个委托表示一个具有一个整型参数的方法模板,而且这个方法会返回一个整数类型的结果。其他类中的很多方法,只要满足这个委托模板的规定,都可以绑定到这个事件委托上来。这些方法会在事件触发时被调用。通常事件委托是如下格式:
1: public delegate void <myEventHandler>(object sender, <EventArgs> e);
这里,<myEventHandler>代表当事件触发时,执行的本地方法。方法的命名约定是使用事件名“EventHandler”作为方法名的后缀,如ClickEventHandler,KeyDownEventHandler和MouseOverEventHandler。如果使用Visual Studio生成的事件处理程序的命名规范是<触发事件的控件名称>_<事件的名称>,如myButton_Click。
当使用.NET内置控件时,<EventArgs>是一个继承自.NET框架的EventArgs类的类型。你可以定义你自己的事件和委托,但是使用上面介绍的结构创建这些事件与委托是最佳的做法。
现在我们看看如何定义事件。事件必须声明在触发它的控件内,并使用下面这种默认方式声明:
1: public event <myEventHandler> <Event_name>;
例如:
1: public event ClickEventHandler Click;
当事件触发时调用的接收器方法叫做事件处理程序。换句话说,当创建一个控件时,要想让某个动作触发时,通知窗体或其他控件,你需要添加一个公共的事件到控件中,并在活动发生时,触发它。然后,任何引用了你的控件的类,可以通过将事件处理程序关联到事件上,从而获得事件的通知。现在让我们看看如何触发事件,以及在另外类中处理这个事件。
触发一个事件,通常是由控件中的一个受保护的虚方法来完成的。它的声明如下:
1: protected virtual void On<event name>(EventArgs e)
2: {3: if(<event name> != null)
4: {5: <event name>(this,e);
6: } 7: }事实上,方法名以“On”开头,并不是必须,但确实推荐的做法。例如,MouseOver事件就是通过OnMouseOver激活事件方法来触发的。这样的代码更容易阅读和理解。
激活方法是虚方法,这就意味着当你需要改变这些方法默认的行为时,可以通过继承这个控件类来重写这些方法。
下面的例子中,如果你设置控件的BackgroundColor属性,就会使控件的backgroundColor成员改变,从而触发BackgroundColorChanged事件。
1: public class MyControl : Control
2: { 3: ...4: private Color backgroundColor;
5: public event EventHandler BackgroundColorChanged;
6: public Color BackgroundColor
7: { 8: get 9: {10: return backgroundColor;
11: } 12: set 13: {14: if(backgroundColor != value)
15: {16: backgroundColor = value;
17: OnBackgroundColorChanged(EventArgs.Empty); 18: } 19: } 20: }21: protected virtual void OnBackgroundColorChanged(EventArgs e)
22: {23: if(BackgroundColorChanged != null)
24: {25: BackgroundColorChanged(this,e);
26: } 27: } 28: ... 29: }事件变量为空时,说明没有事件处理程序连接到它。如果没有异常处理程序附加到这个事件上,则必须通过方法验证或异常处理进行评比。
为了处理我们上面定义的MyControl类的事件,你需要在使用这个控件的类上添加事件处理程序。事件处理程序是一个方法,它可以位于任何想要处理这个事件的类中。事件以EventHandler的形式声明,这只是一个标记而已,而且不需要为事件传递任何数据,因为EventArgs类并没有要求传递参数。在.NET 框架中的声明如下:
1: public delegate void EventHandler(object sender,EventArgs e);
事件处理程序必须满足事件委托定义的方法标识规定。
1: MyControl myControl; 2: ...3: // Attaches the myControl_BackgroundColorChanged method to the event
4: myControl.BackgroundColorChanged += new EventHandler(myControl_BackgroundColorChanged);
5: ...6: private void myControl_BackgroundColorChanged(object sender, EventArgs e)
7: {8: // Code that is executed when the BackgroundColorChanged event is fired.
9: }每次MyControl类中的BackgroundColorChanged事件触发时,调用类都会调用myControl_BackgroundColorChanged方法,并执行其中的代码。
要实现传递数据到事件处理程序中这种更复杂的事件,例如控件的状态或其他参数(比如,单击事件就需要传递鼠标的坐标位置),你既可以使用.NET内置的事件(如ClickEventHandler),也可以创建自己的事件。
你将要编写的一些控件可能需要存储集合项。例如,一个ListView控件就有一个ListViewItems集合,用来保存控件中每一行的数据。
有很多类型的结构可以用来在你的控件中存储这样的数据。我们快速看看这里都有哪些可能,然后我们会在下一篇文章中详细分析他们。
基类对象内使用数组来管理许多元素。但是使用数组来存储有它明显的缺点就是数组大小固定,这就限制了它使用的途径。你不应该使用数组来存储控件中索引的数据,除非数组中的元素个数是固定的(例如数组中的5种颜色)。
1: Color[] colors = new Color[3];
ArrayList是.NET框架中基于索引方式存储任意类型集合项的集合类型。它最主要的优势是具有动态可变的大小,以及动态在某个特定的索引位置添加、插入和移除的能力。但是,在构建控件时,将要索引的数据存储在ArrayList中并不是好的做法,因为对象存入ArrayList时的数据类型,需要在从ArrayList中读取该对象前获得。注意,使用using System.Collections代码可以在代码中引入ArrayList集合对象。
1: ArrayList aList = new ArrayList();
2: aList.Add(Color.Red); 3: aList.Add(Color.Green); 4: aList.Add(Color.Blue); 5: aList.RemoveAt(1);6: // Conversion is needed because the arrayList indexer return type is object
7: Color col = (Color) aList[0];ArrayList并不是System.Collections命名空间中的唯一类别。在后面的文章中我们详细介绍。
你甚至可以通过继承CollectionBase基类来创建自己的集合类。这样你可以创建自定义集合来统一保存你规定的对象类型。ArrayList类可以保存任意类型的对象,通过它的索引可以返回object类型的对象。而你自己定义的集合类型可以返回你集合中所特有的统一对象类型。通过使用自定义集合类,你可以重写基类中的方法,以实现附加的功能。例如,CollectionChanged事件可以追加到集合的父类上,当集合改变时可以触发。此外,集合是大小可变的,不需要像数组那样,在创建时就规定元素的个数。
集合类都是派绳子CollectionBase类。这个类包含了一个内部数组,它可以用来存储元素。最好的做法是将集合命名为以对象的类型为前缀,以“Collection”作为后缀,如ColorCollection。
让我们看看如何构建一个集合类。下面的代码是一个控制台应用程序,其中包含了我们在上面阐述的一些理论。如果你想在Visual Studio中创建一下代码,你需要引用System.Drawing命名空间。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Collections;
6: using System.Drawing;
7: 8: namespace ConsoleApplicationColorCollection
9: {10: public class ColorCollection : CollectionBase
11: {12: //this event is fired when the collection's items have changed
13: public event EventHandler Changed;
14: //this is the constructor of the collection.
15: public ColorCollection()
16: { 17: }18: //the indexer of the collection
19: public Color this[int index]
20: { 21: get 22: {23: return (Color)this.List[index];
24: } 25: }26: //this method fires the Changed event.
27: protected virtual void OnChanged(EventArgs e)
28: {29: if (Changed != null)
30: {31: Changed(this, e);
32: } 33: }34: //returns the index of an item in the collection
35: public int IndexOf(Color item)
36: {37: return InnerList.IndexOf(item);
38: }39: //adds an item to the collection
40: public void Add(Color item)
41: {42: this.List.Add(item);
43: OnChanged(EventArgs.Empty); 44: }45: //inserts an item in the collection at a specified index
46: public void Insert(int index, Color item)
47: {48: this.List.Insert(index, item);
49: OnChanged(EventArgs.Empty); 50: }51: //removes an item from the collection.
52: public void Remove(Color item)
53: {54: this.List.Remove(item);
55: OnChanged(EventArgs.Empty); 56: } 57: }58: class Program
59: {60: static void Main(string[] args)
61: {62: // create a color collection
63: ColorCollection colorCollection = new ColorCollection();
64: // add two colors to the collection
65: colorCollection.Add(Color.Red); 66: colorCollection.Add(Color.Blue);67: // you can reference a color from the collection without making a cast
68: Color color = colorCollection[0];69: // you can refer to collection items like any Color object
70: Console.WriteLine(colorCollection[1].Name); 71: } 72: } 73: }上述代码中描述了一个简单的颜色对象的集合。下一篇文章中我们会看到更多关于集合的课题,其中包含.NET 2.0版本中的泛型。
枚举是用户自定义数据类型,用来存储可命名的相同数据类型的一系列内容。枚举可以用来接受一组固定类型的值。例如,你可以设置一组类型来定义控件边框的风格:none,2D,3D:
1: borderControl.Border = BorderType.Border3D;默认的枚举声明方式:
1: public enum <Name>
2: { 3: <value1>, 4: <value2>, 5: ... 6: <valuen> 7: }边框样式的枚举案例如下:
1: public enum BorderType
2: { 3: None, Border2D, Border3D 4: }你可以将枚举作为控件的属性来使用:
1: public class BorderControl : Control
2: { 3: ...4: private void BorderType border;
5: ...6: public BorderType Border
7: { 8: get 9: {10: return border;
11: } 12: set 13: {14: border = value;
15: } 16: } 17: }现在,要在另一个调用类中设置枚举的值,只需要进行选择即可:
1: BorderControl borderControl; 2: borderControl.Border = BorderType.Border3D;前面我们看了一些理论的内容,现在我们花一些时间创建一个简单的,但是功能健全的自定义控件。
这里我们需要创建一个声音播放控件,叫做TinyNoiseMaker。创建它的过程中,可以告诉你基本的控件架构:怎样构建黑盒,然后使用事件和属性公开接口。这里还要用到两个控件,分别是:OpenFileDialog和SoundPlayer控件。
该控件播放一个本地硬盘的音频文件。它的界面由三个按钮组成。而这三个按钮代表了三个主要的功能:加载,播放和停止。
下面的步骤中将会引导你创建TinyNoiseMaker。首先你会创建一个Windows应用程序,叫做SoundPlayerTest,然后在这个项目内部才会创建TinyNoiseMaker控件。
| 控件类别 | 控件名 | 文本 |
| 按钮 | openButton | Open |
| 按钮 | playButton | Play |
| 按钮 | stopButton | Stop |
| 打开文件对话框 | openFileDialog |
| 属性名 | 属性值 |
| 边框类型 | FixedSingle |
| 背景色 | ControlLight |
| 高度 | 32 |
1: using System.IO;
2: using System.Media;
1: namespace SoundPlayerTest
2: {3: public partial class TinyNoiseMaker : UserControl
4: {5: private SoundPlayer soundPlayer;
1: public TinyNoiseMaker()
2: { 3: InitializeComponent();4: soundPlayer = new SoundPlayer();
1: private void openButton_Click(object sender, EventArgs e)
2: {3: if (openFileDialog.ShowDialog() == DialogResult.OK)
4: soundPlayer.Stream = new FileStream(openFileDialog.FileName, FileMode.Open, FileAccess.Read);
5: }1: private void playButton_Click(object sender, EventArgs e)
2: { 3: soundPlayer.Play(); 4: }1: private void stopButton_Click(object sender, EventArgs e)
2: { 3: soundPlayer.Stop(); 4: }你成功建立了一个Windows窗体应用程序,并且添加了一个新的自定义控件。然而,TinyNoiseMaker控件是独立于应用程序之外的。这个练习的最后一步时,你需要添加这个控件到应用程序的主界面。这样功能就完成了。
TinyNoiseMaker功能依赖于SoundPlayer组件,这个组件是你以私有成员的方式添加的。SoundPlayer控件位于.NET 2.0框架中的System.Media命名空间下,这就是为什么我们需要首先添加针对System.Media的引用。分析openButton_Click,playButton_Click和stopButton_Click,就揭示了你如何从硬盘打开一个文件,以及如何播放。
如果你视图加载一个非支持的类型,应用程序将会抛出异常(如下图所示),这就需要你在生产代码中考虑错误处理相关的技术。
现在,虽然你的应用程序可以运行了,那么接下来值得考虑的是Visual Studio都建立了那些文件:
TinyNoiseMaker.cs文件包含了控件的逻辑,TinyNoiseMaker.Designer.cs包含了Visual Studio为界面生成的代码。
这一节中,你将通过公共的方法、属性和事件为控件添加功能,以便使其功能可以编程访问。与在界面的控件上按下按钮效果相同的是,通过共有方法调用来执行操作。控件的公共接口将会定义为两个方法(Play(),Stop()),以及一个属性(FileName),以及两个事件(PlayStart,PlayStop).
1: public void Play()
2: { 3: soundPlayer.Play(); 4: }5: public void Stop()
6: { 7: soundPlayer.Stop(); 8: }1: private void playButton_Click(object sender, EventArgs e)
2: { 3: Play(); 4: } 5: 6: private void stopButton_Click(object sender, EventArgs e)
7: { 8: Stop(); 9: }1: private string fileName;
2: public string FileName
3: { 4: get 5: {6: return fileName;
7: } 8: set 9: {10: if (fileName != value)
11: {12: fileName = value;
13: soundPlayer.Stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
14: } 15: } 16: }1: private void openButton_Click(object sender, EventArgs e)
2: {3: if (openFileDialog.ShowDialog() == DialogResult.OK)
4: FileName = openFileDialog.FileName;5: //soundPlayer.Stream = new FileStream(openFileDialog.FileName, FileMode.Open, FileAccess.Read);
6: }1: public event EventHandler PlayStart;
2: public event EventHandler PlayStop;
1: // fire the PlayStart event
2: protected virtual void OnPlayStart(EventArgs e)
3: {4: if (PlayStart != null)
5: {6: PlayStart(this, e);
7: } 8: }9: // fire the PlayStop event
10: protected virtual void OnPlayStop(EventArgs e)
11: {12: if (PlayStop != null)
13: {14: PlayStop(this, e);
15: } 16: }1: public void Play()
2: { 3: soundPlayer.Play(); 4: OnPlayStart(EventArgs.Empty); 5: }6: public void Stop()
7: { 8: soundPlayer.Stop(); 9: OnPlayStop(EventArgs.Empty); 10: }1: using System;
2: using System.Collections.Generic;
3: using System.ComponentModel;
4: using System.Drawing;
5: using System.Data;
6: using System.Linq;
7: using System.Text;
8: using System.Windows.Forms;
9: using System.IO;
10: using System.Media;
11: 12: namespace SoundPlayerTest
13: {14: // the TinyNoiseMaker user control
15: public partial class TinyNoiseMaker : UserControl
16: {17: // private members
18: private SoundPlayer soundPlayer;
19: private string fileName;
20: // public events
21: public event EventHandler PlayStart;
22: public event EventHandler PlayStop;
23: // fire the PlayStart event
24: protected virtual void OnPlayStart(EventArgs e)
25: {26: if (PlayStart != null)
27: {28: PlayStart(this, e);
29: } 30: }31: // fire the PlayStop event
32: protected virtual void OnPlayStop(EventArgs e)
33: {34: if (PlayStop != null)
35: {36: PlayStop(this, e);
37: } 38: }39: // public property stores the name of the file to be played
40: public string FileName
41: { 42: get 43: {44: return fileName;
45: } 46: set 47: {48: if (fileName != value)
49: {50: fileName = value;
51: soundPlayer.Stream = new FileStream(fileName, FileMode.Open,
52: FileAccess.Read); 53: } 54: } 55: }56: // constructor initializes the SoundPlayer control
57: public TinyNoiseMaker()
58: { 59: InitializeComponent();60: soundPlayer = new SoundPlayer();
61: }62: // open the file to be played
63: private void openButton_Click(object sender, EventArgs e)
64: {65: if (openFileDialog.ShowDialog() == DialogResult.OK)
66: { 67: FileName = openFileDialog.FileName; 68: } 69: }70: // play the file when the Play button is clicked
71: private void playButton_Click(object sender, EventArgs e)
72: { 73: Play(); 74: }75: // stop playing when the Stop button is clicked
76: private void stopButton_Click(object sender, EventArgs e)
77: { 78: Stop(); 79: }80: // start playing the sound file
81: public void Play()
82: {83: // play the sound
84: soundPlayer.Play();85: // fire the event
86: OnPlayStart(EventArgs.Empty); 87: }88: // stop playing the sound file
89: public void Stop()
90: {91: // stop playing the sound
92: soundPlayer.Stop();93: // fire the event
94: OnPlayStop(EventArgs.Empty); 95: } 96: } 97: }现在,你的控件中包含了可以被其他类通过编程方式操作的属性和事件。我们之所以添加这些功能,是因为通常情况下,可编程访问通常是必要的需求。这样你的客户端可以不仅仅使用可视化的用户界面,就可以使用控件提供的功能了。
接下来,我们看看如何使用这些新的公共方法。当扩展了控件的方法后,你就会看到如何在示例应用程序中使用TinyNoiseMaker控件的公共接口。这些接口包含两个方法(Play(),Stop()),一个属性(FileName),以及两个事件(PlayStart,PlayStop)。
1: private void tinyNoiseMaker1_PlayStart(object sender, EventArgs e)
2: {3: Text = "Play: " + tinyNoiseMaker1.FileName;
4: }5: private void tinyNoiseMaker1_PlayStop(object sender, EventArgs e)
6: {7: Text = "Stop: " + tinyNoiseMaker1.FileName;
8: }1: private void playButton_Click(object sender, EventArgs e)
2: {3: tinyNoiseMaker1.FileName = "C:\\Windows\\Media\\tada.wav";;
4: tinyNoiseMaker1.Play(); 5: }这个练习中,你的客户端程序使用了控件的公有功能来播放声音文件。另外,你处理了控件抛出的两个事件,来告诉用户控件正在做的事情。这些任务通常都是构建自定义控件时常用的。
这里你已经学会了如何构建一个简单的,非自己绘制的控件。它的接口是基于按钮控件的,而且它的功能是基于SoundPlayer和OpenFileDialog组件的。通常的做法是使用控件的可视化界面元素来操作控件,但是你也看到了,你可以自由的允许其它程序,以编程的方式使用你控件抛出的功能。而控件可以通过公有属性、方法、事件来抛出自己的功能。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。