是否可以在进程启动期间隐藏沙漏?

问题描述

背景

我正在开发一个广泛使用 SetParent 功能的应用程序,以便在单个应用程序中播放多个视频播放器,同时控制主应用程序的内存。 每次用户请求观看视频时,都会执行一个新的 player.exe 并将其附加到主窗口。这适用于大多数用例场景。

但是有一个我正在挣扎。在这种情况下,用户正在快速播放大量视频,这意味着主应用程序不断地杀死和创建新的播放器。

每次执行 player.exe 时,鼠标图标上都会出现一个小沙漏图标,考虑到在这种情况下这些玩家创建的速度非常快,沙漏图标会不断播放。

动机

我想这是可能的,例如谷歌浏览器为每个标签使用它,你可以添加多个点击,而不会在每个标签创建时出现繁忙的沙漏图标。

详情

我同时控制主应用程序和播放器应用程序,只是要注意我可以对两者进行任何更改。

我制作了一个小型窗体应用程序作为这种行为的示例,带有 2 个按钮和 1 个面板。

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SetParentTest
{
    public partial class Form1 : Form
    {
        private Process _childProcessplayer;

        public Form1()
        {
            InitializeComponent();

            this.Closing += (sender,args) =>
            {
                Clear();
            };
        }

        public const UInt32 WS_POPUP = 0x80000000;
        public const UInt32 WS_CHILD = 0x40000000;

        [DllImport("user32.dll",SetLastError = true)]
        public static extern IntPtr SetParent(
            IntPtr windowChildHandle,IntPtr windowNewParentHandle);

        [DllImport("user32.dll",SetLastError = true)]
        public static extern UInt32 getwindowlong(IntPtr hWnd,int nIndex);

        [DllImport ( "user32.dll" )]
        public static extern int SetwindowLong ( IntPtr hWnd,int nIndex,uint dwNewLong );

        public enum WindowLongFlags : int
        {
            GWL_EXSTYLE = -20,GWLP_HINSTANCE = -6,GWLP_HWNDPARENT = -8,GWL_ID = -12,GWL_STYLE = -16,GWL_USERDATA = -21,GWL_WNDPROC = -4,DWLP_USER = 0x8,DWLP_MSGRESULT = 0x0,DWLP_DLGPROC = 0x4
        }

        [DllImport("user32.dll",SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool MoveWindow(
            IntPtr windowHandle,int x,int y,int width,int height,[MarshalAs(UnmanagedType.Bool)] bool repaint);
        
        [DllImport("User32.dll",ExactSpelling = true,CharSet = CharSet.Auto)]
        public static extern bool ShowWindow(IntPtr hWnd,int nCmdshow);

        /// <summary>Enumeration of the different ways of showing a window using
        /// ShowWindow</summary>
        private enum WindowShowStyle : uint
        {
            /// <summary>Hides the window and activates another window.</summary>
            /// <remarks>See SW_HIDE</remarks>
            Hide = 0,/// <summary>Activates and displays a window. If the window is minimized
            /// or maximized,the system restores it to its original size and
            /// position. An application should specify this flag when displaying
            /// the window for the first time.</summary>
            /// <remarks>See SW_SHOWnorMAL</remarks>
            Shownormal = 1,/// <summary>Activates the window and displays it as a minimized window.</summary>
            /// <remarks>See SW_SHOWMINIMIZED</remarks>
            ShowMinimized = 2,/// <summary>Activates the window and displays it as a maximized window.</summary>
            /// <remarks>See SW_SHOWMAXIMIZED</remarks>
            ShowMaximized = 3,/// <summary>Maximizes the specified window.</summary>
            /// <remarks>See SW_MAXIMIZE</remarks>
            Maximize = 3,/// <summary>displays a window in its most recent size and position.
            /// This value is similar to "Shownormal",except the window is not
            /// actived.</summary>
            /// <remarks>See SW_SHOWNOACTIVATE</remarks>
            ShownormalNoActivate = 4,/// <summary>Activates the window and displays it in its current size
            /// and position.</summary>
            /// <remarks>See SW_SHOW</remarks>
            Show = 5,/// <summary>Minimizes the specified window and activates the next
            /// top-level window in the Z order.</summary>
            /// <remarks>See SW_MINIMIZE</remarks>
            Minimize = 6,/// <summary>displays the window as a minimized window. This value is
              /// similar to "ShowMinimized",except the window is not activated.</summary>
            /// <remarks>See SW_SHOWMINNOACTIVE</remarks>
            ShowMinNoActivate = 7,/// <summary>displays the window in its current size and position. This
            /// value is similar to "Show",except the window is not activated.</summary>
            /// <remarks>See SW_SHOWNA</remarks>
            ShowNoActivate = 8,/// <summary>Activates and displays the window. If the window is
            /// minimized or maximized,the system restores it to its original size
            /// and position. An application should specify this flag when restoring
            /// a minimized window.</summary>
            /// <remarks>See SW_RESTORE</remarks>
            Restore = 9,/// <summary>Sets the show state based on the SW_ value specified in the
            /// STARTUPINFO structure passed to the CreateProcess function by the
            /// program that started the application.</summary>
            /// <remarks>See SW_SHOWDEFAULT</remarks>
            ShowDefault = 10,/// <summary>Windows 2000/XP: Minimizes a window,even if the thread
            /// that owns the window is hung. This flag should only be used when
            /// minimizing windows from a different thread.</summary>
            /// <remarks>See SW_FORCEMINIMIZE</remarks>
            ForceMinimized = 11
        }

        /// <summary>
        /// Handles the Click event of the button1 control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void button1_Click(object sender,EventArgs e)
        {
            AttachWindow();
        }

        /// <summary>
        /// Handles the Click event of the button1 control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        private void button2_Click(object sender,EventArgs e)
        {
            Clear();
        }

        private void Clear()
        {
            if (_childProcessplayer == null)
                return;

            if (_childProcessplayer.HasExited)
            {
                _childProcessplayer = null;
                return;
            }

            _childProcessplayer.Kill();
            _childProcessplayer = null;
        }

        private void AttachWindow()
        {
            // do it only once per test.
            if (_childProcessplayer != null)
                return;

            // Instance of the remote process to start.
            //_childProcessplayer = Process.GetProcessesByName("notepad").FirstOrDefault();
            _childProcessplayer = new Process
            {
                StartInfo =
                {
                    FileName = @"C:\Windows\System32\notepad.exe",//CreateNowindow = true,UseShellExecute = true,WindowStyle = ProcessWindowStyle.Minimized
                },EnableRaisingEvents = true
            };

            Cursor.Current = Cursors.Default;
            _childProcessplayer.Start();
            Cursor.Current = Cursors.Default;
            _childProcessplayer.WaitForInputIdle();
            Cursor.Current = Cursors.Default;

            ShowWindow(_childProcessplayer.MainWindowHandle,(int)WindowShowStyle.Hide);
            
            // Get process window handle.
            var mainWindowHandle = _childProcessplayer.MainWindowHandle;
                
            // To prevent focus steal when SetParent is called I need to add WS_CHILD to the style.
            uint windowLong = getwindowlong(
                mainWindowHandle,(int) WindowLongFlags.GWL_STYLE);

            // add ws_child
            windowLong |= WS_CHILD;

            // remove pop_up (most cases this is not necessary as it is already unset)
            windowLong &= ~WS_POPUP;

            // modify the style.
            SetwindowLong(
                mainWindowHandle,(int)WindowLongFlags.GWL_STYLE,windowLong);

            // disable panel to prevent focus being stolen. (necessary in some cases)
            panel1.Enabled = false;

            // Execute Set parent.
            SetParent(mainWindowHandle,panel1.Handle);

            // Restore child state in order to allow editing in the notepad.
            windowLong &= ~WS_CHILD;
            SetwindowLong(
                mainWindowHandle,windowLong);

            // Hide panel while notepad is resized.
            panel1.Visible = false;
            
            // Show notepad so resizing work
            ShowWindow(_childProcessplayer.MainWindowHandle,(int)WindowShowStyle.Shownormal);

            // Resize and move the window to the panel size.
            MoveWindow(mainWindowHandle,panel1.Width,panel1.Height,true);

            panel1.Visible = true;
            panel1.Enabled = true;
        }
    }
}

我留下了整个代码,因为有人可能会感兴趣。显然,我无法控制 notepad.exe。 这段代码是我能得到的最接近我在原始应用程序中使用的代码。由于未知原因,使用记事本开始隐藏无法正常工作,所以我不得不在之后隐藏它,以便它在面板中显示得很好,没有闪烁。

已经试过了

  1. 我注意到检索记事本(如果它已经启动)有 没有问题。
  2. 在任务或不同的线程中调用 Process.Start, 没有效果
  3. 我注意到沙漏即使在 你直接在 Windows 上执行它所以沙漏效应可能是我 可以被子可执行文件禁用的东西。 (哪个 意味着提供的临时测试永远不会像它使用的那样工作 记事本.exe)。

问题

最后的问题是:是否可以调用子应用程序的进程启动以避免沙漏出现在鼠标上?

最坏的情况是在 Windows 注册表中禁用沙漏图标(我想这是可能的,尽管我目前不知道)但这将是我最后的选择。

编辑 1:

我尝试过的另一件事: 在调用进程启动之前强制鼠标图标。 我认为它会起作用,因为它适用于手形图标或与指针不同的图标,但如果图标是指针,则它不起作用。

    Cursor cr = new Cursor(Cursors.Arrow.Handle);
    Icon ico = Icon.FromHandle(cr.Handle);
    cr = new Cursor(ico.Handle);
    
    Cursor.Current = cr;
    _childProcessplayer.Start();
    _childProcessplayer.WaitForInputIdle();
    Cursor.Current = Cursors.Default;

解决方法

我想出了 2 个解决方法,虽然不是完美的:

1- 通过配置不同的图标在窗口级别禁用带有沙漏的鼠标。 显然,这会影响一切。

Change mouse icon

2- 如果不知道图标是箭头,则在操作之前强制图标:

    Icon ico = Icon.FromHandle(Cursor.Current.Handle);
    ico = (Icon)ico.Clone();
    Cursor.Current = new Cursor(ico.Handle);
    _childProcessPlayer.Start();
    _childProcessPlayer.BeginOutputReadLine();
    _childProcessPlayer.WaitForInputIdle();

    Cursor.Current = Cursors.Default;

这个其他解决方案的问题在于,它只有在鼠标停留在您的应用程序上时才能正常工作。如果鼠标在它设置之外,则图标 100% 都不起作用。