// TaskFrame.cpp : Implementation of CTaskUIApp and DLL registration. #include "stdafx.h" #include "TaskFrame.h" #include "cbsc.h" ///////////////////////////////////////////////////////////////////////////// // // // Create and initialize an instance of the task frame. // HRESULT CTaskFrame::CreateInstance( // [static] IPropertyBag *pPropertyBag, ITaskPageFactory *pPageFactory, CComObject **ppFrameOut ) { ASSERT(NULL != pPropertyBag); ASSERT(NULL != pPageFactory); ASSERT(!IsBadWritePtr(ppFrameOut, sizeof(*ppFrameOut))); CComObject *pFrame; HRESULT hr = CComObject::CreateInstance(&pFrame); if (SUCCEEDED(hr)) { hr = pFrame->_Init(pPropertyBag, pPageFactory); if (SUCCEEDED(hr)) { pFrame->AddRef(); } else { delete pFrame; pFrame = NULL; } } *ppFrameOut = pFrame; ASSERT(SUCCEEDED(hr) || NULL == *ppFrameOut); return hr; } CTaskFrame::CTaskFrame() : m_pPropertyBag(NULL), m_pPageFactory(NULL), m_pUIParser(NULL), m_hwndNavBar(NULL), m_hwndStatusBar(NULL), m_himlNBDef(NULL), m_himlNBHot(NULL), m_pbmWatermark(NULL) { SetRectEmpty(&m_rcPage); m_ptMinSize.x = m_ptMinSize.y = 0; // DUI initialization InitThread(); } CTaskFrame::~CTaskFrame() { Close(); // m_dpaHistory is self-destructing if (m_himlNBDef) ImageList_Destroy(m_himlNBDef); if (m_himlNBHot) ImageList_Destroy(m_himlNBHot); ATOMICRELEASE(m_pPropertyBag); ATOMICRELEASE(m_pPageFactory); delete m_pbmWatermark; delete m_pUIParser; // DUI shutdown UnInitThread(); } void CALLBACK TaskUIParseError(LPCWSTR pszError, LPCWSTR pszToken, int dLine) { //#if DBG #if 1 WCHAR buf[201]; if (dLine != -1) swprintf(buf, L"%s '%s' at line %d", pszError, pszToken, dLine); else swprintf(buf, L"%s '%s'", pszError, pszToken); MessageBoxW(NULL, buf, L"Parser Message", MB_OK); #endif } HRESULT CTaskFrame::_Init(IPropertyBag* pBag, ITaskPageFactory* pPageFact) { HRESULT hr; if (!pBag || !pPageFact) return E_INVALIDARG; m_pPropertyBag = pBag; m_pPropertyBag->AddRef(); m_pPageFactory = pPageFact; m_pPageFactory->AddRef(); m_iCurrentPage = -1; hr = Parser::Create(IDR_TASKUI_UI, _Module.GetResourceInstance(), TaskUIParseError, &m_pUIParser); if (FAILED(hr)) return hr; if (m_pUIParser->WasParseError()) return E_FAIL; CWndClassInfo& wci = GetWndClassInfo(); if (!wci.m_atom) { // Modify wndclass here if necessary wci.m_wc.style &= ~(CS_HREDRAW | CS_VREDRAW); } return S_OK; } HWND CTaskFrame::CreateFrameWindow(HWND hwndOwner, UINT nID, LPVOID pParam) { if (NULL == m_pPropertyBag) return NULL; // Register the AtlAxHost window class, etc. AtlAxWinInit(); // Default window styles & dimensions DWORD dwWndStyle = GetWndStyle(0); // WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS DWORD dwWndExStyle = GetWndExStyle(0); // WS_EX_APPWINDOW | WS_EX_WINDOWEDGE RECT rcFrame = CWindow::rcDefault; // { CW_USEDEFAULT, CW_USEDEFAULT, 0, 0 } CComVariant var; // Get the initial window dimensions from the property bag if (SUCCEEDED(_ReadProp(TS_PROP_WIDTH, VT_I4, var))) { rcFrame.right = rcFrame.left + var.lVal; } if (SUCCEEDED(_ReadProp(TS_PROP_HEIGHT, VT_I4, var))) { rcFrame.bottom = rcFrame.top + var.lVal; } // See if we're resizable. Default is TRUE; m_bResizable = TRUE; if (SUCCEEDED(_ReadProp(TS_PROP_RESIZABLE, VT_BOOL, var))) { m_bResizable = (VARIANT_TRUE == var.boolVal); } if (m_bResizable) { // Resizable: get minimum dimensions if provided if (SUCCEEDED(_ReadProp(TS_PROP_MINWIDTH, VT_I4, var))) { m_ptMinSize.x = var.lVal; } if (SUCCEEDED(_ReadProp(TS_PROP_MINHEIGHT, VT_I4, var))) { m_ptMinSize.y = var.lVal; } } else { // No resize: switch to a simple border style and don't allow maximize dwWndStyle = (dwWndStyle & ~(WS_THICKFRAME | WS_MAXIMIZEBOX)) | WS_BORDER; } // See if we're modeless. Default is FALSE (modal). BOOL bModeless = FALSE; if (SUCCEEDED(_ReadProp(TS_PROP_MODELESS, VT_BOOL, var))) { bModeless = (VARIANT_TRUE == var.boolVal); } if (!bModeless) { // Modal if (!m_bResizable) dwWndExStyle |= WS_EX_DLGMODALFRAME; // If not a top-level window, disallow minimize if (hwndOwner) dwWndStyle &= ~WS_MINIMIZEBOX; } // Get the application graphics if (SUCCEEDED(_ReadProp(TS_PROP_WATERMARK, VT_BSTR, var))) { CComPtr spStream; HRESULT hr = BindToURL(var.bstrVal, &spStream); if (SUCCEEDED(hr)) { // Create GDI+ Bitmap from stream delete m_pbmWatermark; m_pbmWatermark = Bitmap::FromStream(spStream); if (NULL != m_pbmWatermark && (Ok != m_pbmWatermark->GetLastStatus())) { delete m_pbmWatermark; m_pbmWatermark = NULL; } // Later, when creating a page, set m_pbmWatermark as content of the // "Picture" element } } // Get the window title from the property bag CComVariant varTitle; if (FAILED(_ReadProp(TS_PROP_TITLE, VT_BSTR, varTitle))) { // Use NULL for no title varTitle.bstrVal = NULL; } dwWndExStyle |= WS_EX_CONTROLPARENT; return Create(hwndOwner, rcFrame, varTitle.bstrVal, dwWndStyle, dwWndExStyle, nID, pParam); } STDMETHODIMP CTaskFrame::ShowPage(REFCLSID rclsidNewPage, BOOL bTrimHistory) { HRESULT hr; int iPage; TaskPage *pSavePage = NULL; if (!m_dpaHistory.IsValid()) return E_OUTOFMEMORY; hr = S_OK; // m_iCurrentPage = -1 should only occur when Count = 0 ASSERT(-1 != m_iCurrentPage || 0 == m_dpaHistory.Count()); // If we don't have any pages, then we can't trim the history if (-1 == m_iCurrentPage) bTrimHistory = FALSE; // First remove any forward pages from the history. // Note that we never remove the first page (index 0). for (iPage = m_dpaHistory.Count() - 1; iPage > 0 && iPage > m_iCurrentPage; iPage--) { TaskPage *pPage = m_dpaHistory[iPage]; ASSERT(NULL != pPage); // Optimization: if we are navigating forward and the next page // is the page we want, go directly there and reinitialize it. if (!bTrimHistory && iPage == m_iCurrentPage+1 && rclsidNewPage == pPage->GetID()) { hr = _ActivatePage(iPage, TRUE); if (SUCCEEDED(hr)) { _SetNavBarState(); return hr; } } m_dpaHistory.Remove(iPage); // TODO OPTIMIZATION: cache the page _DestroyPage(pPage); } // Either m_iCurrentPage = -1 and Count = 0, or // m_iCurrentPage is the last page (Count-1) since // we just truncated the history. ASSERT(m_iCurrentPage + 1 == m_dpaHistory.Count()); iPage = m_iCurrentPage; _DeactivateCurrentPage(); // sets m_iCurrentPage to -1 if (bTrimHistory) { // Can't delete this guy right right away since he's still // processing messages (this is the page we just deactivated). pSavePage = m_dpaHistory[iPage]; // Work backwards looking for rclsidNewPage, trimming as we go. // Note that we never remove the first page (index 0). while (0 < iPage) { TaskPage *pPage = m_dpaHistory[iPage]; ASSERT(NULL != pPage); if (rclsidNewPage == pPage->GetID()) break; m_dpaHistory.Remove(iPage); if (pSavePage != pPage) { // TODO OPTIMIZATION: cache the page _DestroyPage(pPage); } --iPage; } } // Create a new page if necessary TaskPage *pNewPage = NULL; TaskPage *pCurrentPage = (-1 == iPage) ? NULL : m_dpaHistory[iPage]; if (NULL == pCurrentPage || rclsidNewPage != pCurrentPage->GetID()) { hr = _CreatePage(rclsidNewPage, &pNewPage); if (SUCCEEDED(hr)) { iPage = m_dpaHistory.Append(pNewPage); } } if (FAILED(hr) || -1 == iPage) { // Something bad happened, try to activate the home page if (0 < m_dpaHistory.Count()) { _ActivatePage(0); } if (NULL != pNewPage) { _DestroyPage(pNewPage); } } else { // Show the page hr = _ActivatePage(iPage, NULL != pNewPage ? FALSE : TRUE); } _SetNavBarState(); if (pSavePage) { // TODO: need to free this guy later (currently leaked) } return hr; } STDMETHODIMP CTaskFrame::Back(UINT cPages) { HRESULT hr; if (-1 == m_iCurrentPage || 0 == m_dpaHistory.Count()) return E_UNEXPECTED; hr = S_FALSE; if (0 < m_iCurrentPage) { int iNewPage; ASSERT(m_iCurrentPage < m_dpaHistory.Count()); if (0 == cPages) iNewPage = m_iCurrentPage - 1; else if (cPages > (UINT)m_iCurrentPage) iNewPage = 0; else // cPages > 0 && cPages <= m_iCurrentPage iNewPage = m_iCurrentPage - cPages; hr = _ActivatePage(iNewPage); } _SetNavBarState(); return hr; } STDMETHODIMP CTaskFrame::Forward() { HRESULT hr; if (-1 == m_iCurrentPage || 0 == m_dpaHistory.Count()) return E_UNEXPECTED; hr = S_FALSE; int iNewPage = m_iCurrentPage + 1; if (iNewPage < m_dpaHistory.Count()) { hr = _ActivatePage(iNewPage); } _SetNavBarState(); return hr; } STDMETHODIMP CTaskFrame::Home() { if (-1 == m_iCurrentPage || 0 == m_dpaHistory.Count()) return E_UNEXPECTED; HRESULT hr = _ActivatePage(0); _SetNavBarState(); return hr; } STDMETHODIMP CTaskFrame::SetStatusText(LPCWSTR pszText) { if (NULL == m_hwndStatusBar) return E_UNEXPECTED; ::SendMessageW(m_hwndStatusBar, SB_SETTEXT, SB_SIMPLEID, (LPARAM)pszText); return S_OK; } HRESULT CTaskFrame::_ReadProp(LPCWSTR pszProp, VARTYPE vt, CComVariant& var) { HRESULT hr; ASSERT(NULL != m_pPropertyBag); var.Clear(); hr = m_pPropertyBag->Read(pszProp, &var, NULL); if (SUCCEEDED(hr)) { hr = var.ChangeType(vt); } return hr; } LRESULT CTaskFrame::_OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { BOOL bNavBar; BOOL bStatusBar; CComVariant var; ASSERT(NULL != m_pPropertyBag); // See if we're supposed to show the NavBar. Default is TRUE. bNavBar = TRUE; if (SUCCEEDED(_ReadProp(TS_PROP_NAVBAR, VT_BOOL, var))) { bNavBar = (VARIANT_TRUE == var.boolVal); } if (bNavBar) { _CreateNavBar(); } // See if we're supposed to show a status bar. Default is FALSE. bStatusBar = FALSE; if (SUCCEEDED(_ReadProp(TS_PROP_STATUSBAR, VT_BOOL, var))) { bStatusBar = (VARIANT_TRUE == var.boolVal); } if (bStatusBar) { DWORD dwStyle = WS_CHILD | WS_VISIBLE | CCS_BOTTOM; if (m_bResizable) dwStyle |= SBARS_SIZEGRIP; m_hwndStatusBar = CreateStatusWindowW(dwStyle, NULL, m_hWnd, IDC_STATUSBAR); } // Force m_rcPage to be calculated LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam; _OnSize(WM_SIZE, SIZE_RESTORED, MAKELONG(LOWORD(pcs->cx),LOWORD(pcs->cy)), bHandled); return 0; } LRESULT CTaskFrame::_OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/) { if (SIZE_RESTORED == wParam || SIZE_MAXIMIZED == wParam) { GetClientRect(&m_rcPage); if (m_hwndNavBar) { RECT rc; ::SendMessageW(m_hwndNavBar, uMsg, wParam, lParam); ::GetWindowRect(m_hwndNavBar, &rc); m_rcPage.top += (rc.bottom - rc.top); } if (m_hwndStatusBar) { RECT rc; ::SendMessageW(m_hwndStatusBar, uMsg, wParam, lParam); ::GetWindowRect(m_hwndStatusBar, &rc); m_rcPage.bottom -= (rc.bottom - rc.top); } // At this point, m_rcPage represents the remaining usable client // area between the toolbar and statusbar. if (-1 != m_iCurrentPage) { // Resize the current page. Other pages will be resized // as necessary when we show them. _SyncPageRect(m_dpaHistory[m_iCurrentPage]); } } return 0; } LRESULT CTaskFrame::_OnTBGetInfoTip(int /*idCtrl*/, LPNMHDR pnmh, BOOL& /*bHandled*/) { LPNMTBGETINFOTIP pgit = (LPNMTBGETINFOTIP)pnmh; ::LoadStringW(_Module.GetResourceInstance(), pgit->iItem, pgit->pszText, pgit->cchTextMax); return 0; } LRESULT CTaskFrame::_OnTBCustomDraw(int /*idCtrl*/, LPNMHDR pnmh, BOOL& bHandled) { LPNMCUSTOMDRAW pcd = (LPNMCUSTOMDRAW)pnmh; switch (pcd->dwDrawStage) { case CDDS_PREPAINT: return CDRF_NOTIFYITEMERASE; case CDDS_PREERASE: FillRect(pcd->hdc, &pcd->rc, GetSysColorBrush(COLOR_3DFACE)); break; } return CDRF_DODEFAULT; } LRESULT CTaskFrame::_OnAppCommand(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& bHandled) { switch (GET_APPCOMMAND_LPARAM(lParam)) { case APPCOMMAND_BROWSER_BACKWARD: Back(1); break; case APPCOMMAND_BROWSER_FORWARD: Forward(); break; case APPCOMMAND_BROWSER_HOME: Home(); break; default: bHandled = FALSE; break; } return 0; } LRESULT CTaskFrame::_OnGetMinMaxInfo(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/) { ((LPMINMAXINFO)lParam)->ptMinTrackSize = m_ptMinSize; return 0; } #define NAVBAR_CX 16 void CTaskFrame::_CreateNavBar() { HINSTANCE hInst = _Module.GetResourceInstance(); const DWORD dwStyle = WS_CHILD | WS_VISIBLE | CCS_TOP | TBSTYLE_FLAT | TBSTYLE_LIST | TBSTYLE_CUSTOMERASE | TBSTYLE_TOOLTIPS; // Create the NavBar toolbar control m_hwndNavBar = CreateWindowExW(TBSTYLE_EX_MIXEDBUTTONS /*| TBSTYLE_EX_DOUBLEBUFFER*/, TOOLBARCLASSNAME, NULL, dwStyle, 0, 0, 0, 0, m_hWnd, (HMENU)IDC_NAVBAR, hInst, NULL); if (m_hwndNavBar) { ::SendMessageW(m_hwndNavBar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0); int idBmp = IDB_NAVBAR; if (SHGetCurColorRes() > 8) idBmp += (IDB_NAVBARHICOLOR - IDB_NAVBAR); m_himlNBDef = ImageList_LoadImageW(hInst, MAKEINTRESOURCE(idBmp), NAVBAR_CX, 0, CLR_DEFAULT, IMAGE_BITMAP, LR_CREATEDIBSECTION); if (m_himlNBDef) ::SendMessageW(m_hwndNavBar, TB_SETIMAGELIST, 0, (LPARAM)m_himlNBDef); m_himlNBHot = ImageList_LoadImageW(hInst, MAKEINTRESOURCE(idBmp+1), NAVBAR_CX, 0, CLR_DEFAULT, IMAGE_BITMAP, LR_CREATEDIBSECTION); if (m_himlNBHot) ::SendMessageW(m_hwndNavBar, TB_SETHOTIMAGELIST, 0, (LPARAM)m_himlNBHot); if (!m_himlNBDef && !m_himlNBHot) { // Must be serious low memory or other resource problems. // There's no point having a toolbar without any images. ::DestroyWindow(m_hwndNavBar); m_hwndNavBar = NULL; } else { TCHAR szBack[64]; TBBUTTON rgButtons[] = { {0, ID_BACK, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE | BTNS_SHOWTEXT, {0}, 0, (INT_PTR)szBack}, {1, ID_FORWARD, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE, {0}, 0, 0}, {2, ID_HOME, TBSTATE_ENABLED, BTNS_BUTTON | BTNS_AUTOSIZE, {0}, 0, 0}, }; ::LoadStringW(hInst, ID_BACK, szBack, ARRAYSIZE(szBack)); ::SendMessageW(m_hwndNavBar, TB_ADDBUTTONSW, ARRAYSIZE(rgButtons), (LPARAM)rgButtons); // This happens in _OnSize //::SendMessageW(m_hwndNavBar, TB_AUTOSIZE, 0, 0); } _SetNavBarState(); } } void CTaskFrame::_SetNavBarState() { if (m_hwndNavBar) { ::SendMessage(m_hwndNavBar, TB_ENABLEBUTTON, ID_BACK, MAKELONG((m_iCurrentPage > 0), 0)); ::SendMessage(m_hwndNavBar, TB_ENABLEBUTTON, ID_HOME, MAKELONG((m_iCurrentPage > 0), 0)); ::SendMessage(m_hwndNavBar, TB_ENABLEBUTTON, ID_FORWARD, MAKELONG((m_iCurrentPage < m_dpaHistory.Count() - 1), 0)); } } HRESULT CTaskFrame::_CreatePage(REFCLSID rclsidPage, TaskPage **ppPage) { HRESULT hr; ASSERT(NULL != ppPage); *ppPage = NULL; if (NULL == m_pPageFactory || NULL == m_pUIParser) return E_UNEXPECTED; // Get this page's ITaskPage interface from the App CComPtr spTaskPage; hr = m_pPageFactory->CreatePage(rclsidPage, IID_ITaskPage, (void**)&spTaskPage.p); if (S_OK == hr) { // Give the ITaskPage our ITaskFrame interface CComQIPtr spThis(this); if (spThis) spTaskPage->SetFrame(spThis); // Create an HWNDElement to contain and layout the page content TaskPage *pNewPage; hr = TaskPage::Create(rclsidPage, m_hWnd, &pNewPage); if (SUCCEEDED(hr)) { Element* pe; // dummy // Fill contents from markup using substitution hr = m_pUIParser->CreateElement(L"main", pNewPage, &pe); if (SUCCEEDED(hr)) { Element::StartDefer(); _SyncPageRect(pNewPage); // Some examples of ways to add graphics to the page Element* pe = pNewPage->FindDescendent(StrToID(L"Picture")); //pe->SetContentGraphic(L"C:\\windows\\ua_bkgnd.bmp", GRAPHIC_EntireAlpha, 64); //pe->SetContentGraphic(L"C:\\windows\\ua_bkgnd.bmp", GRAPHIC_TransColorAuto); //Value* pv = Value::CreateGraphic(MAKEINTRESOURCE(IDB_BACKGROUND), GRAPHIC_TransColorAuto, 0, 0, 0, _Module.GetResourceInstance()); //pe->SetValue(Element::ContentProp, PI_Local, pv); //pv->Release(); #ifdef GADGET_ENABLE_GDIPLUS if (NULL != m_pbmWatermark) { Value* pv = Value::CreateGraphic(m_pbmWatermark); pe->SetValue(Element::ContentProp, PI_Local, pv); pv->Release(); } #endif hr = pNewPage->CreateContent(spTaskPage); Element::EndDefer(); } if (SUCCEEDED(hr)) { *ppPage = pNewPage; } else { _DestroyPage(pNewPage); } } } return hr; } HRESULT CTaskFrame::_ActivatePage(int iPage, BOOL bInit) { HRESULT hr = S_OK; ASSERT(m_dpaHistory.IsValid()); ASSERT(0 < m_dpaHistory.Count()); ASSERT(iPage >= 0 && iPage < m_dpaHistory.Count()); TaskPage *pPage = m_dpaHistory[iPage]; ASSERT(NULL != pPage); if (bInit) { hr = pPage->Reinitialize(); if (FAILED(hr)) { // Can't reinitialize? Create a new instance instead. TaskPage *pNewPage = NULL; hr = _CreatePage(pPage->GetID(), &pNewPage); if (SUCCEEDED(hr)) { m_dpaHistory.Set(iPage, pNewPage); _DestroyPage(pPage); pPage = pNewPage; } } } if (SUCCEEDED(hr)) { if (m_iCurrentPage != iPage) { _DeactivateCurrentPage(); } // In case we were resized since we last showed this page _SyncPageRect(pPage); m_iCurrentPage = iPage; ::ShowWindow(pPage->GetHWND(), SW_SHOW); ::SetFocus(pPage->GetHWND()); } return hr; } HRESULT CTaskFrame::_DeactivateCurrentPage() { if (-1 != m_iCurrentPage) { ASSERT(m_dpaHistory.IsValid()); ASSERT(m_iCurrentPage >= 0 && m_iCurrentPage < m_dpaHistory.Count()); TaskPage *pPage = m_dpaHistory[m_iCurrentPage]; ASSERT(NULL != pPage); m_iCurrentPage = -1; ::ShowWindow(pPage->GetHWND(), SW_HIDE); } return S_OK; } void CTaskFrame::_SyncPageRect(TaskPage* pPage) { if (NULL != pPage) { Element::StartDefer(); pPage->SetX(m_rcPage.left); pPage->SetY(m_rcPage.top); pPage->SetWidth(m_rcPage.right-m_rcPage.left); pPage->SetHeight(m_rcPage.bottom-m_rcPage.top); Element::EndDefer(); } } void CTaskFrame::_DestroyPage(TaskPage* pPage) { if (NULL != pPage) { HWND hwndPage = pPage->GetHWND(); if (NULL != hwndPage && ::IsWindow(hwndPage)) { // This causes pPage to be deleted ::DestroyWindow(hwndPage); } else { // If the window exists, this would not destroy it, so only // do this when there is no window. delete pPage; } } }