Перетаскивание файлов на форму и обратно.
(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)


