目前最通用的客戶端調用3D的方式,就是WPF程序通過Process啟動Unity3D的exe進程,直接上代碼:
//開啟3D進程 internal void Create3DProcess(string processUri) { if (string.IsNullOrWhiteSpace(processUri) || !File.Exists(processUri)) { return; //throw new Exception("Unable to find Unity window,File was not exit"); } var handle = Panel.Handle; this.Dispatcher.InvokeAsync(() => { try { //判斷當前要啟動的進程是否還在啟動,如果還在啟動,先關閉進程再創建進程 CheckProcessByName(processUri); if (process != null) { process.Close(); } process = new Process(); process.StartInfo.Arguments = "-parentHWND " + handle.ToInt32() + " " + Environment.CommandLine + " " + Unity3DProcessArges.Replace("AutoWidth",Panel.Width.ToString()).Replace("AutoHeight",Panel.Height.ToString()); process.StartInfo.FileName = processUri; process.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(processUri); process.StartInfo.UseShellExecute = true; process.StartInfo.CreateNoWindow = true; process.Start(); process.WaitForInputIdle(); EnumChildWindows(handle, WindowEnum, IntPtr.Zero); } catch (Exception ex) { LogHelper.Error(ex.ToString()); } }); } [DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);
啟動進程后,在做WPF應用上的交互,比如點擊了WPF上的一個按鈕,當前的鼠標的焦點會被WPF程序捕獲到,如果3D程序進程沒有做鍵盤或者鼠標焦點的獲取,就會出現鍵盤和鼠標事件無法觸發!
大概的解決思路:在WPF程序上,獲取鼠標滑動的窗體的句柄,判斷當前鼠標停留的窗體的句柄如果跟3D程序的窗體句柄一樣,則激活3D窗體程序(需要用到user32的API),直接上代碼:
1 private IntPtr unityHWND = IntPtr.Zero; //3D窗體的句柄 2 /// <summary> 3 /// 發送消息,觸發激活當前窗體 4 /// </summary> 5 internal void ActiveWindows() 6 { 7 8 if (unityHWND!=IntPtr.Zero) 9 { 10 SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero); 11 } 12 } 13 14 /// <summary> 15 /// 解除激活窗體 16 /// </summary> 17 internal void UnActiveWindows() 18 { 19 if (unityHWND!=IntPtr.Zero) 20 { 21 SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero); 22 } 23 } 24 25 26 //檢測窗體句柄 27 private void CheckWindowsActive() 28 { 29 actionTime = new DispatcherTimer(); 30 actionTime.Interval = new TimeSpan(TimeSpan.TicksPerMillisecond * 300); 31 actionTime.Tick += ActionTime_Tick; 32 actionTime.Start(); 33 } 34 35 IntPtr lastIntprt = IntPtr.Zero; //獲取上一次的鼠標指向的句柄,為了指向相同的位置,避免重復給窗體發送消息 36 private void ActionTime_Tick(object sender, EventArgs e) 37 { 38 try 39 { 40 POINT pOINT; 41 bool isSuccess = GetCursorPos(out pOINT); 42 var intptr = WindowFromPoint(pOINT); 43 if (isSuccess && unityHWND != IntPtr.Zero && lastIntprt != intptr)//判斷當前句柄跟上一次的句柄是否一樣,如果一樣就不再觸發 44 { 45 if (intptr == unityHWND) 46 { 47 ActiveWindows(); 48 } 49 else 50 { 51 UnActiveWindows(); 52 } 53 54 } 55 lastIntprt = intptr; 56 57 } 58 catch (Exception ex) 59 { 60 LogHelper.Error(ex.ToString()); 61 } 62 } 63 64 [DllImport("user32.dll", CharSet = CharSet.Auto)] 65 public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); 66 67 [DllImport("user32.dll", CharSet = CharSet.Auto)] 68 public static extern bool GetCursorPos(out POINT pt); 69 70 [DllImport("user32.dll")] 71 public static extern IntPtr WindowFromPoint(POINT Point); 72 73 public struct POINT 74 { 75 public int X; 76 public int Y; 77 public POINT(int x, int y) 78 { 79 this.X = x; 80 this.Y = y; 81 } 82 }