C++ 使用 ShowWindow 恢复窗口会禁用其最小化功能

Posted

技术标签:

【中文标题】C++ 使用 ShowWindow 恢复窗口会禁用其最小化功能【英文标题】:C++ Restoring a window using ShowWindow disables its minimize capability 【发布时间】:2014-09-12 11:49:46 【问题描述】:

我在只运行一个窗口实例时遇到了问题。

实现细节:我有一个 C++ 应用程序,它在任务栏中显示为一个图标。在双击图标时,我正在使用 ShellExecuteW 函数打开一个新的 delphi 窗口。现在我实现了一个逻辑,即每当用户双击图标时,它只打开一个窗口实例,避免启动多个窗口。如果用户双击该图标,并且已经打开了一个窗口,它只会将窗口带到最前面,或者如果它被最小化,它将恢复窗口。

下面的代码展示了我是如何实现上述逻辑的,它是在双击图标时触发的:

///////////////////////Double Click Code starts/////////////////////////
HWND hWnd = NULL;
HWND hWndFirst = NULL;
DWORD dw = FindProcessId("abc.exe");
if(dw == 0)

  //Open new window
  ShellExecuteW(0, L"open", acExePath, acParams, acFullPath, SW_SHOW);
  hWndFirst = NULL;

else

  //Open existing window
  hWnd = hGetWindowHandleOfProcess(dw);
  if(hWndFirst == NULL) hWndFirst = hWnd;

  if(hWndFirst != hWnd)
  
    //This is just a small work-around as minimizing the window was changing
    //it's window handle. So I preserve the window handle the first time and 
    //use it whenever the window is minimized (i.e. the handle is changed)
    SetForegroundWindow(hWndFirst);
    ShowWindow(hWndFirst, SW_RESTORE);
  
  else
  
    SetForegroundWindow(hWnd);
    ShowWindow(hWnd, SW_RESTORE);
  

///////////////////////Double Click Code ends///////////////////////////

///////////////////////Supporting functions/////////////////////////
struct ProcessHandleData

    unsigned long lProcessId;
    HWND hProcessWindowHandle;
;
//Finds the process id when given the process name
DWORD FindProcessId(char* pcProcessName)

    char* pcBegin = strrchr(pcProcessName, '\\');
    if(pcBegin)
        pcProcessName = pcBegin+1;

    PROCESSENTRY32 sProcessInfo;
    sProcessInfo.dwSize = sizeof(sProcessInfo);

    HANDLE sProcessesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    if ( sProcessesSnapshot == INVALID_HANDLE_VALUE )
        return 0;

    Process32First(sProcessesSnapshot, &sProcessInfo);
    if ( !strcmp(pcProcessName, sProcessInfo.szExeFile) )
    
        CloseHandle(sProcessesSnapshot);
        return sProcessInfo.th32ProcessID;
    

    while ( Process32Next(sProcessesSnapshot, &sProcessInfo) )
    
        if ( !strcmp(pcProcessName, sProcessInfo.szExeFile) )
        
          CloseHandle(sProcessesSnapshot);
          return sProcessInfo.th32ProcessID;
        
    
    CloseHandle(sProcessesSnapshot);
    return 0;


//Search predicate for EnumWindows function    
BOOL CALLBACK bEnumWindowsSearcher(HWND handle, LPARAM lParam)

    ProcessHandleData& sProcesshandleData = *(ProcessHandleData*)lParam;
    unsigned long lProcessId = 0;
    GetWindowThreadProcessId(handle, &lProcessId);
    if (sProcesshandleData.lProcessId != lProcessId)
      return true;
    sProcesshandleData.hProcessWindowHandle = handle;
    return false;


