如何让Process.Start启动的程序直接在副显示器打开,避免主屏闪烁
解决VNC Viewer启动时主显示器闪烁问题
核心问题分析
当前实现依赖轮询检测窗口,存在明显延迟,导致窗口先在主显示器短暂渲染后才被移动/嵌入。要彻底消除闪烁,必须在窗口创建的第一时间就接管控制,避免它在主显示器显示。
解决方案步骤
- 用窗口创建事件监听替代轮询,确保窗口刚创建就被处理
- 启动进程时配置更严格的隐藏参数,阻止初始窗口显示
- 先将窗口绑定为面板的子控件,再调整样式和位置,跳过跨显示器移动步骤
修改后的代码实现
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Forms; public partial class YourForm : Form { private delegate void WinEventDelegate(IntPtr hookPtr, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); [DllImport("user32.dll")] private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); [DllImport("user32.dll")] private static extern bool UnhookWinEvent(IntPtr hWinEventHook); [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hwnd, int nIndex); [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hwnd, int nIndex, int dwNewLong); [DllImport("user32.dll")] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); [DllImport("user32.dll")] private static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); private const uint EVENT_OBJECT_CREATE = 0x8000; private const uint WINEVENT_OUTOFCONTEXT = 0x0000; private const int GWL_STYLE = -16; private const int WS_MINIMIZEBOX = 0x00020000; private const int WS_MAXIMIZEBOX = 0x00010000; private const int WS_SYSMENU = 0x00080000; private const int WS_THICKFRAME = 0x00040000; private const int WS_CAPTION = 0x00C00000; private const int WS_CHILD = 0x40000000; private IntPtr _eventHook; private Process _vncProcess; private Panel _vncPanel; private System.Collections.Generic.List<Process> vncProcesses = new System.Collections.Generic.List<Process>(); public async Task StartVncViewer(string ipAddress, string devicename) { _vncProcess = new Process(); _vncProcess.StartInfo.FileName = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "VNCViewer.exe"); _vncProcess.StartInfo.Arguments = $"-Monitor={devicename}@ -KeepAliveResponseTimeout=9999999 -UriSuppressConnectionPrompt=TRUE -Scaling=Fit -EnableToolbar=False {ipAddress}"; _vncProcess.StartInfo.UseShellExecute = false; _vncProcess.StartInfo.CreateNoWindow = true; _vncProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; // 注册当前进程的窗口创建监听 var winEventDelegate = new WinEventDelegate(OnWindowCreated); _eventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE, IntPtr.Zero, winEventDelegate, (uint)_vncProcess.Id, 0, WINEVENT_OUTOFCONTEXT); _vncProcess.Start(); vncProcesses.Add(_vncProcess); // 进程退出后卸载钩子 await _vncProcess.WaitForExitAsync(); UnhookWinEvent(_eventHook); } private void OnWindowCreated(IntPtr hookPtr, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { // 仅处理主窗口对象 if (idObject != 0 || idChild != 0) return; try { // 立即绑定到目标面板,阻止窗口在主显示器渲染 SetParent(hwnd, _vncPanel.Handle); // 调整窗口样式 int style = GetWindowLong(hwnd, GWL_STYLE); style &= ~WS_MINIMIZEBOX; style &= ~WS_MAXIMIZEBOX; style &= ~WS_SYSMENU; style &= ~WS_THICKFRAME; style &= ~WS_CAPTION; style |= WS_CHILD; SetWindowLong(hwnd, GWL_STYLE, style); // 适配面板大小 MoveWindow(hwnd, 0, 0, _vncPanel.Width, _vncPanel.Height, true); // 注册面板resize事件 _vncPanel.Resize -= VncPanel_Resize; _vncPanel.Resize += VncPanel_Resize; // 找到目标窗口后卸载钩子 UnhookWinEvent(_eventHook); } catch (Exception ex) { Debug.WriteLine($"窗口处理出错: {ex.Message}"); } } private void VncPanel_Resize(object sender, EventArgs e) { if (_vncProcess != null && !_vncProcess.HasExited) { MoveWindow(_vncProcess.MainWindowHandle, 0, 0, _vncPanel.Width, _vncPanel.Height, true); } } }
关键优化说明
- 窗口事件监听:通过
SetWinEventHook监听进程内的窗口创建事件,相比轮询能在窗口刚初始化时立即处理,彻底消除延迟导致的闪烁 - 进程启动配置:设置
UseShellExecute = false和CreateNoWindow = true,从底层阻止VNC Viewer初始窗口的显示 - 处理顺序调整:先将窗口绑定为面板子控件,再修改样式和大小,跳过跨显示器移动步骤,窗口直接在面板内部渲染
内容的提问来源于stack exchange,提问作者Dominic Legendre
相关产品推荐
相关产品推荐

