接收从网页拖动到WPF窗口的图像拖动、图像、窗口、网页

2023-09-04 00:05:12 作者:擦肩而过

我希望我的WPF应用程序是一个下降的目标,我希望能够从任何网页拖动图像。

当图像被从网页拖动,显然它是在DragImageBits格式,其可以被反序列化到输入ShDragImage. (见的问题如何我定义它的底部)

我如何将此转换为WPF的形​​象?

下面是我当前的尝试。 (如果有人知道做desirialization正确的方法,我所有的耳朵)

 私人无效UserControl_Drop(对象发件人,System.Windows.DragEventArgs E)
    {

            字符串[]格式= data.GetFormats();

            // DragImageBits
            如果(formats.Contains(DragImageBits))
            {
            MemoryStream的的ImageStream = data.GetData(DragImageBits)作为MemoryStream的;

            //现在我反序列化这一点,唯一的办法我知道如何
            imageStream.Seek(0,SeekOrigin.Begin);
            BR BinaryReader在新= BinaryReader在(的ImageStream);

            ShDragImage shDragImage;
            shDragImage.sizeDragImage.cx = br.ReadInt32();
            shDragImage.sizeDragImage.cy = br.ReadInt32();
            shDragImage.ptOffset.x = br.ReadInt32();
            shDragImage.ptOffset.y = br.ReadInt32();
            shDragImage.hbmpDragImage =新的IntPtr(br.ReadInt32());
            shDragImage.crColorKey = br.ReadInt32();


            VAR systemDrawingBitmap = System.Drawing.Bitmap.FromHbitmap(shDragImage.hbmpDragImage);
 
wpf中添加窗口加载与关闭函数时,找不到properties窗口

在这一点上我得到一个异常类型的 System.Runtime.InteropServices.ExternalException ,该消息仅仅是通用GDI +误差

有谁知道我应该做的事情?

这里是支持类的定义。我从this博客条目。

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]     公共结构Win32Point     {         公众诠释X;         公众诠释Ÿ;     }     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)     公共结构Win32Size     {         公众诠释CX;         公众诠释CY;     }     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)     公共结构ShDragImage     {         公共Win32Size sizeDragImage;         公共Win32Point ptOffset;         公众的IntPtr hbmpDragImage;         公众诠释crColorKey;     }

解决方案

下面是我所学到的:

DragImageBits是由Windows外壳程序提供,是指只为拖动光标,而不是最终的数据。外壳通过调整大小和透明度转换图像到适当的拖动光标。

例如,如果拖动这个图片:

在SHDRAGIMAGE将呈现为这样的:

如果你真的想从SHDRAGIMAGE提取图像,这里是code。 (部分来自这个答案)

 的MemoryStream的ImageStream = data.GetData(DragImageBits)作为MemoryStream的;
        imageStream.Seek(0,SeekOrigin.Begin);
        BR BinaryReader在新= BinaryReader在(的ImageStream);
        ShDragImage shDragImage;
        shDragImage.sizeDragImage.cx = br.ReadInt32();
        shDragImage.sizeDragImage.cy = br.ReadInt32();
        shDragImage.ptOffset.x = br.ReadInt32();
        shDragImage.ptOffset.y = br.ReadInt32();
        shDragImage.hbmpDragImage =新的IntPtr(br.ReadInt32()); //我不知道这是什么!
        shDragImage.crColorKey = br.ReadInt32();
        INT跨距= shDragImage.sizeDragImage.cx * 4;
        VAR为imageData =新的字节[步幅* shDragImage.sizeDragImage.cy]。
        //我们必须读取的图像数据作为一个循环,所以它是一个翻转的格式
        的for(int i =(shDragImage.sizeDragImage.cy  -  1)*步幅; I> = 0; I  -  =步幅)
        {
            br.Read(为imageData,我,步幅);
        }
        VAR的BitmapSource = BitmapSource.Create(shDragImage.sizeDragImage.cx,shDragImage.sizeDragImage.cy,
                                                    96,96,
                                                    PixelFormats.Bgra32,
                                                    空值,
                                                    为imageData,
                                                    步幅);
 

如果你想利用DragImageBits为它的预期目的(作为拖动图像),请参阅this博客一种简单,可下载的例子。 所以,在DragImageBits是pretty的多,从实际的问题,这是接受的图像的分心从网页拖

从网页拖动图像变得复杂,因为火狐,Chrome,IE9和全部给你一组不同的格式。此外,要同时处理图像和图像的超链接,而这些又区别对待。

