Introduction
This is my first article ever describing a problem I've been working on for some time involving attempting to drag URLs out of a Windows Forms project and have them appear on the desktop as shortcuts. This is the same functionality that you get when you drag links from Internet Explorer to the desktop but it turned out to be a very difficult thing to do involving a lot of trial and error and some magic numbers.
Background
In general, drag and drop within a single Windows Forms project is not that hard. You attach an event handler for ItemDrag
to the drag source and when it gets called you create and fill a DataObject
with one of the predefined formats or your own object. If you are capturing the drag in the same Windows Forms app then everything will usually go smoothly. However there does seem to be problems dragging certain types of formats between two different Windows Forms apps if you populate the DataObject
with an Object
and you try to get it back by looking for that object by Type using DataObject.GetData(Type)
because the clipboard seems to lose the .NET Type information when passing between programs. Presumably if you put the data in using DataObject.SetData(string nameoftype, object)
and then look for the string, this should get around this problem but I haven't tested this.
Anyway that's incidental to what I was attempting to do which was to drag a URL from a Windows Forms ListView
to the desktop and have it create a shortcut similar to the behavior of Internet Explorer. To do this I basically designed a test application which looked at all the different data formats that Internet Explorer was passing to the DataObject
that it creates itself and then tried to mimic them. This took quite a bit of effort to come up with the combination of data formats and magic numbers and memory streams necessary, and that's why I decided to put it into this article.
Using the code
When I finally got everything working, here is what it turns out must be done in order to achieve this effect:
- You must create a
MemoryStream
of 336 bytes with the title of the link plus ".url" contained in ASCII starting at index position 76. This index position is already known from a few Usenet posts but I haven't seen anybody specifying the 336 byte length as the Usenet posts were all concerned with retrieving a URL title from an existing DataObject
dragged into Windows Forms, not creating one to drag out. You must also add the magic numbers 0x1, 0x0, 0x0, 0x0, 0x40, 0x80 to the first 6 bytes and a value of 0x78 at byte 72. Finally you must add this MemoryStream
to the "FileGroupDescriptor
" data format in the data object. The following sample shows the code to create the fileGroupDescriptor
MemoryStream
: private void listView1_ItemDrag(object sender,
System.Windows.Forms.ItemDragEventArgs e)
{
ListViewItem item=e.Item as ListViewItem;
if (item!=null)
{
Byte[] title =
System.Text.Encoding.ASCII.GetBytes(item.SubItems[0].Text
+ ".url");
Byte[] fileGroupDescriptor=new Byte[336];
title.CopyTo(fileGroupDescriptor,76);
fileGroupDescriptor[0]=0x1;
fileGroupDescriptor[4]=0x40;
fileGroupDescriptor[5]=0x80;
fileGroupDescriptor[72]=0x78;
MemoryStream fileGroupDescriptorStream =
new MemoryStream(fileGroupDescriptor);
- The next thing you have to create is a
MemoryStream
containing the actual URL of the link. This is a straightforward conversion from text to a MemoryStream
of ASCII bytes as shown:
String url=item.SubItems[1].Text;
Byte[] urlByteArray=System.Text.Encoding.ASCII.GetBytes(url);
MemoryStream urlStream=new MemoryStream(urlByteArray);
- Now you have to do the really hard part, at least from the point of view of what I had to figure out. Basically the problem with this was on my test application, dragging from Internet Explorer to Windows Forms showed a "FileContents" data format as being present in the
DataObject
, but the call to DataObject.GetData("FileContents")
always threw an exception meaning I couldn't get a look at the object or the memory that was in the clipboard for this. So basically I guessed. I figured it was probably just a memory stream of the .url file format which is shown here. The internet shortcut is actually a special type of file containing certain tags and a ".url" file extension. So I just created the internals of this file and dumped it into a MemoryStream
and prayed, and after a bit of trial and error it worked. As shown here, I've only implemented the Title, and the Link, but there are a few other things you can put in this file if you really feel like it. The simplest form looks likes this: [InternetShortcut]
URL=http://thecodeproject.com
That's all you need to at least get the drag to work. It turns out the title of the link on the desktop is pulled from the "FileDescriptor" that we set in step 1 automatically.
The code for creating the file contents is as shown:
string contents="[InternetShortcut]"+
Environment.NewLine+"URL="+url+Environment.NewLine;
Byte[] contentsByteArray=System.Text.Encoding.ASCII.GetBytes(contents);
MemoryStream contentsStream=new MemoryStream(contentsByteArray);
- Finally we just put all three objects into the
DataObject
, as well as add the URL into "UniformResourceLocator
" which gives us the ability to drag the link straight into the browser instead of the desktop and then we are done!
DataObject data=new DataObject();
data.SetData("FileGroupDescriptor",
fileGroupDescriptorStream);
data.SetData("FileContents",
contentsStream);
data.SetData("UniformResourceLocator",
urlStream);
this.DoDragDrop(data,DragDropEffects.Link);
That's it. I hope this helps people because it's a very useful feature that nobody seems to have figured out yet.
The sample code has a ListView
with some links that you can drag to your desktop or to a browser. Just compile and run and drag away!
History
- Version 1.1 - July 21, 2005.-fixed WinXP problem.