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

具有高级下拉功能的自定义组合框

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (47投票s)

2003年4月23日

CPOL

4分钟阅读

viewsIcon

321237

downloadIcon

12099

包含几个使用Windows主题的组合框,以及带有复选框列表和树状视图的组合框。

自定义组合框的预览

CheckComboBox control
DateComboBox control
TreeComboBox control

引言

在开发 Windows Forms 应用程序时,我发现 .NET 控件不能满足我的需求。经过两天和许多 bug 修复后,我制作了令人满意的控件。我认为这可以帮助其他人。我的控件有时使用 Carlos H. Perez 编写的 UtilityLibrary。感谢他出色的工作(有时会发现一些 bug,但这个库给了我一个很好的范例,教我如何创建自己的控件)。

代码基础

作为基础控件,我使用了 System.Windows.Forms.Control 类。作为子控件,我使用了 TextBox 控件。它只在用户将控件设置为编辑模式时被 Combo 使用。代码结构足够简单,并且我将其用于所有控件。所有属性都采用相同的模板制作

  // pseudo code
  public datatype PropertyName
  {
    get
    {
      return internalVariable;
    }
    set
    {
      if( value != internalVariable )
      {
        internalVariable = value;
        On<PropertyName>Changed();
      }
    }
  }

正如你所见,属性总是在内部变量中,我有时会将它们用作用户数据的缓存。控件属性实现中的主要之处在于,我总是检查它是否是一个新值,或者说与旧值不同。在许多情况下,这有助于我们停止事件引发的递归。
实现中的第二点是,在设置新值后,我总是调用 On<PropertyName>Changed();。在这些方法中,我总是实现控件的逻辑,并检查控件应该如何使自身无效。

  // pseudo code
  protected virtual void On<PropertyName>Changed()
  {
     // ... business logic of control ...
     
     Raise<PropertyName>ChangedEvent();
  }
  
  // event raiser
  private void Raise<PropertyName>ChangedEvent()
  {
    if( <PropertyName>Changed != null )
    {
      <PropertyName>Changed( this, EventArgs.Empty );
    }
  }

为什么当属性值改变时我总是引发事件?在阅读了 .NET 247 网站上关于 .NET 控件数据绑定功能的许多文章后,我决定始终实现属性更改事件。当您尝试在应用程序中使用数据绑定时,它们非常有帮助,并为开发人员提供了在实现任何控件逻辑方面的灵活性。数据绑定可能需要花费大量时间,因此本文将不讨论。

正如你所见,我的控件总是使用这样的模板来实现属性逻辑。

代码

所有组合框都继承自基类 CustomCombo,该基类实现在 UtilityLibrary\Combos\CustomComboBox.cs 文件中。基类是抽象的,所有继承者都必须实现这些方法:

protected abstract void OnPrevScrollItems();
protected abstract void OnNextScrollItems();
protected abstract void OnDropDownControlBinding( EventArgsBindDropDownControl e );
protected abstract void OnValueChanged();

描述
OnPrevScrollItems - 命令控件将组合框的值更改为上一个。
OnNextScrollItems - 命令控件将组合框的值更改为下一个。
OnDropDownControlBinding - 特殊方法,用于继承者将自己的下拉控件附加到组合框。OnValueChanged() - 用于检查组合框值是否正确的有效方法。此方法由 Value 属性代码调用。

控件还有一些可以被继承者覆盖的附加方法:

protected virtual bool OnValueValidate( string value );
protected virtual void OnDropDownSizeChanged();
protected virtual void OnDropDownFormLocation();

描述
OnValueValidate - 方法对传入的值进行验证。
OnDropDownSizeChanged - 方法计算下拉窗体的大小。
OnDropDownFormLocation - 方法计算下拉窗体必须显示的位置。

抽象类还提供了一些辅助方法,用户可以使用它们来快速填充组合框。

public void BeginUpdate();
public void EndUpdate();

描述
BeginUpdate - 控件会跳过类中的所有无效化代码
EndUpdate - 控件在更改后开始使自身无效

如何实现自己的组合框

第一步

组合框被设计为支持两种模式:只读模式和可编辑模式。在只读模式下,控件自行绘制数据,在可编辑模式下,所有数据绘制都由内部 TextBox 控件完成。控件的状态可以通过 Readonly 属性进行控制。

所以,首先选择您想实现的组合框类型。我总是尝试实现控件的两种状态。

第二步

第二步的实现是选择哪个控件必须在下拉窗体中使用。为此,类使用 OnDropDownControlBinding 抽象方法。

  protected override void OnDropDownControlBinding( CustomCombo.EventArgsBindDropDownControl e )
  {
    e.BindedControl = m_tree; // m_tree is a TreeView control
    m_tree.ImageList = m_imgList;
    RaiseFillTreeByData( e );
    
    // in case when we do data load on scroll message then 
    m_ctrlBinded = m_tree;
    m_bControlBinded = true;
  }

第三步

第三步的实现是可选的,可以跳过。只有当您想进行自己的组合框值自定义绘制时才需要。

  protected virtual void OnItemSizeCalculate( object sender, CustomCombo.EventArgsEditCustomSize e )
  {
    if( m_imgList != null )
    {
      int iWidth = m_imgList.ImageSize.Width + 2;
      e.xPos  += iWidth;
      e.Width -= iWidth;
    }
  }

  protected override void OnPaintCustomData(System.Windows.Forms.PaintEventArgs pevent)
  {
    Graphics g = pevent.Graphics;
    Rectangle rc = pevent.ClipRectangle;

    if( m_tree.SelectedNode != null && m_imgList != null )
    {
      Rectangle rcOut = new Rectangle( rc.X + 2, rc.Y+2, m_imgList.ImageSize.Width, rc.Height - 4 );
      int index = m_tree.SelectedNode.ImageIndex;
      
      if( m_imgList.Images.Count > index && m_imgList.Images.Count > 0 )
      {
        if( index < 0 ) index = 0;
        Image img = m_imgList.Images[ index ];
        g.DrawImage( img, rcOut );
      }
    }
  }

第一个方法计算开发者想要绘制的区域以及不绘制的区域。第二个方法是绘制方法。CustomCombo 给您一个覆盖并为您控件的所有项实现自己的绘制的机会。绘制方法逻辑上可以分为背景绘制方法和项绘制方法。在大多数情况下,不需要覆盖背景绘制,但您可以这样做。

数据流

当用户在组合框中键入任何文本时,我们首先检查数据是旧的还是新的,然后通过调用 OnValueValidate 方法来验证该值,如果返回 true,则调用 OnValueChanged 方法。

一些特性

在我实现控件时,我使用以下技术:按需加载数据...您可以在 TreeCombo 类实现中找到此功能。

已知 bug 或尚未实现的功能

在只读模式下,并非所有键盘功能都能正常工作。(上下箭头不起作用)。

© . All rights reserved.