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






4.95/5 (47投票s)
包含几个使用Windows主题的组合框,
自定义组合框的预览
引言
在开发 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 或尚未实现的功能
在只读模式下,并非所有键盘功能都能正常工作。(上下箭头不起作用)。