Unity3D界面嵌入WPF界面中(鼠标键盘均可正常响应)
1、引用System.Windows.Forms.dll和WindowsFormsIntegration.dll
unity3D界面不能够直接嵌入到WPF控件中,但是可以嵌入到WinForm控件中,所以我们需要在WPF中使用WinForm控件作为载体。需要引用上述dll,如下图所示:
然后在Xaml文件中引入命名空间即可使用WinForm控件,例如:
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
<WindowsFormsHost Grid.Row="1">
<wf:Panel/>
</WindowsFormsHost>
2、将Unity3d嵌入到WinForm控件中
public class AppContainer
{
private System.Windows.Forms.Panel _hostPanel;
private readonly ManualResetEvent _eventDone = new ManualResetEvent(false);
private Process _process = null;
internal IntPtr _embededWindowHandle;
public AppContainer(System.Windows.Forms.Panel panel)
{
this._hostPanel = panel;
this._hostPanel.Resize += _hostPanel_Resize;
}
private void _hostPanel_Resize(object sender, EventArgs e)
{
SetBounds();
}
public void ActivateWindow()
{
if (_process == null)
return;
if (_process.MainWindowHandle == IntPtr.Zero)
return;
Win32Api.SendMessage(_process.MainWindowHandle, Win32Api.WM_ACTIVATE, Win32Api.WA_ACTIVE, IntPtr.Zero);
}
public void SetBounds()
{
SetBounds(_hostPanel.Width, _hostPanel.Height);
}
public void SetBounds(int width, int height)
{
if (_process == null)
return;
if (_process.MainWindowHandle == IntPtr.Zero)
return;
if (width <= 0 || height <= 0)
return;
Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, width, height, true);
ActivateWindow();//激活
}
public bool StartAndEmbedProcess(string processPath)
{
if (null != _process)
return true;
var isStartAndEmbedSuccess = false;
_eventDone.Reset();
// Start the process
ProcessStartInfo info = new ProcessStartInfo(processPath);
info.WindowStyle = ProcessWindowStyle.Maximized;//默认最大化,不弹出界面。
info.Arguments = $"-popupwindow";//Unity的命令行参数
_process = Process.Start(info);
if (_process == null)
{
return false;
}
// Wait for process to be created and enter idle condition
_process.WaitForInputIdle();
// Get the main handle
var thread = new Thread(() =>
{
while (true)
{
if (_process.MainWindowHandle != (IntPtr)0)
{
_eventDone.Set();
break;
}
Thread.Sleep(10);
}
});
thread.Start();
//嵌入进程
if (_eventDone.WaitOne(10000))
{
isStartAndEmbedSuccess = EmbedApp(_process);
if (!isStartAndEmbedSuccess)
{
CloseApp(_process);
}
}
return isStartAndEmbedSuccess;
}
public bool EmbedExistProcess(Process process)
{
_process = process;
return EmbedApp(process);
}
/// <summary>
/// 将外进程嵌入到当前程序
/// </summary>
/// <param name="process"></param>
private bool EmbedApp(Process process)
{
//是否嵌入成功标志,用作返回值
var isEmbedSuccess = false;
//外进程句柄
var processHwnd = process.MainWindowHandle;
//容器句柄
var panelHwnd = _hostPanel.Handle;
if (processHwnd != (IntPtr)0 && panelHwnd != (IntPtr)0)
{
//把本窗口句柄与目标窗口句柄关联起来
var setTime = 0;
while (!isEmbedSuccess && setTime < 50)
{
// Put it into this form
isEmbedSuccess = Win32Api.SetParent(processHwnd, panelHwnd) != 0;
Thread.Sleep(10);
setTime++;
}
// Remove border and whatnot
//Win32Api.SetWindowLong(processHwnd, Win32Api.GWL_STYLE, Win32Api.WS_CHILDWINDOW | Win32Api.WS_CLIPSIBLINGS | Win32Api.WS_CLIPCHILDREN | Win32Api.WS_VISIBLE);
SetBounds();
Move the window to overlay it on this window
//Win32Api.MoveWindow(_process.MainWindowHandle, 0, 0, (int)ActualWidth, (int)ActualHeight, true);
}
if (isEmbedSuccess)
{
_embededWindowHandle = _process.MainWindowHandle;
}
return isEmbedSuccess;
}
/// <summary>
/// 关闭进程
/// </summary>
/// <param name="process"></param>
private void CloseApp(Process process)
{
if (process != null && !process.HasExited)
{
process.Kill();
}
}
public void CloseProcess()
{
CloseApp(_process);
_process = null;
}
}
这是一个嵌入的实例类,构造函数会把WinForm控件以参数的形式传入,然后此控件需要订阅Resize事件,该事件处理器进行了unity3D窗体的重新激活。
在进程启动的时候使用了命令行参数info.Arguments = $"-popupwindow";//Unity的命令行参数。
依然使用的Win32Api设置为父窗体, Win32Api.SetParent(processHwnd, panelHwnd)。
实例代码如下:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Button Margin="3" Content="Embed" Click="Button_Click" HorizontalAlignment="Center"/>
</Grid>
<WindowsFormsHost Grid.Row="1">
<wf:Panel x:Name="host"/>
</WindowsFormsHost>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
AppContainer container = new AppContainer(this.host);
container.StartAndEmbedProcess(@"Child.exe");
}
3、效果演示
鼠标和键盘均可正常响应。
最后、如对你有帮助,求关注、求一键三连,感谢
4、.NetCore
目前很多项目使用的.NetCore,然后发现用不了上述说的dll。那么因为需要在项目文件中添加如下信息:
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<AssemblyName>SIFP.Core</AssemblyName>
</PropertyGroup>
5、疑问
将WinForm嵌入到WPF中后,WinForm控件只能覆盖在WPF控件上,不知道有没有大佬知道怎么使WPF控件覆盖在WinForm上???