Перетаскивание файлов на форму и обратно.

(Drag and drop, cut/copy and paste files with Windows Explorer).

Введение.

Недавно, мне нужно было реализовать перетаскивание файлов с Windows Explorer в программу, но все примеры, что я нашел, не демонстрировали то, что мне было нужно, к тому же, был необходим пример на С#.

Для примера я написал программу, которая содержит список файлов (их полных путей) папки, и позволяет перетаскивать их из программы в экслорер и наоборот, причем вы можете использовать горячие клавиши (копировать, перемещать, вставлять), как это делается в эксплорере. Так же можно вызывать контекстное меню для данного файла из списка правой кнопкой мыши. Я обнаружил, что получение уведомлений о событии, когда кто-то вставляет файлы из списка в другую папку невозможно (видимо, автор статьи использовал. Net Framework 1.1, так как начиная с 2.0 присутствует объект FileSystemWatcher, выполняющий эту задачу). Поэтому я реализовал еще и объект слежения (watcher), которое отвечает за изменение файлов в данной папке, чтобы обновлять их список в программе.

И еще…

Я нашел парочку примеров на MSDN по drag and drop, но там не показана работа с файлами, но все-таки они будут полезны для прочтения. (Performing Drag-and-Drop Operations and Control. DoDragDrop Method).

Разбор кода программы.

Программа достаточно проста и понятна, я, лишь, объясню только не совсем очевидные куски кода.

Для того чтобы перетаскивать объект из программы в эксплорер, было реализовано событие ItemDrag из объекта ListView, который вызывается при перетаскивания файла из списка (смещение на несколько пикселей), в событии мы просто вызываем функцию DoDragDrop, список файлов упакован как объект DataObject. Но, на самом деле, вам не нужно знать как работает DataObject, он просто реализует интерфейс IDataObject для связи.

НЕ нашли? Не то? Что вы ищете?

/// <summary>

/// Called when we start dragging an item out of our listview

/// </summary>

private void listView1_ItemDrag(object sender,

System. Windows. Forms. ItemDragEventArgs e)

{

string[] files = GetSelection();

if(files!= null)

{

DoDragDrop(new DataObject(DataFormats. FileDrop, files),

DragDropEffects. Copy |

DragDropEffects. Move /* |

DragDropEffects. Link */);

RefreshView();

}

}

Листинг 1 – реализация события ItemDrag.

Далее, если объект перетаскивается за окно программы, то вызывается метод DragOver. Теперь нужно сообщить объекту, который вызывал что делать. Это можно сделать с помощью свойства e. Effect.

/// <summary>

/// Called when someone drags something over our listview

/// </summary>

private void listView1_DragOver(object sender,

System. Windows. Forms. DragEventArgs e)

{

// Determine whether file data exists in the drop data. If not, then

// the drop effect reflects that the drop cannot occur.

if (!e. Data. GetDataPresent(DataFormats. FileDrop))

{

e. Effect = DragDropEffects. None;

return;

}

// Set the effect based upon the KeyState.

if ((e. KeyState & SHIFT) == SHIFT &&

(e. AllowedEffect & DragDropEffects. Move) == DragDropEffects. Move)

{

e. Effect = DragDropEffects. Move;

}

else if ((e. KeyState & CTRL) == CTRL &&

(e. AllowedEffect & DragDropEffects. Copy) == DragDropEffects. Copy)

{

e. Effect = DragDropEffects. Copy;

}

else if ((e. AllowedEffect & DragDropEffects. Move) == DragDropEffects. Move)

{

// By default, the drop action should be move, if allowed.

e. Effect = DragDropEffects. Move;

// Implement the rather strange behaviour of explorer that if the disk

// is different, then default to a COPY operation

string[] files = (string[])e. Data. GetData(DataFormats. FileDrop);

if (files. Length > 0 && !files[0].ToUpper().StartsWith(homeDisk) &&

// Probably better ways to do this

(e. AllowedEffect & DragDropEffects. Copy) == DragDropEffects. Copy)

e. Effect = DragDropEffects. Copy;

}

else

e. Effect = DragDropEffects. None;

// This is an example of how to get the item under the mouse

Point pt = listView1.PointToClient(new Point(e. X, e. Y));

ListViewItem itemUnder = listView1.GetItemAt(pt. X, pt. Y);

}

Листинг 2 – обработчик DragOver.

