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

Adding Drag and Drop support to a VB.NET control

0.00/5 (No votes)
5 Apr 2003 3  
Article explaining how to add drag and drop support to a VB.NET control

Introduction

Let's first take a look at how the code has been implemented.

Imports System.Text

Public Class Form1
    Inherits System.Windows.Forms.Form
    Implements IMessageFilter

#Region " Windows Form Designer generated code "

    Public Sub New()
        
        MyBase.New()
        InitializeComponent()
        Application.AddMessageFilter(Me)
        DragAcceptFiles(Me.Handle, True)
        
    End Sub

    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    Private components As System.ComponentModel.IContainer


    Friend WithEvents Label1 As System.Windows.Forms.Label
    Friend WithEvents PictureBox1 As System.Windows.Forms.PictureBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.Label1 = New System.Windows.Forms.Label()
        Me.PictureBox1 = New System.Windows.Forms.PictureBox()
        Me.SuspendLayout()
        '

        'Label1

        '

        Me.Label1.Font = New System.Drawing.Font("Arial", 9.75!, 
                         System.Drawing.FontStyle.Regular,
                         System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.Label1.Location = New System.Drawing.Point(80, 24)
        Me.Label1.Name = "Label1"
        Me.Label1.Size = New System.Drawing.Size(248, 24)
        Me.Label1.TabIndex = 0
        Me.Label1.Text = "Drag and Drop Images onto the Form"
        Me.Label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter
        '

        'PictureBox1

        '

        Me.PictureBox1.Location = New System.Drawing.Point(104, 88)
        Me.PictureBox1.Name = "PictureBox1"
        Me.PictureBox1.Size = New System.Drawing.Size(184, 216)
        Me.PictureBox1.TabIndex = 1
        Me.PictureBox1.TabStop = False
        '

        'Form1

        '

        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(440, 357)
        Me.Controls.AddRange(New System.Windows.Forms.Control() 
                {Me.PictureBox1, Me.Label1})
        Me.Name = "Form1"
        Me.Text = "Drag and Drop Images"
        Me.ResumeLayout(False)

    End Sub

#End Region


    Public Function PreFilterMessage(ByRef m As System.Windows.Forms.Message) 
                                    As Boolean 
        Implements IMessageFilter.PreFilterMessage
        
        If m.Msg = WM_DROPFILES Then
            
            'this code to handle multiple dropped files.. 

            'not really neccesary for this example

            Dim nfiles As Integer = DragQueryFile(m.WParam, -1, Nothing, 0)
             
            Dim i As Integer
            For i = 0 To nfiles
                Dim sb As StringBuilder = New StringBuilder(256)
                DragQueryFile(m.WParam, i, sb, 256)
                HandleDroppedFiles(sb.ToString())
            Next
            DragFinish(m.WParam)
            Return True
        End If
        Return False
    End Function

    Public Sub HandleDroppedFiles(ByVal file As String)
        If Len(file) > 0 Then
            LoadPicture(file)
        End If
    End Sub

    Public Function LoadPicture(ByVal File As String) As Boolean
        If Len(File) > 0 Then
            Dim b As Bitmap = New Bitmap(File)
            picturebox1.SizeMode = PictureBoxSizeMode.StretchImage
            picturebox1.Image = b
            Return True
        End If
        Return False
    End Function

    Private Declare Function DragAcceptFiles Lib "shell32.dll" 
        (ByVal hwnd As IntPtr, ByVal accept As Boolean) As Long

    Private Declare Function DragQueryFile Lib "shell32.dll" 
        (ByVal hdrop As IntPtr, ByVal ifile As Integer, 
                  ByVal fname As StringBuilder, 
         ByVal fnsize As Integer) As Integer

    Private Declare Sub DragFinish Lib "Shell32.dll" (ByVal hdrop As IntPtr)

    Public Const WM_DROPFILES As Integer = 563

End Class

Explanation

Lets look at it line by line.

Imports System.Text

This is because you have to use a String buffer and there is a class that is perfect that resides in this namespace called StringBuilder so find the code that says:

Dim sb As StringBuilder = New StringBuilder(256)

This makes 'sb' a String buffer that can hold 256 characters. This will hold the name of the file that has been dropped onto the Form.

The next thing to take note of is :

Public Class Form1
    Inherits System.Windows.Forms.Form
    Implements IMessageFilter

You have to make it so the Form acts as a message retriever/ sender. The reason is because Windows itself sends messages to your program and your program will send messages to Windows/other applications etc. You have to be able to intercept these messages to truly drag and drop from outside of your application to your application.

So, the guys @ Microsoft made the Interface, IMessageFilter to give you a nice cushion from truly using Windows messages like in C or C++. The Interface, IMessageFilter only has 1 method called PreMessageFilter which you HAVE to implement.(When you are implementing an Interface, you have to)!

So, the next thing to take note of is the one method of that Interface called PreMessageFilter which looks like:

Public Function PreFilterMessage(ByRef m As System.Windows.Forms.Message) As Boolean 
    Implements IMessageFilter.PreFilterMessage
    
    If m.Msg = WM_DROPFILES Then
        'this code to handle multiple dropped files.. 

        'not really neccesary for this example

        Dim nfiles As Integer = DragQueryFile(m.WParam, -1, Nothing, 0) 
        
        Dim i As Integer
        For i = 0 To nfiles
            Dim sb As StringBuilder = New StringBuilder(256)
            DragQueryFile(m.WParam, i, sb, 256)
            HandleDroppedFiles(sb.ToString())
        Next
        DragFinish(m.WParam)
        Return True
    End If
    Return False
End Function
    

This function will file when messages are sent to your application, your job is to find out which message is dropping a file! But luckily, I did that one for you already. The message is 563. 563?!?!?! What the hell is 563... well... it's a Windows constant definition of the message that tells your application a file was dropped on it.. It just has to be that way.

So, if your lost right now, the WM_DROPFILES = 563. So the if statement in the above function, if m.msg = WM_DROPFILES means: If the message that the OS passed to my application = 563 then someone dropped a file on the form. So let's move on.

You can't really just "use" the message filter Interface, you have to somehow tell your application what exactly is going to act as the message filter. In this case, the Form itself will act as the message filter and so :-

Public Sub New()
    MyBase.New()

    InitializeComponent()

    Application.AddMessageFilter(Me)
    DragAcceptFiles(Me.Handle, True)
End Sub    
    

In this little snippet, we do just that, we add "Me" as the message filter, where the "Me" is the Form you are currently working with.

Right after that, you must specify to Windows that this handle (this Form, this application, whatever) will be accepting files from anywhere that is why the call to the API function DRAGACCEPTFILES is necessary. Windows uses handles instead of names to identify running processes / applications. It's just some big number that uniquely identifies your application so Windows can "handle" it.

Everything up to this point is strictly dealing with how to make the OS and your application deal with sending and receiving messages from the OS / application vice-versa. Now, we have to deal with accepting the actual file and dealing with that. In the PreMessageFilter function that gets fired every time a message hits the application, it builds a String buffer from an API call called DragQueryFile. It is this String buffer that you will need to tell any other piece of your application that "that file is currently being dragged onto your form" the declaration for this API call is:

Private Declare Function DragQueryFile Lib "shell32.dll" 
    (ByVal hdrop As IntPtr, ByVal ifile As Integer, ByVal fname As StringBuilder, 
     ByVal fnsize As Integer) As Integer

So after that API call fills the String buffer (StringBuilder/ buffer sheesh!) you can handle what happens next. So in the function HandleDroppedFiles() is where to do this one. This is a user function, so this can be anything I want it to be, my 2 functions look like:

Public Sub HandleDroppedFiles(ByVal file As String)
    If Len(file) > 0 Then
        LoadPicture(file)
    End If
End Sub

Public Function LoadPicture(ByVal File As String) As Boolean
    If Len(File) > 0 Then
        Dim b As Bitmap = New Bitmap(File)
        picturebox1.SizeMode = PictureBoxSizeMode.StretchImage
        picturebox1.Image = b
        Return True
    End If
    Return False
End Function

All this does is take the String buffer (after it has been filled and is actually a real String. (I'm a real boy)), as a parameter and it should look like "C:\file.jpg" or whatever, you can then proceed to tell your component/ control/ Form whatever, that its image is going to be that one. So to sum up my first ever tutorial you need these API declarations to successfully use dragging and dropping from outside of your application:

        
Private Declare Function DragAcceptFiles Lib "shell32.dll" 
    (ByVal hwnd As IntPtr, ByVal accept As Boolean) As Long
    
Private Declare Function DragQueryFile Lib "shell32.dll" 
    (ByVal hdrop As IntPtr, ByVal ifile As Integer, ByVal fname As StringBuilder, 
     ByVal fnsize As Integer) As Integer
     
Private Declare Sub DragFinish Lib "Shell32.dll" (ByVal hdrop As IntPtr) 

Public Const WM_DROPFILES As Integer = 563 

Remember that the WM_DROPFILES = 563 is just the constant that Windows uses to realize that there is a file(s) that just got dropped somewhere. Hope this helps all of you newbies out there because it took me a freaking week to get this one down, there's not much on this topic out on the net, but you notice every major application has this functionality. Keep in mind, that once you know the actual path of the file dropped (it could be any file and not just images), you can then do things like, save it to a database, load it into your own quazi-file system, whatever the hell you want to do because you have access to exactly what the user just did.

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