c# - 当 .Net Framework 从 3.5 更改为 4.5 时,WPF Interop Control 呈现黑色

标签 c# wpf winforms-interop wpf-interop

我有一个关于将 WinForms 控件嵌入 WPF 应用程序以及 .Net 框架版本之间的差异的互操作问题。

以下内容应在 WebBrowser 控件上显示透明的 wpf 控件(红色框),并且在使用 .Net 3.5 时按预期工作:

enter image description here

...但是使用 .Net 4.5 编译后会发生以下情况。

enter image description here

切换回 .Net 3.5 后一切正常

以下代码适用于使用 .Net 3.0 到 .Net 3.5 的 Win32 的 WPF,但不适用于 .Net 4.0/4.5:

Win32HostRenderer.xaml:

<UserControl x:Class="WPFInterop.Interop.Win32HostRenderer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
      <Image Name="_interopRenderer" Stretch="None"/>    
    </Grid>
</UserControl>

Win32HostRenderer.xaml.cs:

using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

using Winforms = System.Windows.Forms;
using GDI = System.Drawing;

using System.Runtime;
using System.Runtime.InteropServices;


namespace WPFInterop.Interop
{
    public partial class Win32HostRenderer : System.Windows.Controls.UserControl
    {
        [DllImport("user32.dll")]
        private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);


    [System.Runtime.InteropServices.DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);

    [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
    private static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);

    private DispatcherTimer     _rendererTimer;
    private int                 _rendererInterval = 33;

    private InteropForm         _interopForm;
    private Winforms.Control    _winformControl;

    private BitmapSource        _bitmapSource;
    private BitmapBuffer        _bitmapSourceBuffer;

    private GDI.Bitmap          _gdiBitmap;
    private GDI.Graphics        _gdiBitmapGraphics;
    private IntPtr              _hGDIBitmap;


    public Win32HostRenderer()
    {
        InitializeComponent();
    }

    private void InitializeInteropForm()
    {
        if (_winformControl == null) return;

        if (_interopForm != null)
        {
            TearDownInteropForm();
        }

        _interopForm = new InteropForm();
        _interopForm.Opacity = 0.01;
        _interopForm.Controls.Add(_winformControl);
        _interopForm.Width = _winformControl.Width;
        _interopForm.Height = _winformControl.Height;
    }

    private void TearDownInteropForm()
    {
        if (_interopForm == null) return;
        _interopForm.Hide();
        _interopForm.Close();
        _interopForm.Dispose();
        _interopForm = null;
    }

    private void InitializeRendererTimer()
    {
        TearDownRenderTimer();

        _rendererTimer = new DispatcherTimer();
        _rendererTimer.Interval = new TimeSpan(0, 0, 0, 0, _rendererInterval);
        _rendererTimer.Tick += new EventHandler(_rendererTimer_Tick);
        _rendererTimer.Start();
    }

    void _rendererTimer_Tick(object sender, EventArgs e)
    {
        RenderWinformControl();
    }

    private void TearDownRenderTimer()
    {
        if (_rendererTimer == null) return;

        _rendererTimer.IsEnabled = false;

    }
    private void RegisterEventHandlers()
    {
        Window currentWindow = Window.GetWindow(this);
        currentWindow.LocationChanged += new EventHandler(delegate(object sender, EventArgs e)
        {
            PositionInteropFormOverRender();
        });


        currentWindow.SizeChanged += new SizeChangedEventHandler(delegate(object sender, SizeChangedEventArgs e)
        {
            PositionInteropFormOverRender();
        });

        currentWindow.Deactivated += new EventHandler(delegate(object sender, EventArgs e)
        {
            //_interopForm.Opacity = 0;
        });

        currentWindow.Activated += new EventHandler(delegate(object sender, EventArgs e)
        {
           // _interopForm.Opacity = 0.01;
        });

        currentWindow.StateChanged += new EventHandler(delegate(object sender, EventArgs e)
        {
            PositionInteropFormOverRender();
        });


        _interopRenderer.SizeChanged += new SizeChangedEventHandler(delegate(object sender, SizeChangedEventArgs e)
        {
            PositionInteropFormOverRender();
        });
    }



