将Unity3D应用程序嵌入WPF *,而 * 不会占据整个窗口

hxzsmxv2  于 2023-03-19  发布在  其他
关注(0)|答案(2)|浏览(403)

我正在尝试修改这个问题中发布的代码: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"/>

真的,唯一的区别是,我尝试使用框架内的WPF页面,而不是WinForms面板(试图避免WinForms)。嵌入式Unity应用程序启动良好...除了它占据了整个窗口(即,您再也看不到任何WPF控件)。

**所以,问题是:**如何让Unity应用程序只停留在WPF页面内(位于Frame内)?

enter image description here
(The这个XY问题的Y是,我只是试图在WPF应用程序内部创建一个3D图形显示。)
提前感谢您的帮助。

4dc9hkyq

4dc9hkyq1#

在WPF中使用WindowsFormsHostHwndHost控件。hwnd位于宿主控件的Handle属性中。因此,您可以更改此行以将Unity仅放在宿主控件中。

process.StartInfo.Arguments = "-parentHWND " + hwndHost.Handle.ToInt32() + " " + Environment.CommandLine;

并删除获取浮动帧的hwnd的代码

HwndSource source = (HwndSource)HwndSource.FromVisual(p);
z2acfund

z2acfund2#

上述解决方案的问题是将焦点放在Unity-exe上似乎是不可能的。所以,是的,我能够在某个WPF应用程序的某个用户控件的某个单元格上加载exe,但不能单击Unity-frame中的任何内容。
我花了一整天寻找解决方案,现在可以来一个解决方案,这是在我看来更清洁,也解决了问题的重点。
我将分步骤描述您可以做的事情:

  1. Documentation of Unity:这里解释了如何在Winforms控件中嵌入Unity-exe。甚至还有一个. zip文件“EmbeddedWindow.zip“,您可以在其中下载示例代码。将基本文件从容器x1c 0d1x中复制出来。
    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();
        }
    }

1.不要忘记在“Form1.cs”中查看,有可能您要更新的exe不是“Child.exe”,因此如果它是另一个,只需编辑代码中的字符串。process.StartInfo.FileName = "[INSERT_FILE_NAME_OF_YOUR_EXE].exe";还检查“SelectablePanel.cs”,其中Selectable设置为true是必不可少的。此SelectablePanel用于Form1.Designer.cs

SplitContainer的左面板1是SelectablePanel而不是Panel!

class SelectablePanel : Panel
    {
        public SelectablePanel()
        {
            this.SetStyle(ControlStyles.Selectable, true);
            this.TabStop = true;
        }
    }

还请注意,在Winforms-control的构造函数中,我需要设置Toplevel = false;。Unity的示例中没有提到这一点,但在将其嵌入WPF应用程序时需要避免异常。
1.转到您的WPF应用程序并创建一个将包含Winforms-Control的用户控件。创建一个类似于this link中所做的控件。在此示例中,您有一个名为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;
        }
    }
}

1.不要忘记在顶部添加using System.Windows.Forms.Integration;。请注意,我添加boolean Already Loaded是为了确保当视口发生变化(您希望查看WPF应用程序的另一个页面)时,进程不会再次启动。在我的完整解决方案中,我使用了Microsoft Dependency Injection,并且此控件位于作为单例添加的视图模型中。这样,我只启动进程一次。
就是这样,这对我很有效.
什么是优于其他解决方案,我看到,而谷歌:

  • 调整大小的效果更好,而且是“自动”完成的,我不需要自己调用resize方法(除了Form1.cs中的方法)。
  • 我也可以控制和专注于统一。

PS:如果你想让它看起来更漂亮,更“嵌入”,你可以用Winform控件做以下事情:

  • 选择splitcontainer 1并转到“属性
  • 将BorderStyle设置为“无”
  • 将Panel 2Collapsed设置为“True”

相关问题