【问题标题】:How do I close (not kill) an application which has minimized to the system tray?如何关闭(而不是杀死)已最小化到系统托盘的应用程序?
【发布时间】:2011-06-26 22:45:04
【问题描述】:

我正在编写一个关闭程序、更改其数据文件然后重新打开它的应用程序。我注意到,如果我使用process.Kill(),则会有一些数据未写入文件。

如果我使用process.CloseMainWindow(),主窗口会关闭,但进程会最小化到开始托盘。

有什么方法可以向进程发送优雅的关闭消息吗?

如果它很重要,我正试图关闭的是 Grindstone。

【问题讨论】:

  • 您使用的是 Windows 吗?如果是这样,请添加windows 标签(或更具体的版本标签)。
  • 如何手动关闭 Grindstone?你能在代码中模拟这个吗?
  • 每当我看到与结束进程有关的“数据未写入文件”时,我的第一个想法是“你close() 文件吗?”我不了解您的具体情况,但我建议首先确保在终止进程之前关闭所有打开的文件。
  • 我已经放弃了这个项目,所以我无法验证下面的答案是否有效。

标签: c# windows-7 process system-tray


【解决方案1】:

很遗憾,您无法优雅地终止不合作的应用程序。

建议的方法是将 WM_CLOSE 消息发送到感兴趣的窗口;这在这里不起作用,因为该应用程序选择按照您的描述隐藏自己。但是,这是 Microsoft 认可的唯一方法。

下一步是更严厉一些,将 WM_QUIT 消息发送到线程。这有点问题,因为您必须使用某种形式的进程/线程枚举和 PInvoke PostThreadMessage 找到有问题的线程才能发布 WM_QUIT。但是,MSDN 似乎suggest 认为您不应该这样做(搜索 WM_QUIT)。实际上,它应该可以工作。

如果这不起作用,那么Process.Kill 就是你剩下的全部了。

更新:以上是我自己的理解,但同样的主题也有Microsoft KB article。它适用于 Win32(非托管代码),但可以轻松调整这些想法。

【讨论】:

    【解决方案2】:

    BackgroundWorker 提供的here 解决方案中的EventWaitHandle 对象对我来说效果很好,而且我认为它比使用 win API 消息更容易编写代码。

    基本上,您有一个后台工作线程等待使用myEventWaitHandle.WaitOne 方法发生的某个命名事件。

    另一个应用程序只是创建了相同的命名事件并调用myEventWaitHandle.Set() 来触发它。这会导致后台工作人员中的WaitOne() 方法继续进行,从而触发RunWorkerCompleted。此时您可以安全地关闭您的应用程序。

    您的主要应用程序:

    private void evtBgWorker_DoWork(object sender, DoWorkEventArgs e) { 
       string evtName = "MyExitRequest" + Process.GetCurrentProcess().Id.ToString(); 
       EventWaitHandle evt = new EventWaitHandle(false, EventResetMode.ManualReset, evtName); 
       evt.WaitOne(); // the worker stops here until the event is triggered
     } 
    
    private void evtBgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { 
      this.Close(); 
    } 
    

    你的“优雅杀手”应用:

    private void CloseMainApp() 
    {
       Process[] processes = Process.GetProcessesByName("MyProcessName");
       Process myprocess= null;
       if (processes.Length > 0)
       {
          myprocess = processes[0];
          string evtName = "MyExitRequest" + myprocess.Id; // the same event name
          EventWaitHandle evt = new EventWaitHandle(false, EventResetMode.ManualReset, evtName);
          evt.Set(); // triggers the event at the main app
    
          if (!myprocess.WaitForExit(10000)) // waits until the process ends gracefuly
          {
             // if don't...
             myprocess.Kill();
          }
       }
    }
    

    【讨论】:

      【解决方案3】:

      您需要将WM_CLOSE message 发送到应用程序的主窗口。大概这就是CloseMainWindow 为您抽象的内容。每次都能正常工作。

      不幸的是,听起来有问题的应用程序通过将自身最小化到任务栏的通知区域来处理WM_CLOSE 消息。在这种情况下,如果您尝试以任何其他方式退出应用程序,包括 File -> Exit 或单击标题栏中的“X”按钮,它也会执行相同的操作。

      自以为是的应用程序经常这样做。这是错误的,并且强烈劝阻,甚至有一种正确的方式来实现它,但这些事情以前都没有阻止过人们。

      因此,真正关闭程序的唯一方法是检查相关应用程序的文档并了解如何关闭它,而不是最小化它。我敢打赌,在控制此的首选项对话框之一中有一个选项。确保您已相应地设置了该选项。

      绝对回避任何涉及向线程发送WM_QUIT message 或终止整个进程的建议。正如您在问题中指出的那样,这不是推荐的方法,并且可能会导致许多问题。您需要想办法让应用程序很好地自行关闭。其他一切都完全属于“杀戮”的范畴,正是您希望避免的。

      【讨论】:

        【解决方案4】:

        对于我的应用程序,我试图关闭一个 Python 进程并使用“subprocess.Popen”打开它的衍生进程。我尝试了 TerminateProcess,它太邪恶了。 :) 我终于确定我可以使用控制台命令taskkill。我在 C++ 程序中做到了这一点:

            // standard kill process call
            void stopProcess(DWORD pid)
            {
                STARTUPINFO startupInfo;
                LPPROCESS_INFORMATION processInfo = new PROCESS_INFORMATION;
        
                // clear the memory to prevent garbage
                ZeroMemory(&startupInfo, sizeof(startupInfo));
        
                // set size of structure (not using Ex version)
                startupInfo.cb = sizeof(STARTUPINFO);
                // tell the application that we are setting the window display 
                // information within this structure
                startupInfo.dwFlags = STARTF_USESHOWWINDOW;    
                // hide process
                startupInfo.wShowWindow = SW_HIDE;
        
                //TerminateProcess(itr->second->hProcess, 0);  // not friendly to process, and does not kill child processes
                std::stringstream comStream;       
                comStream << "taskkill /pid ";
                comStream << pid;
                //comStream << " /t /f";  // to be more like TerminateProcess
                _MESSAGE("%s", comStream.str().c_str());             
                //system(comStream.str().c_str()); // works, but pops up a window momentarilly when called        
        
                //LPSTR s = const_cast<char *>(comStream.str().c_str());  
                LPSTR cString = strdup( comStream.str().c_str() );
                if(!CreateProcess(NULL,cString,NULL,NULL,false,NORMAL_PRIORITY_CLASS,NULL,NULL,&startupInfo,processInfo)){
                    _MESSAGE("Could not launch '%s'",cString);
                    SAFE_DELETE(processInfo);
                }else{
                    // clean up
                    CloseHandle(processInfo);
                    SAFE_DELETE(processInfo);
                }
                // clean up 
                free(cString);
            }
        

        你会看到我的其他实验被注释掉了。我最终选择了这种方法,因为它隐藏了任何可能出现的弹出窗口。我还发现这允许 Python 应用程序正确调用 atexit。但是,即使我没有明确结束子进程,它们还是会使用 taskkill 方法关闭。我猜这是由于 Python 代码的设计方式造成的。

        所以,你可以试试上面的方法,等待进程关闭,如果失败了,如果它不配合,你可以使用 TerminateProcess 切换到大炮。如果需要,Taskkill 也有无情杀戮的模式。

        【讨论】:

          猜你喜欢
          • 2015-01-31
          • 1970-01-01
          • 2011-11-29
          • 2012-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-15
          相关资源
          最近更新 更多