    private void PositionInteropFormOverRender()
    {
        if (_interopForm == null) return;
        Window currentWindow = Window.GetWindow(this);

        Point interopRenderScreenPoint = _interopRenderer.PointToScreen(new Point());

        _interopForm.Left = (int)interopRenderScreenPoint.X;
        _interopForm.Top = (int)interopRenderScreenPoint.Y;

        int width = 0;

        if ((int)_interopRenderer.ActualWidth > (int)currentWindow.Width)
        {
            width = (int)currentWindow.Width;
        }
        else
        {
            width = (int)_interopRenderer.ActualWidth;
        }

        if ((int)currentWindow.Width < width) 
                        width = (int)currentWindow.Width;

        _interopForm.Width = width;
    }

    private void InitializeBitmap()
    {
        if (_bitmapSource == null)
        {
            TearDownBitmap();
        }

        int interopRenderWidth = _winformControl.Width;
        int interopRenderHeight = _winformControl.Height;
        int bytesPerPixel = 4;

        int totalPixels = interopRenderWidth * interopRenderHeight * bytesPerPixel;

        byte[] dummyPixels = new byte[totalPixels];

        _bitmapSource = BitmapSource.Create(interopRenderWidth, 
                                    interopRenderHeight, 
                                    96, 
                                    96, 
                                    PixelFormats.Bgr32, 
                                    null,
                                    dummyPixels,
                                    interopRenderWidth * bytesPerPixel);

        _interopRenderer.Source = _bitmapSource;

        _bitmapSourceBuffer = new BitmapBuffer(_bitmapSource);

        _gdiBitmap = new GDI.Bitmap(_winformControl.Width,
                                    _winformControl.Height, 
                                    GDI.Imaging.PixelFormat.Format32bppRgb);

        _hGDIBitmap = _gdiBitmap.GetHbitmap();

        _gdiBitmapGraphics = GDI.Graphics.FromImage(_gdiBitmap);
    }

    private void TearDownBitmap()
    {
        _bitmapSource = null;
        _bitmapSourceBuffer = null;

        if (_gdiBitmap != null)
        {
            _gdiBitmap.Dispose();
        }

        if (_gdiBitmapGraphics != null)
        {
            _gdiBitmapGraphics.Dispose();
        }

        if (_hGDIBitmap != IntPtr.Zero)
        {
            DeleteObject(_hGDIBitmap);
        }
    }

    private void InitializeWinformControl()
    {
        InitializeInteropForm();
        InitializeBitmap();
        RegisterEventHandlers();

        PositionInteropFormOverRender();
        _interopForm.StartPosition = System.Windows.Forms.FormStartPosition.Manual;


        _interopForm.Show();
        InitializeRendererTimer();

    }

    public Winforms.Control Child
    {
        get { return _winformControl; }
        set
        {
            _winformControl = value;
            InitializeWinformControl();
        }
    }

    private void RenderWinformControl()
    {
        PaintWinformControl(_gdiBitmapGraphics, _winformControl);

        GDI.Rectangle lockRectangle = new GDI.Rectangle(0, 
                                                        0, 
                                                        _gdiBitmap.Width, 
                                                        _gdiBitmap.Height);

        GDI.Imaging.BitmapData bmpData = _gdiBitmap.LockBits(lockRectangle, 
                                                        GDI.Imaging.ImageLockMode.ReadOnly, 
                                                        GDI.Imaging.PixelFormat.Format32bppRgb);
        System.IntPtr bmpScan0 = bmpData.Scan0;

        CopyMemory(_bitmapSourceBuffer.BufferPointer, 
                   bmpScan0,
                   (int)_bitmapSourceBuffer.BufferSize);

        _gdiBitmap.UnlockBits(bmpData);

        _interopRenderer.InvalidateVisual(); 
    }

    private void PaintWinformControl(GDI.Graphics graphics, Winforms.Control control)
    { 
        IntPtr hWnd = control.Handle;
        IntPtr hDC = graphics.GetHdc();

        PrintWindow(hWnd, hDC, 0);

        graphics.ReleaseHdc(hDC);
        }
    }
}

BitmapBuffer.cs:

using System;
using System.Collections.Generic;
using System.Text;

using System.Windows;

using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime;
using System.Runtime.InteropServices.ComTypes;

