使用 IExtenderProvider 构建多控件组件






4.50/5 (2投票s)
在本文中,我将向您展示一种使用 IExtenderProvider 构建多控件组件的技术。
引言
本文是关于创建多控件组件的,但这次。我们不是继承现有控件(我在这里展示过),而是使用 IExtenderProvider
。我征求您的意见,关于哪种技术更好,优缺点等...
我为什么要写这篇文章...
在我关于多控件组件的文章之后,我收到了一个名叫 BarCode 的人的反馈,建议我尝试 LabelProvider,该提供者来自 'Robert Verpalen a.k.a Hotdog'(顺便说一句,如果您有空闲时间,可以看看他的页面,他那里有一些不错的代码,但文档有点欠缺)。所以,我下载并试用了它……并且在设计器中遇到了一些错误。我试图修复它们,但由于没有文档,这更像是一种试错式编码。然后,我发现了 CP 上一篇由 James T. Johnson 撰写的很棒的文章,在那之后,我决定编写自己的 LabelProvider
。
前提条件
首先,我的扩展器使用我在此解释过的技术,因此在阅读本文之前,您应该熟悉那篇文章。简而言之,我没有使用标签进行绘制……我实际上是将标签放在窗体上。另外,您应该熟悉 James 关于 IExtenderProvider 的文章。
LabelProvider - 工作原理
我将把它分解成几个部分,然后给出组件的全部代码
- 构造函数、辅助类、列表、哈希表。
- 控件与标签的映射方法。
getIndexFromLabels
获取活动控件并返回列表中的索引,以便我们知道哪个标签与Control
相关联。RemoveControlFromLabels
在Dispose
发生时触发,并确保窗体上没有“幽灵”。AddOrRenewLabel
在您键入属性值时触发。标签通过哈希码映射到Control
。- Getter 和 Setter。
- 实际绘制、重新定位和处置的方法。
我们只有一个属性,那就是
// pretty much standard
[ProvideProperty("AfpLabel", typeof(Control))]
接下来,我们有一个辅助类。这非常重要,因为它是我们控件与标签映射的核心。我决定我的映射基于 GetHashCode()
。我知道这并不 100% 安全,但到目前为止,我找不到更好的方法。我一直在考虑一些组合,比如哈希码+名称,但名称会在某些事件(如 Dispose
)上消失。我的意思是,我有一个控件,在我触发一些事件之后,哈希码保持不变,但名称 == String.Empty
,而且我真的不知道为什么。所以,如果您有更好的技术来创建控件的唯一标识,请给我一些反馈。
接下来,我们将创建一个对象列表和一个名为 activeControl
的变量。该变量将用于跟踪哪个控件触发了事件,哪个控件正在移动等。您不必担心同时处理两个控件,因为 Windows 会依次处理它们的事件(所以不需要 List
或类似的东西)。
protected class AfpLabelInfo
{
// label that will be drawn next to control
protected Label _lab = new Label();
// hash code of control so we know which labels goes to wchich control
protected int _controlHash;
public Label lab
{
get { return _lab; }
set { _lab = value; }
}
public int controlHash
{
get { return _controlHash; }
set { _controlHash = value; }
}
// we will take hashcode of each control
public AfpLabelInfo(int AcontrolName)
{
_controlHash = AcontrolName;
}
}
// remember to use one HashTable per property
protected Hashtable AfpLabelProps = new Hashtable();
protected List<afplabelinfo> _labels = new List<afplabelinfo>();
protected Control activeControl;
所有这些方法都操作我们的 AfpLabelInfo
对象。这些也可以实现为 AfpLabelInfo
类的公共接口。
protected int getIndexFromLabels(Control cnt)
{
for (int i = 0; i < _labels.Count; i++)
{
if (_labels[i].controlHash == (cnt.GetHashCode()) )
return i;
}
return -1;
}
protected void RemoveControlFromLabels(Control cnt)
{
int i = getIndexFromLabels(cnt);
if (i != -1)
{
_labels[i].lab.Dispose();
_labels.RemoveAt(i);
}
}
protected void AddOrRenewLabel(Control cnt)
{
int dummy = cnt.GetHashCode();
for (int i = 0; i < _labels.Count; i++)
{
if (_labels[i].controlHash == (dummy))
{
if (_labels[i].lab == null)
{
_labels[i].lab = new Label();
return;
}
}
}
_labels.Add(new AfpLabelInfo(dummy));
// if there was no control we add it manually
}
这些几乎是标准的,除了当您键入内容时,它会挂钩三个事件:ParentChanged
、LocationChanged
和 Dispose
。activeControl
是跟踪哪个控件实际触发了事件的关键。我们必须手动触发一个挂钩到 ParentChanged
的方法,因为当我们添加一个属性时,父级更改已经发生……
public string GetAfpLabel(Control c)
{
string text = (string)AfpLabelProps[c];
if (text == null)
{
text = String.Empty;
}
return text;
}
public void SetAfpLabel(Control c, string value)
{
activeControl = c;
AfpLabelProps[c] = value;
if (value == String.Empty)
{
c.Disposed -= new EventHandler(c_Disposed);
c.ParentChanged -= new EventHandler(val_ParentChanged);
c.LocationChanged -= new EventHandler(val_LocationChanged);
}
else
{
c.ParentChanged += new EventHandler(val_ParentChanged);
c.LocationChanged += new EventHandler(val_LocationChanged);
c.Disposed += new EventHandler(c_Disposed);
val_ParentChanged(activeControl, null);
}
}
这在我的上一篇文章中已经解释过了。唯一的区别是我们使用 activeControl
来确定我们需要操作哪个标签。
void c_Disposed(object sender, EventArgs e)
{
activeControl = (Control)sender;
if (_labels[getIndexFromLabels(activeControl)].lab != null)
{
RemoveControlFromLabels(activeControl);
}
}
void val_LocationChanged(object sender, EventArgs e)
{
int i = getIndexFromLabels(activeControl);
activeControl = (Control)sender;
if (i != -1)
{
setControlsPosition(_labels[getIndexFromLabels(activeControl)].lab);
}
}
void val_ParentChanged(object sender, EventArgs e)
{
activeControl = (Control)sender;
AddOrRenewLabel(activeControl);
if (activeControl.Parent != null)
{
activeControl.Parent.Controls.Add(
_labels[getIndexFromLabels(activeControl)].lab);
setControlsPosition(_labels[getIndexFromLabels(activeControl)].lab);
}
}
protected virtual void setControlsPosition(Label someLab)
{
if (someLab != null)
{
// setting text
someLab.Text = GetAfpLabel(activeControl);
// autosize is important cause it saves us a lot of code
someLab.AutoSize = true;
// little bit to the right
someLab.Left = activeControl.Left - someLab.Width - 5;
// and little bit below top
someLab.Top = activeControl.Top + 3;
}
}
全部集合
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections;
namespace AfpComponents
{
[ProvideProperty("AfpLabel", typeof(Control))]
[ToolboxBitmap(typeof(Label))]
public partial class AfpLabelProvider : Component, IExtenderProvider
{
#region contructorz and protectorz
protected class AfpLabelInfo
{
// label that will be drawn next to control
protected Label _lab = new Label();
// hash code of control so we know which
// labels goes to wchich control
protected int _controlHash;
public Label lab
{
get { return _lab; }
set { _lab = value; }
}
public int controlHash
{
get { return _controlHash; }
set { _controlHash = value; }
}
// we will take hashcode of each control
public AfpLabelInfo(int AcontrolName)
{
_controlHash = AcontrolName;
}
}
protected Hashtable AfpLabelProps = new Hashtable();
protected List<afplabelinfo> _labels = new List<afplabelinfo>();
protected Control activeControl;
public AfpLabelProvider()
{
InitializeComponent();
}
#endregion
#region Label mapper methods
protected int getIndexFromLabels(Control cnt)
{
for (int i = 0; i < _labels.Count; i++)
{
if (_labels[i].controlHash == (cnt.GetHashCode()) )
return i;
}
return -1;
}
protected void RemoveControlFromLabels(Control cnt)
{
int i = getIndexFromLabels(cnt);
if (i != -1)
{
_labels[i].lab.Dispose();
_labels.RemoveAt(i);
}
}
protected void AddOrRenewLabel(Control cnt)
{
int dummy = cnt.GetHashCode();
for (int i = 0; i < _labels.Count; i++)
{
if (_labels[i].controlHash == (dummy))
{
if (_labels[i].lab == null)
{
_labels[i].lab = new Label();
return;
}
}
}
_labels.Add(new AfpLabelInfo(dummy));
// if there was no control we add it manually
}
#endregion
#region Getters and setterrs
public string GetAfpLabel(Control c)
{
string text = (string)AfpLabelProps[c];
if (text == null)
{
text = String.Empty;
}
return text;
}
public void SetAfpLabel(Control c, string value)
{
activeControl = c;
AfpLabelProps[c] = value;
if (value == String.Empty)
{
c.Disposed -= new EventHandler(c_Disposed);
c.ParentChanged -= new EventHandler(val_ParentChanged);
c.LocationChanged -= new EventHandler(val_LocationChanged);
}
else
{
c.ParentChanged += new EventHandler(val_ParentChanged);
c.LocationChanged += new EventHandler(val_LocationChanged);
c.Disposed += new EventHandler(c_Disposed);
val_ParentChanged(activeControl, null);
}
}
#endregion
void c_Disposed(object sender, EventArgs e)
{
activeControl = (Control)sender;
if (_labels[getIndexFromLabels(activeControl)].lab != null)
{
RemoveControlFromLabels(activeControl);
}
}
void val_LocationChanged(object sender, EventArgs e)
{
int i = getIndexFromLabels(activeControl);
activeControl = (Control)sender;
if (i != -1)
setControlsPosition(_labels[getIndexFromLabels(activeControl)].lab);
}
void val_ParentChanged(object sender, EventArgs e)
{
activeControl = (Control)sender;
AddOrRenewLabel(activeControl);
if (activeControl.Parent != null)
{
activeControl.Parent.Controls.Add(
_labels[getIndexFromLabels(activeControl)].lab);
setControlsPosition(_labels[getIndexFromLabels(activeControl)].lab);
}
}
protected virtual void setControlsPosition(Label someLab)
{
if (someLab != null)
{
// setting text
someLab.Text = GetAfpLabel(activeControl);
// autosize is important cause it saves us a lot of code
someLab.AutoSize = true;
// little bit to the right
someLab.Left = activeControl.Left - someLab.Width - 5;
// and little bit below top
someLab.Top = activeControl.Top + 3;
}
}
#region IExtenderProvider Members
public bool CanExtend(object extendee)
{
if (extendee is Control)
return true;
else
return false;
}
#endregion
}
}
好的。没问题……但是按钮呢?
现在,我们来到了问题开始的部分。我浏览了网络,找不到关于如何添加事件的信息。添加一个按钮本身不是问题,将按钮的事件添加到控件是一个问题。这时我就需要您的帮助了。到目前为止,我尝试过(或正在考虑)以下方法:
- 克隆另一个未使用的事件 - 是的,当您不使用该事件时,它很好。如果您构建了一个项目,并且突然真的需要该事件,那将很麻烦。然后怎么办?修改所有基类……这是一个糟糕的主意。
- 手动创建事件 - 我曾想过在提供者上创建一个事件,该事件将在窗体创建时触发,并在其中包含类似
control1.acompButton.click += new .......
的内容,因为我可以像映射标签一样映射按钮。唯一的问题是删除。就像您手动添加事件一样,您也必须手动删除它。 - 使用状态机挂钩到一个已使用的事件(例如
Click
)- 嗯,这似乎很疯狂,但如果我们使用发送者(发送者将是控件或按钮)做一些开关,它可能会起作用。
所以,我期待您的反馈。发送电子邮件或留下评论。
可能的错误
当我尝试使用此组件并进行测试时,发生了一件奇怪的事情。不知何故,Control.Parent
(在我的情况下是 DateTimePicker
)为 null
。我尝试过,但无法重现它。所以,如果有人遇到这个问题,请给我发电子邮件或留言。
最后的寄语
我必须承认 IExtenderProvider
是一个极好的工具。当我编写这个组件时,我学到了很多关于它的知识。现在,我的问题是:您更喜欢哪种技术?继承?还是扩展?我知道扩展更方便(一个组件统治所有控件……:-)),但是它也会带来问题(事件)。请发送反馈并告诉我您的想法……
历史
- 2009 年 2 月 11 日 - 错误修复。
- 2009 年 2 月 7 日 - 初始版本。