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

时间:2023-04-26
本文介绍了接收从网页拖动到 WPF 窗口的图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我希望我的 WPF 应用程序成为放置目标,并且我希望能够从任何网页拖动图像.

从网页拖拽图片时,显然是DragImageBits"格式,可以反序列化输入)

 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());//我不知道这是干什么用的!shDragImage.crColorKey = br.ReadInt32();int stride = shDragImage.sizeDragImage.cx * 4;var imageData = new byte[stride * shDragImage.sizeDragImage.cy];//我们必须循环读取图像数据,所以它是翻转格式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,空值,图像数据,步幅);

如果您想将 DragImageBits 用于其预期目的(作为拖动图像),请参阅 Shell 样式拖放在 .NET(WPF 和 WinForms)中(存档 here) 获取一个简单的可下载示例.


因此,DragImageBits"几乎分散了实际问题的注意力,即接受从网页拖动的图像.

从网页中拖动图像变得很复杂,因为 Firefox、Chrome 和 IE9 都为您提供了一组不同的格式.此外,您希望同时处理图像和图像超链接,它们的处理方式再次不同.

Google 和 Firefox 提供了一个text/html"格式,它为您提供单个 HTML 元素作为图像.Google 将其作为 ASCII 字符串提供给您,而 Firefox 将其作为 unicode 字符串提供给您.所以这是我编写的处理它的代码:

 System.Windows.IDataObject 数据 = e.Data;string[] 格式 = data.GetFormats();if (formats.Contains("text/html")){var obj = data.GetData("text/html");字符串 html = string.Empty;if (obj 是字符串){html = (string)obj;}else if (obj 是 MemoryStream){MemoryStream ms = (MemoryStream)obj;字节[] 缓冲区 = 新字节[ms.Length];ms.Read(buffer, 0, (int)ms.Length);if (buffer[1] == (byte)0)//检测 unicode{html = System.Text.Encoding.Unicode.GetString(buffer);}别的{html = System.Text.Encoding.ASCII.GetString(buffer);}}//使用正则表达式解析 HTML,但仅用于此示例 :-)var match = new Regex(@"<img[^/]src=""([^""]*)""").Match(html);如果(匹配.成功){Uri uri = new Uri(match.Groups[1].Value);SetImageFromUri(uri);}}

在这种情况下,正则表达式将同时处理直接图像和图像超链接.

还有我的 SetImageFromUri 函数:

 private void SetImageFromUri(Uri uri){字符串文件名 = System.IO.Path.GetTempFileName();使用 (WebClient webClient = new WebClient()){webClient.DownloadFile(uri, fileName);}使用 (FileStream fs = File.OpenRead(fileName)){byte[] imageData = new byte[fs.Length];fs.Read(imageData, 0, (int)fs.Length);this.ImageBinary = imageData;}文件.删除(文件名);}

对于 IE9,您可以处理FileDrop";格式.这在 IE9 中运行良好.铬不支持它.Firefox 确实支持它,但会将图像转换为位图并将透明像素转换为黑色.因此,您应该只处理FileDrop";格式如果text.html";格式不可用.

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

FileDrop"如果从 IE9 中拖动图像超链接,则不提供格式.我还没有弄清楚如何将图像从 IE9 中的图像超链接拖到我的图像控件上.


**额外信息**

如果您正在使用此示例,但仍需要将此二进制数据转换为图像,这里有一个有用的代码片段:

 BitmapImage sourceImage = new BitmapImage();使用 (MemoryStream ms = new MemoryStream(imageBinary)){sourceImage.BeginInit();sourceImage.CacheOption = BitmapCacheOption.OnLoad;sourceImage.StreamSource = ms;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 Shell Style Drag and Drop in .NET (WPF and WinForms) (archived here) 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();
                }

这篇关于接收从网页拖动到 WPF 窗口的图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:C#拖放文件形成 下一篇:重新排序 WPF TabControl 中的选项卡

相关文章