Sie sind auf Seite 1von 13

Creating Smart Application Layouts with Windows Forms 2.

0
Visual Studio 2005

https://msdn.microsoft.com/en-us/library/aa730847%28v=vs.80%29.aspx?
f=255&MSPPError=-2147217396

Applies to:
Microsoft Visual Studio 2005
Microsoft .NET Framework 2.0
Microsoft Windows Forms 2.0

Summary: Learn how to use new controls in Windows Forms 2.0 to create smart and
extensible application layouts. (15 printed pages)

Download code samples in C# and Visual Basic (903 KB) at the Microsoft Download
Center.

Contents
Introduction
The Limits of TabControl
Tab-Style Tool Strip
Outlook-Style Tool Strip
Collapsible Menus
Flyout Panel
Conclusion

Introduction
Microsoft Windows Forms 2.0 allows you to organize the functionality of your
application in unique ways that are easy for your customers to use. Using new
controls, such as ToolStrip, FlowLayoutPanel, and TableLayoutPanel, you can create
smart and extensible application layouts. This article describes four application
layouts: a tab-style tool strip, an Outlook-style tool strip, collapsible menus,
and a flyout panel. All of these layouts are simple to create with Windows Forms
2.0. This article assumes you know the basics of Windows Forms and are familiar
with UserControls.

The Limits of TabControl


Recently, I wanted to build a simple application that served as a countdown timer
for various activities, such as exercising or meditating. The core functionality
would be a timer that would play a sound repeatedly after the set time (say, 30
minutes) had expired. I knew that this simple application would have three major
sections: A Home panel that displays at start up, a Countdown panel on which you
set and run the countdown clock, and a Settings panel on which you set a custom
sound and check whether you want to keep a record of your sessions.

I decided that the best way to go about this was a tab-based approach to
navigation. I defined the major areas of functionality on three different tabs. At
first, the TabControl, which you get for free with Windows Forms, seemed like a
natural choice. But then came the catch. I didn't want this application to look
like a normal Windows application, with the default gray color schemes. And I
didn't want to use top-aligned tabs, which are the default. I wanted the tabs to
stretch down the right side of the TabControl. I wanted everything to be white,
without distracting borders, and I wanted the three tabs to stretch from the top to
the bottom of the application.

I soon discovered that the TabControl wouldn't do what I wanted. While TabControl
supported right-aligning and left-aligning tabs, by default it would render the
text vertically instead of horizontally. I found that if Visual Styles were
enabled, no text was rendered in the side-aligned tabs at all!
I found a way to get close to the effect that I wanted by using owner-draw. (For
details, see my Window Forms TabControl: Using Right-Aligned or Left-Aligned Tabs
post on the Windows Forms Documentation Updates blog.) However, there were other
obstacles that prevented me from using TabControl. In particular, owner-draw could
only redraw the contents of the tabs. I could not customize or eliminate altogether
the tab borders, either on the tabs or surrounding the tab panels. This limited the
customization I could accomplish. To top it all off, the controls on the tabs
themselves are sometimes not laid out correctly when you apply Visual Styles. Given
all these problems and the lack of customizability, I was no longer jazzed about
using TabControl.

Tab-Style Tool Strip


I took a step back and realized that there was another, more inventive way to
approach this problem. I remembered from my past tinkering that the new ToolStrip
control in Windows Forms was very flexible. Could I use it to create exactly the
look and feel I was aiming for?

As it turned out, I could�and with only a minimum of programming. Figure 1 shows


the finished result.

Aa730847.laywf201(en-US,VS.80).gif

Figure 1. The completed application, which uses a tab-style tool strip.

Here's how I created the application. The main form is split into two parts. On the
left side is a custom UserControl I wrote called Slideshow (included with the
Countdown sample). This is just a "showy" control that uses the managed WebBrowser
wrapper control to fade between a series of images using Dynamic HTML.

What's interesting for this article is the right side, on which I positioned a
SplitContainer control. SplitContainer is also a new control in Windows Forms. It
replaces the old Splitter control, providing greater functionality for the number
and directionality of split panels. SplitContainer has two split panels, Panel1 and
Panel2. Panel1 is the content panel that will contain various controls for each
button. Panel2 will include buttons that will allow navigation between the three
panels: Home, Countdown, and Settings. Panel2 will host the ToolStrip control. For
my application, I kept the Orientation property on SplitContainer set to Vertical
(the default), and resized the panels in Microsoft Visual Studio to make the left
one larger than the right one. Figure 2 shows the Countdown application prior to
adding the ToolStrip control to Panel2.

