HotDog's Blog

Hotdog (Robert Verpalen) about C# and vb.net

This blog hosted by:
http://blogs.vbcity.com      
  Home :: Syndication  :: Login

SepOctober 2008Nov
SMTWTFS
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

Articles

Archives

Topics

CONTACT

Fun but useful linkies

General

VS 2005

Wolfenstein ET

Wanted to have a wrapper around the drag and drop outlook attachments for the File Select Control, but that turned somewhat harder than expected. Saving one attachment was easy enough. It could be done completely managed (found some other examples that do the same on the net), but saving multiple wasn't to be found in .net (at least not as far as I could find :-/) and even though the File Select control doesn't support multiple files (yet), the wrapper certainly should :p

The problem is that with the windows forms DataObject created when dropping, you can only get data on the default index. The only solution seemed to be using the unmanaged wrappers.Luckily the first step was easy: the DataObject explicitely implements the ComTypes.IDataObject interface and thus could be parsed. Getting the proper formatter and all seemed to be a bigger headache. To make a long story (and more than half a day of trial and error frustration to create the entire wrapper) short, a combination of using the .net DataObject and the unmanaged wrappers lead to the solution (see below in this post to read just that part).

The entire wrapper: (for an example, see the File Select Control mentioned above)

Code CopyHideScrollFull