using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WPFInterop
{
    class BitmapBuffer
    {
        private BitmapSource _bitmapImage = null;

        private object _wicImageHandle = null;

        private object _wicImageLock = null;

        private uint _bufferSize = 0;

        private IntPtr _bufferPointer = IntPtr.Zero;

        private uint _stride = 0;

        private int _width;

        private int _height;

        public BitmapBuffer(BitmapSource Image)
        {
            //Keep reference to our bitmap image
            _bitmapImage = Image;

            //Get around the STA deal
            _bitmapImage.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new System.Windows.Threading.DispatcherOperationCallback(delegate
            {
                //Cache our width and height
                _width = _bitmapImage.PixelWidth;
                _height = _bitmapImage.PixelHeight;
                return null;
            }), null);

            //Retrieve and store our WIC handle to the bitmap
            SetWICHandle();

            //Set the buffer pointer
            SetBufferInfo();
        }

        /// <summary>
        /// The pointer to the BitmapImage's native buffer
        /// </summary>
        public IntPtr BufferPointer
        {
            get
            {
                //Set the buffer pointer
                SetBufferInfo();
                return _bufferPointer;
            }
        }

        /// <summary>
        /// The size of BitmapImage's native buffer
        /// </summary>
        public uint BufferSize
        {
            get { return _bufferSize; }
        }

        /// <summary>
        /// The stride of BitmapImage's native buffer
        /// </summary>
        public uint Stride
        {
            get { return _stride; }
        }

        private void SetBufferInfo()
        {
            int hr = 0;

            //Get the internal nested class that holds some of the native functions for WIC
            Type wicBitmapNativeMethodsClass = Type.GetType("MS.Win32.PresentationCore.UnsafeNativeMethods+WICBitmap, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");

            //Get the methods of all the static methods in the class
            MethodInfo[] info = wicBitmapNativeMethodsClass.GetMethods(BindingFlags.Static | BindingFlags.NonPublic);

            //This method looks good
            MethodInfo lockmethod = info[0];

            //The rectangle of the buffer we are
            //going to request
            Int32Rect rect = new Int32Rect();

            rect.Width = _width;
            rect.Height = _height;

            //Populate the arguments to pass to the function
            object[] args = new object[] { _wicImageHandle, rect, 2, _wicImageHandle };

            //Execute our static Lock() method
            hr = (int)lockmethod.Invoke(null, args);

            //argument[3] is our "out" pointer to the lock handle
            //it is set by our last method invoke call
            _wicImageLock = args[3];

            //Get the internal nested class that holds some of the
            //other native functions for WIC
            Type wicLockMethodsClass = Type.GetType("MS.Win32.PresentationCore.UnsafeNativeMethods+WICBitmapLock, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");

            //Get all the native methods into our array
            MethodInfo[] lockMethods = wicLockMethodsClass.GetMethods(BindingFlags.Static | BindingFlags.NonPublic);

            //Our method to get the stride value of the image
            MethodInfo getStrideMethod = lockMethods[0];

            //Fill in our arguments
            args = new object[] { _wicImageLock, _stride };

            //Execute the stride method
            getStrideMethod.Invoke(null, args);

            //Grab out or byref value for the stride
            _stride = (uint)args[1];

            //This one looks perty...
            //This function will return to us 
            //the buffer pointer and size
            MethodInfo getBufferMethod = lockMethods[1];

            //Fill in our arguments
            args = new object[] { _wicImageLock, _bufferSize, _bufferPointer };

            //Run our method
            hr = (int)getBufferMethod.Invoke(null, args);

            _bufferSize = (uint)args[1];
            _bufferPointer = (IntPtr)args[2];

            DisposeLockHandle();
        }

        private void DisposeLockHandle()
        {
            MethodInfo close = _wicImageLock.GetType().GetMethod("Close");
            MethodInfo dispose = _wicImageLock.GetType().GetMethod("Dispose");

            close.Invoke(_wicImageLock, null);
            dispose.Invoke(_wicImageLock, null);
        }

        private void SetWICHandle()
        {
            //Get the type of bitmap image
            Type bmpType = typeof(BitmapSource);

            //Use reflection to get the private property WicSourceHandle
            FieldInfo fInfo = bmpType.GetField("_wicSource",
                                               BindingFlags.NonPublic | BindingFlags.Instance);

            //Retrieve the WIC handle from our BitmapImage instance
            _wicImageHandle = fInfo.GetValue(_bitmapImage);
        }

    }
}

