Clipboard.GetData() returns null when it should not

て烟熏妆下的殇ゞ 提交于 2019-12-06 15:37:55

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();
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!