谷歌和火狐提供了一个text / html的格式,它给你一个单一的HTML元素为图像。谷歌把它交给你作为一个ASCII字符串,而Firefox将其提供给您作为一个单code字符串。因此,这里的code我写来处理它:

  System.Windows.IDataObject数据= e.Data;
        字符串[]格式= data.GetFormats();


        如果(formats.Contains(text / html的))
        {

            VAR OBJ = data.GetData(text / html的);
            字符串的HTML =的String.Empty;
            如果(obj是字符串)
            {
                HTML =(字符串)目标文件;
            }
            否则,如果(obj为MemoryStream的)
            {
                的MemoryStream毫秒​​=(MemoryStream的)目标文件;
                byte []的缓冲区=新的字节[ms.Length]
                ms.Read(缓冲液,0,(int)的ms.Length);
                如果(缓冲[1] ==(字节)0)//检测单code
                {
                    HTML = System.Text.Encoding.Uni code.GetString(缓冲区);
                }
                其他
                {
                    的HTML = System.Text.Encoding.ASCII.GetString(缓冲液);
                }
            }
            //使用正则表达式解析HTML,但只是在这个例子中:-)
            VAR匹配=新的正则表达式(@< IMG [^ /] SRC =([^] *),)。匹配(HTML);
            如果(match.Success)
            {
                开放的我们的uri =新的URI(match.Groups [1]。价值);
                SetImageFromUri(URI);
            }
        }
 

在这种情况下,常规的前pression将同时处理一直图像和图像的超链接。

和我的 SetImageFromUri 功能:

 私人无效SetImageFromUri(URI URI)
    {
        字符串文件名= System.IO.Path.GetTempFileName();
        使用(Web客户端Web客户端=新的Web客户端())
        {
            webClient.DownloadFile(URI,文件名);
        }
        使用(的FileStream FS = File.OpenRead(文件名))
        {
            byte []的为imageData =新的字节[fs.Length]
            fs.Read(为imageData,0,(int)的fs.Length);
            this.ImageBinary =为imageData;
        }
        File.Delete(文件名);
    }
 

有关IE9可以处理FileDrop格式。这在IE9效果很好。 Chrome浏览器不支持它。 Firefox没有支持它,但将图像转换为位图和透明的像素转换为黑色。出于这个原因,你应该只处理FileDrop格式,如果text.html的格式是不具备的。

 否则,如果(formats.Contains(FileDrop))
    {
        VAR文件路径=(字符串[])data.GetData(FileDrop);
        使用(VAR FILESTREAM = File.OpenRead(文件路径[0]))
        {
            VAR缓冲区=新的字节[fileStream.Length]
            fileStream.Read(缓冲液,0,(int)的fileStream.Length);
            this.ImageBinary =缓冲区;
        }
    }
 

如果你从IE9拖动图像的超链接,不提供FileDrop格式。我还没有想出如何从IE9图像超链接的图像拖到我的形象的控制。

额外的资讯

如果您使用的是这个例子,但还是需要这个二进制数据转换成图像,这里是一个有用的code片断:

 的BitmapImage sourceImage =新的BitmapImage();
                使用(MemoryStream的毫秒=新的MemoryStream(imageBinary))
                {
                    sourceImage.BeginInit();
                    sourceImage.CacheOption = BitmapCacheOption.OnLoad;
                    sourceImage.StreamSource =毫秒;
                    sourceImage.EndInit();
                }
 

I want my WPF application to be a drop target, and I want to be able to drag an image from any web page.

