Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Create your Own Runtime Movable Windows Forms Controls

0.00/5 (No votes)
18 Mar 2010 4  
A walkthrough on building a Windows Forms label control that can be moved by the user at runtime.
Control testing sandbox screenshot

Introduction

The questions have been posted more than a few times: "How can I make my own controls?" or "How do I make a label control that's moveable at runtime?" The answer to both is pretty easy! I'm going to walk you through making your own control, a label control that's movable at runtime, not just design time. I'll show you how to take an existing Label control and build upon it. I'll also show you how to build an application to test your new control and use it to watch the control being built up. But, before we dig into the details, we need some specifications. After all, we can't hang any code on this new MovableLabel control without knowing what we want it to do, right? So, what do we want, and better yet, what do we "need" this thing to do?

Some Specifications

  • First, we want all of the functionality the standard Label control offers. We're just adding the ability to move it with the mouse at runtime. Rule #1 of creating your own custom controls is, if another control already exists with most of the functionality you need, inherit from it!
  • We need to move our Label control when the mouse button is being held down on it.
  • We need some kind of indicator to show that our control is movable when the mouse is over it.
  • We also need to change the mouse cursor when the mouse is being held down to show the user that we're in the "moving" mode.

Starting the Code

Inheriting from Label

The first thing on our list is getting all of the functionality of the standard Label control into our MovableLabel control. Start by creating a new Windows Class Library project, and call it Movable Controls Library. In the Solution Explorer, rename the file Class1.vb to MovableLabel.vb. This will allow you to create your own library of runtime movable controls. All you need to do is add another class to the Movable Controls Library project for each control that you are going to make.

Now, since we'll be creating a Windows Forms control, we need to import a couple of things. The System.Windows.Forms namespace gives us access to the standard Windows Forms Label control and all the little things we need to interact with Windows Forms. To add this, go to the "Project" menu, and click "Add Reference...". Make sure the ".NET" tab is selected, and scroll down its list to find the System.Windows.Forms.dll file. Double-click it to add the reference.

The mouse coordinates we get from the mouse event arguments will all be passed in as Point structures. So, we'll need the System.Drawing namespace to get access to the Point structure. Go through the same procedure to add a reference to the System.Drawing.dll file.

With the references out of the way, we just need to change the code a bit to import the namespaces these two DLL files expose, and tell our MovableLabel control which control class it's inheriting from:

Imports System.Windows.Forms
Imports System.Drawing

Public Class MovableLabel
    Inherits System.Windows.Forms.Label

End Class

That's all there is to it! Now we have a class that does the exact same thing as the standard Label control. But, we can't test it like this. If you try to hit [F5] to start the project, you'll get an error that says "A project with an Output Type of Class Library cannot be started directly..." A class library needs something to instantiate it in order to test it. We'll have to add a second project to our solution to test our new control in.

Creating a Sandbox

What's a "sandbox"? That's what I call a small application that is written to test the functionality of a class library or some other code idea. Since we're creating a new Windows Forms control, we'll need a Windows Forms application to test our control in. Go to the "File" menu and click "Add...", then click "New Project...". Select "VB.NET Windows Application", and call it "Movable Controls Sandbox". This will add a second project to our solution. Notice, in the Solution Explorer, that the new project name is bolded, compared to our other project name. This means that this is the startup project. When you hit [F5] to start the debugger running your code, this is the project that will launch first. If, for any reason, you need to change the startup project, just right-click the project name in the Solution Explorer and click "Set as Startup Project" in the context menu that pops up.

The Sandbox project needs a little bit of modification so we know what we're looking at when we run it. In the Solution Explorer, find the file called "Form1.vb" and rename it to "MainForm.vb". Then double the new filename to open the form in the Designer. In the Properties pane below the Solution Explorer, find the Text property, under the Appearance section, and change it to read "Movable Controls Sandbox".

Now, we need to drop an instance of our new control on the form so we can see that it works so far. If you haven't already done so, hit [Ctrl]+[Shift]+B to build the solution. This will check to see that all of the projects in the solution are compiled into executable code and up to date with the latest changes to the source code. It will also add our new control to the Toolbox on the left side of the Designer window. Open the Toolbox, and find and expand the section called "Movable Controls Library". In it, you should find the MovableLabel control. Double-click the MovableLabel control in the Toolbox. The Designer should add a new instance of the control to the form. Move the control to about the middle of the form. Then, look at the Properties pane under the Solution Explorer. Notice that all of the properties you would find in a normal Label control are here. We've got a working Label control, just with a different class name!

