I am copying cells from an excel document to the clipboard so they can be inserted as an image elsewhere. The cells get copied to the clipboard fine, as I can paste the image manually after the code is run. However I cannot get ahold of the data. Here is my code:
tempWorkSheet.Range[tempWorkSheet.Cells[1, 1], tempWorkSheet.Cells[3, 3]].CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlPicture);
// returns true
var test = Clipboard.GetDataObject().GetDataPresent(DataFormats.EnhancedMetafile);
// returns true
var test2 = Clipboard.ContainsData(DataFormats.EnhancedMetafile);
// returns null
var test3 = Clipboard.GetData(DataFormats.EnhancedMetafile);
// returns null
var test4 = Clipboard.GetDataObject().GetData(DataFormats.EnhancedMetafile);
The data is stored as an EnhancedMetaFile
and I can see the data in there but I cannot pull it out. I am at my wits end trying to figure this out. Does anybody see something I am missing?
I saw this question posted but it did not help me much. I'm hoping somebody can.
I found a solution. The Clipboard.GetData(DataFormats.EnhancedMetafile)
call seems to be broken. But I managed to get it working using P/Invoke.
It's not the prettiest code, but it works, so for posterity here it is in all it's glory:
[DllImport("user32.dll", SetLastError = true)]
static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")]
static extern IntPtr GetClipboardData(uint uFormat);
[DllImport("user32.dll", SetLastError = true)]
static extern bool CloseClipboard();
[DllImport("user32.dll")]
static extern bool EmptyClipboard();
[DllImport("gdi32.dll")]
static extern IntPtr CopyEnhMetaFile(IntPtr hemfSrc, string lpszFile);
[DllImport("gdi32.dll")]
static extern bool DeleteEnhMetaFile(IntPtr hemf);
public Image GetMetaImageFromClipboard()
{
OpenClipboard(IntPtr.Zero);
IntPtr pointer = GetClipboardData(14);
string fileName = @"C:\Test\" + Guid.NewGuid().ToString() + ".emf";
IntPtr handle = CopyEnhMetaFile(pointer, fileName);
Image image;
using (Metafile metafile = new Metafile(fileName))
{
image = new Bitmap(metafile.Width, metafile.Height);
Graphics g = Graphics.FromImage(image);
EmptyClipboard();
CloseClipboard();
g.DrawImage(metafile, 0, 0, image.Width, image.Height);
}
DeleteEnhMetaFile(handle);
File.Delete(fileName);
return image;
}
The problem is that you are trying to paste data in the same routine that copied it. You need to have separate handlers. Excel will use Delayed Rendering when you copy the image, and will have to have to process a rendering request in order to actually provide the image if/when requested. Meanwhile, other applications that have registered as Clipboard Viewers, are also processing the clipboard update. And here you are, insisting on immediate service for the image, without giving the system a chance to breath. You need to put your paste logic into a separate routine, so that it's not in the same call stack as the copy.
I use this code:
tempWorkSheet.Range[tempWorkSheet.Cells[1, 1], tempWorkSheet.Cells[3, 3]].CopyPicture(Excel.XlPictureAppearance.xlScreen, Excel.XlCopyPictureFormat.xlBitmap);
var data = System.Windows.Forms.Clipboard.GetDataObject();
using (var ms = data.GetData(System.Windows.Forms.DataFormats.Dib) as MemoryStream)
{
byte[] buff = new byte[ms.Capacity];
if (ms.CanRead)
{
ms.Read(buff, 0, ms.Capacity);
}
MemoryStream ms2 = new MemoryStream();
byte[] bmpHeader = new byte[] { 0x42, 0x4D, 0x96, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00 };
ms2.Write(bmpHeader, 0, bmpHeader.Length);
ms2.Write(buff, 0, buff.Length);
string local_filename = "E:\TEST.png";
File.WriteAllBytes(local_filename, ms2.ToArray());
ms2.Dispose();
}
Did you run the code from an STA thread? For example:
class Program
{
static void Main(string[] args)
{
RunInSta(() =>
{
var dataObject = Clipboard.GetDataObject();
foreach (string format in dataObject.GetFormats())
{
Console.WriteLine(format);
}
});
}
internal static void RunInSta(Action action)
{
Thread thread = new Thread(() => action());
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
}