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

ScrollSelector

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.10/5 (8投票s)

2009 年 8 月 31 日

CPOL

5分钟阅读

viewsIcon

22206

downloadIcon

310

一个带有设计时功能的动画、可滚动“TabControl”。

引言

在我正在进行的一个项目中,我需要为用户提供一些高级搜索选项。这些选项被分组,一次只能使用一个组。在本篇文章中,我将其简化为在三个不同的流行搜索引擎之间进行选择。我最初的想法是使用分组框,如下所示:

Suggestion 1 - using group boxes

或者使用选项卡控件,如下所示:

Suggestion 2 - using 'normal' TabControl

在我看来,选项卡控件是最有趣的解决方案,因为它一次只显示较少的控件,从而呈现更“简洁”的外观。但是,我想要比这更精致的呈现方式。

因此,我提出了 ScrollSelector 的概念,它基本上是一个带有不同面板(滚动面板 - 而不是选项卡页面)的 TabControl。您可以通过单击向上/向下按钮在面板之间滚动。为了完善它,我还为控件添加了一个标题。最终的控件看起来像这样(顶部部分,其余部分是普通的 WebBrowser 控件):

The ScrollSelector control

背景

我以前实现过很多控件,最初我对自己说:“我可以在几分钟内搞定这个控件”——错了!我忽略了一件非常重要的事情:与“普通”控件不同,您应该能够在设计时切换面板——这样您就可以在不同的面板中放置您想要的控件。

看看 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:有时设计器会错误地绘制控件。标题标签停靠在控件的顶部,因此应该始终填满控件的整个长度。包含按钮的面板停靠在右侧,因此应该始终位于右侧。

但在我的演示应用程序中,我将控件停靠在窗体的顶部。大多数时候看起来还可以,但有时我会看到这个:

The control is painted wrong

因为控件停靠在窗体的顶部,标题标签停靠在控件的顶部,所以标签的宽度应该与控件本身相同。您可以看到控件具有正确的宽度(或者至少按钮面板具有正确的宽度),但标题标签具有默认大小——而不是经过调整大小的控件应该强加给它的尺寸。

而且,按钮相对于控件左侧的位置是默认位置——而不是停靠在右侧。

我不明白这一点——这一定是 .NET 处理 Dock 属性的一个错误。

问题 2:当通过设计器中的向上/向下按钮更改面板时,属性网格中相应的 SelectedIndex 属性不会更改——尽管包含索引的变量已由设计器调用的方法更新。

不过,我认为这是一个小问题,目前就先这样吧……

改进建议

我想将标题属性合并为一个具有适当子属性的单一属性(就像 Font 属性一样),并创建了一个 Header 类来保存这些属性。但是,无论我怎么做,我都会在新 Header 属性中遇到序列化问题,所以最后我放弃了,而是将子属性作为独立的属性放在控件中。这可以工作,但不如我想要的解决方案那么好。等我有更多时间时,我会再次研究它……

历史

  • 版本 1.0 (2009 年 8 月 31 日) - 初始发布。
© . All rights reserved.