Aa730847.laywf202(en-US,VS.80).gif

Figure 2. The Countdown application with a custom Slideshow UserControl and a


SplitContainer.

After adding the custom Slideshow UserControl and SplitContainer, I added a


ToolStrip control to Panel2. By default, the Dock property of ToolStrip is set to
Top. This makes sense; the default for most applications is to use ToolStrip to
create command toolbars, like you find in Microsoft Word and other applications.
But for my purposes, I needed to set the Dock property to Fill. I also didn't want
the slick Office-like rendering that ToolStrip uses by default, so I set RenderMode
to System and BackColor to White. I set GripStyle to Hidden, so that the grip used
to grab the ToolStrip and relocate it to another area of the screen wasn't visible.
Finally, since I wanted the navigation buttons to flow from top to bottom, one on
top of the other, I changed the value of LayoutStyle to VerticalStackWithOverflow.

Next came the actual navigation buttons. Since I would have three panels, I created
three buttons. I used the Items Collection Editor in the visual designer to create
three ToolStripButton objects in the ToolStrip. To open the Items Collection
Editor, I clicked the ellipsis button next to the Items property for the ToolStrip.
(To open the Items Collection Editor, I could have also clicked the ToolStrip's
smart tag and in the ToolStrip Tasks menu, clicked Edit Items.)

Next, I needed to stylize the buttons' look and feel. I created an image for each
button with stock photography, and resized the images to the same height and width.
I clicked the ellipsis button next to the Image property for each ToolStripButton
and used the Select Resource dialog box to import the image for each button. By
default, ToolStripButton will scale the image down to the default size of the
button. Since I wanted the image at its actual size, I set the ImageScaling
property on each button to None.

I entered the appropriate text in the Text property of each ToolStripButton. In


order to get the text to appear below the image, I had to change two properties.
First, I had to set the DisplayStyle property to ImageAndText. This made the text
appear to the center right of the image. To put the text underneath the image, I
set the TextImageRelation property to ImageAboveText. I then changed the font of
ToolStripButton so that the text rendered to my liking.

For my last magic trick, I decided that I wanted the buttons to appear "checked"
when they were clicked, so that the user had a quick visual indicator as to which
panel was currently active. (The checked effect appears as a border around the
second button in Figure 1.) To accomplish this, I set the CheckOnClick property for
each button to True.

Making the Tool Strip Tabs Functional


This was the limit of what I could do with the designer. As you can see, I
accomplished a lot without writing a lick of code. Now, it was time to design the
individual panels and hook everything together. For my tab-style tool strip, I
sought to implement the following logic:

Display the Home panel by default when the application starts.


Detect when a ToolStrip button is clicked. If the corresponding panel for that
button is not already displayed, create and display it.
Uncheck the previously checked button.
For each of the panels, I created a separate UserControl: one for the Home panel,
one for the main Countdown panel, and one for the Settings panel. I named these
panels CDHomePanel, CDCountdownPanel, and CDSettingsPanel, respectively.

Once I had implemented the bare shell of these UserControls, I set about writing
the logic to display them in Panel1 of the SplitContainer. Since I only had three
panels, I could have hard-coded a separate event handler for each button that
instantiated and displayed the appropriate panel. However, that isn't a very
extensible approach. It's fine for three buttons�but what if I eventually have six
buttons? What if I want to use this same approach in a larger application that
requires 10 or 20 buttons? I decided to write a single event handler to handle all
of the buttons without hard-coding any values. I wanted the code to be abstract
enough that, in the future, I could add another button and another panel with
minimal changes to the ToolStripButton event handler code.

First, I went back to the designer and, for each ToolStripButton, set the Tag
property to the name of the UserControl, prefixed with its namespace, to which the
button corresponds. I did this so that my event handler could use the Tag property
of the button that was clicked to deduce which UserControl it should instantiate.
For example, for my CDHomePanel UserControl, which exists in the namespace
Countdown, the fully qualified name is Countdown.CDHomePanel. I assigned
"Countdown.CDHomePanel" to the Tag property of the Home button. Similarly, I set
the Tag property of the Countdown button to "Countdown.CDCountdownPanel", and the
Tag property of the Settings button to "Countdown.CDSettingsPanel".

