Friday, August 06, 2004

 

Drag & Drop between Managed WinFom and Explorer

1.
By Felix
From http://blog.joycode.com/felix/articles/38825.aspx

如果要实现把文件从Explorer里拖到WinForm上做处理,可以参见下面的KB:

How To Provide File Drag-and-Drop Functionality in a Visual C# .NET Application
http://support.microsoft.com/?id=307966

The ListBox control provides two drag-and-drop events that you need to handle: DragEnter and DragDrop. The DragEnter event occurs when you drag an object within the bounds of the control and is used to determine whether the object that is being dragged is one that you want to allow to be dropped on the control. You handle this event for cases in which a file or files are dragged to the control. This allows the appropriate icon to be displayed when the object is dragged over the control, depending on the object that is being dragged. The DragDrop event occurs when the object that is being dragged has been released on the control. You handle this event to retrieve the object. The Data object is used to retrieve the data.

The Data object's GetData method returns an array of strings that contain the full path names of the files that were dragged to the ListBox control. You can use this file path information to perform whatever operations are needed on the files. For example, you can use classes in the System.IO namespace to open and read the files, move the files, or copy the files to a new location. In this example, you just add the full path to the files that are dragged to the ListBox control.

To provide file drag-and-drop functionality in a Visual C# .NET application, follow these steps: 1. Create a new Windows Forms application in Visual C# .NET. Form1 is created by default.
2. Use the toolbox to add a ListBox control to Form1.
3. In the Properties window, change the AllowDrop property of the ListBox control to True to allow objects to be dragged onto the control.
4. In Solution Explorer, right-click Form1, and then click View Code.
5. To handle the DragEnter event, add the following method below the code section that the Windows Form Designer generates in the Form1 class:private void listBox1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
if(e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.All;
else
e.Effect = DragDropEffects.None;
}


6. To handle the DragDrop event, add the following method to the Form1 class immediately following the method that you added in step 5:private void listBox1_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
string[] s = (string[]) e.Data.GetData(DataFormats.FileDrop, false);
int i;
for(i = 0; i < s.Length; i++)
listBox1.Items.Add(s[i]);
}


7. To associate the two event handlers with the control events, add the following code in the InitializeComponent method of Form1. Be sure to add the code after ListBox1 has been instantiated:this.listBox1.DragDrop += new
System.Windows.Forms.DragEventHandler(this.listBox1_DragDrop);
this.listBox1.DragEnter += new
System.Windows.Forms.DragEventHandler(this.listBox1_DragEnter);



8. Build and run the project.
9. Drag one or more files from either the desktop or another folder to the ListBox control. Note that the full path of the files is added to the ListBox control.
back to the top
REFERENCES
For more information, see the following Microsoft Developer Network (MSDN) Web sites:
Control.DragEnter Event
http://msdn.microsoft.com/library/en-us/ cpref/html/frlrfSystemWindowsFormsControlClassDragEnterTopic.asp

Control.DragDrop Event
http://msdn.microsoft.com/library/en-us/ cpref/html/frlrfSystemWindowsFormsControlClassDragDropTopic.asp

如果要实现把WinForm item拖到Explorer里生成文件,复杂了一点……详见:

The following code will enable dragging ListView items and dropping in Explorer to generate files. Temporary files will be generated and deferred rendering will be used...


using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

using System.IO;



namespace WinFormDragNDropExplorer