To make things a little easier later on, find the Text property and change it to read "This is a runtime movable label control". Then, find the AutoSize property, near the bottom of the Properties list, and change it to "False". We'll resize the control on the form to something a little bigger to click on and drag around. So grab one of the corner handles on our new control, and drag it so that the outline of the control appears roughly square and you can read the complete text in the box. Now, make the MovableLabel a little prettier, by adding some color and centering the text in the box.

Find the BackColor property, and type "Green" in its value box, then find ForeColor and change it to "White". Change the BorderStyle to FixedSingle and the TextAlign property to MiddleCenter. You should end up with something that looks like this:

Sandbox application screenshot

Now hit [F5] to run the Sandbox app. You'll find that our MovableLabel looks and feels exactly like any other Label control - static and boring! Let's start adding some new functionality to it! Click on the little "X" on the form to close the Sandbox application and stop the debugger.

User Feedback

Static Mode

Our MovableLabel needs to be able to give some kind of feedback to the user. Since our control has two modes, Static and Movable, we should give the user some feedback to let them know what mode the control is in.

Static mode is where the control is sitting stationary, waiting for the mouse to do something we're interested in. In this case, Static mode should be looking for a MouseDown event. The firing of this event is what will trigger the control to switch to Movable mode. While we're waiting for this to happen, we should give the user a clue that we're in Static mode and waiting. We can do this by setting the cursor to something like a pointing hand when the mouse pointer passes over our control. Since the Label control we inherited from already exposes a Cursor property that does this, all we have to do is set the value of Cursor in our control's constructor. This way, each and every instance of MovableLabel will initialize itself to show this Hand cursor from the moment it's dropped on the form. Click on the "MovableLabel.vb" tab in the editor window, and add the following code between the Inherits line and End Class:

    Public Sub New()
        ' Call the constructor of the base Label class to properly
        ' initialize our control
        MyBase.New()
        ' Setup the Cursor property so that, by default, when the
        ' mouse passes over our control, the cursor changes to a hand
        Me.Cursor = Cursors.Hand
    End Sub

Now hit [F5] to see the results. You should now see that whenever the mouse passes over the MovableLabel, the cursor changes to a Hand pointer and changes back when the mouse moves off our control.

The way that this is implemented does not take away the ability of the user to change the Cursor property to whatever cursor they feel like while in Static mode. It merely sets up the default cursor used if one is not specified in design mode. Try it. All you have to do is drag another MovableLabel control onto the form and look at the Cursor property in the Properties pane. Notice, it doesn't say "Default" anymore. Let's move on...

A Little Inheritance Background

What about that MyBase.New() line? That line is there because we are using all of the code, properties, and methods of the Label class we inherited from. The Label class has kind of become our slave. We're wrapping the Label class inside a layer of our own code, and manipulating the Label through that layer of code. But, it still is a separate class that has its own rules about how it functions and what it needs to function. So, all Windows Forms controls have an InitializeComponent() method that is called when they are instantiated. This call nearly always appears in the control's constructor, or New method. Since the Label class is doing nearly all of the work we need to have done, we have to tell the Label to initialize itself properly so the rest of the control will work. All we do to facilitate this is call the constructor of the class we inherited from, or what's called the "base class". In VB.NET syntax, MyBase always refers to the base class we inherit from. So, to call the base class' constructor, we use MyBase.New().

Movable Mode

Here's where we really start to hang some code on this thing. We need to add support to change the mouse pointer when the left mouse button is held down on our control, and we need to change it back to what it was when the mouse button is released. This is easily done by handling the MouseDown and MouseUp events exposed by the Label class.

First, we need to add a field to our class that keeps track of what the current Cursor value is of our Label control. We'll set this value when the MouseDown event fires, then use it to restore the Cursor when the MouseUp event fires. Add the following code between the Inherits line and the Public Sub New() line:

    ' Used to store the current cursor shape before we start
    ' to move our control
    Private m_CurrentCursor As Cursor

Now we have to add code to change the cursor at the appropriate times. Remember, we're controlling a wrapped-up Label class, so it's going to be raising all the events we see through the MyBase identifier. We'll need to save the current cursor state when the mouse button is held down, change the cursor to signify that we've entered Movable mode, then change it back to what we found when the mouse button is released.