Next, I added some code that my form would require for the event handler. Since I
was going to instantiate the UserControl panels dynamically, I would need to use
classes defined in the System.Reflection and System.Runtime.Remoting namespaces. I
also defined two class-level private variables my event handler would need:
_CurrentControl, a reference to the panel currently visible to the user; and
_CurrentClickedButton, a reference to the ToolStripButton corresponding to the
visible panel.

Imports System.Threading
Imports System.Drawing.Drawing2D
Imports System.Reflection
Imports System.Runtime.Remoting

Public Class Form1


Private _CurrentControl As Control
Private _CurrentClickedButton As ToolStripButton = Nothing
Next, I added code to the Load event handler of my form to instantiate the Home
panel, display it to the user, and initialize the _CurrentControl and
_CurrentClickedButton variables with valid object references. Since I only have to
do this once in the lifetime of the application, I directly created an instance of
CDHomePanel. Once the panel was instantiated, I added it to Panel1 of my
SplitContainer.

Dim HomePanel As New CDHomePanel()


HomePanel.Tag = "Countdown.CDHomePanel"
HomePanel.Dock = DockStyle.Fill
SplitContainer1.Panel1.Controls.Add(HomePanel)
_CurrentControl = HomePanel
_CurrentClickedButton = HomeButton
Finally, I wrote a method called Navigate, which is designed to be called from the
MouseDown event of each ToolStripButton. (I'll explain later in the section
Incorporating Prompts when Switching Panels why I factored this code out into a
separate method.) Since I was authoring in Visual Basic, I could use the Handles
keyword to specify that this event handler applied to all three buttons. The
handler looks at the Controls collection of Panel1 of my SplitContainer, and sees
if the UserControl corresponding to this button has been created yet. If not, it
creates it by passing the Tag property of the ToolStripButton to the CreateInstance
method, which is defined on the AppDomain class.

Private Sub Navigate(ByVal sender As Object)


Dim _NewControl As Control
Dim _CurrentButton As ToolStripButton = CType(sender, ToolStripButton)
Dim _ControlName As String = _CurrentButton.Tag.ToString()

' Uncheck the previous clicked button.


_CurrentClickedButton.Checked = False
_CurrentClickedButton = _CurrentButton

' First, make sure this isn't a redundant event - are we already
' displaying the control?
If (Not (_ControlName = _CurrentControl.Name)) Then
' Get the control to use, and instantiate it dynamically
' if it isn't already defined.
_NewControl = SplitContainer1.Panel1.Controls(_ControlName)
If (_NewControl Is Nothing) Then
' Control not found - instantiate it.
Dim _Oh As ObjectHandle = _
AppDomain.CurrentDomain.CreateInstance( _
Assembly.GetExecutingAssembly().FullName, _ControlName)
_NewControl = _Oh.Unwrap()
_NewControl.Name = _ControlName
_NewControl.Dock = DockStyle.Fill
SplitContainer1.Panel1.Controls.Add(_NewControl)
End If

' Hide the old control, and show the new one.
_CurrentControl.Visible = False
_NewControl.Visible = True
_CurrentControl = _NewControl
End If
End Sub
With that, I was finished, and could compile and run the application successfully.
Given the way I wrote the code, it would be easy to add a new button and panel in
four steps.

Author the UserControl.


Create the ToolStripButton.
Assign the name of the UserControl to the Tag property.
Add the ToolStripButton to the Handles clause of the event handler.
As you can see, the most difficult step here is creating the new UserControl. Once
that is done, all that is required is to configure the button as I did earlier and
modify one line of code. You now have a replacement for the TabControl that is easy
to extend and more open to customization.

Incorporating Prompts when Switching Panels


Once I finished all of this, some questions occurred to me. What happens when users
click the Settings button when they are on the Countdown panel and the clock is
running? And what happens if they are on the Settings panel and click the Countdown
button before saving their settings? Should I keep the changes? Throw them away?
Ask the user?

It became clear that the UserControl panels needed a mechanism by which their
parent form could inform the UserControl panels that a navigation was about to
occur. Likewise, the panels each needed a way to either allow or forbid such
navigation, based on the panel's state. To achieve this, I defined an interface
called IPanelNavigating, and declared a single method named CanNavigate for classes
to implement. The CanNavigate method returns a Boolean value that indicates whether
the panel can be switched.