{

///

/// Summary description for Form1.

///


public class Form1 : System.Windows.Forms.Form

{

private System.Windows.Forms.ListView listView1;

///

/// Required designer variable.

///


private System.ComponentModel.Container components = null;



public Form1()

{

//

// Required for Windows Form Designer support

//

InitializeComponent();



//

// TODO: Add any constructor code after InitializeComponent call

//

}



///

/// Clean up any resources being used.

///


protected override void Dispose( bool disposing )

{

if( disposing )

{

if (components != null)

{

components.Dispose();

}

}

base.Dispose( disposing );

}



#region Windows Form Designer generated code

///

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

///


private void InitializeComponent()

{

System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem("Item 1");

System.Windows.Forms.ListViewItem listViewItem2 = new System.Windows.Forms.ListViewItem("Item 2");

System.Windows.Forms.ListViewItem listViewItem3 = new System.Windows.Forms.ListViewItem("Item 3");

this.listView1 = new System.Windows.Forms.ListView();

this.SuspendLayout();

//

// listView1

//

this.listView1.Items.AddRange(new System.Windows.Forms.ListViewItem[] {

listViewItem1,

listViewItem2,

listViewItem3});

this.listView1.Location = new System.Drawing.Point(40, 24);

this.listView1.Name = "listView1";

this.listView1.Size = new System.Drawing.Size(384, 280);

this.listView1.TabIndex = 0;

this.listView1.QueryContinueDrag += new System.Windows.Forms.QueryContinueDragEventHandler(this.listView1_QueryContinueDrag);

this.listView1.ItemDrag += new System.Windows.Forms.ItemDragEventHandler(this.listView1_ItemDrag);

//

// Form1

//

this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);

this.ClientSize = new System.Drawing.Size(472, 333);

this.Controls.Add(this.listView1);

this.Name = "Form1";

this.Text = "Form1";

this.ResumeLayout(false);



}

#endregion



///

/// The main entry point for the application.

///


[STAThread]

static void Main()

{

Application.Run(new Form1());

}



private ShellDataObject dataObj = null;



private void listView1_QueryContinueDrag(object sender, System.Windows.Forms.QueryContinueDragEventArgs e)

{

//ESC pressed

if (e.EscapePressed)

{

e.Action = DragAction.Cancel;

return;

}

//Drop!

if (e.KeyState == 0)

{

dataObj.SetData(ShellClipboardFormats.CFSTR_INDRAGLOOP, 0);

e.Action = DragAction.Drop;

return;

}

e.Action = DragAction.Continue;

}





private void listView1_ItemDrag(object sender, System.Windows.Forms.ItemDragEventArgs e)

{

String[] fileNames;

//Create the data object used for the drag drop operation

dataObj = new ShellDataObject();

//Create an array of strings to hold the filename(s)

fileNames = new String[listView1.SelectedItems.Count];

//Create temporary files that the user can drag.

for (int i = 0; i < listView1.SelectedItems.Count; i++)

{

fileNames[i] = CreateTemporaryFileName(listView1.SelectedItems[i].Text + ".txt");

}

//Add the list of files to the data object

dataObj.SetData(DataFormats.FileDrop, fileNames);

//Set the preferred drop effect

dataObj.SetData(ShellClipboardFormats.CFSTR_PREFERREDDROPEFFECT, DragDropEffects.Move);

//Indicate that we are in a drag loop

dataObj.SetData(ShellClipboardFormats.CFSTR_INDRAGLOOP, 1);

//Initiate the drag operation

listView1.DoDragDrop(dataObj, DragDropEffects.Move | DragDropEffects.Copy);

//Free the data object

dataObj = null;

}



private String CreateTemporaryFileName(String name)

{

String path;

String fileName;

System.IO.StreamWriter file;

//Build a path for the temporary file

path = System.IO.Path.GetTempPath();

fileName = System.IO.Path.Combine(path, name);

//Create the temporary file to make sure it exists

file = new System.IO.StreamWriter(fileName);

file.Close();

return fileName;

}

}



//Constant declarations. Please refer to "shlobj.h"

public class ShellClipboardFormats

