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

Windows Forms 自定义控件的自白书 [ComboView] – 第一部分。

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.28/5 (25投票s)

2003年12月28日

5分钟阅读

viewsIcon

97112

downloadIcon

343

我是谁?我为什么在这里 CodeProject?嗯,我会自己回答所有这些问题以及其他问题。

Sample Image - ComboView.jpg

引言

我是谁?我为什么在这里 CodeProject?嗯,我会自己回答所有这些问题以及其他问题。

我是一个 ComboView 控件。你可能会想,那是什么?嗯,我是一个 ComboBox,能够在一个 ListView 中显示列式项。嗯,你可能会问,有很多其他类似的控件可用,那么我有什么特别之处呢?没什么,唯一不同的是,我将要向你讲述我是如何被创造出来的,以及我的创造者在这段创造之旅中学到的经验。

起初

就在几天前,一位疯狂的开发者想到了创造我。这不是出于对我的爱,而是出于贪婪。对学习某事物的贪婪,对理解控件开发的复杂性的贪婪等等。所以,你看到了,一个人是很贪婪的。他做的一切都是为了满足自己的需求。

好吧,让我们看看他在计划创造我的时候,脑子里都有什么。

  • 他需要学习在 .NET 中创建控件(特别是用 C#)。
  • 他需要理解如何创建一个复合控件。
  • 他需要理解 ListView 的工作方式。
  • 他需要理解如何提供自定义数据绑定的细节。
  • 他需要理解如何处理自定义事件。
  • 等等……

好吧,我需要承认一件事。我不是最好的控件之一,甚至不是最接近的。但我是快乐的,因为在一定程度上,正是通过我,我的创造者理解了上述所有目标(或者我假设他理解了)。

让我们来解剖我自己,在这段旅程中,你和我都可以学到一些东西。

解剖我自己

首先。为了让我诞生,我需要有一个父控件。开发者决定 UserControl 应该是我的父控件。

所以,我继承自 System.Windows.Forms.Usercontrol。这意味着我将拥有我的父控件(UserControl)的所有属性和行为。

public class MyCombo : System.Windows.Forms.UserControl
{

因为我是一个 ComboView,为了让我诞生,我需要一些其他控件的支持,特别是 ButtonListViewTextBox

    private System.Windows.Forms.ListView lvObject;
    private System.Windows.Forms.TextBox txtObject;
    private System.Windows.Forms.Button btnDropDown;

好的,我将解释为什么需要以上三个控件。

  • ListView 将用于以多列形式显示数据。
  • TextBox 将用于显示选定的值。
  • Button 将用于实现我的展开和收起功能。

然后,开发者需要一些变量来控制我的行为。所以,他将它们声明在自己的区域里。

#region MyCombo Variables
    private object _dataSource;
    private string _dataMember;
    private string _displayMember;
    private string _valueMember;

    protected ArrayList _columnHeader = new ArrayList();

    private object _selectedValue;
    private object _selectedText;

    //Size
    const int MIN_WIDTH = 140;
    const int MIN_HEIGHT = 22;
#endregion

让我告诉你其中一些变量对我意味着什么,其余的都很明显。(稍后,我们将为上述变量添加相应的 set/get 属性。)

  • _dataSource

    我需要能够用来自不同来源的数据填充我自己,例如自定义类、数据库、XML 文件等。使用此属性来分配数据源。

  • _dataMember

    一个 _dataSource 可以包含多个表。通过此属性指定我应该从哪个表获取数据。

  • _displayMember

    由于我将以多列格式显示自己,请通过此属性指定要在 TextBox 中显示的列。

  • _valueMember

    这应该是每行数据中每个数据元素的唯一 ID。您在查询、保存等方面需要此值。

  • _selectedValue

    此属性返回 _valueMember 变量的值。

  • _selectedText

    此属性返回 _displayMember 变量的值。

  • _columnHeader

    这是一个 ArrayList。它将存储列标题。

现在轮到事件部分了。事件是为了让控件的用户知道何时发生某些事情而需要的。目前,开发者只提供了一个自定义事件。

#region MyCombo Events
    public delegate void 
      SelectedItemChangedEventHandler(object sender, EventArgs e);
    public event SelectedItemChangedEventHandler 
      OnSelectedItemChangedHandler;
#endregion

当用户从 ListView 中选择任何列表项时,将引发 OnSelectedItemChangedHandler

正如我所承诺的,这是我相应的 get/set 访问器。我的用户将看到这些属性,而不是我们上面看到的变量。

    public string DisplayMember
    {
        get 
        {
            return _displayMember;
        }
        set 
        {
            _displayMember = value;
        }
 
    }

    public string ValueMember
    {
        get 
        {
            return _valueMember;
        }
        set 
        {
            _valueMember = value;
        }
    }

    public string DataMember
    {
        get 
        {
            return _dataMember;
        }
        set 
        {
            _dataMember = value;
        }
    }

    public object DataSource
    {
        get 
        {
            return _dataSource;
        }
        set 
        {
            _dataSource = value;
            OnDataBind();
        }
    }

    public object SelectedText
    {
        get 
        {
            return _selectedText;
        }
        set 
        {
            _selectedText=value;
        }
    }

    public object SelectedValue
    {     
        get 
        {
            return _selectedValue;
        }
        set 
        {
            _selectedValue = value;
        }
    }

DataSource 属性中,您会看到一个 OnDataBind() 方法。这将在我们继续前进时进行解释。

现在,应该有一个机制,让控件的用户能够分配列给我。这由以下函数处理。该函数接受两个参数,列名和列的宽度。此函数仅将列名添加到我们上面看到的 ArrayList 中。

    public void Columns(string columnName, int colWidth)
    {
        _columnHeader.Add(columnName);
        OnHeaderBind(columnName,colWidth);
    }

现在,让我们看看 OnHeaderBind() 函数。这个函数将 columnName 添加到实际的 ListView 对象中,并设置其宽度。

    protected void OnHeaderBind(object v, int colWidth)
    {
        lvObject.Columns.Add(v.ToString(), colWidth, HorizontalAlignment.Left);
    }

还应该提供一种手动添加数据给我的方法,而不是使用数据源和数据成员。这可以通过以下函数实现。该函数接受一个 ID 和一个要添加到 ListView 的项目数组。所以,我的用户可以像这样添加一个项目给我:

<instance of me>.Add (“101”,”Rajesh”,”23)
    public void Add(string id, params string [] items)
    {
        ListViewItem lvi = new ListViewItem(id);

        foreach(string s in items)
        {
            lvi.SubItems.Add(s);
        }
        lvObject.Items.Add(lvi);
    }

现在,正如之前所承诺的,让我们来谈谈 OnDataBind() 方法。每当您将数据源分配给我时,都会调用此方法。此方法只会用数据源中的信息填充 ListView

    protected void OnDataBind()
    {
        if (_dataSource == null)
            return;
        IList iList = InnerDataSource();

        Type  type = iList.GetType();

        string s;

        for (int i = 0; i < iList.Count ; i++)
        {
            s=  GetField(RuntimeHelpers.GetObjectValue(iList[i]),_valueMember);
            ListViewItem lvi = new ListViewItem(s);
            for (int j = 1; j < _columnHeader.Count; j++)
            {
              lvi.SubItems.Add(GetField(RuntimeHelpers.GetObjectValue(iList[i]), 
                                                  _columnHeader[j].ToString()));
            }

            lvObject.Items.Add(lvi);
        }
    }

RuntimeHelpers.GetObjectValue(obj) 做什么?如果 obj 是值类型,它会返回 obj 的装箱副本;否则,它会返回 obj 本身。

InnerDataSource() 函数返回与数据源关联的 IList 对象。稍后,我们可以枚举 IList 对象来获取它包含的每个列表值。

    private IList InnerDataSource()
    {
        IList iList;

        if (_dataSource is DataSet)
        {
            if (_dataMember.Length > 0)
            {
                iList = ((IListSource)
                        ((DataSet)_dataSource).Tables[_dataMember]).GetList();
            }
            else
            {
                iList = ((IListSource)((DataSet)_dataSource).Tables[0]).GetList();
            }
        }
        else if (_dataSource is IListSource)
        {
            iList = ((IListSource)_dataSource).GetList();
        }
        else if (_dataSource is ICollection)
        {
            object[] objs = new object[((ICollection)_dataSource).Count];
            ((ICollection)_dataSource).CopyTo(objs,0);
            iList = objs;
        }
        else
        {
            iList = (IList)_dataSource;
        }
        return iList;
}
  • IListSource:此接口提供对象返回可绑定到数据源的列表的功能。
  • ICollection:此接口定义所有集合的大小、枚举器和同步方法。

编译我,添加一个测试项目并引用我,我就准备好面对世界了。

现在就到这里了。有很多事情需要在我身上完成。也许,那会在以后。

我希望您喜欢阅读我。任何建议、批评、想法都应该直接发送给我的创造者,以便他可以改进我或多学一点。

有关完整的源代码,请查看本文随附的示例项目。

暂别,

ComboView

致谢

InnerDataSource 及相关函数 -> 这些函数是从 The Code Project/或互联网上的某个地方获取的。如果有人知道原作者,请给我留言,我很乐意给他署名。

修订历史

  • 2003-12-28:原文。
© . All rights reserved.