//Gets the window handle of the process (input process id)
HWND hGetWindowHandleOfProcess(unsigned long lProcessId)

    ProcessHandleData sProcessHandleData;
    sProcessHandleData.lProcessId = lProcessId;
    sProcessHandleData.hProcessWindowHandle = 0;
    EnumWindows(bEnumWindowsSearcher, (LPARAM)&sProcessHandleData);  //enumerate all windows
    return sProcessHandleData.hProcessWindowHandle;

问题: 上面的代码可以正常工作。但是我面临一个问题。如果打开的窗口未处于活动状态或位于任何窗口后面,则上述代码会将其置于前面(如预期的那样)。但是如果我最小化窗口并双击图标,它会恢复窗口并将其带到前面(再次如预期的那样)。但是在此操作之后,我不再能够使用窗口右上角的最小化栏来最小化窗口。某些东西会禁用(不是物理上)窗口的最小化栏。

非常感谢您在这方面的帮助。如果您需要有关该问题的更多信息,请告诉我。

非常感谢。

PS:上面的代码不完全是我的,我已经研究了各种网站以获得运行问题的各个部分的代码。

【问题讨论】:

【参考方案1】:

问题的根源在于您以错误的方式从错误的进程中恢复前一个窗口。按照您的方式进行操作,前一个窗口的状态会不同步,这就是最小化停止工作的原因。

相反,设计您的应用程序,以便新实例可以向前一个实例发送自定义窗口消息(或您选择的任何其他形式的 IPC),并且作为对该消息的响应,前一个实例可以使用 Application->Restore()SetForegroundWindow() 正确恢复自身。

此外,您的整个进程枚举逻辑简直是矫枉过正。

试试类似的方法:

const UINT uiMyMsg = RegisterWindowMessage(TEXT("MY_RESTORE_MSG"));
...

if (!FindProcessId("abc.exe"))

    //Open new window
    ShellExecuteW(0, L"open", acExePath, acParams, acFullPath, SW_SHOW);

else

    if (uiMyMsg != 0)
        PostMessage(HWND_BROADCAST, uiMyMsg, 0, 0);

然后在abc.exe 内部,执行如下操作:

const UINT uiMyMsg = RegisterWindowMessage(TEXT("MY_RESTORE_MSG"));

__fastcall TMainForm::TMainForm(TComponent *Owner)
    : TForm(Owner)

    Application->HookMainWindow(&AppHook);


__fastcall TMainForm::~TMainForm()

    Application->UnhookMainWindow(&AppHook);


bool __fastcall TMainForm::AppHook(TMessage &Message)

    if ((Message.Msg == uiMyMsg) && (uiMyMsg != 0))
    
        ShowMe();
        return true;
    
    return false;


void __fastcall TMainForm::WndProc(TMessage &Message)

    if ((Message.Msg == uiMyMsg) && (uiMyMsg != 0))
        ShowMe();
    else
        TForm::WndProc(Message);


void __fastcall TMainForm::ShowMe()

    Application->Restore();
    if (WindowState == wsMinimized)
        WindowState = wsNormal;
    Show();
    SetForegroundWindow(Handle);

【讨论】:

感谢 Remy,但是在 Delphi abc.exe 中,RegisterWindowMessage 返回负值。我目前正在调查为什么会这样。 RegisterWindowMessage() 返回一个unsigned int,所以它不能返回一个负值。确保将值分配给 UINT 或至少是 LongWord,而不是 Integer 是的,我想通了。再次感谢您提供如此干净的解决方案。

以上是关于C++ 使用 ShowWindow 恢复窗口会禁用其最小化功能的主要内容,如果未能解决你的问题,请参考以下文章

C++请问ShowWindow(hwnd,nCmdShow)和ShowWindow(hwnd,iCmdShow)的区别是啥?

VB showwindow隐藏窗口

Hook

在 ATL 无模式对话框上调用 ShowWindow(SW_SHOW) 不会将窗口置于前面

调用API函数ShowWindow()来隐藏窗口

windowsAPI里面的ShowWindow函数怎么用?主要是关于窗口激活的用法。