热点 .NET 控件






4.76/5 (24投票s)
该控件包含一组 [动画] 图片,用作多状态按钮
摘要
HotSpot 控件构成一组不规则形状的可视元素,用作多状态按钮。这些可视元素中的每一个都可能具有多个状态。每个状态都由其自己的图像表示。左键单击特定元素会导致其从一个状态转换到另一个状态。这种转换通过元素图像从对应于初始状态的图片更改为对应于新状态的图片来体现。单击特定元素可能会根据预定义的规则,不仅更改此元素的状态,还会更改某些其他元素的状态。应通过事件/委托机制通知控件的容器(父级)状态转换事件。
控件也可以是动态的。也就是说,控件的可视元素能够根据预定义的规则随时间移动和/或重新塑形。这种修改不是由元素状态的变化引起的。动态可视元素具有与同一状态相对应的不同位置/图像。
在本文中,我们介绍了基础的热点控件类,使开发人员能够轻松定义状态转换和动态的规则,并提供简单的父级通知机制。代码是用 C# 编写的。
如何使用?
设计了两个热点控件对象的基础类。第一个是 HotSpotControl
(继承自 UserControl
.NET 框架类),它包含一组静态多状态可视元素。第二个是 DynamicHotSpotControl
(继承自 HotSpotControl
),它包含了动态功能,允许可视元素更改其形状和位置。
HotSpotLib 程序集包含这些类以及其他支持类和接口。该程序集由两个文件组成,即 HotSpotLib.dll 和 DynamicHotSpotLib.netmodule。前者包含静态热点控件的代码,而后者支持“动态扩展”类。DLL 文件包含程序集清单。如果您的应用程序仅限于静态热点控件,则不需要将 NETMODULE 文件部署给最终用户(尽管在构建应用程序时需要它)。
要使用 DynamicHotSpot
控件,必须执行以下步骤。
- 将
HotSpotLib
和DynamicHotSpotLib
命名空间添加到项目的 **References** 部分。using HotSpotLib; using DynamicHotSpotLib;
- 派生自
DynamicHotSpotControl
的新类MyDynamicHotSpotControl
。MyDynamicHotSpotControl
实现IHotSpotChangeState
和IDynamicHotSpot
接口。public class MyDynamicHotSpotControl : DynamicHotSpotControl, IHotSpotChangeState, IDynamicHotSpot
- 接口
IHotSpotChangeState
定义了以下需要实现的**方法**。void ChangeStateRules()
– 此**方法**定义控件每个可视元素的**状态**之间的转换。通常,它使用HotSpotControl
的HitVisualElement
属性来确定左键单击的可视元素。该属性返回VisualElement
类的对象**引用**。可以通过控件的名称或 ID(序列号)索引器获取VisualElement
对象的**引用**。类似地,VisualElement
类提供名称和 ID 索引器以获取其State
对象**引用**。VisualElement
类的CurrentState
属性允许**开发人员**设置/获取当前元素**状态**。在下面的示例中,“
Beetle
”和“Ground
”这两个可视元素各自有两个**状态**(0 和 1)。单击“Beetle
”会**切换**两个元素的**状态**。单击“Ground
”只会更改所单击元素**状态**。public void ChangeStateRules() { if (null != HitVisualElement) { HitVisualElement.CurrentState = (HitVisualElement.CurrentState + 1) % 2; if ("Beetle" == HitVisualElement.Name) this["Ground"].CurrentState = (this["Ground"].CurrentState + 1) % 2; } }
- 可以(可选地)**重写**以下 **virtual** **方法**。
void OnChangeState()
– 此**方法**定义控件在其某个可视元素被单击时要执行的操作。可能不需要执行任何操作(默认实现),因为控件的父级会通过事件/委托机制单独**通知**(参见下文)。string ConstructTip(string visualElementName, string stateName)
– 此**方法**构造当前位于光标下的元素的提示。这种构造的“砖块”是可视元素的名称及其当前**状态**的名称。如果**方法**返回空**字符串**,则不显示提示。默认实现返回可视元素的名称。
- 接口
IDynamicHotSpot
定义了以下需要实现的**方法**。void DynamicsRules()
– 此**方法**定义可视元素的移动**规则**。底层DynamicHotSpotControl
对象在每个**计时器****滴答**时调用一次。下面可以找到用于两个对象往复移动的**方法**实现的示例。private int step = 2; // in pixels public void DynamicsRules() { // Rules of dynamic changes if (0 == TimerTicksCount) step = -step; // Movements this["Beetle"].X += step; this["Ground"].X -= step; }
在此示例中,
DynamicHotSpotControl
类的受保护的只读**属性**TimerTicksCount
用于**同步**对象移动。
MyDynamicHotSpotControl
类的构造函数用于创建控件的所有可视元素以及每个可视元素的所有**状态**。它接受两个与控件动态相关的整数**参数**。第一个**参数**定义两个相邻**计时器****滴答**之间的时间间隔(即,**计时器**采样间隔,以毫秒为单位)。第二个**参数**定义**计时器****滴答**计数**阈值**。当**计时器****滴答**计数超过**阈值**后,将其重置为零。构造函数调用基类DynamicHotSpotControl
的构造函数,并将**参数**传递给它。下面提供了MyDynamicHotSpotControl
构造函数的示例。public MyDynamicHotSpotControl(int timerInterval, int timerClicksCountMax) : base(timerInterval, timerClicksCountMax) { IHotSpotChangeStateImpl = this; IDynamicHotSpotImpl = this; string resourceDir = "res\\"; ActiveCursorFileName = resourceDir + "Hand.cur"; // "Ground" Visual Element VisualElement ve0 = AddVisualElement("Ground", new Point(15, 0)); ve0.AddState("Green", resourceDir + "Ground0", "bmp"); ve0.AddState("Cyan", resourceDir + "Ground1", "bmp"); // "Beetle" Visual Element VisualElement ve1 = AddVisualElement("Beetle", new Point(30, 0)); ve1.AddState("Image", resourceDir + "BeetleImage", "bmp"); ve1.AddState("Text", resourceDir + "BeetleText", "bmp"); }
需要将实现
IHotSpotChangeState
和IDynamicHotSpot
接口的**引用**传递给祖先对象。这通过受保护的只写IHotSpotChangeStateImpl
和IDynamicHotSpotImpl
**属性**来完成。IHotSpotChangeStateImpl = this; IDynamicHotSpotImpl = this;
当左键在可视元素上按下时,光标的形状会**改变**。使用
ActiveCursorFileName
**属性**设置新的光标形状。AddVisualElement()
**方法**使用名称和位置**点**作为**参数**创建新的可视元素。VisualElement
类的AddState()
**方法**基于**状态**名称、图片文件名称和图片文件扩展名创建可视元素的新**状态**。该**方法**查找所有名称符合以下**规则**的图片文件。"picture file name" + "_" + i.ToString() ,
其中
i
– 整数序列号。所有这些文件都被加载,并且它们的图片在**计时器****滴答**时显示,为每个**状态**生成可视对象的动画图像。- 上述图片文件已准备好并放置在正确的路径中。
- 第 2 至第 7 段处理了派生自
DynamicHotSpotControl
的自定义MyDynamicHotSpotControl
。本段描述了应添加到控件父窗体的代码。将创建一个新控件,并可能在父级构造函数中初始化其**属性**。此时,将添加HotSpotEvent
的**处理程序**。当左键在同一可视元素上按下并释放时(元素被单击),将**触发**此事件。事件**处理程序**的委托在HotSpotControl
类中定义。它接受两个**参数**:对HotSpotControl
对象(事件源)的**引用**,以及标准的EventArgs
。下面列出了显示父构造函数代码片段的示例。// Dynamic HotSpot Control MyDynamicHotSpotControl myDynamicHotSpotCtrl = new MyDynamicHotSpotControl(500, 60); myDynamicHotSpotCtrl.Name = "DYNAMIC HotSpot"; myDynamicHotSpotCtrl.Dock = DockStyle.Top; myDynamicHotSpotCtrl.Size = new Size(0x50, 0x50); // Add event handler myDynamicHotSpotCtrl.HotSpotEvent += new HotSpotControl.HotSpotEventHandler(OnHotSpot); Controls.Add(myDynamicHotSpotCtrl);
上述**过程**是针对动态热点控件的。使用静态版本更容易。在这种情况下,自定义控件派生自
HotSpotControl
类,并且仅实现IHotSpotChangeState
接口。不使用DynamicHotSpotLib
命名空间,并且不实现IDynamicHotSpot
接口。特定可视元素的每个**状态**仅由一个图片**表示**。可以在随附**示例**代码的 Form1.cs 文件中找到静态和动态版本控件的**示例**。
里面有什么?
热点控件的基础由三个类实现:State
、VisualElement
和 HotSpotControl
。实际自定义控件必须派生自 HotSpotControl
类,并实现 IHotSpotChangeState
接口的 ChangeStateRules()
**方法**。
State
类**表示**可视元素的每个**状态**。它包含名称、ID 和图片(原始图片以及具有透明背景的相同图片)等**参数**。State
类的 HitTest()
**方法**检查鼠标单击是否在**图片**的非透明区域执行。
VisualElement
类定义了控件的每个可视元素。VisualElement
包含可视元素所有**状态**的 ArrayList alState
。这些**状态**可以通过 ID 和名称索引器在类外部访问。**公共** **方法** AddState()
允许**开发人员**创建新的 State
对象并将其添加到 alState
列表中。AddState()
**方法**内部使用受保护的 virtual **方法** StateFactory()
来创建 State
对象或 State
派生类的对象。HitTest()
**方法**检查是否单击了可视对象。为此,它依赖于可视元素的当前**状态**的 HitTest()
**方法**。Draw()
**方法**负责在当前**状态**下绘制可视元素。
HotSpotControl
类派生自标准的 .NET UserControl
类。它作为自定义热点控件类或 DynamicHotSpotControl
类的基础。毫不奇怪,HotSpotControl
包含控件所有可视元素的 ArrayList alVisualElement
。ID 和名称索引器将可视元素暴露给外部世界。HotSpotControl
处理鼠标按下、释放和移动事件。它执行命中测试并绘制其可视元素——通过循环其可视元素列表 alVisualElement
来完成。类中受保护的 virtual **方法**及其某些**公共** **属性**在“如何使用?”部分进行了讨论。HotSpotControl
定义了事件 HotSpotEvent
和相应的委托 HotSpotEventHandler
。当可视元素被单击时(即,左键在同一可视元素上按下并释放),将**触发**此事件。假定控件的父窗体提供了委托的实现。
动态热点控件的基础由三个类实现:MultipictureState
(派生自 State
类)、MultipictureVisualElement
(派生自 VisualElement
类)和 DynamicHotSpotControl
(派生自 HotSpotControl
)。**开发人员**自定义的实际控件必须派生自 HotSpotControl
类,并实现 IHotSpotChangeState
和 IDynamicHotSpot
接口的 ChangeStateRules()
和 DynamicsRules()
**方法**。
与静态版本不同,在动态热点控件中,可视元素以“动画”图形图像**表示**,即使它们处于同一**状态**。为了支持此**功能**,MultipictureState
类可以容纳多个图片,DynamicHotSpotControl
类实现了**计时器**机制。MultipictureState
类有一个**私有**类 PictureContext
(包含原始图片、具有透明背景的相同图片和背景颜色)以及此**状态**所有图片上下文的 ArrayList alPictureContext
。
代码示例
代码**示例**由以下文件组成。HotSpot.cs 文件包含 HotSpotControl
相关类(HotSpotLib
命名空间),并将编译为 HotSpotLib.dll。DynamicHotSpot.cs 文件包含 DynamicHotSpotControl
相关类(DynamicHotSpotLib
命名空间),并将编译为 DynamicHotSpotLib.netmodule。文件 HotSpotLib.dll(包含程序集清单)和 DynamicHotSpotLib.netmodule 构成 HotSpotLib 程序集。Form1.cs 文件包含使用控件的代码**示例**。它将编译为 TestHotSpot.exe 文件。RunMe.bat 文件提供了编译和运行**测试**。TestHotSpot.exe 是**示例**的可执行文件。包含动态和静态热点控件的**测试**窗体如下图所示。
在**示例**中,Form1
包含两个热点控件,一个动态和一个静态。每个控件包含三个可视元素:“Beetle
”、“Ground
”和“Blot
”。可以看到与动态控件的“Ground
”可视元素相关的提示(尽管光标位于提示区域左上角上方)。单击每个可视元素都会根据自定义控件中定义的**规则**导致元素**状态**的**改变**。静态控件永久停靠在底部,而动态控件允许停靠——同样,根据控件自定义定义的**规则**。作为对可视元素左键单击的响应,控件会重绘所有可视元素(处于新的**状态**)并连续显示两个消息框。第一个由自定义控件发出,第二个指示控件父级**状态**更改**处理程序**的调用。要更改动态控件的停靠位置,用户应按住左键在任何可视元素上,然后在其他位置释放按钮。
改进和进一步发展
所提出的热点控件可以通过多种方式**改进**。在某些情况下,处理鼠标按钮在一个可视元素上按下并在另一个可视元素上释放的情况可能很有用。可以支持一个控件内,甚至控件之间的可视元素的拖放操作。
可以通过**避免**重绘时的闪烁效果(这是一个需要单独解决的众所周知的问题)来**改进**动态**功能**,以及更复杂的图片动态**转换**(例如,旋转)。添加播放动画 GIF 文件的**能力**也很有用。
这些热点控件还可以作为其他类型 .NET 控件(例如,可自定义工具栏控件)的**基础**和出发点。
结论
已开发出 .NET 热点控件的静态和动态版本。其图像充当多状态按钮。状态转换和动态的**规则**易于自定义。通过一些**改进**和修改,该控件可以作为其他控件类的**基础**。
谢谢
我要感谢 Stas Levin(Windows GUI 开发专家,BCGSoft)和迷人的 Irina Zherdenovsky(Creo)提供了有用的专业讨论。