{

public const string CFSTR_SHELLIDLIST = "Shell IDList Array";

public const string CFSTR_SHELLIDLISTOFFSET = "Shell Object Offsets";

public const string CFSTR_NETRESOURCES = "Net Resource";

public const string CFSTR_FILEDESCRIPTORA = "FileGroupDescriptor";

public const string CFSTR_FILEDESCRIPTORW = "FileGroupDescriptorW";

public const string CFSTR_FILECONTENTS = "FileContents";

public const string CFSTR_FILENAMEA = "FileName";

public const string CFSTR_FILENAMEW = "FileNameW";

public const string CFSTR_PRINTERGROUP = "PrinterFreindlyName";

public const string CFSTR_FILENAMEMAPA = "FileNameMap";

public const string CFSTR_FILENAMEMAPW = "FileNameMapW";

public const string CFSTR_SHELLURL = "UniformResourceLocator";

public const string CFSTR_INETURLA = CFSTR_SHELLURL;

public const string CFSTR_INETURLW = "UniformResourceLocatorW";

public const string CFSTR_PREFERREDDROPEFFECT = "Preferred DropEffect";

public const string CFSTR_PERFORMEDDROPEFFECT = "Performed DropEffect";

public const string CFSTR_PASTESUCCEEDED = "Paste Succeeded";

public const string CFSTR_INDRAGLOOP = "InShellDragLoop";

public const string CFSTR_DRAGCONTEXT = "DragContext";

public const string CFSTR_MOUNTEDVOLUME = "MountedVolume";

public const string CFSTR_PERSISTEDDATAOBJECT = "PersistedDataObject";

public const string CFSTR_TARGETCLSID = "TargetCLSID";

public const string CFSTR_LOGICALPERFORMEDDROPEFFECT = "Logical Performed DropEffect";

public const string CFSTR_AUTOPLAY_SHELLIDLISTS = "Autoplay Enumerated IDList Array";

}



public class ShellDataObject: DataObject

{

//This flag is used to prevent multiple callings to "GetData" when dropping in Explorer.

private bool downloaded = false;

public override object GetData(String format)

{

Object obj = base.GetData(format);

if (System.Windows.Forms.DataFormats.FileDrop == format &&

!InDragLoop() && !downloaded)

{

int i;

String[] files = (String[])obj;

for (i = 0; i < files.Length; i++)

{

DownloadFile(files[i]);

}

downloaded = true;

}

return obj;

}



private void DownloadFile(String filename)

{

StreamWriter file;

// Create the temporary file and its data

file = new StreamWriter(filename);

file.WriteLine(filename);

file.Close();

}



private bool InDragLoop()

{

return (0 != (int)GetData(ShellClipboardFormats.CFSTR_INDRAGLOOP));

}

}

}

2.
在VC中,我们可以如此做

Querying information from an Explorer window
By Raymond Chen
From http://weblogs.asp.net/oldnewthing/archive/2004/07/20.aspx

Sometimes software development is inventing new stuff. But often, it's just putting together the stuff you already have. Today's puzzle is one of the latter type of problem.

Given a window handle, you can you determine (1) whether it is an Explorer window, and if so (2) what folder it is viewing, and (3) what item is currently focused.

This is not an inherently difficult task. You just have to put together lots of small pieces.

Start with the ShellWindows object which represents all the open shell windows. You can enumerate through them all with the Item property. This is rather clumsy from C++ because the ShellWindows object was designed for use by a scripting language like JScript or Visual Basic.

IShellWindows *psw;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
IID_IShellWindows, (void**)&psw))) {
VARIANT v;
V_VT(&v) = VT_I4;
IDispatch *pdisp;
BOOL fFound = FALSE;
for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
V_I4(&v)++) {
...
pdisp->Release();
}
psw->Release();
}

From each item, we can ask it for its window handle and see if it's the one we want.

IWebBrowserApp *pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
hwndWBA == hwndFind) {
fFound = TRUE;
...
}
pwba->Release();
}

Okay, now that we have found the folder via its IWebBrowserApp, we need to get to the top shell browser. This is done by querying for the SID_STopLevelBrowser service and asking for the IShellBrowser interface.

IServiceProvider *psp;
if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
IShellBrowser *psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
IID_IShellBrowser, (void**)&psb))) {
...
psb->Release();
}
psp->Release();
}

From the IShellBrowser, we can ask for the current shell view via the QueryActiveShellView method.

IShellView *psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
...
psv->Release();
}

Of course, what we really want is the IFolderView interface, which is the automation object that contains all the real goodies.

IFolderView *pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
(void**)&pfv))) {
...
pfv->Release();
}

Okay, now we're golden. What do you want to get from the view? How about the location of the IShellFolder being viewed. To do that, we need to use IPersistFolder2::GetCurFolder. The GetFolder method will give us access to the shell folder, from which we ask for IPersistFolder2. (Most of the time you want the IShellFolder interface, since that's where most of the cool stuff hangs out.)

