Download ShapedForm.zip - 13.47 KB
Introduction
It's fairly easy to produce a shaped form in VB.net. In fact there are
already several articles on the subject. But what most existing articles do not
address is how to enable the user to drag the form while it's displaying its
shaped appearance. If you're working on a project which requires you to produce
a shaped form, then I hope this article will be of use to you.
What Is A Shaped Form?
A shaped form is essentially a window that doesn't look like a window
normally would. It doesn't conform to the standard way a window should look, but
instead uses graphics to render those areas of the window that would usually be
handled by the operating system. The screenshot below illustrates this in more
detail. Figure 1 shows a form with a familiar border and title bar (shall we say
'normal mode'), whereas figure 2 shows the same window but with the normal
window frame switched off (let's call that 'compact mode'). I used
GIMP to produce a simple custom shape to use as
the basis for my window, but you can use whichever graphics package you normally
use. When the user has the form displayed in either normal or compact mode, it
can be dragged around using any area on the form that is not covered by a
control. In normal mode though, the user can also drag the form around by the
title bar as they would expect to be able to do.

Some General Advice On This Subject
Don't use custom forms in your application without good reason. Most software
users don't care what an application looks like as long as it does the job that
they installed it to do. Many software developers fall into the trap of thinking
that users are constantly looking at, and admiring their applications while using them.
Most often this is just not the case. The average user is often looking
somewhere else, or talking to someone, while interacting with your application.
By adopting a custom form, your application departs from the standard Windows
behaviour model, and this forces your user to enter a learning curve concerning
how to perform basic tasks with your application. An example of when not to use
a custom form is a straight forward data entry application. There are however,
many situations where a custom form brings that special something to an
application. One example would be if you're writing a media player of some kind.
In that example, the application taking on a cool car stereo look may be just
what the user wants.
Using the code
Download the project files and examine the contents. You'll see that the form
is a standard form (I named it 'ShapedForm'). I added two controls to the form.
One is a link label that allows the user to close the form. The other is a
checkbox that allows the user to toggle between compact and normal mode. The
form start up in normal mode. It's constructor look like this:
Public Sub New()
InitializeComponent()
With Me
.BackgroundImage = My.Resources.frame
.BackgroundImageLayout = ImageLayout.None
.TransparencyKey = Color.Lime End With
End Sub
The image used for the form background has transparent regions. So in normal
mode, Windows will draw those regions of the form that lay under those
transparent regions. In other words, the form's surface will show through the
transparent parts of the image. Note that although I've told Windows what colour
to use for transparency, I haven't yet told it that transparency applies to the
form surface. The user will cause that to happen next. When the user selects compact mode the following code is called:
Private Sub DoCompactMode()
Me.BackColor = Me.TransparencyKey
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
End Sub
There is a similar routine (not shown here) which puts the form back into
normal mode. The code above is all that's required to display your custom shaped
form because setting the form background colour to the same colour used for
transparency, tells Windows that transparency now applies to the form surface
too. So Windows doesn't draw any part of the form that lays beneath a
transparent regions of the image. Instead it draws what's beneath that region of
the form, thereby causing the form to look as though it consists only of the
graphic. Switching off the form's border completes the effect.
Staying Draggable
If the above code was all that was contained in the form, the user would be
stuck with a problem when entering compact mode. Because the window title bar
is gone, their primary means of moving the form around the screen has gone with
it. So too has the ability to to minimise, maximise, and close the window. All gone.
As the developer of a shaped window, it's your responsibility to provide these
facilities by other means. For minimise, maximise, and close you could supply
controls that when clicked, perform those functions simply enough. But giving
the user the ability to drag the window is a little more tricky. Firstly you
have to intercept the mouse down event for the form:
Private Sub ShapedForm_MouseDown(ByVal sender As Object, _
ByVal e As MouseEventArgs) _
Handles Me.MouseDown
If e.Button = MouseButtons.Left Then
With Cursor.Position
mptMouseOffset = New Point(-(.X - Me.Left), -(.Y - Me.Top))
End With
mblnDragEnabled = True
End If
End Sub
In the MouseDown event handler, detect that it was the left mouse button that was depressed. Don't worry
about left handed users who have switched their mouse buttons. In such a case,
Windows tells you the left button was clicked even though it was the right one,
because the swapping of mouse buttons is an agreement between the user and
Windows and is not your concern. Windows informs your code it was the left
button because the concept of 'right' to a left-handed person is equivalent to
the concept of 'left' to a right-handed one. You then have to calculate where
the mouse is in relation to the top left corner of the form. This information is
not used now, but stored at module level scope for when the user starts the mouse
moving. Finally set a flag (also defined at module level) to indicate that the
mouse is down (dragging is enabled).
Private Sub ShapedForm_MouseMove(ByVal sender As Object, _
ByVal e As MouseEventArgs) _
Handles Me.MouseMove
If mblnDragEnabled Then
Dim mousePos As Point = Cursor.Position
mousePos.Offset(mptMouseOffset)
Me.Location = mousePos
End If
End Sub
In the MouseMove event handler, if the mouse button is down, then the user
must be dragging. This is when you need the offset value you calculated on
MouseDown. Take the current mouse position and offset it by that amount, before
causing the window to redraw at the new location. Remember that this code will be
executed many times per second, as the user drags, so keep your code efficient,
or the user may experience lag.
Private Sub ShapedForm_MouseUp(ByVal sender As Object, _
ByVal e As MouseEventArgs) _
Handles Me.MouseUp
If e.Button = MouseButtons.Left Then
mblnDragEnabled = False
End If
End Sub
Finally on the MouseUp event, confirm it was the left mouse that was released
and reset your drag flag.
Points of Interest
You should be aware of, and consider, the following issues when implementing
windows with custom shapes:
- The user may not appreciate the cool looking window frame you've supplied.
So consider giving the user an option to use a standard look as demonstrated
by the sample code provided with this article.
- You cannot know how often Windows will invoke your MouseMove event
handler. On a fast computer with little else to do, it could be many time per
second, but on an older system, or earlier version of the operating system, or
a busy computer, it could be just a few. So make sure your code is as quick
and efficient as possible.
- Pay careful attention to what functions the user looses as a result of
your application displaying a custom form. The system box menu for example,
which appears when a user clicks on the icon inside a normal Windows title
bar.
- It may surprise you to learn that some users don't use a mouse at all, so
as with any other application, make sure your form still works Ok when using
just the keyboard.
- The sample code allows the user to drag the form using the form's surface
(and not just the title bar) when in normal mode. You may decide that the user
shouldn't be able to do that in normal mode. If so, your code will have to add
and remove the event handlers where appropriate. The DoCompactMode and
DoNormalMode routines are the best place for this.
- Consider how to let the user (who may be looking at your application for
the first time) know how to move the window. The most obvious way is to change
the mouse pointer to something appropriate (such as a hand) as it moves into
the form (MouseEnter event), and reset it to default as it moves out (MouseLeave
event).
History
None.