When an image is dragged from a web page, apparently it is in the "DragImageBits" format, which can be deserialized to type ShDragImage. (See the bottom of the question for how I've defined it)

How do I convert this to a WPF image?

Here's my current attempt. (If anybody knows the correct way to do the desirialization, I'm all ears)

   private void UserControl_Drop(object sender, System.Windows.DragEventArgs e)
    {

            string[] formats = data.GetFormats();

            // DragImageBits
            if (formats.Contains("DragImageBits"))
            {
            MemoryStream imageStream = data.GetData("DragImageBits") as MemoryStream;

            // Now I'm deserializing this, the only way I know how
            imageStream.Seek(0, SeekOrigin.Begin);
            BinaryReader br = new BinaryReader(imageStream);

            ShDragImage shDragImage;
            shDragImage.sizeDragImage.cx = br.ReadInt32();
            shDragImage.sizeDragImage.cy = br.ReadInt32();
            shDragImage.ptOffset.x = br.ReadInt32();
            shDragImage.ptOffset.y = br.ReadInt32();
            shDragImage.hbmpDragImage = new IntPtr(br.ReadInt32());
            shDragImage.crColorKey = br.ReadInt32();


            var systemDrawingBitmap = System.Drawing.Bitmap.FromHbitmap(shDragImage.hbmpDragImage);

At this point I get an exception of type System.Runtime.InteropServices.ExternalException, with the message simply being Generic GDI+ error.

Does anyone know what I should be doing?

And here are the supporting class definitions. I copied them from this blog entry.

[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct Win32Point
    {
        public int x;
        public int y;
    }

    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct Win32Size
    {
        public int cx;
        public int cy;
    }

    [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct ShDragImage
    {
        public Win32Size sizeDragImage;
        public Win32Point ptOffset;
        public IntPtr hbmpDragImage;
        public int crColorKey;
    }

解决方案

Here's what I've learnt:

"DragImageBits" is provided by the windows shell, and is meant only for the drag cursor, not for the final data. The shell transforms the image to an appropriate drag cursor through resizing and transparency.

For example, if you drag this image:

The SHDRAGIMAGE will render as this:

If you really want to extract the image from the SHDRAGIMAGE, here is the code. (Partially lifted from this answer)

        MemoryStream imageStream = data.GetData("DragImageBits") as MemoryStream;
        imageStream.Seek(0, SeekOrigin.Begin);
        BinaryReader br = new BinaryReader(imageStream);
        ShDragImage shDragImage;
        shDragImage.sizeDragImage.cx = br.ReadInt32();
        shDragImage.sizeDragImage.cy = br.ReadInt32();
        shDragImage.ptOffset.x = br.ReadInt32();
        shDragImage.ptOffset.y = br.ReadInt32();
        shDragImage.hbmpDragImage = new IntPtr(br.ReadInt32()); // I do not know what this is for!
        shDragImage.crColorKey = br.ReadInt32();
        int stride = shDragImage.sizeDragImage.cx * 4;
        var imageData = new byte[stride * shDragImage.sizeDragImage.cy];
        // We must read the image data as a loop, so it's in a flipped format
        for (int i = (shDragImage.sizeDragImage.cy - 1) * stride; i >= 0; i -= stride) 
        {
            br.Read(imageData, i, stride);
        }
        var bitmapSource = BitmapSource.Create(shDragImage.sizeDragImage.cx, shDragImage.sizeDragImage.cy,
                                                    96, 96,
                                                    PixelFormats.Bgra32,
                                                    null,
                                                    imageData,
                                                    stride);

If you want to utilize the DragImageBits for it's intended purpose (as a drag image), see this blog for a simple, downloadable example. So, the "DragImageBits" was pretty much a distraction from the actual problem, which is to accept an image dragged from a web page.

Dragging an image from a web page gets complicated, because Firefox, Chrome, and IE9 all give you a different set of formats. Also, you want to handle both an image and an image hyperlink, and these are treated differently again.

Google and Firefox provides a "text/html" format, which gives you a single HTML element as an image. Google gives it to you as an ASCII string, and Firefox gives it to you as a unicode string. So here's the code I wrote to handle it:

     System.Windows.IDataObject data = e.Data;
        string[] formats = data.GetFormats();


        if (formats.Contains("text/html"))
        {

            var obj = data.GetData("text/html");
            string html = string.Empty;
            if (obj is string)
            {
                html = (string)obj;
            }
            else if (obj is MemoryStream)
            {
                MemoryStream ms = (MemoryStream)obj;
                byte[] buffer = new byte[ms.Length];
                ms.Read(buffer, 0, (int)ms.Length);
                if (buffer[1] == (byte)0)  // Detecting unicode
                {
                    html = System.Text.Encoding.Unicode.GetString(buffer);
                }
                else
                {
                    html = System.Text.Encoding.ASCII.GetString(buffer);
                }
            }
            // Using a regex to parse HTML, but JUST FOR THIS EXAMPLE :-)
            var match = new Regex(@"<img[^/]src=""([^""]*)""").Match(html);
            if (match.Success)
            {
                Uri uri = new Uri(match.Groups[1].Value);
                SetImageFromUri(uri);
            }
        }

In this case, the regular expression will handle both a straight image and an image hyperlink.

And my SetImageFromUri function:

    private void SetImageFromUri(Uri uri)
    {
        string fileName = System.IO.Path.GetTempFileName();
        using (WebClient webClient = new WebClient())
        {
            webClient.DownloadFile(uri, fileName);
        }
        using (FileStream fs = File.OpenRead(fileName))
        {
            byte[] imageData = new byte[fs.Length];
            fs.Read(imageData, 0, (int)fs.Length);
            this.ImageBinary = imageData;
        }
        File.Delete(fileName);
    }

For IE9 you can handle the "FileDrop" format. This works well in IE9. Chrome does not support it. Firefox does support it, but converts the image to a bitmap and converts transparent pixels to black. For this reason, you should only handle the "FileDrop" format if the "text.html" format isn't available.

    else if (formats.Contains("FileDrop"))
    {
        var filePaths = (string[])data.GetData("FileDrop");
        using (var fileStream = File.OpenRead(filePaths[0]))
        {
            var buffer = new byte[fileStream.Length];
            fileStream.Read(buffer, 0, (int)fileStream.Length);
            this.ImageBinary = buffer;
        }
    }

The "FileDrop" format is not provided if you drag an image hyperlink from IE9. I haven't figured out how to drag an image from an image hyperlink in IE9 onto my image control.

Extra Info

If you're using this example, but still need to convert this binary data into an image, here's a useful code snippet:

                BitmapImage sourceImage = new BitmapImage();
                using (MemoryStream ms = new MemoryStream(imageBinary))
                {
                    sourceImage.BeginInit();
                    sourceImage.CacheOption = BitmapCacheOption.OnLoad;
                    sourceImage.StreamSource = ms;
                    sourceImage.EndInit();
                }