InteropForm只是一个派生的System.Windows.Forms.Form,没有什么特殊的魔力。

集成到 WPF-Page 中很简单:

  <interop:Win32HostRenderer x:Name="_host" Grid.Row="0">
  </interop:Win32HostRenderer>

在窗口 Loaded 事件之后:

    System.Windows.Forms.WebBrowser browser = new System.Windows.Forms.WebBrowser();
    browser.Navigate("http://www.youtube.com");
    browser.Width = 700;
    browser.Height = 500;

    _host.Child = browser;

(代码部分来自 Jeremiah Morrill,请参阅 this blog 了解更多信息)

最佳答案

好吧,您的第一个问题是这段代码:

    //Get the methods of all the static methods in the class
    MethodInfo[] info = wicBitmapNativeMethodsClass.GetMethods(BindingFlags.Static | BindingFlags.NonPublic);

    //This method looks good
    MethodInfo lockmethod = info[0];

在 .Net 4 中,“Lock”方法位于索引 1(可能是因为添加了新的“静态”方法)...因此您的反射代码使用了错误的方法。

enter image description here

相反,您可以使用它按名称获取它(建议将其他 GetMethods 更改为 GetMethod):

MethodInfo lockmethod = wicBitmapNativeMethodsClass.GetMethod("Lock", BindingFlags.Static | BindingFlags.NonPublic);

这里它正在工作(在.NET 4中)...我让它填充了客户端空间...因为我没有完整的源代码...根据需要进行更改以进行视觉反射等:

enter image description here

我所做的更改是:

MethodInfo lockmethod = wicBitmapNativeMethodsClass.GetMethod("Lock", BindingFlags.Static |   BindingFlags.NonPublic);

private void PositionInteropFormOverRender()
{
    if (_interopForm == null) return;
    Window currentWindow = Window.GetWindow(this);

    FrameworkElement firstchild = this.Content as FrameworkElement;
    if (firstchild != null)
    {
        Point interopRenderScreenPoint = currentWindow.PointToScreen(new Point());

        _interopForm.Left = (int)interopRenderScreenPoint.X;
        _interopForm.Top = (int)interopRenderScreenPoint.Y;

        _interopForm.Width = (int)firstchild.RenderSize.Width;
        _interopForm.Height = (int)firstchild.RenderSize.Height;
    }
}

private void _host_Loaded(object sender, RoutedEventArgs e)
{
    System.Windows.Forms.WebBrowser browser = new System.Windows.Forms.WebBrowser();
    browser.Navigate("http://www.youtube.com");
    browser.Width = 700;
    browser.Height = 500;
    browser.Dock = System.Windows.Forms.DockStyle.Fill;

    _host.Child = browser;
}

private void InitializeInteropForm()
{
    if (_winformControl == null) return;

    if (_interopForm != null)
    {
        TearDownInteropForm();
    }

    _interopForm = new InteropForm();
    _interopForm.Opacity = 0.5;
    _interopForm.Controls.Add(_winformControl);
    _interopForm.Width = _winformControl.Width;
    _interopForm.Height = _winformControl.Height;
}

您还需要修复/做/注意其他事情,即:

  • 当屏幕 DPI 不是 96dpi 时,您必须相应地转换 RenderSize.Width/Height 值(参见: How do I convert a WPF size to physical pixels? )
  • 当您拖动主窗口时,您的“浏览器”窗口会隐藏在 z 顺序中...您只需将其带回到前面即可。

关于c# - 当 .Net Framework 从 3.5 更改为 4.5 时,WPF Interop Control 呈现黑色,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25159740/

相关文章:

c# - EFv1 一对多映射与 POCO 的关系

wpf - WPF 中的独立命令对象

wpf - 使用一个处理程序的WPF Toggle Button Checked/Unchecked事件

c# - 如何使用反射在 Winform 中托管 WPF 应用程序

c# - 在 Windows 窗体中托管 IDeskBand

.net - WinForms 到 WPF - 我们如何从这里到达那里?

c# - 如何从 .vcxproj 文件中获取所有项目宏及其值

c# - 如何向 Array.IndexOf 添加不区分大小写的选项

c# - 发送失败时重新发送 smtp 电子邮件的循环

c# - Application.Resources 用于存储应用程序数据