To add the MouseDown and MouseUp event handlers, it's recommended that you pick them from the two dropdown lists at the top of the Editor window. This is to ensure that you get the event argument signatures correct. If they don't match, you'll run into problems when you compile the code. In the left-side drop down, pick "(MovableLabel Events)", with the little lightning bolt next to it. This will show only our class' available events in the right-side dropdown. In that dropdown, pick MouseDown. This will automatically generate an empty event handler, with the appropriate argument signature for that event. Do the same for the MouseUp. Fill in the empty handlers with the following code:

    Private Sub MovableLabel_MouseDown( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseDown
        ' Check to see if the correct button has been pressed
        If e.Button = Windows.Forms.MouseButtons.Left Then
            ' If so, the save the current cursor and
            m_CurrentCursor = MyBase.Cursor
            ' replace it with our new image that says were in Movable mode
            MyBase.Cursor = Cursors.SizeAll
        End If
    End Sub

    Private Sub MovableLabel_MouseUp( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseUp
        ' The button was released, so we're going back to Static mode.
        ' Restore the cursor image to the way we found it when the mouse
        ' button was pressed
        MyBase.Cursor = m_CurrentCursor
    End Sub

Hit [F5] to see what you've done. Be sure to hold the mouse button down on the MovableLabel control, and watch what happens.

Tracking the Mouse

OK. Now, we get down to actually making the MovableLabel move! I'll start with a little explanation of how we're going to calculate the new position of the label based on the mouse position.

Control Math Layout

When the user presses the mouse button down on our control, it raises the MouseDown event. In the MouseEventArgs, we'll get the position of the mouse (point 3) relative to the top-left corner of our control (point 2). Since we want the mouse pointer to maintain this position inside our control while it's moving, we need to store the position we get here. We'll use it later to maintain this relationship between the control and the mouse position.

When we calculate the position of the control, we need the position of the mouse relative to the parent container of our control, in our case, the MainForm client area (point 1). In the MouseEventArgs that we're going to get, we'll still get the position of the mouse inside the control the mouse is moving in, but that doesn't do us any good. We need to convert that location to a point inside the parent control. We do this by calling the PointToClient method of the parent container of our control. The Label class, along with any other class that inherits from Control, exposes a Parent property that returns a reference to whatever control container is holding our control, or Parent. The PointToClient method will convert any point (point 3) relative to the inside of any of its child controls (point 2) to the same point relative to the top-left corner of itself (point 1).

Now we've got the two pieces of information we need to calculate the new position of our control (point 2). Point2 = (Point3 relative to Point1) - (Point 3 relative to Point 2), or Point2 = (Point3 relative to Point1) - (saved coordinates from the MouseDown event).

Add a MoveMove event handler to the code. Now, so long as the mouse is over our control and moves so much as a single pixel, this event is raised, whether the mouse button is down or not. We don't want to do anything if we're in Static mode, so we need a flag to check in this event handler to see what mode we're in. If we're in Movable mode, we need to calculate a new position for our control. If we're in Static mode, we do not do anything and let the event handler terminate. So let's add this flag to our class' fields. Change the field code near the top of the class code to read:

    ' Used to store the current cursor shape when we start
    ' to move the control
    Private m_CurrentCursor As Cursor
    ' Holds the mouse position relative to the inside of
    ' our control when the mouse button goes down
    Private m_CursorOffset As Point
    ' Used by the MoveMove event handler to show that the
    ' setup to move the control has completed
    Private m_Moving As Boolean

The flag to signal moving has been setup, but now we need to manage the status of that flag in the appropriate places. Since our Movable mode can only be entered inside the MouseDown event, the m_Moving flag should be set to True in that event. Modify the MouseDown event code as follows. We'll also be adding the code to save the mouse position inside our control when the event is raised.

    Private Sub MovableLabel_MouseDown( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseDown
        ' Check to see if the correct button has been pressed
        If e.Button = Windows.Forms.MouseButtons.Left Then
            ' If so, save the current cursor and
            m_CurrentCursor = MyBase.Cursor

            ' replace it with our new image that says were in Movable mode
            MyBase.Cursor = Cursors.SizeAll

            ' Save the location of the mouse pointer relative to the top-left
            ' corner of our control
            m_CursorOffset = e.Location

            ' Set the mode flag to signal the MouseMove event handler that it
            ' needs to now calculate new positions for our control
            m_Moving = True
        End If
    End Sub

Modify the MouseUp event handler to look like the following:

    Private Sub MovableLabel_MouseUp( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseUp
        ' The button was released, so we're going back to Static mode.
        m_Moving = False

        ' Restore the cursor image to the way we found it when the mouse
        ' button was pressed
        MyBase.Cursor = m_CurrentCursor
    End Sub

Almost done! Fill in the MouseMove event handler:

    Private Sub MovableLabel_MouseMove( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseMove
        ' Check which mode we're in. If we're supposed to be moving
        ' our control
        If m_Moving Then
            ' get the screen position of the mouse pointer and map it
            ' to the position relative to the top-left corner of our
            ' parent container
            Dim clientPosition As Point = _
                MyBase.Parent.PointToClient
                (System.Windows.Forms.Cursor.Position)

            ' Calculate the new position of our control, maintaining
            ' the relative position stored by the MoveDown event
            Dim adjustedLocation As New Point( _
                clientPosition.X - m_CursorOffset.X, _
                clientPosition.Y - m_CursorOffset.Y)

            ' Set the new position of our control
            MyBase.Location = adjustedLocation
        End If
    End Sub

Hit [F5] to run it, and see if you can move your new MovableLabel control around!

Future Expansion

The code built in this article is a simplified version of the solution that is supplied in the download at the top of the article. The MovableLabel class in the download has a slew of additions, such as:

  • Support for staying within the bounds of the parent container
  • Maintaining the bounds restriction during resizing of the parent container
  • Adding a property to turn the Parent Container Bounds support on and off
  • Exposing a ValueChanged event and maintaining a list of subscribers to that event
  • Support for moving the control from one parent to another at design time
  • Designer Serialization support for maintaining the values of properties at design time
  • Setting new property default values

The complete MovableControl.vb file as written in this article:

Imports System.Windows.Forms
Imports System.Drawing

Public Class MovableLabel
    Inherits System.Windows.Forms.Label

    ' Used to store the current cursor shape when we start
    ' to move the control
    Private m_CurrentCursor As Cursor

    ' Holds the mouse position relative to the inside of
    ' our control when the mouse button goes down
    Private m_CursorOffset As Point

    ' Used by the MoveMove event handler to show that the
    ' setup to move the control has completed
    Private m_Moving As Boolean

    Public Sub New()
        ' Call the constructor of the base Label class to properly
        ' initialize our control
        MyBase.New()
        ' Setup the Cursor property so that, by default, when the
        ' mouse passes over our control, the cursor changes to a hand
        Me.Cursor = Cursors.Hand
    End Sub

    Private Sub MovableLabel_MouseDown( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseDown
        ' Check to see if the correct button has been pressed
        If e.Button = Windows.Forms.MouseButtons.Left Then
            ' If so, save the current cursor and
            m_CurrentCursor = MyBase.Cursor

            ' replace it with our new image that says were in Movable mode
            MyBase.Cursor = Cursors.SizeAll

            ' Save the location of the mouse pointer relative to the top-left
            ' corner of our control
            m_CursorOffset = e.Location

            ' Set the mode flag to signal the MouseMove event handler that it
            ' needs to now calculate new positions for our control
            m_Moving = True
        End If
    End Sub

    Private Sub MovableLabel_MouseMove( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseMove
        ' Check which mode we're in. If we're supposed to be moving
        ' our control
        If m_Moving Then
            ' get the screen position of the mouse pointer and map it
            ' to the position relative to the top-left corner of our
            ' parent container
            Dim clientPosition As Point = _
                MyBase.Parent.PointToClient
                    (System.Windows.Forms.Cursor.Position)

            ' Calculate the new position of our control, maintaining
            ' the relative position stored by the MoveDown event
            Dim adjustedLocation As New Point( _
                clientPosition.X - m_CursorOffset.X, _
                clientPosition.Y - m_CursorOffset.Y)

            ' Set the new position of our control
            MyBase.Location = adjustedLocation
        End If
    End Sub

    Private Sub MovableLabel_MouseUp( _
                ByVal sender As Object, _
                ByVal e As System.Windows.Forms.MouseEventArgs) _
                Handles MyBase.MouseUp
        ' The button was released, so we're going back to Static mode.
        m_Moving = False

        ' Restore the cursor image to the way we found it when the mouse
        ' button was pressed
        MyBase.Cursor = m_CurrentCursor
    End Sub

End Class

This article was written expressly for The Code Project. If you find this article, in part or in whole, on any other website, it is a blatent copyright violation. Please inform the CodeProject staff of this at webmaster@codeproject.com. Thank you.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here