И, наконец, вызывается метод DragDrop, когда кнопка мыши отпускается в поле окна, непосредственно здесь, уже делаем операции копировать\вставить.

/// <summary>

/// Somebody dropped something on our listview - perform the action

/// </summary>

private void listView1_DragDrop(object sender,

System. Windows. Forms. DragEventArgs e)

{

// Can only drop files, so check

if (!e. Data. GetDataPresent(DataFormats. FileDrop))

{

return;

}

string[] files = (string[])e. Data. GetData(DataFormats. FileDrop);

foreach (string file in files)

{

string dest = homeFolder + "\\" + Path. GetFileName(file);

bool isFolder = Directory. Exists(file);

bool isFile = File. Exists(file);

if (!isFolder && !isFile)

// Ignore if it doesn't exist

continue;

try

{

switch(e. Effect)

{

case DragDropEffects. Copy:

if(isFile)

// TODO: Need to handle folders

File. Copy(file, dest, false);

break;

case DragDropEffects. Move:

if (isFile)

File. Move(file, dest);

break;

case DragDropEffects. Link:

// TODO: Need to handle links

break;

}

}

catch(IOException ex)

{

MessageBox. Show(this, "Failed to perform the" +

" specified operation:\n\n" + ex. Message,

"File operation failed", MessageBoxButtons. OK,

MessageBoxIcon. Stop);

}

}

RefreshView();

}

Листинг 3 – обработчик DragDrop.

Вот, в принципе, и все. Но, вот еще несколько замечаний. Если нужно использовать свои курсоры, то можно реализовать событие GiveFeedback, и если необходимо знать, когда перемещаемый объект выходит за границы вашего окна, то нужно реализовать событие QueryContinueDrag, но я не стал этого делать.

Копирование и вставка с помощью горячих клавиш реализована еще проще, мы просто заносим имя файла в буфер обмена с помощью объекта DataObject, который уже встречался выше.

/// <summary>

/// Write files to clipboard (from

/// http://blogs. /idecember/

/// archive/2005/10/27/10979.aspx)

/// </summary>

/// <param name="cut">True if cut, false if copy</param>

void CopyToClipboard(bool cut)

{

string[] files = GetSelection();

if(files!= null)

{

IDataObject data = new DataObject(DataFormats. FileDrop, files);

MemoryStream memo = new MemoryStream(4);

byte[] bytes = new byte[]{(byte)(cut? 2 : 5), 0, 0, 0};

memo. Write(bytes, 0, bytes. Length);

data. SetData("Preferred DropEffect", memo);

Clipboard. SetDataObject(data);

}

}

Листинг 4 – Копирование в буфер обмена.

И, теперь, когда вставляем объект в программу.

/// <summary>

/// Paste context menu option

/// </summary>

private void pasteMenuItem_Click(object sender, System. EventArgs e)

{

IDataObject data = Clipboard. GetDataObject();

if (!data. GetDataPresent(DataFormats. FileDrop))

return;

string[] files = (string[])

data. GetData(DataFormats. FileDrop);

MemoryStream stream = (MemoryStream)

data. GetData("Preferred DropEffect", true);

int flag = stream. ReadByte();

if (flag!= 2 && flag!= 5)

return;

bool cut = (flag == 2);

foreach (string file in files)

{

string dest = homeFolder + "\\" +

Path. GetFileName(file);

try

{

if(cut)

File. Move(file, dest);

else

File. Copy(file, dest, false);

}

catch(IOException ex)

{

MessageBox. Show(this, "Failed to perform the" +

" specified operation:\n\n" + ex. Message,

"File operation failed",

MessageBoxButtons. OK, MessageBoxIcon. Stop);

}

}

RefreshView();

}

Листинг 5 – Вставка объекта в список.

Интересный момент.

Самая дуратская штука была в том, что при перетаскивании объекта из программы в эксплорер, он перемещал (вырезал) его из данной папки, и не было способа получить уведомление об этом, так как нужно обновлять список файлов в программе. И пришлось реализовывать и это (На самом деле начиная с. Net Framework 2.0 существует объект FileSystemWatcher, так что это можно пропустить).

Приложение.

Автор оригинальной статьи на английском: Paul Tallett (http://www. /KB/shell/Explorer_Drag_Drop. aspx)

Лицензия: The Code Project Open License (CPOL)