Public Interface IPanelNavigating


Function CanNavigate() As Boolean
End Interface
Since I would never have a need to stop navigation on the Home panel, I didn't
implement it for this class. I implemented it for my Countdown panel, and use a
message box to ask users if they want to end the countdown. If they do, I reset the
timer and return True from CanNavigate. If they do not wish to end the session, I
return False.

To implement this in my main form, I added some logic to the MouseDown event
handler I defined for all three ToolStripButton objects.

Private Sub ToolStripButton_MouseDown(ByVal sender As System.Object, _


ByVal e As MouseEventArgs) Handles HomeButton.MouseDown, _
CountdownButton.MouseDown, SettingsButton.MouseDown
' Attempt to cast to an IPanelNavigating. If not implemented,
' just navigate.
If (TypeOf _CurrentControl Is IPanelNavigating) Then
Dim _CanNavigate As IPanelNavigating = _
CType(_CurrentControl, IPanelNavigating)
If (_CanNavigate.CanNavigate()) Then
Navigate(sender)
End If
Else
' Just navigate.
Navigate(sender)
End If
End Sub
I finished up the application by fleshing out the programming of the UserControl
panels, and adding support for a custom title bar, which provided me another unique
use of the ToolStrip control. (For details on how to implement this, see my Using
ToolStrip to Create a Custom Title Bar post on the Windows Forms Documentation
Updates blog.)

Outlook-Style Tool Strip


The great thing about the Countdown application architecture is that it is easy to
reuse in other applications. Thanks to the power of the ToolStrip control, you can
customize the look and feel radically to fit your application's needs. Figure 3
shows another example, a mock-up of a banking application. Here the navigation has
an Outlook-style tool strip.

Aa730847.laywf203(en-US,VS.80).gif

Figure 3. A simple mock layout that has an Outlook-style tool strip.

For this application, I placed the ToolStrip on the left, and left the right panel
of the SplitContainer available for the UserControl panels. I formatted the
ToolStripButton objects by setting DisplayStyle to ImageAndText, setting ImageAlign
to MiddleCenter, setting TextAlign to MiddleRight, and setting TextImageRelation to
Overlay. All of the logic I wrote for the Countdown application works fine in this
application, and only needs minimal adjustment to work for an arbitrary number of
ToolStripButton controls.

You can extend this concept further by adding buttons with submenus. ToolStrip
supports a control called ToolStripDropDownButton, which allows you to add child
controls that are displayed as a submenu when the user clicks the button. Figure 4
shows the mock banking sample modified so that the Settings button is a
ToolStripDropDownButton with several submenu items denoting the different types of
settings the user can modify.

Aa730847.laywf204(en-US,VS.80).gif

Figure 4. The simple mock layout from Figure 3, with ToolStripDropDownButton


controls that display submenus.

Collapsible Menus
After I had finished my Countdown application, I decided to look at other ways to
implement complex menus and navigation systems using the new controls in Windows
Forms. The first thing that caught my eye as "similar, yet different" was the
Toolbox window in Visual Studio. If you have done any programming in Windows Forms
or ASP.NET with Visual Studio, you're familiar with the way in which the Toolbox
categorizes controls in a series of collapsible menus. Figure 5 shows an example of
the collapsible menus in the Toolbox.

Aa730847.laywf205(en-US,VS.80).gif
Figure 5. Collapsible menus in the Toolbox window of Visual Studio.

In previous versions of Windows Forms, emulating the Toolbox would have involved
adding a lot of code for repositioning the collapsible menus. In Windows Forms, the
task is greatly simplified by the FlowLayoutPanel control. FlowLayoutPanel hosts an
arbitrary number of Windows Forms controls in a sequential flow, one control
positioned after the other. The great thing about FlowLayoutPanel is that it is
dynamic. If you remove or hide a control at run time, the other controls after it
will "collapse" into the space it leaves. This is just what you need to implement
collapsible menus.

I broke the collapsible control itself into two separate controls:

