有什么方法可以重命名已启动的应用程序的窗口标题栏?即,如果我启动 Notepad.exe,我可以将其标题栏从“Untitled - Notepad”重命名为“New Notepad Name”。
7 回答
您可以使用 P/Invoke 执行此操作:
[DllImport("user32.dll")]
static extern int SetWindowText(IntPtr hWnd, string text);
private void StartMyNotepad()
{
Process p = Process.Start("notepad.exe");
Thread.Sleep(100); // <-- ugly hack
SetWindowText(p.MainWindowHandle, "My Notepad");
}
代码示例中丑陋的 hack 的背景是,似乎您在启动进程后立即调用 SetWindowText,标题不会改变。也许消息在记事本的消息队列中结束得太早了,所以记事本会在之后再次设置标题。
另请注意,这是一个非常简短的更改;如果用户选择 File -> New(或执行任何其他会导致记事本更新窗口标题的操作),则原始标题将返回...
实际上,我自己对其进行了排序,并且效果很好。不管怎么说,还是要谢谢你。
[DllImport("user32.dll")]
static extern SetWindowText(IntPtr hWnd, string windowName);
IntPtr handle = p.MainWindowHandle;
SetWindowText(handle, "This is my new title");
您不能在 C# 中执行此操作,但您可以使用低级 API 执行此操作。在进程中注入一个线程,从中调用SetWindowText()
正如@Fredrik Mörk 认为的那样,问题在于需要等待窗口可以接收消息才能设置其标题。等待 100 毫秒可能会破坏程序的内部消息循环,这只是一种解决方法。要接收消息,窗口必须有一个用于引用该窗口的句柄,因此您可以简单地等待窗口的句柄,它在启动时应该是IntPtr.Zero
(一个空的句柄)。
这是这种方法的一个示例:
Process p = Process.Start("notepad.exe");
while (p.MainWindowHandle == IntPtr.Zero)
Application.DoEvents();
SetWindowText(p.MainWindowHandle, "My Notepad");
使用Application.DoEvents()
该程序将继续接收和处理系统消息,因此它不会阻塞(也不会崩溃),尽管它不是异步的。
您还可以考虑通过用调用替换while
语句来避免 CPU 开销SpinWait.SpinUntil
(因此,您需要 import System.Threading
):
Process p = Process.Start("notepad.exe");
SpinWait.SpinUntil(() => p.MainWindowHandle != IntPtr.Zero);
SetWindowText(p.MainWindowHandle, "My Notepad");
我实施了@Davide Cannizo 解决方案,并发现一个问题可能与其他循环的解决方案也一样——该过程可能在意识到MainWindowHandle
非零之前就已经完成。当我并行运行许多进程并且一两个进程非常短暂时,这发生在我身上。具体问题是 aSystemAggregrationError
因为检查p.MainWindowHandle != IntPtr.Zero
导致多个InvalidOperationError
'.
解决方案是添加对进程是否退出的检查。寻找p.HasExited
以下内容。
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("user32.dll")]
static extern int SetWindowText(IntPtr hWnd, string text);
static void Main(string[] args)
{
using (System.Diagnostics.Process p = new System.Diagnostics.Process())
{
p.StartInfo = new System.Diagnostics.ProcessStartInfo()
{
FileName = @"notepad.exe",
Arguments = @""
};
string window_title = "notepad demo";
p.Start();
System.Threading.SpinWait.SpinUntil(() => p.HasExited || p.MainWindowHandle != IntPtr.Zero);
if (!p.HasExited)
SetWindowText(p.MainWindowHandle, window_title);
p.WaitForExit();
}
}
}
另外两点需要注意:
- 在开发这个时,我首先尝试使用 .NET Core。那失败了——
p.MainWindowHandler
总是0。我不得不切换到.NET Framework。 - 如果进程退出,我不相信这
SetWindowText(p.MainWindowHandle, window_title)
会导致异常。根据此处的文档,它可能会失败,但只会返回 0 而不是非零。这意味着如果我们完成了SpinUntil
因为MainWindowHandle
是非零的,但是在那个点和SetWindowText
进程退出之间,它不会导致异常。但是,在我的测试中,我确实必须添加该if (!p.HasExited)
行。
我必须使用以下代码才能使其工作:
while (p.MainWindowHandle == IntPtr.Zero)
Thread.Sleep(100);
不。
这将要求目标应用程序完全允许修改窗口标题。许多程序使用它们的标题来显示有用的信息(例如在记事本中打开以进行编辑的文件的名称,或<TITLE>
在 Firefox 中打开的 HTML 文档的名称)。
我知道的一种情况是让用户在很少限制的情况下设置标题文本是在控制台窗口中运行的 CMD.EXE。CMD 支持TITLE
根据其参数设置窗口标题的内置命令。但是,如果不将击键注入特定的控制台窗口,则无法通过第二个窗口来完成,通常不建议这样做。
编辑:
由于这个想法是浮动的,它SetWindowText()
会为你做这件事,让我澄清一下。
该 API 函数确实会更改顶级窗口的标题栏。事实上,像记事本这样的应用程序可能会在它认为标题已更改时使用它来设置自己的标题。
我声称这不是解决方案的原因是记事本确实会在需要时更改标题。支持任意更改其标题的应用程序将具有某种机制来记住标题已更改,而不是任意恢复其首选标题字符串。