IPersistFolder2 *ppf2;
if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
(void**)&ppf2))) {
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
...
CoTaskMemFree(pidlFolder);
}
ppf2->Release();
}

Let's convert that pidl into a path, for display purposes.

if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
lstrcpyn(g_szPath, TEXT(""), MAX_PATH);
}
...

What else can we do with what we've got? Oh right, let's see what the currently-focused object is.

int iFocus;
if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
...
}

Let's display the name of the focused item. To do that we need the item's pidl and the IShellFolder. (See, I told you the IShellFolder is where the cool stuff is.) The item comes from the Item method (surprisingly enough).

LPITEMIDLIST pidlItem;
if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
...
CoTaskMemFree(pidlItem);
}

(If we had wanted a list of selected items we could have used the Items method, passing SVGIO_SELECTION.)

After we get the item's pidl, we also need the IShellFolder:

IShellFolder *psf;
if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
(void**)&psf))) {
...
psf->Release();
}

Then we put the two together to get the item's display name, with the help of the GetDisplayNameOf method.

STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
SHGDN_INFOLDER,
&str))) {
...
}

We can use the helper function StrRetToBuf to convert the kooky STRRET structure into a boring string buffer. (The history of the kooky STRRET structure will have to wait for another day.)

StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);

Okay, let's put this all together. It looks rather ugly because I put everything into one huge function instead of breaking them out into subfunctions. In "real life" I would have broken things up into little helper functions to make things more manageable.

Start with the scratch program and add this new function:

#include
#include

TCHAR g_szPath[MAX_PATH];
TCHAR g_szItem[MAX_PATH];

void CALLBACK RecalcText(HWND hwnd, UINT, UINT_PTR, DWORD)
{
HWND hwndFind = GetForegroundWindow();
g_szPath[0] = TEXT('\0');
g_szItem[0] = TEXT('\0');

IShellWindows *psw;
if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
IID_IShellWindows, (void**)&psw))) {
VARIANT v;
V_VT(&v) = VT_I4;
IDispatch *pdisp;
BOOL fFound = FALSE;
for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
V_I4(&v)++) {
IWebBrowserApp *pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
hwndWBA == hwndFind) {
fFound = TRUE;
IServiceProvider *psp;
if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
IShellBrowser *psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
IID_IShellBrowser, (void**)&psb))) {
IShellView *psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
IFolderView *pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
(void**)&pfv))) {
IPersistFolder2 *ppf2;
if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
(void**)&ppf2))) {
LPITEMIDLIST pidlFolder;
if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
lstrcpyn(g_szPath, TEXT(""), MAX_PATH);
}
int iFocus;
if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
LPITEMIDLIST pidlItem;
if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
IShellFolder *psf;
if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
(void**)&psf))) {
STRRET str;
if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
SHGDN_INFOLDER,
&str))) {
StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
}
psf->Release();
}
CoTaskMemFree(pidlItem);
}
}
CoTaskMemFree(pidlFolder);
}
ppf2->Release();
}
pfv->Release();
}
psv->Release();
}
psb->Release();
}
psp->Release();
}
}
pwba->Release();
}
pdisp->Release();
}
psw->Release();
}
InvalidateRect(hwnd, NULL, TRUE);
}

Now all we have to do is call this function periodically and print the results.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
SetTimer(hwnd, 1, 1000, RecalcText);
return TRUE;
}

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
TextOut(pps->hdc, 0, 0, g_szPath, lstrlen(g_szPath));
TextOut(pps->hdc, 0, 20, g_szItem, lstrlen(g_szItem));
}

We're ready to roll. Run this program and set it to the side. Then launch an Explorer window and watch the program track the folder you're in and what item you have focused.

Okay, so I hope I made my point: Often, the pieces you need are already there; you just have to figure out how to put them together. Notice that each of the pieces is in itself not very big. You just had to recognize that they could be put together in an interesting way.



<< Home

This page is powered by Blogger. Isn't yours?