问题描述
背景
我正在开发一个广泛使用 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。 这段代码是我能得到的最接近我在原始应用程序中使用的代码。由于未知原因,使用记事本开始隐藏无法正常工作,所以我不得不在之后隐藏它,以便它在面板中显示得很好,没有闪烁。
已经试过了
- 我注意到检索记事本(如果它已经启动)有 没有问题。
- 在任务或不同的线程中调用 Process.Start, 没有效果。
- 我注意到沙漏即使在 你直接在 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- 通过配置不同的图标在窗口级别禁用带有沙漏的鼠标。 显然,这会影响一切。
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% 都不起作用。