Перетащите файл из WPF в Проводник - с обработкой файла перед удалением
В моем приложении я хочу, чтобы пользователи могли перетаскивать файлы (визуальное представление) из моего окна в проводник. Но мне нужно пропустить файлы через "очистку" (то есть это не просто копирование и вставка, я на самом деле отправляю файл на сервер CDR и возвращаю его обратно).
Вопрос - как мне все это инициировать? Поскольку кажется, что вы не можете получить назначение перетаскивания... Я хочу отложить фактическое событие перетаскивания и ввести свою логику между ними.
Я нашел один обходной путь, основанный на этом посте в блоге, для создания виртуального файла - это позволяет мне в основном отправить функцию очистки, дождаться создания очищенного файла в некотором временном местоположении, а затем выполнить магию перетаскивания.,
Это прекрасно работает, но есть один недостаток - он загружает весь файл в память... Так как файлы могут быть довольно большими, меня это беспокоит. Я почти уверен, что должен быть способ сделать это без этого (возможно даже без виртуального беспорядка файла), я просто не понимаю достаточно, как сделать это. Кто-нибудь может предложить какую-либо помощь?
КОД:
С моей точки зрения:
private void ItemMouseDown(object sender, MouseButtonEventArgs e)
{
var t = MainWindow.CurrentMainWindow.GetSelected();
DragDropFile(SanitizeFiles, t, LocalTempPath);
}
Метод dragdrop (полный код можно найти в блоге Дэвида Ансона - ссылка выше):
public void DragDropFile(Func<string[], string, bool> sanitizeFiles, string path, string tempSanitizedLocation)
{
string sanitizedFile = Path.Combine(tempSanitizedLocation, Path.GetFileName(path));
var x = new[]
{
new VirtualFileDataObject.FileDescriptor
{
Name = Path.GetFileName(path),
ChangeTimeUtc = DateTime.Now,
StreamContents = stream =>
{
sanitizeFiles(path, tempSanitizedLocation);
int i = 0;
while (!File.Exists(sanitizedFile))
{
Thread.Sleep(500);
i++;
if (i > 1000) // timeout = 500 seconds
break;
}
// fast - spike in memory, immediate drop (GC)
var content = File.ReadAllBytes(sanitizedFile);
stream.Write(content, 0, content.Length);
// slow - gradual rise in memory to same level as the fast, and immediate drop for GC
//using (var fs = new FileStream(sanitizedFile, FileMode.Open, FileAccess.Read))
//{
// fs.CopyTo(stream, 131072);
//}
// delete temp file
try
{
File.Delete(sanitizedFile);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
};
var virtualFileDataObject = new VirtualFileDataObject();
virtualFileDataObject.SetData(x);
try
{
VirtualFileDataObject.DoDragDrop(virtualFileDataObject, DragDropEffects.Copy);
}
catch (COMException)
{
// Failure; no way to recover
}
}
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "dragSource", Justification = "Parameter is present so the signature matches that of System.Windows.DragDrop.DoDragDrop.")]
public static DragDropEffects DoDragDrop(System.Runtime.InteropServices.ComTypes.IDataObject dataObject, DragDropEffects allowedEffects)
{
int[] finalEffect = new int[1];
try
{
// (the ole method)
NativeMethods.DoDragDrop(dataObject, new DropSource(), (int)allowedEffects, finalEffect);
}
finally
{
var virtualFileDataObject = dataObject as VirtualFileDataObject;
if ((null != virtualFileDataObject) && !virtualFileDataObject.IsAsynchronous && virtualFileDataObject._inOperation)
{
// Call the end action and exit the operation
if (null != virtualFileDataObject._endAction)
{
virtualFileDataObject._endAction(virtualFileDataObject);
}
virtualFileDataObject._inOperation = false;
}
}
return (DragDropEffects)(finalEffect[0]);
}
(Пока игнорируйте цикл while- это можно улучшить, немного изменив метод sanitizaion.)
Может быть, можно как-то изменить метод SetData / класс DataObject - чтобы вместо ожидания потока и воссоздания файла (что на самом деле не то, что мне нужно), он возьмет строку и сделает немедленное копирование?
Еще немного кода:
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Deliberate to provide obvious coupling.")]
public class FileDescriptor
{
/// <summary>
/// Gets or sets the name of the file.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets the (optional) length of the file.
/// </summary>
public Int64? Length { get; set; }
/// <summary>
/// Gets or sets the (optional) change time of the file.
/// </summary>
public DateTime? ChangeTimeUtc { get; set; }
/// <summary>
/// Gets or sets an Action that returns the contents of the file.
/// </summary>
public Action<Stream> StreamContents { get; set; }
}
public void SetData(IEnumerable<FileDescriptor> fileDescriptors)
{
// Prepare buffer
var bytes = new List<byte>();
// Add FILEGROUPDESCRIPTOR header
bytes.AddRange(StructureBytes(new NativeMethods.FILEGROUPDESCRIPTOR { cItems = (uint)(fileDescriptors.Count()) }));
// Add n FILEDESCRIPTORs
foreach (var fileDescriptor in fileDescriptors)
{
// Set required fields
var FILEDESCRIPTOR = new NativeMethods.FILEDESCRIPTOR
{
cFileName = fileDescriptor.Name,
};
// Set optional timestamp
if (fileDescriptor.ChangeTimeUtc.HasValue)
{
FILEDESCRIPTOR.dwFlags |= NativeMethods.FD_CREATETIME | NativeMethods.FD_WRITESTIME;
var changeTime = fileDescriptor.ChangeTimeUtc.Value.ToLocalTime().ToFileTime();
var changeTimeFileTime = new System.Runtime.InteropServices.ComTypes.FILETIME
{
dwLowDateTime = (int)(changeTime & 0xffffffff),
dwHighDateTime = (int)(changeTime >> 32),
};
FILEDESCRIPTOR.ftLastWriteTime = changeTimeFileTime;
FILEDESCRIPTOR.ftCreationTime = changeTimeFileTime;
}
// Set optional length
if (fileDescriptor.Length.HasValue)
{
FILEDESCRIPTOR.dwFlags |= NativeMethods.FD_FILESIZE;
FILEDESCRIPTOR.nFileSizeLow = (uint)(fileDescriptor.Length & 0xffffffff);
FILEDESCRIPTOR.nFileSizeHigh = (uint)(fileDescriptor.Length >> 32);
}
// Add structure to buffer
bytes.AddRange(StructureBytes(FILEDESCRIPTOR));
}
// Set CFSTR_FILEDESCRIPTORW
SetData(FILEDESCRIPTORW, bytes);
// Set n CFSTR_FILECONTENTS
var index = 0;
foreach (var fileDescriptor in fileDescriptors)
{
SetData(FILECONTENTS, index, fileDescriptor.StreamContents);
index++;
}
}
ОБНОВЛЕНИЕ: если я изменю дескриптор файла, чтобы содержать Func<string>
вместо Action<Stream>
Я могу вернуть путь, а затем в SetData(FILECONTENTS, index, fileDescriptor.StreamContents)
- Я могу получить IntPtr этого пути к файлу и вернуть этот кортеж. Похоже, это "работает" - файл копируется в папку размещения и не загружается в память. Единственная "маленькая" проблема заключается в том, что проводник получает исключение (при перетаскивании на рабочий стол он перезагружается с черным экраном, при открытии проводника в открытой папке он закрывает это окно); Postmortem с WinDbg показывает, что я получаю "Нарушение прав доступа"... Но это насколько я могу получить... Я вижу, вы также можете изменить некоторые свойства, называемые tymed, чтобы быть TYMED.TYMED_FILE
вместо стрима, но это для меня пока не получается полностью.