我正在尝试修改此问题中发布的代码: https://stackoverflow.com/a/44059700
允许我将Unity3D应用嵌入WPF应用中。
这是我稍作修改的版本:
namespace WPFWithUnity
{
public partial class Page1 : Page
{
[DllImport("User32.dll")]
static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
private Process process;
private IntPtr unityHWND = IntPtr.Zero;
private const int WM_ACTIVATE = 0x0006;
private readonly IntPtr WA_ACTIVE = new IntPtr(1);
private readonly IntPtr WA_INACTIVE = new IntPtr(0);
Frame p = MainWindow.Instance.floatingFrame;
bool initialized = false;
public Page1()
{
InitializeComponent();
MainWindow.Instance.MainWindowClosing += Application_Exit;
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += attemptInit;
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
dispatcherTimer.Start();
}
void attemptInit(object sender, EventArgs e) {
if (initialized)
return;
HwndSource source = (HwndSource)HwndSource.FromVisual(p);
Console.WriteLine("attempting to get handle...");
if (source == null) {
Console.WriteLine("Failed to get handle source");
return;
}
IntPtr hWnd = source.Handle;
try
{
process = new Process();
process.StartInfo.FileName = "Child.exe";
process.StartInfo.Arguments = "-parentHWND " + hWnd.ToInt32() + " " + Environment.CommandLine;
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForInputIdle();
// Doesn't work for some reason ?!
//unityHWND = process.MainWindowHandle;
EnumChildWindows(hWnd, WindowEnum, IntPtr.Zero);
//unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
Console.WriteLine("Unity HWND: 0x" + unityHWND.ToString("X8"));
panel1_Resize(this, EventArgs.Empty);
initialized = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to UnityGame.exe.");
}
}
private void ActivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
private void DeactivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
}
private int WindowEnum(IntPtr hwnd, IntPtr lparam)
{
unityHWND = hwnd;
ActivateUnityWindow();
return 0;
}
private void panel1_Resize(object sender, EventArgs e)
{
MoveWindow(unityHWND, 0, 0, (int)p.Width, (int)p.Height, true);
Console.WriteLine("RESIZED UNITY WINDOW TO: " + (int)p.Width + "x" + (int)p.Height);
ActivateUnityWindow();
}
// Close Unity application
private void Application_Exit(object sender, EventArgs e)
{
try
{
process.CloseMainWindow();
Thread.Sleep(1000);
while (!process.HasExited)
process.Kill();
}
catch (Exception)
{
}
}
private void Form1_Activated(object sender, EventArgs e)
{
ActivateUnityWindow();
}
private void Form1_Deactivate(object sender, EventArgs e)
{
DeactivateUnityWindow();
}
}
}
这是XAML的相关部分:
<Frame Name="floatingFrame" Grid.Row="15" Grid.RowSpan="5" Grid.Column="0" Grid.ColumnSpan="2" Width="640" Height="480" Margin="100,0,0,0" Panel.ZIndex="100" Source="Page1.xaml"/>
真的,唯一的区别是我试图在框架内而不是WinForms面板上使用WPF页面(试图避免WinForms)。嵌入式Unity应用程序可以正常启动...除了它占用整个窗口(即您再也看不到任何WPF控件)了。
所以,这个问题: 如何使Unity应用程序仅停留在WPF页面(位于框架内)内?
(此XY问题的Y在于我只是试图在WPF应用程序内部创建3D图形显示的东西。)
在此先感谢您的帮助。
答案 0 :(得分:2)
在WPF中使用WindowsFormsHost
或HwndHost
控件。 hwnd在主机控件的Handle
属性中。因此,您可以更改此行以将Unity放入主机控件中。
process.StartInfo.Arguments = "-parentHWND " + hwndHost.Handle.ToInt32() + " " + Environment.CommandLine;
并删除获取浮动框架hwnd的代码
HwndSource source = (HwndSource)HwndSource.FromVisual(p);
答案 1 :(得分:2)
上述解决方案的问题在于,关注 Unity-exe 似乎是不可能的。所以是的,我能够在某个 WPF 应用程序的某个用户控件的某个单元格上加载 exe,但无法单击 Unity 框架中的任何内容。
我花了一整天的时间寻找解决方案,现在可以提出一个解决方案,在我看来,它更清晰,也解决了重点问题。
我描述了您可以按步骤执行的操作:
Form1.cs 包含与 this question 完全相同的代码。
public partial class Form1: Form
{
[DllImport("User32.dll")]
private static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);
internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
[DllImport("user32.dll")]
internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
private Process process;
private IntPtr unityHWND = IntPtr.Zero;
private const int WM_ACTIVATE = 0x0006;
private readonly IntPtr WA_ACTIVE = new IntPtr(1);
private readonly IntPtr WA_INACTIVE = new IntPtr(0);
public Form1()
{
InitializeComponent();
TopLevel = false;
try
{
process = new Process();
process.StartInfo.FileName = "[INSERT_FILE_NAME_OF_YOUR_EXE].exe";
process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForInputIdle();
// Doesn't work for some reason ?!
//unityHWND = process.MainWindowHandle;
EnumChildWindows(panel1.Handle, WindowEnum, IntPtr.Zero);
unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + ".\nCheck if Container.exe is placed next to Child.exe.");
}
}
private void ActivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
}
private void DeactivateUnityWindow()
{
SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
}
private int WindowEnum(IntPtr hwnd, IntPtr lparam)
{
unityHWND = hwnd;
ActivateUnityWindow();
return 0;
}
private void panel1_Resize(object sender, EventArgs e)
{
MoveWindow(unityHWND, 0, 0, panel1.Width, panel1.Height, true);
ActivateUnityWindow();
}
// Close Unity application
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
process.CloseMainWindow();
Thread.Sleep(1000);
while (process.HasExited == false)
process.Kill();
}
catch (Exception)
{
}
}
private void Form1_Activated(object sender, EventArgs e)
{
ActivateUnityWindow();
}
private void Form1_Deactivate(object sender, EventArgs e)
{
DeactivateUnityWindow();
}
}
process.StartInfo.FileName = "[INSERT_FILE_NAME_OF_YOUR_EXE].exe";
还要检查“SelectablePanel.cs”,其中 Selectable 设置为 true 是必不可少的。此 SelectablePanel 用于 Form1.Designer.cs
.SplitContainer 的左侧 panel1 是 SelectablePanel 而不是 Panel !
class SelectablePanel : Panel
{
public SelectablePanel()
{
this.SetStyle(ControlStyles.Selectable, true);
this.TabStop = true;
}
}
另请注意,在 Winforms-control 的构造函数中,我需要设置 Toplevel = false;
。这在 Unity 的示例中没有提到,但是当您将其嵌入 WPF 应用程序时需要避免异常。
Grid_To_Embed_Winforms_Control_In
的网格和一小段代码隐藏,如底层代码。public partial class WPF_User_Control: UserControl
{
public bool Already_Loaded = false;
public WPF_User_Control()
{
InitializeComponent();
}
private void On_Load(object sender, RoutedEventArgs e)
{
if (!Already_Loaded)
{
// Create the interop host control.
var host =
new WindowsFormsHost();
// Embed the Winforms Control
host.Child = new Embed_Unity_Exe_Winforms_Control();
// Add the interop host control to the Grid
// control's collection of child controls.
Grid_To_Embed_Winforms_Control_In.Children.Add(host);
Already_Loaded = true;
}
}
}
using System.Windows.Forms.Integration;
。请注意,我添加了 boolean Already Loaded
以确保当视口发生变化(您想查看 WPF 应用程序的另一个页面)时,该过程不会再次启动。在我的完整解决方案中,我使用了 Microsoft 依赖注入,并且此控件位于作为单例添加的视图模型中。这样,我只启动一次过程。就是这样,这对我有用。
有什么比我在谷歌搜索时看到的其他解决方案更好:
PS:如果你想让它看起来更好看,更“嵌入”,你可以用 Winform-control 做以下事情: