ScrollSelector
一个带有设计时功能的动画、可滚动“TabControl”。
引言
在我正在进行的一个项目中,我需要为用户提供一些高级搜索选项。这些选项被分组,一次只能使用一个组。在本篇文章中,我将其简化为在三个不同的流行搜索引擎之间进行选择。我最初的想法是使用分组框,如下所示:
或者使用选项卡控件,如下所示:
在我看来,选项卡控件是最有趣的解决方案,因为它一次只显示较少的控件,从而呈现更“简洁”的外观。但是,我想要比这更精致的呈现方式。
因此,我提出了 ScrollSelector
的概念,它基本上是一个带有不同面板(滚动面板 - 而不是选项卡页面)的 TabControl
。您可以通过单击向上/向下按钮在面板之间滚动。为了完善它,我还为控件添加了一个标题。最终的控件看起来像这样(顶部部分,其余部分是普通的 WebBrowser
控件):
背景
我以前实现过很多控件,最初我对自己说:“我可以在几分钟内搞定这个控件”——错了!我忽略了一件非常重要的事情:与“普通”控件不同,您应该能够在设计时切换面板——这样您就可以在不同的面板中放置您想要的控件。
看看 TabControl
:当您将其放在窗体上时,您可以在设计器中与之交互,并通过单击选项卡在 TabPage
之间切换。当您创建一个复合控件(通常是 UserControl)并将其放在窗体上时,它只会显示 User Control 的静态图像。当然,您可以更改属性网格中的属性来更改外观,但如果您在 User Control 中有一个按钮,您就无法在设计器中响应按钮单击事件。
实现设计时交互
我首先要做的就是更改设计器,使控件充当其他控件的容器。像这样:
<Designer(GetType(ParentControlDesigner))> _
Public Class ScrollSelector
...
End Class
我不确定,但我想这在 .NET 之前要容易得多……据我回忆(那已经是很久以前的事了),以前您只需设置一个属性,然后就搞定了!——控件就是一个容器控件……
我提供了一个 SelectedIndex
属性,通过该属性,您可以在设计时更改属性网格中的选定面板。这可以工作,但我并不满意。我希望能够像 TabControl
一样,在设计器中单击向上/向下按钮并切换面板。
因此,我到处查找关于如何设计时与控件交互的信息,经过大量搜索,我发现必须提供一个特殊的设计器。所以我必须创建一个 Designer
类(ScrollDesigner
),我从 ParentControlDesigner
继承了它。
#Region "Imports"
Imports System.ComponentModel
Imports System.ComponentModel.Design
Imports System.Windows.Forms
Imports System.Windows.Forms.Design
'Imports System.Runtime.InteropServices
#End Region
Public Class ScrollDesigner
Inherits ParentControlDesigner
#Region "Private Constant Declarations"
'All constants and calls through API can be found in a program
'called API-Viewer. I strongly suggest the use of the program
'when programming on the Windows Platform.
Private Const WM_LBUTTONUP As Integer = &H202
Private Const WM_PAINT As Integer = &HF
Private Const WM_LBUTTONDOWN As Integer = &H201
Private Const WM_MOUSEMOVE As Integer = &H200
Private Const WM_MOVING As Integer = &H216
#End Region
Private m_MouseOver As Boolean = False
Protected Overrides Sub OnMouseEnter()
m_MouseOver = True
MyBase.OnMouseEnter()
End Sub
Protected Overrides Sub OnMouseLeave()
m_MouseOver = False
MyBase.OnMouseLeave()
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m_MouseOver Then
Select Case m.Msg
Case WM_LBUTTONDOWN
Dim cur As Point = Cursor.Position
Dim hitPoint As Point
Dim meAsControl As ScrollSelector = CType(Me.Control, ScrollSelector)
Dim bHitUp As Boolean = False
Dim bHitDown As Boolean = False
hitPoint = Me.Control.PointToClient(New Point(cur.X, cur.Y))
If hitPoint.X > (meAsControl.Width - meAsControl.pnlButtons.Width) _
And hitPoint.X < meAsControl.Width Then
If meAsControl.HeaderAlignment = ScrollHeaderAlignment.Bottom Or _
meAsControl.lblHeader.Visible = False Then
bHitUp = (hitPoint.Y < meAsControl.pnlButtons2.Height \ 2)
bHitDown = _
((hitPoint.Y < meAsControl.pnlButtons2.Height) And Not bHitUp)
Else
If hitPoint.Y > meAsControl.lblHeader.Height Then
bHitUp = (hitPoint.Y < ((meAsControl.pnlButtons2.Height \ 2) + _
meAsControl.lblHeader.Height))
bHitDown = ((hitPoint.Y < (meAsControl.pnlButtons2.Height + _
meAsControl.lblHeader.Height)) And Not bHitUp)
End If
End If
End If
If bHitUp Then meAsControl.ScrollUp()
If bHitDown Then meAsControl.ScrollDown()
Case Else
'Ignore
End Select
End If
MyBase.WndProc(m)
End Sub
End Class
然后,我像以前一样将其附加到控件上:
<Designer(GetType(ScrollDesigner))> _
Public Class ScrollSelector
...
End Class
这奏效了。基本上,我重写了 WndProc
方法来拦截控件上的鼠标点击。然后,我确定鼠标点击是否在按钮上,如果是,我调用控件中的相应方法。
控件的怪癖
就目前而言,控件还有一些可以改进的地方:
问题 1:有时设计器会错误地绘制控件。标题标签停靠在控件的顶部,因此应该始终填满控件的整个长度。包含按钮的面板停靠在右侧,因此应该始终位于右侧。
但在我的演示应用程序中,我将控件停靠在窗体的顶部。大多数时候看起来还可以,但有时我会看到这个:
因为控件停靠在窗体的顶部,标题标签停靠在控件的顶部,所以标签的宽度应该与控件本身相同。您可以看到控件具有正确的宽度(或者至少按钮面板具有正确的宽度),但标题标签具有默认大小——而不是经过调整大小的控件应该强加给它的尺寸。
而且,按钮相对于控件左侧的位置是默认位置——而不是停靠在右侧。
我不明白这一点——这一定是 .NET 处理 Dock
属性的一个错误。
问题 2:当通过设计器中的向上/向下按钮更改面板时,属性网格中相应的 SelectedIndex
属性不会更改——尽管包含索引的变量已由设计器调用的方法更新。
不过,我认为这是一个小问题,目前就先这样吧……
改进建议
我想将标题属性合并为一个具有适当子属性的单一属性(就像 Font
属性一样),并创建了一个 Header
类来保存这些属性。但是,无论我怎么做,我都会在新 Header
属性中遇到序列化问题,所以最后我放弃了,而是将子属性作为独立的属性放在控件中。这可以工作,但不如我想要的解决方案那么好。等我有更多时间时,我会再次研究它……
历史
- 版本 1.0 (2009 年 8 月 31 日) - 初始发布。