背景
我正在开发一个广泛使用 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。这段代码是我能得到的最接近我在原始应用程序中使用的代码。由于未知的原因,开始隐藏在记事本上不能正常工作,所以我不得不在之后隐藏它,这样它看起来很好,不会在面板中闪烁。
已经试过了
- 我注意到如果记事本已经启动,则检索记事本没有问题。
- 在任务或其他线程中调用 Process.Start 无效。
- 我注意到即使您直接在 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;