552 lines
16 KiB
C
552 lines
16 KiB
C
|
/*
|
||
|
* BUSY.C
|
||
|
*
|
||
|
* Implements the OleUIBusy function which invokes the "Server Busy"
|
||
|
* dialog.
|
||
|
*
|
||
|
* Copyright (c)1992 Microsoft Corporation, All Right Reserved
|
||
|
*/
|
||
|
|
||
|
#define STRICT 1
|
||
|
#include "ole2ui.h"
|
||
|
#include "common.h"
|
||
|
#include "utility.h"
|
||
|
#include "busy.h"
|
||
|
#include <ctype.h> // for tolower() and toupper()
|
||
|
|
||
|
#ifndef WIN32
|
||
|
#include <toolhelp.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
* OleUIBusy
|
||
|
*
|
||
|
* Purpose:
|
||
|
* Invokes the standard OLE "Server Busy" dialog box which
|
||
|
* notifies the user that the server application is not receiving
|
||
|
* messages. The dialog then asks the user to either cancel
|
||
|
* the operation, switch to the task which is blocked, or continue
|
||
|
* waiting.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* lpBZ LPOLEUIBUSY pointing to the in-out structure
|
||
|
* for this dialog.
|
||
|
*
|
||
|
* Return Value:
|
||
|
* OLEUI_BZERR_HTASKINVALID : Error
|
||
|
* OLEUI_BZ_SWITCHTOSELECTED : Success, user selected "switch to"
|
||
|
* OLEUI_BZ_RETRYSELECTED : Success, user selected "retry"
|
||
|
* OLEUI_CANCEL : Success, user selected "cancel"
|
||
|
*/
|
||
|
|
||
|
STDAPI_(UINT) OleUIBusy(LPOLEUIBUSY lpOBZ)
|
||
|
{
|
||
|
UINT uRet = 0;
|
||
|
HGLOBAL hMemDlg=NULL;
|
||
|
|
||
|
#if !defined( WIN32 )
|
||
|
// BUGBUG32: this is not yet ported to NT
|
||
|
|
||
|
uRet=UStandardValidation((LPOLEUISTANDARD)lpOBZ, sizeof(OLEUIBUSY)
|
||
|
, &hMemDlg);
|
||
|
|
||
|
// Error out if the standard validation failed
|
||
|
if (OLEUI_SUCCESS!=uRet)
|
||
|
return uRet;
|
||
|
|
||
|
// Validate HTASK
|
||
|
if (!IsTask(lpOBZ->hTask))
|
||
|
uRet = OLEUI_BZERR_HTASKINVALID;
|
||
|
|
||
|
// Error out if our secondary validation failed
|
||
|
if (OLEUI_ERR_STANDARDMIN <= uRet)
|
||
|
{
|
||
|
if (NULL!=hMemDlg)
|
||
|
FreeResource(hMemDlg);
|
||
|
|
||
|
return uRet;
|
||
|
}
|
||
|
|
||
|
// Invoke the dialog.
|
||
|
uRet=UStandardInvocation(BusyDialogProc, (LPOLEUISTANDARD)lpOBZ,
|
||
|
hMemDlg, MAKEINTRESOURCE(IDD_BUSY));
|
||
|
#endif
|
||
|
|
||
|
return uRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* BusyDialogProc
|
||
|
*
|
||
|
* Purpose:
|
||
|
* Implements the OLE Busy dialog as invoked through the OleUIBusy function.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* Standard
|
||
|
*
|
||
|
* Return Value:
|
||
|
* Standard
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
BOOL CALLBACK EXPORT BusyDialogProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
LPBUSY lpBZ;
|
||
|
UINT uRet = 0;
|
||
|
|
||
|
//Declare Win16/Win32 compatible WM_COMMAND parameters.
|
||
|
COMMANDPARAMS(wID, wCode, hWndMsg);
|
||
|
|
||
|
//This will fail under WM_INITDIALOG, where we allocate it.
|
||
|
lpBZ=(LPBUSY)LpvStandardEntry(hDlg, iMsg, wParam, lParam, &uRet);
|
||
|
|
||
|
//If the hook processed the message, we're done.
|
||
|
if (0!=uRet)
|
||
|
return (BOOL)uRet;
|
||
|
|
||
|
//Process the temination message
|
||
|
if (iMsg==uMsgEndDialog)
|
||
|
{
|
||
|
BusyCleanup(hDlg);
|
||
|
StandardCleanup(lpBZ, hDlg);
|
||
|
EndDialog(hDlg, wParam);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
// Process our special "close" message. If we get this message,
|
||
|
// this means that the call got unblocked, so we need to
|
||
|
// return OLEUI_BZ_CALLUNBLOCKED to our calling app.
|
||
|
if (iMsg == uMsgCloseBusyDlg)
|
||
|
{
|
||
|
SendMessage(hDlg, uMsgEndDialog, OLEUI_BZ_CALLUNBLOCKED, 0L);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
switch (iMsg)
|
||
|
{
|
||
|
case WM_INITDIALOG:
|
||
|
FBusyInit(hDlg, wParam, lParam);
|
||
|
return TRUE;
|
||
|
|
||
|
case WM_ACTIVATEAPP:
|
||
|
{
|
||
|
/* try to bring down our Busy/NotResponding dialog as if
|
||
|
** the user entered RETRY.
|
||
|
*/
|
||
|
BOOL fActive = (BOOL)wParam;
|
||
|
if (fActive) {
|
||
|
// If this is the app BUSY case, then bring down our
|
||
|
// dialog when switching BACK to our app
|
||
|
if (lpBZ && !(lpBZ->dwFlags & BZ_NOTRESPONDINGDIALOG))
|
||
|
SendMessage(hDlg,uMsgEndDialog,OLEUI_BZ_RETRYSELECTED,0L);
|
||
|
} else {
|
||
|
// If this is the app NOT RESPONDING case, then bring down
|
||
|
// our dialog when switching AWAY to another app
|
||
|
if (lpBZ && (lpBZ->dwFlags & BZ_NOTRESPONDINGDIALOG))
|
||
|
SendMessage(hDlg,uMsgEndDialog,OLEUI_BZ_RETRYSELECTED,0L);
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
case WM_COMMAND:
|
||
|
switch (wID)
|
||
|
{
|
||
|
case IDBZ_SWITCHTO:
|
||
|
{
|
||
|
BOOL fNotRespondingDlg =
|
||
|
(BOOL)(lpBZ->dwFlags & BZ_NOTRESPONDINGDIALOG);
|
||
|
|
||
|
// If user selects "Switch To...", switch activation
|
||
|
// directly to the window which is causing the problem.
|
||
|
if (IsWindow(lpBZ->hWndBlocked))
|
||
|
MakeWindowActive(lpBZ->hWndBlocked);
|
||
|
else
|
||
|
StartTaskManager(); // Fail safe: Start Task Manager
|
||
|
|
||
|
// If this is the app not responding case, then we want
|
||
|
// to bring down the dialog when "SwitchTo" is selected.
|
||
|
// If the app is busy (RetryRejectedCall situation) then
|
||
|
// we do NOT want to bring down the dialog. this is
|
||
|
// the OLE2.0 user model design.
|
||
|
if (fNotRespondingDlg)
|
||
|
SendMessage(hDlg, uMsgEndDialog, OLEUI_BZ_SWITCHTOSELECTED, 0L);
|
||
|
break;
|
||
|
}
|
||
|
case IDBZ_RETRY:
|
||
|
SendMessage(hDlg, uMsgEndDialog, OLEUI_BZ_RETRYSELECTED, 0L);
|
||
|
break;
|
||
|
|
||
|
case IDCANCEL:
|
||
|
SendMessage(hDlg, uMsgEndDialog, OLEUI_CANCEL, 0L);
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* FBusyInit
|
||
|
*
|
||
|
* Purpose:
|
||
|
* WM_INITIDIALOG handler for the Busy dialog box.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* hDlg HWND of the dialog
|
||
|
* wParam WPARAM of the message
|
||
|
* lParam LPARAM of the message
|
||
|
*
|
||
|
* Return Value:
|
||
|
* BOOL Value to return for WM_INITDIALOG.
|
||
|
*/
|
||
|
|
||
|
BOOL FBusyInit(HWND hDlg, WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
LPBUSY lpBZ;
|
||
|
LPOLEUIBUSY lpOBZ;
|
||
|
HFONT hFont;
|
||
|
LPTSTR lpTaskName;
|
||
|
LPTSTR lpWindowName;
|
||
|
HICON hIcon;
|
||
|
|
||
|
lpBZ=(LPBUSY)LpvStandardInit(hDlg, sizeof(OLEUIBUSY), TRUE, &hFont);
|
||
|
|
||
|
// PvStandardInit sent a termination to us already.
|
||
|
if (NULL==lpBZ)
|
||
|
return FALSE;
|
||
|
|
||
|
// Our original structure is in lParam
|
||
|
lpOBZ = (LPOLEUIBUSY)lParam;
|
||
|
|
||
|
// Copy it to our instance of the structure (in lpBZ)
|
||
|
lpBZ->lpOBZ=lpOBZ;
|
||
|
|
||
|
//Copy other information from lpOBZ that we might modify.
|
||
|
lpBZ->dwFlags = lpOBZ->dwFlags;
|
||
|
|
||
|
// Set default information
|
||
|
lpBZ->hWndBlocked = NULL;
|
||
|
|
||
|
// Insert HWND of our dialog into the address pointed to by
|
||
|
// lphWndDialog. This can be used by the app who called
|
||
|
// OleUIBusy to bring down the dialog with uMsgCloseBusyDialog
|
||
|
if (lpOBZ->lphWndDialog &&
|
||
|
!IsBadWritePtr((VOID FAR *)lpOBZ->lphWndDialog, sizeof(HWND)))
|
||
|
{
|
||
|
*lpOBZ->lphWndDialog = hDlg;
|
||
|
}
|
||
|
|
||
|
// Update text in text box --
|
||
|
// GetTaskInfo will return two pointers, one to the task name
|
||
|
// (file name) and one to the window name. We need to call
|
||
|
// OleStdFree on these when we're done with them. We also
|
||
|
// get the HWND which is blocked in this call
|
||
|
//
|
||
|
// In the case where this call fails, a default message should already
|
||
|
// be present in the dialog template, so no action is needed
|
||
|
|
||
|
if (GetTaskInfo(hDlg, lpOBZ->hTask, &lpTaskName, &lpWindowName, &lpBZ->hWndBlocked))
|
||
|
{
|
||
|
// Build string to present to user, place in IDBZ_MESSAGE1 control
|
||
|
BuildBusyDialogString(hDlg, lpBZ->dwFlags, IDBZ_MESSAGE1, lpTaskName, lpWindowName);
|
||
|
OleStdFree(lpTaskName);
|
||
|
OleStdFree(lpWindowName);
|
||
|
}
|
||
|
|
||
|
// Update icon with the system "exclamation" icon
|
||
|
hIcon = LoadIcon(NULL, IDI_EXCLAMATION);
|
||
|
SendDlgItemMessage(hDlg, IDBZ_ICON, STM_SETICON, (WPARAM)hIcon, 0L);
|
||
|
|
||
|
// Disable/Enable controls
|
||
|
if ((lpBZ->dwFlags & BZ_DISABLECANCELBUTTON) ||
|
||
|
(lpBZ->dwFlags & BZ_NOTRESPONDINGDIALOG)) // Disable cancel for "not responding" dialog
|
||
|
EnableWindow(GetDlgItem(hDlg, IDCANCEL), FALSE);
|
||
|
|
||
|
if (lpBZ->dwFlags & BZ_DISABLESWITCHTOBUTTON)
|
||
|
EnableWindow(GetDlgItem(hDlg, IDBZ_SWITCHTO), FALSE);
|
||
|
|
||
|
if (lpBZ->dwFlags & BZ_DISABLERETRYBUTTON)
|
||
|
EnableWindow(GetDlgItem(hDlg, IDBZ_RETRY), FALSE);
|
||
|
|
||
|
// Call the hook with lCustData in lParam
|
||
|
UStandardHook((LPVOID)lpBZ, hDlg, WM_INITDIALOG, wParam, lpOBZ->lCustData);
|
||
|
|
||
|
// Update caption if lpszCaption was specified
|
||
|
if (lpBZ->lpOBZ->lpszCaption && !IsBadReadPtr(lpBZ->lpOBZ->lpszCaption, 1)
|
||
|
&& lpBZ->lpOBZ->lpszCaption[0] != '\0')
|
||
|
SetWindowText(hDlg, lpBZ->lpOBZ->lpszCaption);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* BuildBusyDialogString
|
||
|
*
|
||
|
* Purpose:
|
||
|
* Builds the string that will be displayed in the dialog from the
|
||
|
* task name and window name parameters.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* hDlg HWND of the dialog
|
||
|
* dwFlags DWORD containing flags passed into dialog
|
||
|
* iControl Control ID to place the text string
|
||
|
* lpTaskName LPSTR pointing to name of task (e.g. C:\TEST\TEST.EXE)
|
||
|
* lpWindowName LPSTR for name of window
|
||
|
*
|
||
|
* Caveats:
|
||
|
* The caller of this function MUST de-allocate the lpTaskName and
|
||
|
* lpWindowName pointers itself with OleStdFree
|
||
|
*
|
||
|
* Return Value:
|
||
|
* void
|
||
|
*/
|
||
|
|
||
|
void BuildBusyDialogString(HWND hDlg, DWORD dwFlags, int iControl, LPTSTR lpTaskName, LPTSTR lpWindowName)
|
||
|
{
|
||
|
LPTSTR pszT, psz1, psz2, psz3;
|
||
|
UINT cch;
|
||
|
LPTSTR pszDot, pszSlash;
|
||
|
UINT uiStringNum;
|
||
|
|
||
|
/*
|
||
|
* We need scratch memory for loading the stringtable string,
|
||
|
* the task name, and constructing the final string. We therefore
|
||
|
* allocate three buffers as large as the maximum message
|
||
|
* length (512) plus the object type, guaranteeing that we have enough
|
||
|
* in all cases.
|
||
|
*/
|
||
|
cch=512;
|
||
|
|
||
|
// Use OLE-supplied allocation
|
||
|
if ((pszT = OleStdMalloc((ULONG)(3*cch))) == NULL)
|
||
|
return;
|
||
|
|
||
|
psz1=pszT;
|
||
|
psz2=psz1+cch;
|
||
|
psz3=psz2+cch;
|
||
|
|
||
|
// Parse base name out of path name, use psz2 for the task
|
||
|
// name to display
|
||
|
// In Win32, _fstrcpy is mapped to handle UNICODE stuff
|
||
|
_fstrcpy(psz2, lpTaskName);
|
||
|
pszDot = _fstrrchr(psz2, TEXT('.'));
|
||
|
pszSlash = _fstrrchr(psz2, TEXT('\\')); // Find last backslash in path
|
||
|
|
||
|
if (pszDot != NULL)
|
||
|
#ifdef UNICODE
|
||
|
*pszDot = TEXT('\0'); // Null terminate at the DOT
|
||
|
#else
|
||
|
*pszDot = '\0'; // Null terminate at the DOT
|
||
|
#endif
|
||
|
|
||
|
if (pszSlash != NULL)
|
||
|
psz2 = pszSlash + 1; // Nuke everything up to this point
|
||
|
|
||
|
#ifdef LOWERCASE_NAME
|
||
|
// Compile this with /DLOWERCASE_NAME if you want the lower-case
|
||
|
// module name to be displayed in the dialog rather than the
|
||
|
// all-caps name.
|
||
|
{
|
||
|
int i,l;
|
||
|
|
||
|
// Now, lowercase all letters except first one
|
||
|
l = _fstrlen(psz2);
|
||
|
for(i=0;i<l;i++)
|
||
|
psz2[i] = tolower(psz2[i]);
|
||
|
|
||
|
psz2[0] = toupper(psz2[0]);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Check size of lpWindowName. We can reasonably fit about 80
|
||
|
// characters into the text control, so truncate more than 80 chars
|
||
|
if (_fstrlen(lpWindowName)> 80)
|
||
|
#ifdef UNICODE
|
||
|
lpWindowName[80] = TEXT('\0');
|
||
|
#else
|
||
|
lpWindowName[80] = '\0';
|
||
|
#endif
|
||
|
|
||
|
// Load the format string out of stringtable, choose a different
|
||
|
// string depending on what flags are passed in to the dialog
|
||
|
if (dwFlags & BZ_NOTRESPONDINGDIALOG)
|
||
|
uiStringNum = IDS_BZRESULTTEXTNOTRESPONDING;
|
||
|
else
|
||
|
uiStringNum = IDS_BZRESULTTEXTBUSY;
|
||
|
|
||
|
if (LoadString(ghInst, uiStringNum, psz1, cch) == 0)
|
||
|
return;
|
||
|
|
||
|
// Build the string. The format string looks like this:
|
||
|
// "This action cannot be completed because the '%s' application
|
||
|
// (%s) is [busy | not responding]. Choose \"Switch To\" to activate '%s' and
|
||
|
// correct the problem."
|
||
|
|
||
|
wsprintf(psz3, psz1, (LPSTR)psz2, (LPTSTR)lpWindowName, (LPTSTR)psz2);
|
||
|
SetDlgItemText(hDlg, iControl, (LPTSTR)psz3);
|
||
|
OleStdFree(pszT);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* BusyCleanup
|
||
|
*
|
||
|
* Purpose:
|
||
|
* Performs busy-specific cleanup before termination.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* hDlg HWND of the dialog box so we can access controls.
|
||
|
*
|
||
|
* Return Value:
|
||
|
* None
|
||
|
*/
|
||
|
void BusyCleanup(HWND hDlg)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* GetTaskInfo()
|
||
|
*
|
||
|
* Purpose: Gets information about the specified task and places the
|
||
|
* module name, window name and top-level HWND for the task in the specified
|
||
|
* pointers
|
||
|
*
|
||
|
* NOTE: The two string pointers allocated in this routine are
|
||
|
* the responsibility of the CALLER to de-allocate.
|
||
|
*
|
||
|
* Parameters:
|
||
|
* hWnd HWND who called this function
|
||
|
* htask HTASK which we want to find out more info about
|
||
|
* lplpszTaskName Location that the module name is returned
|
||
|
* lplpszWindowName Location where the window name is returned
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
BOOL GetTaskInfo(HWND hWnd, HTASK htask, LPTSTR FAR* lplpszTaskName, LPTSTR FAR*lplpszWindowName, HWND FAR*lphWnd)
|
||
|
{
|
||
|
BOOL fRet = FALSE;
|
||
|
#if !defined( WIN32 )
|
||
|
TASKENTRY te;
|
||
|
#endif
|
||
|
HWND hwndNext;
|
||
|
LPTSTR lpszTN = NULL;
|
||
|
LPTSTR lpszWN = NULL;
|
||
|
HWND hwndFind = NULL;
|
||
|
|
||
|
// Clear out return values in case of error
|
||
|
*lplpszTaskName = NULL;
|
||
|
*lplpszWindowName = NULL;
|
||
|
|
||
|
#if !defined( WIN32 )
|
||
|
te.dwSize = sizeof(TASKENTRY);
|
||
|
if (TaskFindHandle(&te, htask))
|
||
|
#endif
|
||
|
{
|
||
|
// Now, enumerate top-level windows in system
|
||
|
hwndNext = GetWindow(hWnd, GW_HWNDFIRST);
|
||
|
while (hwndNext)
|
||
|
{
|
||
|
// See if we can find a non-owned top level window whose
|
||
|
// hInstance matches the one we just got passed. If we find one,
|
||
|
// we can be fairly certain that this is the top-level window for
|
||
|
// the task which is blocked.
|
||
|
//
|
||
|
// REVIEW: Will this filter hold true for InProcServer DLL-created
|
||
|
// windows?
|
||
|
//
|
||
|
if ((hwndNext != hWnd) &&
|
||
|
#if !defined( WIN32 )
|
||
|
(GetWindowWord(hwndNext, GWW_HINSTANCE) == (WORD)te.hInst) &&
|
||
|
#else
|
||
|
((HTASK) GetWindowThreadProcessId(hwndNext,NULL) == htask) &&
|
||
|
#endif
|
||
|
(IsWindowVisible(hwndNext)) &&
|
||
|
!GetWindow(hwndNext, GW_OWNER))
|
||
|
{
|
||
|
// We found our window! Alloc space for new strings
|
||
|
if ((lpszTN = OleStdMalloc(OLEUI_CCHPATHMAX_SIZE)) == NULL)
|
||
|
return TRUE; // continue task window enumeration
|
||
|
|
||
|
if ((lpszWN = OleStdMalloc(OLEUI_CCHPATHMAX_SIZE)) == NULL)
|
||
|
return TRUE; // continue task window enumeration
|
||
|
|
||
|
// We found the window we were looking for, copy info to
|
||
|
// local vars
|
||
|
GetWindowText(hwndNext, lpszWN, OLEUI_CCHPATHMAX);
|
||
|
#if !defined( WIN32 )
|
||
|
LSTRCPYN(lpszTN, te.szModule, OLEUI_CCHPATHMAX);
|
||
|
#else
|
||
|
/* WIN32 NOTE: we are not able to get a module name
|
||
|
** given a thread process id on WIN32. the best we
|
||
|
** can do is use the window title as the module/app
|
||
|
** name.
|
||
|
*/
|
||
|
LSTRCPYN(lpszTN, lpszWN, OLEUI_CCHPATHMAX);
|
||
|
#endif
|
||
|
hwndFind = hwndNext;
|
||
|
|
||
|
fRet = TRUE;
|
||
|
goto OKDone;
|
||
|
}
|
||
|
|
||
|
hwndNext = GetWindow(hwndNext, GW_HWNDNEXT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
OKDone:
|
||
|
|
||
|
// OK, everything was successful. Set string pointers to point to
|
||
|
// our data.
|
||
|
|
||
|
*lplpszTaskName = lpszTN;
|
||
|
*lplpszWindowName = lpszWN;
|
||
|
*lphWnd = hwndFind;
|
||
|
|
||
|
return fRet;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* StartTaskManager()
|
||
|
*
|
||
|
* Purpose: Starts Task Manager. Used to bring up task manager to
|
||
|
* assist in switching to a given blocked task.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
StartTaskManager()
|
||
|
{
|
||
|
WinExec("taskman.exe", SW_SHOW);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* MakeWindowActive()
|
||
|
*
|
||
|
* Purpose: Makes specified window the active window.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
void MakeWindowActive(HWND hWndSwitchTo)
|
||
|
{
|
||
|
// Move the new window to the top of the Z-order
|
||
|
SetWindowPos(hWndSwitchTo, HWND_TOP, 0, 0, 0, 0,
|
||
|
SWP_NOSIZE | SWP_NOMOVE);
|
||
|
|
||
|
// If it's iconic, we need to restore it.
|
||
|
if (IsIconic(hWndSwitchTo))
|
||
|
ShowWindow(hWndSwitchTo, SW_RESTORE);
|
||
|
}
|