namespace
Subro.Outlook
{
using System;
using
System.Collections.Generic;
using
System.Text;
using
System.Windows.Forms;
using
System.IO;
//the interopservices are needed to get to unmanaged code to get the streams

//for attachments if there are more. (for one, it can be done problemless with managed code)

using
System.Runtime.InteropServices;
using
ct =System.Runtime.InteropServices.ComTypes;
using
System.Runtime.InteropServices.ComTypes;
///
///
Wrapper around the drop data from attachments from outlook
///

public
class OutlookAttachmentDropCatcher
{
public static string[] GetFiles(DragEventArgs e)
{
return new OutlookAttachmentDropCatcher(e).GetFiles();
}
///
///
The name to identify a file group when dragging outlook files
///

public
const string GroupIdentifier = "FileGroupDescriptor";
///

///
The name to identify the raw data of the attachments in the clipboard
///

DataFormats
.Format ContentsIdentifier = DataFormats.GetFormat("FileContents");
///
///
The drop data with which this OutlookAttachment object was created
///

public
readonly DataObject DropData;
public
OutlookAttachmentDropCatcher(DragEventArgs e)
: this((DataObject)e.Data)
{
}
public
OutlookAttachmentDropCatcher(DataObject Data)
{
//Source: http://blogs.vbcity.com/hotdog/archive/2006/03/16/5896.aspx
//check there for updates....

DropData = Data;
MemoryStream
s = (MemoryStream)Data.GetData(GroupIdentifier);
ReadHeaders(s);
s.Close();
}
#region COM structs
struct FILETIME
{
int dwLowDateTime;
int
dwHighDateTime;
public FILETIME(BinaryReader br)
{
dwLowDateTime = br.ReadInt32();
dwHighDateTime = br.ReadInt32();
}
}
struct CLSID
{
byte[] ID;
public
CLSID(BinaryReader br)
{
ID = br.ReadBytes(16);
}
}
struct
SizeL
{
public int X, Y;
public
SizeL(BinaryReader br)
{
X = br.ReadInt32();
Y = br.ReadInt32();
}
}
struct
FILEDESCRIPTOR
{
int dwFlags;
CLSID
clsid;
SizeL
sizel;
SizeL
pointl;
int
dwFileAttributes;
FILETIME
ftCreationTime;
FILETIME
ftLastAccessTime;
FILETIME
ftLastWriteTime;
int
nFileSizeHigh;
int
nFileSizeLow;
public
string FileName;
public FILEDESCRIPTOR(BinaryReader br)
{
//these flags aren't really filled when coming
//from outlook, but who knows....someday it might be used :p

dwFlags = br.ReadInt32();
clsid = new CLSID(br);
sizel = new SizeL(br);
pointl = new SizeL(br);
dwFileAttributes = br.ReadInt32();
ftCreationTime =  new FILETIME(br);
ftLastAccessTime = new FILETIME(br);
ftLastWriteTime = new FILETIME(br);
nFileSizeHigh = br.ReadInt32();
nFileSizeLow = br.ReadInt32();
const int MAX_PATH = 256;
byte
[] bytes = br.ReadBytes(MAX_PATH);
int
i = 0;
for
(; i < MAX_PATH && bytes[i] != 0; i++)
{ }
FileName = Encoding.ASCII.GetString(bytes, 0, i);
br.ReadInt32(); //not entirely sure what this entry does...
}
}
struct FILEGROUPDESCRIPTOR
{
uint cItems;
FILEDESCRIPTOR
[] fgd;
public
readonly string[] Names;
public FILEGROUPDESCRIPTOR(MemoryStream s)
{
BinaryReader br = new BinaryReader(s);
cItems = br.ReadUInt32();
fgd = new FILEDESCRIPTOR[cItems];
Names = new string[cItems];
for
(int i = 0; i < cItems; i++)
{
fgd[i] = new FILEDESCRIPTOR(br);
Names[i] = fgd[i].FileName;
}
}
}
#endregion
FILEGROUPDESCRIPTOR descriptor;
void
ReadHeaders(MemoryStream s)
{
descriptor = new FILEGROUPDESCRIPTOR(s);
}
string tempfolder = Environment.GetFolderPath(Environment.SpecialFolder.InternetCache) + @"\OLK3D2\";
///

///
The folder where temporary files are stored (when dragging files from outlook)
///

string
TempFolder
{
get
{
return tempfolder;
}
set

{
if (value == null) throw new ArgumentNullException();
tempfolder = value;
if
(!tempfolder.EndsWith(@"\")) tempfolder += @"\";
}
}
///
///
Gets the stream of the specified attachment (without saving)
///
NB: Name must be one of the attachment names (see )
///

///

///

public
MemoryStream GetStream(string Name)
{
return GetStream(IndexOf(Name));
}
public int IndexOf(string AttachmentName)
{
return Array.IndexOf<string>(descriptor.Names, AttachmentName);
}
///
///
Gets the stream of the FIRST attachment
///

///

public
MemoryStream GetStream()
{
return GetStream(0);
}
///
///
Gets the stream at Index of the available attachments
///

///

///

public
MemoryStream GetStream(int Index)
{
if (Index == 0)
{
//the first is easy: just copy the formatted .net data
MemoryStream
ms = (MemoryStream)DropData.GetData(ContentsIdentifier.Name);
return
ms;
}
return
GetHigherAttachment(Index);
}
#region
unmanaged

MemoryStream GetHigherAttachment(int Index)
{
//this is where it gets ugly :p
ct.IDataObject data = (ct.IDataObject)DropData;
STGMEDIUM
medium;
#region possible errors (not used)
/*
unchecked
{
const int
S_OK = 0,
DV_E_LINDEX = (int)0x80040068,
DV_E_FORMATETC = (int)0x80040064,
DV_E_TYMED = (int)0x80040069,
//DV_E_DVASPECT = -2147221397,
E_INVALIDARG = (int)0x80070057,
E_UNEXPECTED = (int)0x8000FFFF,
OLE_E_NOTRUNNING = (int)0x80040005,
E_OUTOFMEMORY = (int)0x8007000E;
}*/

#endregion
//find the proper format
IEnumFORMATETC
formats = data.EnumFormatEtc(DATADIR.DATADIR_GET);
int
cnt = DropData.GetFormats().Length;
FORMATETC
[] resF = new FORMATETC[cnt];
int
[] resI = new int[cnt];
formats.Next(cnt, resF, resI);
int
index = Array.IndexOf<string>(DropData.GetFormats(), ContentsIdentifier.Name);
FORMATETC
format = resF[index];
//set the index to get
format.lindex = Index;
//get the istream object
data.GetData(ref format, out medium);
IStream
stream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
//read the istream into a memorystream (saving could have been done directly, but this is the more general approach)
MemoryStream
ms = new MemoryStream();
const
int Block_Length = 1024;
byte
[] bytes = new byte[Block_Length];
ulong
count = 0;
IntPtr
countptr = Marshal.AllocHGlobal(8);
Marshal
.StructureToPtr(count, countptr, true);
try

{
for (; ; )
{
stream.Read(bytes, Block_Length, countptr);
count = (uint)Marshal.PtrToStructure(countptr, typeof(uint));
if
(count == 0) break;
ms.Write(bytes, 0, (int)count);
}
}
finally

{
Marshal.FreeHGlobal(countptr);
}
return ms;
}
#endregion
///
///
The number of dropped attachments
///

public
int AttachmentCount
{
get { return descriptor.Names.Length; }
}
///
///
Returns the attachment names
///

///

public
string[] GetNames()
{
return descriptor.Names;
}
///
///
Saves the file to the temp directory and returns the full location.
///
NB: Name must be one of the attachments (see )
///

///

///

public
string GetFile(string Name)
{
return GetFile(IndexOf(Name));
}
///
///
Saves the FIRST file to the temp directory and returns its url. You can use this function
///
if you are sure there's only one attachment
///

///

public
string GetFile()
{
return GetFile(0);
}
///
///
Saves the files at the Index position of the available attachments
///

///

///

public
string GetFile(int Index)
{
string Name = descriptor.Names[Index];
string
url = tempfolder + Name;
return
SaveAttachment(Name, url);
}
private bool alwaysoverwrite = true;
///

///
Indicates if (temporary) files may always be overwritten when saving
///

public
bool AlwaysOverWrite
{
get { return alwaysoverwrite; }
set
{ alwaysoverwrite = value; }
}


///
///
Saves the Attachment to the specified location
///
NB: Name must be one of the attachments (see )
///

///

///

public
string SaveAttachment(string AttachmentName, string Path)
{
return SaveAttachment(AttachmentName, Path, alwaysoverwrite);
}
public string SaveAttachment(int AttachmentIndex, string Path)
{
return SaveAttachment(AttachmentIndex, Path, alwaysoverwrite);
}
public
string SaveAttachment(string AttachmentName, string Path, bool AlwaysOverWrite)
{
return SaveAttachment(IndexOf(AttachmentName), Path, AlwaysOverWrite);
}
public
string SaveAttachment(int AttachmentIndex, string Path, bool AlwaysOverWrite)
{
if (!AlwaysOverWrite && File.Exists(Path))
{
switch (MessageBox.Show("Temporary file for " + descriptor.Names[AttachmentIndex] + " allready exists. Overwrite?", "file exists", MessageBoxButtons.YesNoCancel))
{
case DialogResult.No:
return Path;
case DialogResult.Cancel:
return null;
}
}
File.WriteAllBytes(Path, GetStream(AttachmentIndex).ToArray());
return
Path;
}
///
///
saves all files to the temporary directory and returns the url's
///

///

public
string[] GetFiles()
{
string[] files = new string[descriptor.Names.Length];
int
i = 0;
foreach
(string name in descriptor.Names)
{
files[i++] = GetFile(name);
}
return
files;
}
}
}
. . .

The specific multiple attachments code:

Code CopyHideScrollFull
#region unmanaged

MemoryStream GetHigherAttachment(int Index)
{
//this is where it gets ugly :p
ct.IDataObject data = (ct.IDataObject)DropData;
STGMEDIUM
medium;
#region possible errors (not used)
/*
unchecked
{
const int
S_OK = 0,
DV_E_LINDEX = (int)0x80040068,
DV_E_FORMATETC = (int)0x80040064,
DV_E_TYMED = (int)0x80040069,
//DV_E_DVASPECT = -2147221397,
E_INVALIDARG = (int)0x80070057,
E_UNEXPECTED = (int)0x8000FFFF,
OLE_E_NOTRUNNING = (int)0x80040005,
E_OUTOFMEMORY = (int)0x8007000E;
}*/

#endregion
//find the proper format
IEnumFORMATETC
formats = data.EnumFormatEtc(DATADIR.DATADIR_GET);
int
cnt = DropData.GetFormats().Length;
FORMATETC
[] resF = new FORMATETC[cnt];
int
[] resI = new int[cnt];
formats.Next(cnt, resF, resI);
int
index = Array.IndexOf<string>(DropData.GetFormats(), ContentsIdentifier.Name);
FORMATETC
format = resF[index];
//set the index to get
format.lindex = Index;
//get the istream object
data.GetData(ref format, out medium);
IStream
stream = (IStream)Marshal.GetObjectForIUnknown(medium.unionmember);
//read the istream into a memorystream (saving could have been done directly, but this is the more general approach)
MemoryStream
ms = new MemoryStream();
const
int Block_Length = 1024;
byte
[] bytes = new byte[Block_Length];
ulong
count = 0;
IntPtr
countptr = Marshal.AllocHGlobal(8);
Marshal
.StructureToPtr(count, countptr, true);
try

{
for (; ; )
{
stream.Read(bytes, Block_Length, countptr);
count = (uint)Marshal.PtrToStructure(countptr, typeof(uint));
if
(count == 0) break;
ms.Write(bytes, 0, (int)count);
}
}
finally

{
Marshal.FreeHGlobal(countptr);
}
return ms;
}
#endregion
. . .
posted on Thursday, March 16, 2006 10:35 AM