A control named ListHeader to represent the shaded user interface element that
displays the header text and the plus/minus collapsible indicator on the left side.
ListHeader defines an event that it raises whenever the collapsible indicator is
clicked.
A control named CollapsibleControl that combines ListHeader with another arbitrary
control (which I'll call the "content control"). CollapsibleControl contains the
FlowLayoutPanel, which displays an arbitrary number of content sections that can be
collapsed and expanded.
I won't get into the details of how I implemented the ListHeader control;
interested readers can look at the code. The important fact here is that I defined
a ListHeaderStateChanged event to signal to CollapsibleControl whether the content
control should be collapsed or displayed. ListHeaderStateChanged passes a
ListHeaderEventArgs, which defines a State property of type ListHeaderState.
ListHeaderState is an enumeration with two possible values:
ListHeaderState.Expanded or ListHeaderState.Collapsed.

With ListHeader complete, I set about creating CollapsibleControl.


CollapsibleControl is divided into two parts.

First, I created a CollapsibleControlSection class. CollapsibleControlSection


associates a content control with a header name.

Public Class CollapsingControlSection


Inherits Object

Private _SectionName As String = Nothing


Private _Control As Control = Nothing

Public Sub New(ByVal sectionName As String, ByVal control As Control)


_SectionName = sectionName
_Control = control
End Sub

Public Property SectionName() As String


Get
Return _SectionName
End Get
Set(ByVal value As String)
_SectionName = value
End Set
End Property

Public Property SectionControl() As Control


Get
Return _Control
End Get
Set(ByVal value As Control)
_Control = value
End Set
End Property
End Class
The CollapsibleControl class uses the ListHeader control and one or more
CollapsibleControlSection objects to display a set of content controls separated by
headers. When one content control is collapsed, or hidden, the other controls will
fill the space left behind. To pull off this trick, I first opened
CollapsibleControlSection in the Visual Studio designer and added a FlowLayoutPanel
to the control. I set the Dock property of my FlowLayoutPanel to Fill, so that it
occupied the entire area of the control.

Second, I created the CollapsibleControl itself. The logic needed to make this
control work is surprisingly simple. I imported the namespace
System.Collections.Generic so that I could use the generic List(Of T) class to
store CollapsibleControlSection objects. This List is populated by the AddSection
method, which performs the following operations:

Creates a ListHeader for the content control, and adds it and the content control
to the end of the FlowLayoutPanel.
Uses the Tag property of the ListHeader control to associate the content control
with its ListHeader control, so that CollapsibleControl knows which content control
to show and hide.
Adds a handler for the ListHeader control's ListHeaderStateChanged event, so that
it can show or hide the appropriate content panel when the user clicks the
collapsible indicator on ListHeader.
The following is the code for the CollapsibleControl class.

Public Class CollapsibleControl


Dim SectionList As List(Of CollapsibleControlSection)

Public Sub New()


' This call is required by the Windows Form Designer.
InitializeComponent()

SectionList = New List(Of CollapsibleControlSection)()


End Sub

Public Sub AddSection(ByVal NewSection As CollapsibleControlSection)


SectionList.Add(NewSection)
' Add a new row.
Dim Header As New ListHeader()
Header.Text = NewSection.SectionName
Header.Width = Me.Width
AddHandler Header.ListHeaderStateChanged, _
AddressOf Header_ListHeaderStateChanged
FlowLayoutPanel1.Controls.Add(Header)
' Get the position of the control we're going to add,
' so that we can show and hide it when needed.
Header.Tag = FlowLayoutPanel1.Controls.Count

Dim c As Control = NewSection.SectionControl


c.Width = Me.Width
FlowLayoutPanel1.Controls.Add(c)
End Sub
Sub Header_ListHeaderStateChanged(ByVal sender As Object, _
ByVal e As ListHeaderStateChangedEventArgs)
Dim header As ListHeader = CType(sender, ListHeader)
Dim c As Control = FlowLayoutPanel1.Controls(CInt(header.Tag))

If e.State = ListHeaderState.Collapsed Then


c.Hide()
Else
c.Show()
End If
End Sub
End Class
Since I wasn't concerned about making the control designable, I didn't implement a
RemoveSection method, or provide any of the other infrastructure necessary to
enable adding or removing sections in the Visual Studio designer. The control, as
written, must be created and populated using code.

To test this code, I created a UserControl called ToolboxContentControl, which will


serve as my content control. ToolboxContentControl contains a ToolStrip control
that has two ToolStripButton controls configured to look like items in the Visual
Studio Toolbox. (If I were to build a real world application using
CollapsibleControl, I would have a number of content controls, each with a number
of options. For my test application, however, I used four instances of
ToolboxContentControl.) Then I added a CollapsibleControl to my application's main
form class, and a little code to populate it with four sections:

Private Sub Form1_Load(ByVal sender As System.Object, _


ByVal e As System.EventArgs) Handles MyBase.Load
Dim NewSection As New CollapsibleControlSection( _
"Toolbox Controls", New ToolboxContentControl())
Me.CollapsibleControl1.AddSection(NewSection)

Dim NewSection2 As New CollapsibleControlSection( _


"Toolbox Controls 2", New ToolboxContentControl())
Me.CollapsibleControl1.AddSection(NewSection2)

Dim NewSection3 As New CollapsibleControlSection( _


"Toolbox Controls 3", New ToolboxContentControl())
Me.CollapsibleControl1.AddSection(NewSection3)

Dim NewSection4 As New CollapsibleControlSection( _


"Toolbox Controls 4", New ToolboxContentControl())
Me.CollapsibleControl1.AddSection(NewSection4)
End Sub
The result was exactly what I was aiming for; a set of collapsible menus that were
re-positioned when one or more of the menus were collapsed�thanks to the power of
FlowLayoutPanel. Figure 6 shows a sample of the collapsible menus.

Aa730847.laywf206(en-US,VS.80).gif

Figure 6. Collapsible menus sample in action.

The collapsible menu sample imitates the menus in the Visual Studio Toolbox, but
the Toolbox window also has the ability to slide in and out of view. This type of
window is sometimes called a flyout panel.

Flyout Panel
A flyout panel is a panel that slides into view when requested by the user and
slides out of view when not used. The Visual Studio Toolbox window slides into view
when you position the mouse pointer over the Toolbox icon. When the mouse pointer
leaves the vicinity of both the Toolbox icon and the panel, the panel automatically
slides out of view.

You can implement this same behavior in Windows Forms by once again employing our
good friend, the ToolStrip control. For my first step, I created a new UserControl
called ToolboxPanel. I did the same thing for ToolboxPanel that I did for the
collapsible menu test form; I added a CollapsibleControl, and added some code to
populate it with four sections for testing purposes.

Next, I created a new form for my project, and set it as the startup form. Then I
added a ToolStrip, and set the Dock property to Left. I created a single
ToolStripLabel icon, assigned it a Toolbox-like image, and set the Text property to
the word "Toolbox". To make the text display vertically, I set the TextDirection
property of the ToolStrip button to Vertical90.

For my test form, all I wanted to do was show a single flyout panel. However, in
the real world, you would probably want a series of flyout panels, like Visual
Studio uses. I wrote my code in a way that would eventually support multiple flyout
panels (albeit not without further enhancements). Borrowing from a trick I used
earlier with my Countdown application, I set the Tag property of my ToolStripLabel
to "TestPanelFlyoutVB.ToolboxPanel", which is the fully qualified name of the
ToolboxPanel control I created earlier. The fully qualified name is used in the
MouseEnter event of the ToolStripLabel to instantiate the ToolboxPanel control if
it doesn't already exist.

I placed two Timer controls on the form to help with the flyout animation.
PanelTimer is activated when the flyout panel needs to be shown or hidden.
MouseLeaveTimer provides a one-second delay in retracting the panel when the mouse
leaves the ToolStripLabel. This is because our flyout panel logic has to
accommodate two possibilities:

The user places the mouse back over the ToolStripLabel almost immediately, in which
case the flyout panel should remain displayed.
The mouse leaves the ToolStripLabel, but enters the area of the flyout panel, in
which case the flyout panel should remain displayed so that the user can interact
with it. There is an interesting edge case here in which the mouse can be
momentarily positioned over the thin border of the ToolStrip control that resides
between the ToolStripLabel and the flyout panel itself. The one-second delay gives
the user time to finish moving the cursor onto the flyout panel.
With these in place, I wrote the following code to tie everything together.

Imports System.Runtime.Remoting
Imports System.Reflection

Public Class FlyoutForm

Dim _DoFade As Boolean = False


Dim _HaveProcessedMouseEnter As Boolean = False
Dim _CurrentControl As Control = Nothing
Dim _CachedControlPoint As Point = Nothing

Sub ToolBoxLabel_MouseEnter(ByVal sender As Object, _


ByVal e As EventArgs) Handles ToolBoxLabel.MouseEnter
If Not _HaveProcessedMouseEnter Then
_HaveProcessedMouseEnter = True

If (_DoFade) Then
PanelTimer.Stop()
_DoFade = False
ElseIf (_CurrentControl Is Nothing) Then
' Get the control to use, and instantiate it dynamically.
Dim controlName As String = CType(sender, _
ToolStripLabel).Tag.ToString()
Dim oh As ObjectHandle = _
AppDomain.CurrentDomain.CreateInstance( _
Assembly.GetExecutingAssembly().FullName, controlName)
_CurrentControl = CType(oh.Unwrap(), Control)
_CurrentControl.Height = Me.Height
_CurrentControl.Location = New Point( _
toolStrip1.Location.X + toolStrip1.Width - _
_CurrentControl.Width, 0)
_CachedControlPoint = _CurrentControl.Location
Me.Controls.Add(_CurrentControl)

' The following calls make the panel appear above all
' other controls on the form,
' *except* the ToolStrip with the panel buttons.
_CurrentControl.BringToFront()
toolStrip1.BringToFront()
End If

PanelTimer.Start()
End If
End Sub 'toolBoxLabel_MouseEnter

Sub ToolBoxLabel_MouseLeave(ByVal sender As Object, _


ByVal e As EventArgs) Handles ToolBoxLabel.MouseLeave
' Slight problem: We could be transitioning between the ToolStrip
'and the flyout panel. If the
' mouse is in an inbetween state where it's not over the
' ToolStripLabel but *is* over the
' thin wedge of the ToolStrip itself, we'll fade the panel
' erroneously. So let's kick off this
' timer to give the user time to transition. The delay built into
' VS is about 1 second; that
' should work for us.
MouseLeaveTimer.Start()
End Sub

Private Sub PanelTimer_Tick(ByVal sender As Object, _


ByVal e As EventArgs) Handles PanelTimer.Tick
If _DoFade Then
' Hide the panel.
If _CachedControlPoint.X + _CurrentControl.Size.Width > _
toolStrip1.Location.X + toolStrip1.Width Then
_CachedControlPoint.Offset(-20, 0)
_CurrentControl.Location = _CachedControlPoint
Else
PanelTimer.Stop()
_HaveProcessedMouseEnter = False
_DoFade = False
End If
Else
' Show the panel.
If _CachedControlPoint.X < toolStrip1.Location.X + _
toolStrip1.Width Then
_CachedControlPoint.Offset(20, 0)
_CurrentControl.Location = _CachedControlPoint
Else
PanelTimer.Stop()
_HaveProcessedMouseEnter = False
_DoFade = True
End If
End If
End Sub

Private Sub MouseLeaveTimer_Tick(ByVal sender As Object, _


ByVal e As EventArgs) Handles MouseLeaveTimer.Tick
' If we're over the flyout panel, don't trigger the leave event.
Dim controlUnderMouse As Control = _
Me.GetChildAtPoint(Me.PointToClient( _
System.Windows.Forms.Cursor.Position))

' This may get us whatever child control is under the ToolStrip.
' We need to inspect
' parent controls until we reach the Form. If we don't find a
' control that matches
' _CurrentControl in the parenting chain, we fade.
Dim overPanel As Boolean = False
While controlUnderMouse IsNot Me
' If we're over the blank form area, controlOverMouse
' will be null.
If controlUnderMouse Is Nothing Then
overPanel = False
Exit While
End If

If controlUnderMouse Is _CurrentControl Then


overPanel = True
Exit While
End If

controlUnderMouse = controlUnderMouse.Parent
End While
If Not overPanel Then
' Since the mouse must leave the panel area SOMETIME,
' keep checking until we've left.
MouseLeaveTimer.Stop()
PanelTimer.Start()
End If
End Sub
End Class
With this code incorporated, my flyout panel sample worked smoothly. Figure 7 shows
the flyout panel in full view.

Aa730847.laywf207(en-US,VS.80).gif

Figure 7. Flyout panel sample in action.

Note The sample application runs slower under the Visual Studio debugger. In
particular, the flyout panel slows down to about half its expected speed. The
sample runs as expected when it is executed outside of the debugger.
There is obviously more work that can be done here in terms of stylizing the flyout
panel, incorporating multiple panels, and implementing such features as pinning
(where the flyout panel remains visible instead of flying in and out). Also, in
order to support adding other flyout panel tabs, I would need to add logic to check
for an open flyout panel and retract it before displaying the new panel.

Das könnte Ihnen auch gefallen