上一篇:1.开篇 http://www.cnblogs.com/conexpress/archive/2009/03/02/component_control_01.html
示例代码:http://files.cnblogs.com/conexpress/SimulateLinkLabel.rar
从本篇开始会通过实例介绍如何实现组件控件编程。在上一篇中提到通过组合实现组件编程,达到灵活添加功能的效果。那么是如何组合的呢?一般是通过事件,在组件中处理控件的相关事件,在事件处理程序中封装需要的功能。
本篇的实例是用Label模拟网页链接的效果。在.NET控件库中已经提供了LinkLabel控件,但该控件强制显示下划线,而且只能改变链接颜色,不能改变背景色。这里通过处理Label控件的鼠标事件,动态改变其显示相关属性,即可模拟出网页链接的效果。而且在事件中可以加入更多的效果,比LinkLabel控件更绚丽。
下面介绍实现的过程。
首先打开VS.NET,创建项目,选择项目类型为类库,输入项目名称SimulateLinkLabel,然后确定。
项目建立好后,在“解决方案资源管理器”中对项目点右键,选择添加组件。
在添加新项对话框中输入组件名称SimulateLinkLabel,点击“添加”按钮。即可完成添加组件,此时,可以将默认添加的类Class1删掉。
因为本组件涉及到WinForm控件和颜色,需要添加相关引用。对项目或者项目引用点右键,选择添加引用。
在“添加引用”对话框中选择System.Drawing,点击确定。然后重复上一步,选择System.Windows.Forms,点击确定。
添加完引用之后,打开SimulateLinkLabel组件的代码,在代码顶部添加命名空间。
using System.Windows.Forms;
以上是创建组件的初始化步骤,需要变化的是根据不同的需要添加不同的引用。如果创建的是Windows控件库,也可以在新建项目时选择“Windows控件库”。但类库是最通用的项目类型,在其中可以包含组件,控件甚至窗体,所以一般选择类库即可。接下来就开始真正实现本组件。
本组件是为了让Label显示为网页链接的效果,那么首先组件必须知道对哪个Label进行操作。那么首先添加一个Label类型的属性。篇首提到组件一般是对控件的事件进行处理,那么就必须用“+=”去绑定控件事件和事件处理方法。在什么时候绑定?下面让我们先了解一下窗体背后的故事。
在“解决方案资源管理器”中随便打开一个窗体的Designer.cs文件,可以看到如下代码段。
分析代码可以看出来,最底部是窗体中控件和组件变量的声明,展开IniitalizeComponent方法,可以看到首先是控件变量的实例化,然后是对各个控件的属性设置。其中包含了对事件处理程序的设置,例如下面对button1的Click事件的设置:this.button1.Click += new System.EventHandler(this.button1_Click);
而切换到窗体代码,可以看到如下代码:
在窗体类的构造函数中,调用了InitializeComponent方法,这意味着窗体实例化的时候完成了对窗体上所有控件组件以及相关事件处理方法的设置。
了解窗体背后的故事之后,我们就可以在设置组件的属性的时候将目标Label控件的事件绑定到事件处理方法,这样就可以保证窗体实例化的时候将事件绑定到对应的处理方法上。当然还有其他方法,比如采用ISupportInitialize接口,在以后的文章中会介绍。声明的属性如下:
private Label m_TargetLabel;
/// <summary>
/// 目标 Label 控件
/// </summary>
[Category(c_ControlCategory), Description("目标 Label 控件。")]
public Label TargetLabel
{
get { return m_TargetLabel; }
set
{
if (m_TargetLabel != value)
{
this.HandleLabelMouseEvent(m_TargetLabel, false);//取消绑定之前控件的事件
m_TargetLabel = value;
this.HandleLabelMouseEvent(m_TargetLabel, true);//绑定当前控件的事件
}
}
}
/// <summary>
/// 绑定Label的鼠标事件
/// </summary>
/// <param name="TargetLabel">目标Label控件</param>
/// <param name="HandleFlag">绑定标志位,true为绑定,false为取消绑定</param>
private void HandleLabelMouseEvent(Label TargetLabel, bool HandleFlag)
{
if (TargetLabel != null && !this.DesignMode)
{
if (HandleFlag)
{
TargetLabel.MouseEnter += new EventHandler(Label_MouseEnter);
TargetLabel.MouseLeave += new EventHandler(Label_MouseLeave);
TargetLabel.MouseDown += new MouseEventHandler(Label_MouseDown);
TargetLabel.MouseUp += new MouseEventHandler(Label_MouseUp);
}
else
{
TargetLabel.MouseEnter -= new EventHandler(Label_MouseEnter);
TargetLabel.MouseLeave -= new EventHandler(Label_MouseLeave);
TargetLabel.MouseDown -= new MouseEventHandler(Label_MouseDown);
TargetLabel.MouseUp -= new MouseEventHandler(Label_MouseUp);
}
}
}
分析这段代码,首先在属性上方有[Category(c_ControlCategory), Description("目标 Label 控件。")]。这叫做Attribute,中文称为特性,以便与Property(属性)区别。关于特性的详细介绍,请参考开篇中给出的相关链接资料。
这里介绍一下涉及到的特性的相关知识。要使用特性,必须添加对System.ComponentModel命名控件的引用,即在代码顶部添加using System.ComponentModel。而特性最直观的应用就是在PropertyGrid中可以看到,如下图。详细的介绍请参考如下链接:
http://www.cnblogs.com/guanjinke/archive/2006/12/06/584714.html
http://www.cnblogs.com/mapserver/articles/344458.html
Catagory特性是用来在PropertyGrid控件中进行分类的,通过相同的Category特性标记的属性会被分配到一组中。这里声明了两个私有常量,因为Category特性不接受变量。
//CategoryAttribute相关常量
private const string c_ControlCategory = "控制";
private const string c_ApparanceCategory = "Appearance";
这里声明了两个用于Category特性的常量,一个是“控制”,一个是“Appearance”,但是在PropertyGrid控件中显示的却都是中文。因为在.NET中已经集成了常用的Category,比如Appearance,Behavior,Layout等,当Category设置为这些值的时候,IDE就会将其显示为IDE的当前语言。
Description特性是用来在PropertyGrid控件中显示属性的描述信息的。当需要对属性进行详细解释的时候,就采用Description特性,方便用户理解。
在set代码段中首先判断之前的目标Label控件是否与设置的值是否一致,不一致则取消之前的Label控件的事件绑定,然后再绑定新控件。为什么必须先取消呢,因为事件绑定到处理方法上之后会一直有效,如果不取消,就算将其他对象的事件绑定到同一个处理方法上,只要之前的事件发生,还是会调用处理方法。这一点就必须理解委托的概念,因为事件就是通过委托实现的。关于委托,这里不作过多的阐述,不了解的朋友可以查阅相关资料。
这里又提出一个问题:如果窗体上有很多个Label控件都需要实现网页链接的效果,那岂不是需要有很多个SimulateLinkLabel组件与之一一对应?这样做当然可以,但效率有点低了。通过上面介绍的事件绑定机制,可以灵活避免这个问题。因为多个控件的事件可以绑定到同一个方法,那么可以将HandleLabelMouseEvent方法设置为public,通过代码去绑定需要网页链接效果的Label控件,这些Label控件任何一个触发了事件都会去调用对应的事件处理方法。
另外在HandleLabelMouseEvent方法中有一个this.DesignMode,这代表当前状态是否是设计时。有时候需要根据设计时还是运行时做出一些特定的操作。这里表示如果在设计时就不需要绑定控件事件,因为这时候绑定了也没什么效果。但有时候确实需要在设计时执行特定操作,就必须判断这个属性了。至于具体的应用,在以后的文章中遇到了再详细分析。
为了模拟网页链接效果,在鼠标相关事件处理方法中需要改变Label控件的外观。而这些外观要做到灵活设置,就必须添加相关的属性。这里添加了部分颜色相关的属性,如果需要,还可以添加其他外观相关的属性,比如图片,边框,不同状态的字体等。
private Color m_ActiveForeColor = Color.Yellow;
/// <summary>
/// 活动链接的前景色
/// </summary>
[Category(c_ApparanceCategory), Description("活动链接的前景色。"), DefaultValue(typeof(Color), "Yellow")]
public Color ActiveForeColor
{
get { return m_ActiveForeColor; }
set { m_ActiveForeColor = value; }
}
private bool m_EnableActiveForeColor = true;
/// <summary>
/// 指示是否启用活动链接前景色
/// </summary>
[Category(c_ControlCategory), Description("指示是否启用活动链接前景色。"), DefaultValue(true)]
public bool EnableActiveForeColor
{
get { return m_EnableActiveForeColor; }
set { m_EnableActiveForeColor = value; }
}
private Color m_ActiveBackColor = Color.Blue;
/// <summary>
/// 活动链接的背景色
/// </summary>
[Category(c_ApparanceCategory), Description("活动链接的背景色。"), DefaultValue(typeof(Color), "Blue")]
public Color ActiveBackColor
{
get { return m_ActiveBackColor; }
set { m_ActiveBackColor = value; }
}
private bool m_EnableActiveBackColor = false;
/// <summary>
/// 指示是否启用活动链接背景色
/// </summary>
[Category(c_ControlCategory), Description("指示是否启用活动链接背景色。"), DefaultValue(false)]
public bool EnableActiveBackColor
{
get { return m_EnableActiveBackColor; }
set { m_EnableActiveBackColor = value; }
}
private Color m_HoverForeColor = Color.Red;
/// <summary>
/// 悬浮状态前景色
/// </summary>
[Category(c_ApparanceCategory), Description("悬浮状态前景色。"), DefaultValue(typeof(Color), "Red")]
public Color HoverForeColor
{
get { return m_HoverForeColor; }
set { m_HoverForeColor = value; }
}
private bool m_EnableHoverForeColor = true;
/// <summary>
/// 指示是否启用悬浮状态前景色
/// </summary>
[Category(c_ControlCategory), Description("指示是否启用悬浮状态前景色。"), DefaultValue(true)]
public bool EnableHoverForeColor
{
get { return m_EnableHoverForeColor; }
set { m_EnableHoverForeColor = value; }
}
private Color m_HoverBackColor = Color.Blue;
/// <summary>
/// 悬浮状态背景色
/// </summary>
[Category(c_ApparanceCategory), Description("悬浮状态背景色。"), DefaultValue(typeof(Color), "Blue")]
public Color HoverBackColor
{
get { return m_HoverBackColor; }
set { m_HoverBackColor = value; }
}
private bool m_EnableHoverBackColor = false;
/// <summary>
/// 指示是否启用悬浮状态背景色
/// </summary>
[Category(c_ControlCategory), Description("指示是否启用悬浮状态背景色。"), DefaultValue(false)]
public bool EnableHoverBackColor
{
get { return m_EnableHoverBackColor; }
set { m_EnableHoverBackColor = value; }
}
这里为每一个颜色属性提供了一个对应的标志位属性,指示是否启用对应颜色属性。这些属性上方除了Category和Description特性外,还多了一个DefaultValue特性。DefaultValue特性是用来指示属性的默认值的,详细资料可以参考http://www.cnblogs.com/guanjinke/archive/2006/12/24/602451.html
DefaultValue特性最直观的理解就是:在PropertyGrid中如果是默认值,则显示为普通字体,如果被修改了,则显示为粗体。在Designer.cs代码中如果是默认值,则不会生成设置该属性的代码。
一般情况下DefaultValue特性只接受简单类型的值,比如数值、逻辑值、字符串。对颜色的设置则需要特殊处理,因为Color不是简单类型。从代码中可以看到颜色属性上方的DefaultValue特性格式为DefaultValue(typeof(Color), "ColorName"),MSDN中的解释是:
DefaultValueAttribute(Type, String) :初始化 DefaultValueAttribute 类的新实例,将指定的值转换为指定的类型,并将固定区域性作为翻译上下文。
参数
type
类型:System.Type
表示要将值转换为的类型的 Type。
value
类型:System.String
可以通过该类型的 TypeConverter 和美国英语转换为该类型的 String。
这里又出现了新的概念,TypeConverter,详细介绍可以参考
http://www.cnblogs.com/guanjinke/archive/2006/12/11/588609.html
http://www.cnblogs.com/guanjinke/archive/2006/12/11/589372.html
http://www.cnblogs.com/guanjinke/archive/2006/12/14/592605.html
http://www.cnblogs.com/mapserver/articles/353722.html
当然并不是所有类型的属性都可以通过这种方法设置默认值的,这种属性的类型必须有一个对应的TypeConverter,其中的CanConvertFrom方法必须能接受字符串才行。通过Reflector查看Color的代码,可以看到Color类有一个对应的TypeConvert特性,其中的类型是ColorConverter。
ColorConverter继承了TypeConverter,其中的CanConvertFrom方法如下,说明可以从字符串转换成Color。当然,真正从字符串转换为Color的代码必须在ConvertFrom中实现。
以上介绍完属性,就该介绍方法了。绑定事件的方法HandleLabelMouseEvent已经在上面给出了,剩下就是鼠标事件的处理方法。
#region 鼠标事件处理方法
/// <summary>
/// 【鼠标进入事件】处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseEnter(object sender, EventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
//保存之前的控件样式
m_LabelForeColor = item.ForeColor;
m_LabelBackColor = item.BackColor;
m_LabelFont = item.Font;
item.Cursor = Cursors.Hand;//改变光标样式
item.Font = new Font(m_LabelFont, m_LabelFont.Style | FontStyle.Underline);//显示下划线以模拟链接效果
if (m_EnableHoverForeColor)
item.ForeColor = m_HoverForeColor;//改变前景色
if (m_EnableHoverBackColor)
item.BackColor = m_HoverBackColor;//改变背景色
}
}
/// <summary>
/// 【鼠标离开事件】处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseLeave(object sender, EventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
//恢复控件默认样式
item.Cursor = Cursors.Default;
item.ForeColor = m_LabelForeColor;
item.BackColor = m_LabelBackColor;
item.Font = m_LabelFont;
}
}
/// <summary>
/// 【按下鼠标按钮事件】处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseDown(object sender, MouseEventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
if (m_EnableActiveForeColor)
item.ForeColor = m_ActiveForeColor;//显示活动前景色
if (m_EnableActiveBackColor)
item.BackColor = m_ActiveBackColor;//显示活动背景色
}
}
/// <summary>
/// 【释放鼠标按钮事件】处理程序
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Label_MouseUp(object sender, MouseEventArgs e)
{
Label item = sender as Label;
if (item != null && item.Enabled)
{
if (m_EnableActiveForeColor)
item.ForeColor = m_EnableHoverForeColor ? m_HoverForeColor : m_LabelForeColor;//恢复前景色
if (m_EnableActiveBackColor)
item.BackColor = m_EnableHoverBackColor ? m_HoverBackColor : m_LabelBackColor;//恢复背景色
}
}
#endregion 鼠标事件处理方法
这些方法的逻辑都很简单,主要就是根据不同的鼠标动作去改变Label控件的样式。其中需要注意的是Font类型,该类型的属性都是只读的,比如它的字体大小是没法修改的。如果需要修改字体,只能重新实例化一个。如果需要根据之前的字体实例化,则可以通过Font(Font, FontStyle)构造函数进行实例化,或者用更多参数的构造函数。
另外在类顶部给类添加一个DefaultProperty特性,指定为TargetLabel,这样在选中本组件的时候,属性窗口中的PropertyGrid就会默认选中TargetLabel,给开发人员提供更好的交互操作。
下面给出本组件的类图
完成以上代码之后,生成一下。添加一个测试的Windows应用程序项目,在该项目用添加对SimulateLinkLabel项目的引用。添加项目引用的方法和之前添加引用类似,只是要在“添加引用”对话框中选择“项目”选项卡,然后选择要引用的项目即可。此时工具箱中就会多出一个选项卡,如下图:
将该组件拖动到窗体上,然后在窗体上添加一个Label控件,设置TargetLabel为新增的Label控件。然后按F5运行,就可以看到当鼠标对Label控件操作时,该控件会显示成网页链接的效果。
到此就实现了一个简单的组件,如果希望有更多的功能也可以根据需要添加。例如这里没有实现VisitedLinkColor,可以加入一个bool字段记录点击状态。文中给出一些链接资料,不懂的朋友可以参考。
示例代码:http://files.cnblogs.com/conexpress/SimulateLinkLabel.rar