65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 IExtenderProvider 构建多控件组件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2009年2月10日

BSD

5分钟阅读

viewsIcon

30581

downloadIcon

296

在本文中,我将向您展示一种使用 IExtenderProvider 构建多控件组件的技术。

引言

本文是关于创建多控件组件的,但这次。我们不是继承现有控件(我在这里展示过),而是使用 IExtenderProvider。我征求您的意见,关于哪种技术更好,优缺点等...

我为什么要写这篇文章...

在我关于多控件组件的文章之后,我收到了一个名叫 BarCode 的人的反馈,建议我尝试 LabelProvider,该提供者来自 'Robert Verpalen a.k.a Hotdog'(顺便说一句,如果您有空闲时间,可以看看他的页面,他那里有一些不错的代码,但文档有点欠缺)。所以,我下载并试用了它……并且在设计器中遇到了一些错误。我试图修复它们,但由于没有文档,这更像是一种试错式编码。然后,我发现了 CP 上一篇由 James T. Johnson 撰写的很棒的文章,在那之后,我决定编写自己的 LabelProvider

前提条件

首先,我的扩展器使用我在此解释过的技术,因此在阅读本文之前,您应该熟悉那篇文章。简而言之,我没有使用标签进行绘制……我实际上是将标签放在窗体上。另外,您应该熟悉 James 关于 IExtenderProvider 的文章。

LabelProvider - 工作原理

我将把它分解成几个部分,然后给出组件的全部代码

  1. 构造函数、辅助类、列表、哈希表。
  2. 我们只有一个属性,那就是

    // 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;
  3. 控件与标签的映射方法。
  4. 所有这些方法都操作我们的 AfpLabelInfo 对象。这些也可以实现为 AfpLabelInfo 类的公共接口。

    • getIndexFromLabels 获取活动控件并返回列表中的索引,以便我们知道哪个标签与 Control 相关联。
    • RemoveControlFromLabelsDispose 发生时触发,并确保窗体上没有“幽灵”。
    • AddOrRenewLabel 在您键入属性值时触发。标签通过哈希码映射到 Control
    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
    }
  5. Getter 和 Setter。
  6. 这些几乎是标准的,除了当您键入内容时,它会挂钩三个事件:ParentChangedLocationChangedDisposeactiveControl 是跟踪哪个控件实际触发了事件的关键。我们必须手动触发一个挂钩到 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);
        }
    }
  7. 实际绘制、重新定位和处置的方法。
  8. 这在我的上一篇文章中已经解释过了。唯一的区别是我们使用 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 日 - 初始版本。
© . All rights reserved.