1

背景

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

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

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

动机

我想这是可能的,例如 google chrome 对每个选项卡都使用了它,您可以添加多个水龙头,而不会在每个选项卡创建时出现繁忙的沙漏图标。

细节

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

我制作了一个小窗口窗体应用程序作为这种行为的示例,带有 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,
                (int)WindowLongFlags.GWL_STYLE,
                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, 0, 0, panel1.Width, panel1.Height, true);

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

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

已经试过了

  1. 我注意到如果记事本已经启动,则检索记事本没有问题。
  2. 在任务或其他线程中调用 Process.Start 无效。
  3. 我注意到即使您直接在 Windows 上执行沙漏也会出现,因此沙漏效果可能会被子可执行文件禁用。(这意味着提供的临时测试永远不会像使用 notepad.exe 那样工作)。

问题

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

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

编辑1:

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

    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;
4

1 回答 1

1

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

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

更改鼠标图标

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% 的情况下无法正常工作。

于 2021-06-30T10:13:31.240 回答