【问题标题】:C# - How can I rename a process window that I started?C# - 如何重命名我启动的进程窗口?
【发布时间】:2010-11-04 05:56:45
【问题描述】:

有什么方法可以重命名已启动的应用程序的窗口标题栏? IE。如果我启动 Notepad.exe,我可以将其标题栏从“无标题 - 记事本”重命名为“新记事本名称”。

【问题讨论】:

  • 你的意思是在它运行的时候?
  • 是的......虽然如果我可以在启动进程之前指定标题栏,那也是可以接受的。
  • 一般情况下,除非应用程序支持。许多人没有。例如,记事本在其标题栏中包含当前打开的文件的名称,并且不支持对标题进行任何其他更改。另一方面,CMD.EXE 有内置命令 TITLE,可以控制控制台窗口的标题。
  • 除非应用程序支持 Rberteig 提到的那样,否则我想不出办法。

标签: c# process window titlebar


【解决方案1】:

您可以使用 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,标题不会改变。也许消息在记事本的消息队列中结束得太早了,所以记事本会在之后重新设置标题。

还要注意这是一个非常简短的更改;如果用户选择文件 -> 新建(或执行任何其他会导致记事本更新窗口标题的操作),原始标题将返回...

【讨论】:

  • 就像我在回答和对该问题的评论中所说,记事本(和大多数其他应用程序)不支持此功能。使用 SetWindowText() 强制更改标题并不是真正的解决方案,因为应用程序只会在自己选择的某个时刻将其放回原处。
  • 这是我的问题的解决方案,因为我使用的应用程序永远不会设置它。我只以记事本为例。另外,如果您真的想这样做,创建自己的事件来处理窗口句柄的标题更改然后再次重置它非常简单。
  • 只是要注意“丑陋的黑客”,我不需要这样做,因为我已经控制了该过程并且知道它正在运行并且标题栏可见。
  • 我会争辩说,如果更改另一个应用程序的窗口标题不是为了通过某种形式的文档化 API 支持它而设计的,那么它是一个非常糟糕的主意。
  • MS 建议在有 Sleep 调用的地方调用 WaitForInputIdle。 msdn.microsoft.com/en-us/library/… 这把它从“Hack”变成了“Recommended Practice”(有时我觉得这是必要的)
【解决方案2】:

其实是我自己整理的,效果很好。还是谢谢。

[DllImport("user32.dll")]
static extern SetWindowText(IntPtr hWnd, string windowName);

IntPtr handle = p.MainWindowHandle;
SetWindowText(handle, "This is my new title");

【讨论】:

  • 不要期望您的标题能够在其他应用程序中导致其更新其标题的任意事件之后继续存在。当用户打开不同的文档时,记事本会执行此操作,例如选择文件|新建或文件|另存为。
  • 我刚刚创建了一个事件来处理这种可能性,所以一旦文本发生变化,它就会重新设置。
【解决方案3】:

您无法在 C# 中执行此操作,但您可以使用低级 API 执行此操作。 在进程中注入一个线程,从中调用SetWindowText()

【讨论】:

  • 任何使用标题来传达信息的应用程序都会在某些时候用自己的文本覆盖该文本,就像记事本一样。
【解决方案4】:

正如@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 语句替换为SpinWait.SpinUntil 调用来避免CPU 开销(因此,您需要导入System.Threading):

Process p = Process.Start("notepad.exe");
SpinWait.SpinUntil(() => p.MainWindowHandle != IntPtr.Zero);
SetWindowText(p.MainWindowHandle, "My Notepad");

【讨论】:

  • 这是使用 SpinWait 选项最容易实现的。我使用了更紧凑的符号:SpinWait.SpinUntil( () =&gt; p.MainWindowHandle != IntPtr.Zero );
  • 你是对的,@Marc Meketon。我一直在使用的语法看起来既古老又奇怪,lambdas 更友好。我刚刚更新了我的答案。谢谢你的评论
  • 在实现这个之后,我遇到了一个需要调整SpinUntil 条件的错误。我刚刚留下了一个新答案,所以我可以显示完整的代码..
  • 谢谢,@Marc Meketon。我写答案时没有遇到这个问题,所以我很高兴你找到了解决你自己注意到的问题的方法。
【解决方案5】:

我实现了@Davide Cannizo 解决方案,发现一个问题可能与其他循环的解决方案也一样——该过程可能在意识到MainWindowHandle 非零之前就已经完成。当我并行运行许多进程并且一两个进程非常短暂时,这发生在我身上。具体问题是SystemAggregrationError,因为检查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) 会导致异常。根据文档here,它可能会失败,但只会返回 0 而不是非零。这意味着如果我们完成SpinUntil,因为MainWindowHandle 不为零,但在该点和SetWindowText 之间进程退出,它不会导致异常。但是,在我的测试中,我确实必须添加 if (!p.HasExited) 行。

【讨论】:

    【解决方案6】:

    我必须使用以下代码才能使其工作:

    while (p.MainWindowHandle == IntPtr.Zero)
          Thread.Sleep(100);
    

    【讨论】:

      【解决方案7】:

      没有。

      这将要求目标应用程序完全允许修改窗口标题。许多程序使用它们的标题来显示有用的信息(例如在记事本中打开以进行编辑的文件的名称,或在 Firefox 中打开的 HTML 文档的 &lt;TITLE&gt;)。

      我知道的一种让用户设置标题文本的限制很少的情况是在控制台窗口中运行的 CMD.EXE。 CMD 支持TITLE 内置命令,该命令根据其参数设置窗口标题。但是,如果不将击键注入特定的控制台窗口,则无法通过第二个窗口来完成,通常不建议这样做。

      编辑:

      由于SetWindowText() 会为你做这件事的想法是浮动的,让我澄清一下。

      该 API 函数确实改变了顶级窗口的标题栏。事实上,像记事本这样的应用程序可能会在它认为标题已更改时使用它来设置自己的标题。

      我声称这不是解决方案的原因是记事本确实会在需要时更改标题。支持任意更改其标题的应用程序将具有某种机制来记住标题已更改,而不是任意恢复其首选标题字符串。

      【讨论】:

      • SetWindowText 是解决问题的方法。它完全符合我的要求,因此不可否认它是解决问题的方法。无论如何,我实际上并没有要求它是永久的,但是就像我在其他 cmets 中发布的那样,无论如何都有一个简单的解决方法可以让它保持永久。
      • 这不是一个通用的解决方案。您要求一个通用的解决方案,并以在标题中维护信息的应​​用程序为例。
      猜你喜欢
      • 1970-01-01
      • 2017-12-09
      • 1970-01-01
      • 2010-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-03
      相关资源
      最近更新 更多