// StatsDlg.cpp : Implementation of CStatusDlg #include "stdafx.h" #include "WizChain.h" #include "StatsDlg.h" // This is the thread that displays the status dialog DWORD WINAPI DialogThreadProc( LPVOID lpv ) { HRESULT hr; CStatusDlg * pSD = (CStatusDlg *)lpv; // Increment the ref count so that the object does not disappear when the user releases it hr = pSD->AddRef(); if( SUCCEEDED(hr) && pSD ) { pSD->DoModal(NULL); } // Decrement the ref count pSD->Release(); return 0; } STDMETHODIMP CStatusDlg::AddComponent( BSTR bstrComponent, long * plIndex ) { HRESULT hr = S_OK; // If the dialog is already displayed // we are not accepting new components if( m_hThread ) return E_UNEXPECTED; // Validate the arguments if( NULL == bstrComponent || NULL == plIndex ) return E_INVALIDARG; // Get a new index long lNewIndex = m_mapComponents.size(); if( m_mapComponents.find(lNewIndex) == m_mapComponents.end() ) { // Add the new component BSTR bstrNewComponent = SysAllocString(bstrComponent); if( bstrNewComponent ) { m_mapComponents.insert(COMPONENTMAP::value_type(lNewIndex, bstrNewComponent)); } else { hr = E_OUTOFMEMORY; } } else { hr = E_UNEXPECTED; // This cannot happen! } if( SUCCEEDED(hr) ) { *plIndex = lNewIndex; } return hr; } STDMETHODIMP CStatusDlg::Initialize( BSTR bstrWindowTitle, BSTR bstrWindowText, VARIANT varFlags ) { HRESULT hr = S_OK; // If the dialog is already displayed // do not allow to reinitialize if( m_hThread ) return E_UNEXPECTED; if( !bstrWindowTitle || !bstrWindowText ) return E_INVALIDARG; if( VT_I2 != V_VT(&varFlags) && VT_I4 != V_VT(&varFlags) ) return E_INVALIDARG; if( VT_I2 == V_VT(&varFlags) ) { m_lFlags = (long) varFlags.iVal; } else { m_lFlags = varFlags.lVal; } if( SUCCEEDED(hr) ) { // Initialize the common control library INITCOMMONCONTROLSEX initCommonControlsEx; initCommonControlsEx.dwSize = sizeof(initCommonControlsEx); initCommonControlsEx.dwICC = ICC_PROGRESS_CLASS | ICC_LISTVIEW_CLASSES; if( !::InitCommonControlsEx(&initCommonControlsEx) ) { hr = HRESULT_FROM_WIN32(GetLastError()); } } if( SUCCEEDED(hr) ) { if( bstrWindowTitle ) { m_strWindowTitle = bstrWindowTitle; // Status Dialog Title } if( bstrWindowText ) { m_strWindowText = bstrWindowText; // Status Dialog Text } } return hr; } STDMETHODIMP CStatusDlg::SetStatus(long lIndex, SD_STATUS lStatus) { HRESULT hr = S_OK; BOOL bToggleActive = FALSE; COMPONENTMAP::iterator compIterator; // Validate the arguments if( (SD_STATUS_NONE > lStatus) || (SD_STATUS_RUNNING < lStatus) ) { return E_INVALIDARG; } compIterator = m_mapComponents.find(lIndex); if( compIterator == m_mapComponents.end() ) { return E_INVALIDARG; // Cannot find the component } if( IsWindow() ) { if( m_pProgressList ) { CProgressItem * pPI; compIterator = m_mapComponents.begin(); // Make sure that no component has status "running" while( compIterator != m_mapComponents.end() ) { pPI = m_pProgressList->GetProgressItem(compIterator->first); if( pPI && pPI->m_bActive ) { m_pProgressList->ToggleActive(compIterator->first); } compIterator++; } if( SD_STATUS_RUNNING == lStatus ) { m_pProgressList->ToggleActive(lIndex); // New status is "running" } else { // Update the state of the component on the Listview m_pProgressList->SetItemState(lIndex, (ItemState) lStatus); // If the component's done, update the total progress if( (lStatus == SD_STATUS_SUCCEEDED) || (lStatus == SD_STATUS_FAILED) ) { // TO DO: No need to do this, just send a message to the dialog to do that PBRANGE range; SendDlgItemMessage(IDC_PROGRESS1, PBM_GETRANGE, FALSE, (LPARAM) &range); SendDlgItemMessage(IDC_PROGRESS1, PBM_SETPOS, range.iHigh, 0); InterlockedExchangeAdd(&m_lTotalProgress, range.iHigh); } } } else { hr = E_UNEXPECTED; } if( SUCCEEDED(hr) ) { SetupButtons( ); } } else { hr = E_UNEXPECTED; } return hr; } void CStatusDlg::SetupButtons( ) { HWND hWnd = NULL; HWND hWndOK = NULL; HWND hWndCancel = NULL; CString csText; BOOL bFailed = FALSE; BSTR bstrText = NULL; USES_CONVERSION; hWndOK = GetDlgItem(IDOK); hWndCancel = GetDlgItem(IDCANCEL); if( IsWindow() && hWndOK && hWndCancel ) { if( AreAllComponentsDone(bFailed) ) { // Enable OK button ::EnableWindow(hWndOK, TRUE); // Disable Cancel button ::EnableWindow(hWndCancel, FALSE); // Default button is the Close button ::SendMessage(m_hWnd, WM_NEXTDLGCTL, (WPARAM) hWndOK, 1); // // When all components are done we will hide the progress bars to give the user // a visual clue to realize that the wizard is done. I know, that sounds stupid. // hWnd = GetDlgItem(IDC_STATIC2); // Component progress text if (NULL != hWnd) { ::ShowWindow(hWnd, SW_HIDE); } hWnd = GetDlgItem(IDC_PROGRESS1); // Component progress text if (NULL != hWnd) { ::ShowWindow(hWnd, SW_HIDE); } hWnd = GetDlgItem(IDC_STATIC3); // Overall progress text if (NULL != hWnd) { ::ShowWindow(hWnd, SW_HIDE); } hWnd = GetDlgItem(IDC_PROGRESS2); // Overall progress if (NULL != hWnd) { ::ShowWindow(hWnd, SW_HIDE); } if (FALSE == bFailed) { if (csText.LoadString(IDS_STATUS_SUCCESS)) { bstrText = T2BSTR(csText); if (NULL != bstrText) { SetStatusText(bstrText); } } } else { if (csText.LoadString(IDS_STATUS_FAIL)) { bstrText = T2BSTR(csText); if (NULL != bstrText) { SetStatusText(bstrText); } } } if (NULL != bstrText) { ::SysFreeString(bstrText); } } else { // Disable OK button ::EnableWindow( hWndOK, FALSE ); if( m_lFlags & SD_BUTTON_CANCEL ) { ::EnableWindow( hWndCancel, TRUE ); } } } } STDMETHODIMP CStatusDlg::Display( BOOL bShow ) { HRESULT hr = S_OK; if( bShow ) { if( m_hThread != NULL ) { if( !IsWindowVisible() ) // We are already on { ShowWindow(SW_SHOW); } } else { // Create a new thread which will DoModal the status dialog m_hThread = CreateThread( NULL, 0, DialogThreadProc, (void *) this, 0, NULL ); if( NULL == m_hThread ) { hr = HRESULT_FROM_WIN32(GetLastError()); } else if( m_hDisplayedEvent ) // Wait till the dialog is displayed { if( WAIT_OBJECT_0 != WaitForSingleObject(m_hDisplayedEvent, INFINITE) ) { hr = E_UNEXPECTED; } } } } else { // Will close the dialog if( m_hThread != NULL ) { EndDialog(IDCANCEL); WaitForSingleObject(m_hThread, INFINITE); } } return hr; } // The wizard writer should call this function to wait on user response // If the user has already responded: clicked OK or Cancel // then this method will return immediately STDMETHODIMP CStatusDlg::WaitForUser( ) { if( m_hThread ) { WaitForSingleObject(m_hThread, INFINITE); } return S_OK; } STDMETHODIMP CStatusDlg::get_Cancelled( BOOL *pVal ) { if( NULL == pVal ) { return E_INVALIDARG; } if( m_lFlags & SD_BUTTON_CANCEL ) { if( m_iCancelled == 0 ) { *pVal = FALSE; } else { *pVal = TRUE; } } else { *pVal = FALSE; } return S_OK; } STDMETHODIMP CStatusDlg::get_ComponentProgress( IStatusProgress** pVal ) { HRESULT hr = S_OK; if( NULL == pVal ) { return E_INVALIDARG; } if( m_lFlags & SD_PROGRESS_COMPONENT ) { // Create component progress object if( m_pComponentProgress == NULL ) { hr = CComObject::CreateInstance(&m_pComponentProgress); if( SUCCEEDED(hr) ) { hr = m_pComponentProgress->AddRef(); } if( SUCCEEDED(hr) && IsWindow() ) { // Initialize the component progress with the progress bar handle hr = m_pComponentProgress->Initialize(this, GetDlgItem(IDC_PROGRESS1), FALSE); } } if( SUCCEEDED(hr) ) { hr = (m_pComponentProgress->QueryInterface(IID_IStatusProgress, (void **) pVal)); } } return hr; } LRESULT CStatusDlg::OnInitDialog( UINT uint, WPARAM wparam, LPARAM lparam, BOOL& bbool ) { HWND hWndText = GetDlgItem(IDC_STATIC1); HWND hWndLV = GetDlgItem(IDC_LIST2); HWND hWndCompText = GetDlgItem(IDC_STATIC2); HWND hWndCompProgress = GetDlgItem(IDC_PROGRESS1); HWND hWndOverallText = GetDlgItem(IDC_STATIC3); HWND hWndOverallProgress = GetDlgItem(IDC_PROGRESS2); HWND hWndOK = GetDlgItem(IDOK); HWND hWndCancel = GetDlgItem(IDCANCEL); LOGFONT logFont; HIMAGELIST hILSmall; HBITMAP hBitmap; HDC hDC; TEXTMETRIC tm; RECT rect; CWindow wnd; int iResizeLV = 0; int iResize = 0; // Attach to the Listview wnd.Attach(hWndLV); wnd.GetWindowRect(&rect); hDC = GetDC(); GetTextMetrics(hDC, &tm); ReleaseDC(hDC); // Check if size of the list view is OK enough to hold all the components iResizeLV = rect.bottom - rect.top - ((tm.tmHeight + 2) * (m_mapComponents.size() + 1)); // Depending on the options selected, decide whether the stus dialog will shrink or expand if( (m_lFlags & SD_PROGRESS_COMPONENT) && !(m_lFlags & SD_PROGRESS_OVERALL) ) { iResize = GetWindowLength(hWndOverallText, hWndOverallProgress); } else if( !(m_lFlags & SD_PROGRESS_COMPONENT) && (m_lFlags & SD_PROGRESS_OVERALL) ) { iResize = GetWindowLength(hWndCompText, hWndCompProgress); } else if( !(m_lFlags & SD_PROGRESS_COMPONENT) && !(m_lFlags & SD_PROGRESS_OVERALL) ) { iResize = GetWindowLength(hWndCompText, hWndOverallProgress); } // Hide component progress if necessary if( !(m_lFlags & SD_PROGRESS_COMPONENT) ) { ::ShowWindow(hWndCompText, SW_HIDE); ::ShowWindow(hWndCompProgress, SW_HIDE); } // Hide overall progress if necessary if( !(m_lFlags & SD_PROGRESS_OVERALL) ) { ::ShowWindow(hWndOverallText, SW_HIDE); ::ShowWindow(hWndOverallProgress, SW_HIDE); } if ((!(m_lFlags & SD_PROGRESS_OVERALL)) || (!(m_lFlags & SD_PROGRESS_COMPONENT))) { // We need to get rid of the space between the progress bars iResize -= GetWindowLength(hWndCompText, hWndOverallProgress) - GetWindowLength(hWndOverallText, hWndOverallProgress) - GetWindowLength(hWndCompText, hWndOverallProgress) + 4; } // Well, we may need to make LV bigger, but the dialog length could stay the same // if the user does not want component and/or overall progress if( iResizeLV < 0 ) // Will need to make the LV bigger { iResize += iResizeLV; } else { iResizeLV = 0; // We will not touch the LV } if( iResizeLV != 0 || iResize != 0 ) // We will need to do some moving and resizing { VerticalResizeWindow(m_hWnd, iResize); VerticalMoveWindow(hWndOK, iResize); VerticalMoveWindow(hWndCancel, iResize); // Location of progress bars completely depend on the resizing of the LV VerticalMoveWindow(hWndOverallText, iResizeLV); VerticalMoveWindow(hWndOverallProgress, iResizeLV); VerticalMoveWindow(hWndCompText, iResizeLV); VerticalMoveWindow(hWndCompProgress, iResizeLV); // Last, but not the least, resize the LV VerticalResizeWindow(hWndLV, iResizeLV); } if( !(m_lFlags & SD_BUTTON_CANCEL) ) // We will only have an OK button { LONG_PTR dwStyle = ::GetWindowLongPtr( m_hWnd, GWL_STYLE ); if( 0 != dwStyle ) { // Get rid of the System Menu (Close X) as well dwStyle &= ~WS_SYSMENU; ::SetWindowLongPtr( m_hWnd, GWL_STYLE, dwStyle ); } ReplaceWindow(hWndCancel, hWndOK); } // if we only have overall progress, we need to move it up // so that it replaces the component progress if( (m_lFlags & SD_PROGRESS_OVERALL) && !(m_lFlags & SD_PROGRESS_COMPONENT) ) { ReplaceWindow(hWndCompText, hWndOverallText); ReplaceWindow(hWndCompProgress, hWndOverallProgress); } // Set some style for the LV ::SendMessage(hWndLV, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_SUBITEMIMAGES); ::SendMessage(hWndLV, LVM_SETBKCOLOR, 0, (LPARAM) CLR_NONE); ::SendMessage(hWndLV, LVM_SETTEXTBKCOLOR, 0, (LPARAM) CLR_NONE); LVCOLUMN col; ZeroMemory (&col, sizeof(col)); col.mask = LVCF_WIDTH; col.cx = 500; SendMessage( hWndLV, LVM_INSERTCOLUMN, 0, (LPARAM)&col ); // Thanks to Jeffzi m_pProgressList->Attach( hWndLV ); COMPONENTMAP::iterator compIterator; compIterator = m_mapComponents.begin(); while( compIterator != m_mapComponents.end() ) { // Add each component to the LV m_pProgressList->AddItem(compIterator->second); m_pProgressList->SetItemState(compIterator->first, IS_NONE, FALSE ); compIterator++; } if( m_pComponentProgress ) { // Initialize the component progress with the progress bar handle m_pComponentProgress->Initialize( this, hWndCompProgress, FALSE ); } if (m_pOverallProgress) { // Initialize the overall progress with the progress bar handle m_pOverallProgress->Initialize( this, hWndOverallProgress, TRUE ); } // Here comes the dialog title and text SetWindowText(m_strWindowTitle.c_str()); ::SetWindowText(hWndText, m_strWindowText.c_str()); SetupButtons(); // Center the window, no Jeff this works just right if you have 2 monitors CenterWindow(); if( m_hDisplayedEvent ) { SetEvent(m_hDisplayedEvent); } return TRUE; } BOOL CStatusDlg::VerticalMoveWindow( HWND hWnd, int iResize ) { BOOL bRet; CWindow wnd; RECT rect; wnd.Attach( hWnd ); // Returns void if(wnd.GetWindowRect(&rect) ) { rect.top -= iResize; rect.bottom -= iResize; // GetWindowRect fills in RECT relative to the desktop // We need to make it relative to the dialog // as MoveWindow works that way if( ScreenToClient(&rect) ) { bRet = wnd.MoveWindow(&rect); } else { bRet = FALSE; } } else { bRet = FALSE; } return bRet; } BOOL CStatusDlg::ReplaceWindow( HWND hWndOld, HWND hWndNew ) { BOOL bRet; CWindow wnd; RECT rect; wnd.Attach(hWndOld); // Get the coordinates of the old Window if( wnd.GetWindowRect(&rect) ) { // Hide it, we are trying to replace it wnd.ShowWindow(SW_HIDE); // Attach to the new one wnd.Attach(hWndNew); // Map the coordinates and move the window on top of the old one if( ScreenToClient(&rect) ) { bRet = wnd.MoveWindow(&rect); } else { bRet = FALSE; } } else { bRet = FALSE; } return bRet; } BOOL CStatusDlg::VerticalResizeWindow( HWND hWnd, int iResize ) { CWindow wnd; RECT rect; BOOL bRet = FALSE; if( iResize ) { // Attach to the window wnd.Attach(hWnd); // Get the coordinates if( wnd.GetWindowRect(&rect) ) { rect.bottom -= iResize; // Increase the bottom if( ScreenToClient(&rect) ) { bRet = wnd.MoveWindow(&rect); // Resize } else { bRet= FALSE; } } else { bRet = FALSE; } } return bRet; } int CStatusDlg::GetWindowLength( HWND hWndTop, HWND hWndBottom ) { CWindow wnd; RECT rect; int iTop; wnd.Attach(hWndTop); if( wnd.GetWindowRect(&rect) ) { iTop = rect.top; wnd.Attach(hWndBottom); if( wnd.GetWindowRect(&rect) ) { return rect.bottom - iTop; } } return 0; } STDMETHODIMP CStatusDlg::get_OverallProgress( IStatusProgress** pVal ) { HRESULT hr = S_OK; if( NULL == pVal ) { return E_INVALIDARG; } if( m_lFlags & SD_PROGRESS_OVERALL ) { // Create component progress object if( m_pOverallProgress == NULL ) { hr = CComObject::CreateInstance(&m_pOverallProgress); if( SUCCEEDED(hr) ) { hr = m_pOverallProgress->AddRef(); } if( SUCCEEDED(hr) && IsWindow() ) { // Initialize the overall progress with the progress bar handle hr = m_pOverallProgress->Initialize(this, GetDlgItem(IDC_PROGRESS2), TRUE); } } hr = m_pOverallProgress->QueryInterface(IID_IStatusProgress, (void **) pVal); } return hr; } BOOL CStatusDlg::AreAllComponentsDone( BOOL& bFailedComponent ) { BOOL bComponentToRun = FALSE; COMPONENTMAP::iterator compIterator = m_mapComponents.begin(); if( m_pProgressList ) { // Look for a component that's not done while( m_pProgressList && !bComponentToRun && compIterator != m_mapComponents.end() ) { CProgressItem * pPI = m_pProgressList->GetProgressItem(compIterator->first); if( NULL != pPI ) { // Is the component done? if( IS_NONE == pPI->m_eState ) { bComponentToRun = TRUE; } else if( IS_FAILED == pPI->m_eState ) { bFailedComponent = TRUE; } } else { _ASSERT( pPI ); } compIterator++; } } return !bComponentToRun; } LRESULT CStatusDlg::OnCloseCmd( WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/ ) { EndDialog(wID); return 0; } LRESULT CStatusDlg::OnCancelCmd( WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/ ) { HWND hWnd = NULL; if( 0 == m_iCancelled ) { InterlockedIncrement((LONG *) &m_iCancelled); } // Disable the Cancel button hWnd = GetDlgItem(IDCANCEL); if( hWnd && ::IsWindow(hWnd) ) { ::EnableWindow( hWnd, FALSE ); } // EndDialog(wID); // Leaving it to the component to close the dialog return 0; } LRESULT CStatusDlg::OnDrawItem( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if( wParam == IDC_LIST2 ) { if( m_pProgressList ) { m_pProgressList->OnDrawItem( lParam ); } } return 0; } LRESULT CStatusDlg::OnMeasureItem( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if( wParam == IDC_LIST2) { if( m_pProgressList ) { m_pProgressList->OnMeasureItem( lParam ); } } return 0; } LRESULT CStatusDlg::OnClose( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if( m_lFlags & SD_BUTTON_CANCEL ) // Cancel button? { if( ::IsWindowEnabled( GetDlgItem(IDCANCEL) ) ) // Is it enabled? { if( 0 == m_iCancelled ) { InterlockedIncrement( (LONG*)&m_iCancelled ); } EndDialog(0); } else if( ::IsWindowEnabled( GetDlgItem(IDOK) ) ) { // it could be OK button sending WM_CLOSE or the user // As long as OK button is enabled we need to close the dialog EndDialog(1); } } else if( ::IsWindowEnabled( GetDlgItem(IDOK) ) ) { EndDialog( 1 ); } return 0; } LRESULT CStatusDlg::OnTimerProgress( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { HRESULT hr; long lPosition; if( wParam && (m_lTimer == wParam) ) // Make sure that this is for our timer { lPosition = SendDlgItemMessage(IDC_PROGRESS1, PBM_GETPOS, 0, 0); if( lPosition < m_lMaxSteps ) // Do we still have room for progress? { SendDlgItemMessage(IDC_PROGRESS1, PBM_STEPIT, 0, 0); // Step 1 SendMessage(WM_UPDATEOVERALLPROGRESS, 0, 0); // Update the overall progress } else { // There's no room to progress, we've reached the max // Let's kill the timer SendMessage(WM_KILLTIMER, 0); } } return 0; } LRESULT CStatusDlg::OnUpdateOverallProgress( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { long lPosition = 0; if( m_lFlags & SD_PROGRESS_COMPONENT ) // Make sure that there's a component progress { lPosition = SendDlgItemMessage(IDC_PROGRESS1, PBM_GETPOS, 0, 0); // Update the overall progress SendDlgItemMessage(IDC_PROGRESS2, PBM_SETPOS, m_lTotalProgress + lPosition, 0); } return 0; } LRESULT CStatusDlg::OnStartTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if( !m_lTimer ) // There might be a timer already { m_lTimer = SetTimer(SD_TIMER_ID, wParam * 500); // Create a timer m_lMaxSteps = (long) lParam; // Max. not to exceed for the progress bar } return 0; } LRESULT CStatusDlg::OnKillTimer( UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled ) { if( m_lTimer ) // Is there a timer? { KillTimer( m_lTimer ); // Kill it m_lTimer = 0; m_lMaxSteps = 0; } return 0; } STDMETHODIMP CStatusDlg::SetStatusText(BSTR bstrText) { if( !bstrText ) return E_POINTER; HRESULT hr = S_OK; HWND hWnd = GetDlgItem(IDC_STATIC1); if( hWnd && ::IsWindow(hWnd) ) { if( 0 == ::SetWindowText(hWnd, OLE2T(bstrText)) ) { hr = HRESULT_FROM_WIN32(GetLastError()); } } else { hr = E_FAIL; } return hr; } STDMETHODIMP CStatusDlg::DisplayError(BSTR bstrError, BSTR bstrTitle, DWORD dwFlags, long * pRet) { if( !bstrError || !bstrTitle || !pRet ) return E_POINTER; HRESULT hr = S_OK; *pRet = MessageBox( bstrError, bstrTitle, dwFlags ); if( 0 == *pRet ) { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; }