C# - 如何重命名我启动的进程窗口?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1016823/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:37:52  来源:igfitidea点击:

C# - How can I rename a process window that I started?

c#processwindowtitlebar

提问by djdd87

Is there any way I can rename the window titlebar of an application that I've launched? I.e. if I launched Notepad.exe, I could rename its title bar from "Untitled - Notepad" to "New Notepad Name".

有什么办法可以重命名我启动的应用程序的窗口标题栏?即,如果我启动 Notepad.exe,我可以将其标题栏从“Untitled - Notepad”重命名为“New Notepad Name”。

采纳答案by Fredrik M?rk

You can do it using P/Invoke:

您可以使用 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");
}

The background of the ugly hack in the code sample is that it seems as if you call SetWindowText immediately after starting the process, the title will not change. Perhaps the message ends up too early in the message queue of Notepad, so that notepad will set the title again afterwards.

代码示例中的丑陋黑客的背景是,如果您在启动过程后立即调用SetWindowText,则标题不会改变。也许消息在记事本的消息队列中结束得太早,以便记事本之后会再次设置标题。

Also note that this is a very brief change; if the user selects File -> New (or does anything else that will cause Notepad to update the window title), the original title will be back...

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

回答by RBerteig

No.

不。

This would require that the target application allow the window title to be modified at all. Many programs use their titles to show useful information (such as the name of the file open for editing in Notepad, or the <TITLE>of the HTML document open in Firefox).

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

The one case I am aware of that lets a user set the title text with few restrictions is CMD.EXE running in a console window. CMD supports the TITLEbuilt-in command that sets the window title based on its arguments. But that can't be done by a second window without injecting key strokes into the particular console window, which is generally not recommended.

我知道的一种情况是,允许用户设置标题文本而几乎没有限制,它是在控制台窗口中运行的 CMD.EXE。CMD 支持TITLE基于参数设置窗口标题的内置命令。但这不能通过第二个窗口完成,而无需将键击注入特定的控制台窗口,这通常是不推荐的。

Edit:

编辑:

Since the idea is floating that SetWindowText()will do this for you, let me clarify.

由于这个想法是浮动的,SetWindowText()可以为您做到这一点,让我澄清一下。

That API function does indeed change the title bar of a top level window. It is, in fact, the call that an application like Notepad is likely using to set its own title any time it thinks that the title has changed.

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

The reason I claim that this is nota solution is that Notepad does indeed change the title when it wants to. An application that supported arbitrary changes to its title would have a mechanism of some kind to remember that the title was changed and not arbitrarily restore its preferred title string.

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

回答by djdd87

Actually, I sorted it myself and it works perfectly. Thanks anyway.

实际上,我自己对它进行了排序,并且效果很好。不管怎么说,还是要谢谢你。

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

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

回答by J-16 SDiZ

You can't do it in C#, but you can do it using low level API. Inject a thread into the process, call SetWindowText()from it

你不能在 C# 中做到这一点,但你可以使用低级 API 做到这一点。将线程注入进程,从中调用SetWindowText()

回答by Davide Cannizzo

As @Fredrik M?rk thinks, the problem is that there's need to wait for the window can receive messages to set its title. Waiting 100 milliseconds can break the internal message looper of the program and it's just a workaround. To receive a message, the window must have a handle that's used for referencing to that window, thus you can simply wait for the handle of the window, which at the startup should be IntPtr.Zero(an empty handle).

正如@Fredrik M?rk 认为的那样,问题在于需要等待窗口可以接收消息来设置其标题。等待 100 毫秒可能会破坏程序的内部消息循环程序,这只是一种解决方法。要接收消息,窗口必须有一个用于引用该窗口的句柄,因此您可以简单地等待窗口的句柄,它在启动时应该是IntPtr.Zero(一个空句柄)。

Here's an example of this approach:

下面是这种方法的一个例子:

Process p = Process.Start("notepad.exe");
while (p.MainWindowHandle == IntPtr.Zero)
    Application.DoEvents();
SetWindowText(p.MainWindowHandle, "My Notepad");

Using Application.DoEvents()the program will continue to receive and process system messages, so it won't block (nor crash), although it isn't asynchronous.

使用Application.DoEvents()该程序将继续接收和处理系统消息,因此它不会阻塞(也不会崩溃),尽管它不是异步的。

You can also think to avoid CPU overhead by replacing the whilestatement with a SpinWait.SpinUntilcall (so, you'll need to import System.Threading):

您还可以考虑通过将while语句替换为SpinWait.SpinUntil调用来避免 CPU 开销(因此,您需要导入System.Threading):

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

回答by Marc Meketon

I implemented @Davide Cannizo solution and found an issue that is probably also true with other solutions that loop - that the process could have finished before realization that the MainWindowHandleis non-zero. This happened to me when I ran a lot of processes in parallel and one or two were very short-lived. The specific issue was a SystemAggregrationErrorbecause the check p.MainWindowHandle != IntPtr.Zerowas causing multiple InvalidOperationError's.

我实施了@Davide Cannizo 解决方案,并发现了一个问题,该问题可能也适用于其他循环解决方案 - 该过程可能在意识到MainWindowHandle非零之前已经完成。当我并行运行很多进程并且一两个进程非常短暂时,这发生在我身上。具体问题是 aSystemAggregrationError因为检查p.MainWindowHandle != IntPtr.Zero导致多个InvalidOperationError's。

The solution was to add a check on whether the process exited. Look for the p.HasExitedbelow.

解决方案是添加一个检查进程是否退出。寻找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();
    }
  }
}

Two other things to note:

另外两点需要注意:

  • in developing this, I first tried to use .NET Core. That failed - p.MainWindowHandlerwas always 0. I had to switch to .NET Framework.
  • I do not believe that SetWindowText(p.MainWindowHandle, window_title)causes an exception if the process has exited. According to the documentation here, it can fail but will only return 0 instead of a non-zero. That means that if we finish the SpinUntilbecause the MainWindowHandleis non-zero, but between that point and the SetWindowTextthe process exited, it will not cause an exception. However, in my testing I did have to add the if (!p.HasExited)line.
  • 在开发这个时,我首先尝试使用 .NET Core。失败 -p.MainWindowHandler始终为 0。我不得不切换到 .NET Framework。
  • SetWindowText(p.MainWindowHandle, window_title)如果进程退出,我认为这不会导致异常。根据此处的文档,它可能会失败,但只会返回 0 而不是非零。这意味着如果我们SpinUntil因为MainWindowHandle非零而结束,但在该点和SetWindowText进程退出之间,它不会导致异常。但是,在我的测试中,我确实必须添加该if (!p.HasExited)行。