Drag/Drop Outlook message to form
I want to be able to drag & drop an email message from Outlook onto my form & then save the message to a specified location & also save any attachments to a specified location. I created a label that will accept drag/drop & have the required event handlers in my code. The problem now is I don't know how to cast the dropped message to an Outlook.MailItem object.
When I drop a message onto the label I get this error:
"Unable to cast object of type 'System.Windows.Forms.DataObject' to type 'Microsoft.Office.Interop.Outlook._MailItem'"
Is there a way to convert the DataObject directly to an MailItem object. Thanks for any help...
This is the code I am trying.
Code:
Imports Microsoft.Office.Interop
Private Sub Label1_DragEnter(sender As Object, e As DragEventArgs) Handles Label1.DragEnter
Dim dataFormat = e.Data.GetFormats(False)(1)
If dataFormat = "RenPrivateLatestMessages" Then
e.Effect = DragDropEffects.Copy
End If
End Sub
Private Sub Label1_DragDrop(sender As Object, e As DragEventArgs) Handles Label1.DragDrop
Try
If e.Data.GetFormats(False)(1) = "RenPrivateLatestMessages" Then
Dim oApp As Outlook._Application
Dim oMsg As Outlook._MailItem
oApp = New Outlook.Application
oMsg = CType(e.Data, Outlook._MailItem) <-- stops here
End If
Catch ex As Exception
'handle error
End Try
End Sub
Re: Drag/Drop Outlook message to form
Re: Drag/Drop Outlook message to form
I don't believe this can be handled natively. You have to build in the feature of accepting an OLE object, instead of the few simple items it can handle. This GitHub repository has some info on doing this https://github.com/yasoonOfficial/outlook-dndprotocol, it is in C# but maybe you can get enough information from the setup to make it work, or just write your Winform app in C#.
Re: Drag/Drop Outlook message to form
Quote:
Originally Posted by
.paul.
Thanks for the reply, but not really what I'm looking for. I know how to use the MailItem object. I just can't figure out how to cast the dropped object to a MailItem object.
Re: Drag/Drop Outlook message to form
Quote:
Originally Posted by
nbrege
Thanks for the reply, but not really what I'm looking for. I know how to use the MailItem object. I just can't figure out how to cast the dropped object to a MailItem object.
I don't think you can cast it. While searching, i saw something about creating a new MailItem from an object. Not sure if you can do that with a DataObject
2 Attachment(s)
Re: Drag/Drop Outlook message to form
I played around with the Winform (they're using .Net Framework 4.5) project from the GitHub link. It didn't have the OleDataReader that the WPF project did, so I moved that over. In this code it takes the dragged object and retrieves the mail item EntryID and then uses that to grab the mail item from outlook and you can do with it what you normally can.
This is still C#; I've not converted it over to vb.net yet.
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Outlook = Microsoft.Office.Interop.Outlook;
namespace OutlookDndWinForms
{
public partial class Form1 : Form
{
private LocalDropTarget myDropTarget = null;
public Form1()
{
InitializeComponent();
}
public IOleDropTarget GetDropTarget()
{
this.myDropTarget = new LocalDropTarget();
return myDropTarget;
}
class LocalDropTarget : IOleDropTarget
{
private OleOutlookData _outlookObj;
public OleOutlookData outlookObj
{
get => _outlookObj;
}
public void OnDragDrop(System.Windows.DataObject d)
{
Trace.WriteLine("OnDragDrop");
var formats = d.GetFormats();
foreach (var format in formats)
Trace.WriteLine(format);
System.Windows.DataObject obj = d;
MemoryStream data = (MemoryStream)obj.GetData("RenPrivateMessages");
if (data is MemoryStream)
{
OleDataReader reader = new OleDataReader(data);
_outlookObj = reader.ReadOutlookData();
Trace.WriteLine($"Subject:{_outlookObj.Items[0].Subject}");
Trace.WriteLine($"Entry ID:{_outlookObj.Items[0].EntryId}");
}
}
public int OleDragEnter(object pDataObj, int grfKeyState, long pt, ref int pdwEffect)
{
Trace.WriteLine("OleDragEnter");
Marshal.FinalReleaseComObject(pDataObj);
return 0;
}
public int OleDragOver(int grfKeyState, long pt, ref int pdwEffect)
{
Trace.WriteLine("OleDragOver");
return 0;
}
public int OleDragLeave()
{
Trace.WriteLine("OleDragEnter");
return 0;
}
public int OleDrop(object pDataObj, int grfKeyState, long pt, ref int pdwEffect)
{
Trace.WriteLine("OleDrop");
System.Windows.DataObject data = new System.Windows.DataObject(pDataObj);
OnDragDrop(data);
Marshal.FinalReleaseComObject(pDataObj);
return 0;
}
}
private void button1_Click(object sender, EventArgs e)
{
var res = RegisterDragDrop(this.panel1.Handle, GetDropTarget());
lbInfo.Items.Add("registered");
}
//Native imports
[DllImport("ole32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int RegisterDragDrop(IntPtr hwnd, IOleDropTarget target);
[ComImport(), Guid("00000122-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleDropTarget
{
[PreserveSig]
int OleDragEnter(
[In, MarshalAs(UnmanagedType.Interface)]
object pDataObj,
[In, MarshalAs(UnmanagedType.U4)]
int grfKeyState,
[In, MarshalAs(UnmanagedType.U8)]
long pt,
[In, Out]
ref int pdwEffect);
[PreserveSig]
int OleDragOver(
[In, MarshalAs(UnmanagedType.U4)]
int grfKeyState,
[In, MarshalAs(UnmanagedType.U8)]
long pt,
[In, Out]
ref int pdwEffect);
[PreserveSig]
int OleDragLeave();
[PreserveSig]
int OleDrop(
[In, MarshalAs(UnmanagedType.Interface)]
object pDataObj,
[In, MarshalAs(UnmanagedType.U4)]
int grfKeyState,
[In, MarshalAs(UnmanagedType.U8)]
long pt,
[In, Out]
ref int pdwEffect);
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void btnFindMail_Click(object sender, EventArgs e)
{
OleOutlookData outlookData = myDropTarget.outlookObj;
lbInfo.Items.Add($"Searching Outlook for Entry-ID: {outlookData.Items[0].EntryId}");
lbInfo.Items.Add("Getting Outlook reference");
Outlook.Application oApp = new Outlook.Application();
var ns = oApp.GetNamespace("MAPI");
var item = ns.GetItemFromID(outlookData.Items[0].EntryId) as Outlook.MailItem;
lbInfo.Items.Add(item.SenderName);
lbInfo.Items.Add(item.SenderEmailAddress);
lbInfo.Items.Add(item.To);
//oApp.Quit();
}
}
}
1 Attachment(s)
Re: Drag/Drop Outlook message to form
Here is the VB.NET version:
Code:
Imports System.Runtime.InteropServices
Imports Outlook = Microsoft.Office.Interop.Outlook
Imports System.Windows
Imports System.IO
Public Class Form1
Dim myDropTarget As LocalDropTarget = Nothing
Public Function GetDropTarget() As IOleDropTarget
Me.myDropTarget = New LocalDropTarget
Return myDropTarget
End Function
Friend Class LocalDropTarget
Implements IOleDropTarget
Private _outlookObj As OleOutlookData
Public ReadOnly Property outlookObj As OleOutlookData
Get
Return _outlookObj
End Get
End Property
Public Sub OnDragDrop(d As System.Windows.DataObject)
Trace.WriteLine("OnDragDrop")
Dim frmts = d.GetFormats()
For Each fmt In frmts
Trace.WriteLine(fmt)
Next
Dim obj As System.Windows.DataObject = d
Dim data = CType(obj.GetData("RenPrivateMessages"), MemoryStream)
If TypeOf data Is MemoryStream Then
Dim reader As OleDataReader = New OleDataReader(data)
_outlookObj = reader.ReadOutlookData()
Trace.WriteLine($"Subject:{_outlookObj.Items(CInt(0)).Subject}")
Trace.WriteLine($"Entry ID:{_outlookObj.Items(CInt(0)).EntryId}")
End If
End Sub
Private Function IOleDropTarget_OleDragEnter(pDataObj As Object, grfKeyState As Integer, pt As Long, ByRef pdwEffect As Integer) As Integer Implements IOleDropTarget.OleDragEnter
Trace.WriteLine("OleDragEnter")
Marshal.FinalReleaseComObject(pDataObj)
Return 0
End Function
Private Function IOleDropTarget_OleDragOver(grfKeyState As Integer, pt As Long, ByRef pdwEffect As Integer) As Integer Implements IOleDropTarget.OleDragOver
Trace.WriteLine("OleDragOver")
Return 0
End Function
Private Function IOleDropTarget_OleDragLeave() As Integer Implements IOleDropTarget.OleDragLeave
Trace.WriteLine("OleDragLeave")
Return 0
End Function
Private Function IOleDropTarget_OleDrop(pDataObj As Object, grfKeyState As Integer, pt As Long, ByRef pdwEffect As Integer) As Integer Implements IOleDropTarget.OleDrop
Trace.WriteLine("OleDrop")
Dim data As System.Windows.DataObject = New System.Windows.DataObject(pDataObj)
Me.OnDragDrop(data)
Marshal.FinalReleaseComObject(pDataObj)
Return 0
End Function
End Class
<ComImport(),
Guid("00000122-0000-0000-C000-000000000046"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IOleDropTarget
<PreserveSig()>
Function OleDragEnter(<[In], MarshalAs(UnmanagedType.Interface)> ByVal pDataObj As Object,
<[In], MarshalAs(UnmanagedType.U4)> ByVal grfKeyState As Integer,
<[In], MarshalAs(UnmanagedType.U8)> ByVal pt As Long,
<[In]> ByRef pdwEffect As Integer) As Integer
<PreserveSig()>
Function OleDragOver(<[In], MarshalAs(UnmanagedType.U4)> ByVal grfKeyState As Integer,
<[In], MarshalAs(UnmanagedType.U8)> ByVal pt As Long,
<[In]> ByRef pdwEffect As Integer) As Integer
<PreserveSig()>
Function OleDragLeave() As Integer
<PreserveSig()>
Function OleDrop(<[In], MarshalAs(UnmanagedType.Interface)> ByVal pDataObj As Object,
<[In], MarshalAs(UnmanagedType.U4)> ByVal grfKeyState As Integer,
<[In], MarshalAs(UnmanagedType.U8)> ByVal pt As Long,
<[In]> ByRef pdwEffect As Integer) As Integer
End Interface
'Native imports
<DllImport("ole32.dll", ExactSpelling:=True, CharSet:=CharSet.Auto)>
Private Shared Function RegisterDragDrop(ByVal hwnd As IntPtr, ByVal target As IOleDropTarget) As Integer
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'registering at load doesn't work, might need a timer or something
'Dim Res = RegisterDragDrop(Me.Panel1.Handle, GetDropTarget())
'If Res <> 0 Then
' MsgBox("Registering DragDrop was unsuccessful")
' Exit Sub
'End If
'lbInfo.Items.Add("registered")
End Sub
Private Sub btnFindMail_Click(sender As Object, e As EventArgs) Handles btnFindMail.Click
Dim outlookData As OleOutlookData = myDropTarget.outlookObj
lbInfo.Items.Add($"Searching Outlook for Entry-ID: {outlookData.Items(0).EntryId}")
lbInfo.Items.Add("Getting Outlook reference")
Dim oApp As New Outlook.Application
Dim oNS As Outlook.NameSpace = oApp.GetNamespace("MAPI")
Dim mailItem As Outlook.MailItem = oNS.GetItemFromID(outlookData.Items(0).EntryId)
lbInfo.Items.Add(mailItem.SenderName)
lbInfo.Items.Add(mailItem.SenderEmailAddress)
lbInfo.Items.Add(mailItem.To)
End Sub
Private Sub btnRegister_Click(sender As Object, e As EventArgs) Handles btnRegister.Click
Dim Res = RegisterDragDrop(Me.Panel1.Handle, GetDropTarget())
If Res <> 0 Then
MsgBox("Registering DragDrop was unsuccessful")
Exit Sub
End If
lbInfo.Items.Add("registered")
End Sub
End Class
OleOutlookData
Code:
Friend Class OleOutlookData
Public Property StoreId As String
Public Property FolderId As String
Public Property Items As OleOutlookItemData()
End Class
Friend Class OleOutlookItemData
Public Property Subject As String
Public Property EntryId As String
Public Property SearchKey As String
Public Property MessageClass As String
End Class
OleDataReader
Code:
'converted from:
' --------------------------------------------------------------------------
' Licensed under MIT License.
'
' Outlook DnD Data Reader
'
' File : OleDataReader.cs
' Author : Tobias Viehweger <[email protected] / @mnkypete>
'
' --------------------------------------------------------------------------
Imports System.IO
Imports System.Text
Friend Class OleDataReader
Private stream As MemoryStream
Public Sub New(inStream As MemoryStream)
stream = inStream
End Sub
Public Function ReadOutlookData() As OleOutlookData
Dim reader As BinaryReader = New BinaryReader(stream)
'1. First 4 bytes are the length of the FolderId (In bytes)
' Note: These are possibly uint? We don't expect it to be that long nevertheless..
Dim folderIdLength As Integer = reader.ReadInt32()
'2. Read FolderId
Dim folderId = reader.ReadBytes(folderIdLength)
Dim folderIdHex = ByteArrayToString(folderId)
'3. Next 4 bytes are the StoreId length (In bytes)
Dim storeIdLength As Integer = reader.ReadInt32()
'4. Read StoreId
Dim storeId = reader.ReadBytes(storeIdLength)
Dim storeIdHex = ByteArrayToString(storeId)
'5. There are now some bytes which are not identified yet..
reader.ReadBytes(4)
reader.ReadBytes(4)
reader.ReadBytes(4) ' <== These appear to be folder dependent somehow..
'6. Read items count, again, we assume int instead of uint because that much items
' => Other problems =)
Dim itemCount As Integer = reader.ReadInt32()
Dim items = New OleOutlookItemData(itemCount - 1) {}
For i = 0 To itemCount - 1
'First 4 bytes, represent the MAPI property 0x8014 ("SideEffects" in OlSpy)
Dim sideEffects As Integer = reader.ReadInt32()
'Next byte tells us the length of the message class string (i.e. IPM.Note)
Dim classLength As Byte = reader.ReadByte()
'Now, read type
Dim messageClass = Encoding.ASCII.GetString(reader.ReadBytes(classLength))
'Next, read the unicode char (!) count of the subject
' Note: It seems that Outlook limits this to 255, cross reference mail spec sometime..
Dim subjectLength As Byte = reader.ReadByte()
'Read the subject, note that this is unicode, so we need to read 2 bytes per char!
Dim subject = Encoding.Unicode.GetString(reader.ReadBytes(subjectLength * 2))
'Next up: EntryID including it's length (same as for store + folder)
Dim entryIdLength As Integer = reader.ReadInt32()
Dim entryId = reader.ReadBytes(entryIdLength)
Dim entryIdHex = ByteArrayToString(entryId)
'Now the SearchKey MAPI property of the item
Dim searchKeyLength As Integer = reader.ReadInt32()
Dim searchKey = reader.ReadBytes(searchKeyLength)
Dim searchKeyHex = ByteArrayToString(searchKey)
'Some more stuff which is not quite clear, the next 4 bytes seem to be always
' => E0 80 E9 5A
reader.ReadBytes(4)
'The next 24 byte are some more flags which are not worked out yet, afterwards
' the next item begins
reader.ReadBytes(24)
items(i) = New OleOutlookItemData With {
.EntryId = entryIdHex,
.MessageClass = messageClass,
.SearchKey = searchKeyHex,
.Subject = subject
}
Next
Dim data As OleOutlookData = New OleOutlookData()
data.StoreId = storeIdHex
data.FolderId = folderIdHex
data.Items = items
Return data
End Function
Private Function ByteArrayToString(ba As Byte()) As String
Dim hex As StringBuilder = New StringBuilder(ba.Length * 2)
For Each b In ba
hex.AppendFormat("{0:x2}", b)
Next
Return hex.ToString()
End Function
End Class
Re: Drag/Drop Outlook message to form