我有一个关于将 WinForms 控件嵌入 WPF 应用程序以及 .Net 框架版本之间的差异的互操作问题。
以下应在 WebBrowser 控件上显示一个透明的 wpf 控件(红色框),并在使用 .Net 3.5 时按预期工作:
...但在使用 .Net 4.5 编译后会发生以下情况。
切换回 .Net 3.5 后一切正常
以下代码适用于使用 .Net 3.0 到 .Net 3.5 但不适用于 .Net 4.0/4.5 的 Win32 的 WPF:
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);
}
}
}
位图缓冲区.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>
在窗口加载事件之后:
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 的代码部分,请参阅此博客了解更多信息)