次の方法で共有


Hidden vs Minimized

Writing that post about pausing audio playback reminded me of a little utility I wrote, uh, over a decade ago, which would hide rather than minimize windows, so that I could free up a bit of space on the task bar by not showing the window's icon there and I thought I'm talk about that here.

In the ShowWindow documentation, you'll see a couple of similar looking operations, SW_MINIMIZE and SW_HIDE: they both remove the specified window from the screen and activate some other window, but the difference is that SW_MINIMIZE leaves the task bar icon whereas SW_HIDE removes that too. At the time I wrote this application, I was using some tool which seemed to spawn an awful lot of extra and usually uninteresting windows, and occupied a lot of the task bar - this was before the days of coalescing of task bar icons into convenient little stacks. Incidentally, it was also before the days of Outlook's "hide when minimized" option - that was another program that I tended to use my hiding application with. So, there's probably very little need for this application any more, but its explanation has a few features some people might find interesting.

As I said, the aim of the application was to hide (and restore, of course) windows belonging to some particular running application - here's pretty much how I did that (subProgram is the process identifier for the program whose windows I want to manipulate):

 BOOL CALLBACK EnumProc(HWND hwnd, LPARAM lParam)
{
  /* Is this a top level window belonging to this process? */
  DWORD pid;
  GetWindowThreadProcessId(hwnd, &pid);
  if (GetParent(hwnd) == NULL && pid == subProgram)
  {
    TCHAR dummy[3];  /* Arbitrary size */
    RECT rect;
    if (GetWindowText(hwnd, dummy, sizeof(dummy) / sizeof(TCHAR)) > 0 &&
        GetWindowRect(hwnd, &rect) &&
        rect.right - rect.left > 1 && rect.bottom - rect.top > 1 &&
        GetWindow(hwnd, GW_CHILD))
    {
      if (lParam)
        ShowWindow(hwnd, SW_HIDE);
      else
      {
        ShowWindow(hwnd, SW_SHOW);
        SetForegroundWindow(hwnd);
      }
    }
  }
  return TRUE;
}

And this is called via EnumWindows(EnumProc, visible), where visible is a boolean value indicating whether the program's windows should be made visible or hidden. Notice the checks in the core of that routine, intended to weed out windows that belong to the program but which were never intended to be seen. Some experimentation and observation (Spy++ is a very useful tool!) suggested that typical markers for these intentionally hidden windows were: no window title, single pixel size or no child windows. There will almost certainly be other cases, but this was adequate for most programs at the time.

The next question is: how do I get the process id of the program to manipulate? That's easy: my little application spawns the program as a sub-process. ShellExecute would have been a very nice way to do that, because it can automatically work out what application to use if given a document file but, unfortunately, it doesn't hand back a process handle or id, and I've not worked out how to get such a thing from what it does return. Instead, I had to use the more basic CreateProcess:

 STARTUPINFO start;
PROCESS_INFORMATION proc;
ZeroMemory(&start, sizeof(start));
start.cb = sizeof(start);
start.dwFlags = STARTF_USESHOWWINDOW;
start.wShowWindow = SW_HIDE;

if (CreateProcess(NULL, cmdLine, NULL, NULL,
                  FALSE, 0, NULL, NULL,
                  &start, &proc))
{
  subProgram = proc.dwProcessId;
  subProcess = proc.hProcess;
  CloseHandle(proc.hThread);
  ...
}
else
{
  /* Handle error ... */
}

You have to remember to close the thread and process handles returned by CreateProcess - that is, unless you're going to use them. I hang on to the process handle (in the subProcess variable) to be able to detect when the user has exited the program. I run a background thread which executes this routine:

 DWORD WINAPI Waiter(void* hwnd)
{
  WaitForSingleObject(subProcess, INFINITE);
  return PostMessage((HWND)hwnd, WM_DESTROY, 0, 0);
}

The argument is the handle to my utility's top level window, so this waits for the sub-process to exit, and then sends a terminate message to my window procedure which then tears down my application.

As in the previous post, my application has no visible window, but hides itself behind a notification area icon. I could have made the utility manage some arbitrary number of sub-processes, but I chose to make it a single notification area icon per process so that a single click on that icon would show or hide the sub-process window. Because, then, I might have a number of these icons in the notification area, I chose to try to make them different, by grabbing the application icon from the sub-process I was spawning. This I did via:

 HICON ExtractIconFromCommand(TCHAR* cmdLine)
{
  HICON largeIcon = 0;
  HICON smallIcon = 0;
  TCHAR name[MAX_PATH];
  TCHAR* app;
  TCHAR* q;
  lstrcpyn(name, cmdLine, MAX_PATH);
  if (name[0] == _T('"'))
  {
    app = name + 1;
    q = Find(app, _T('"'));
  }
  else
  {
    app = name;
    q = Find(app, _T(' '));
  }
  if (q)
    *q = 0;

  ExtractIconEx(app, 0, &largeIcon, &smallIcon, 1);
  if (!smallIcon)
    return largeIcon;
  if (largeIcon)
    DestroyIcon(largeIcon);
  return smallIcon;
}

My Find routine is a cheap standin for strchr - as last time, I chose to forgo the CRT and make the executable as teeny as I could. In this routine, I try to find the first "word" on the command line given to my application, on the assumption that this is the actual executable. If it happens to start with a quotation mark, scan for the next one and assume that everything in between is the executable path, otherwise just take everything up to the first space. I could then use the icon handed back, of there was one, for the notification area item.

If you're interested in using the tool, or want to get the complete source, I'll point you at a copy on the Wayback Machine. (I did say at the start that this was a fairly ancient tool!)