///////////////////////////////////////////////////////////////////////////// // AutoSizer.cpp | Implementation of the CAutoSizer class. #include "stdafx.h" #include "AutoSizer.h" ///////////////////////////////////////////////////////////////////////////// // CAutoSizer ///////////////////////////////////////////////////////////////////////////// // Construction / Destruction CAutoSizer::CAutoSizer() : m_hwnd(NULL), m_bRecomputeMinWindow(false) { ::SetRectEmpty(&m_rcMinWindow); ::SetRectEmpty(&m_rcMinClient); ::SetRectEmpty(&m_rcClient); ::SetRectEmpty(&m_rcClientOriginal); } ///////////////////////////////////////////////////////////////////////////// // Attributes bool CAutoSizer::SetWindowAndRules(HWND hwnd, const AutoSizerRule* pRules) { // This should only be called once or reinitialized in WM_DESTROY _ASSERTE(!m_hwnd); // Save the specified HWND _ASSERTE(::IsWindow(hwnd)); m_hwnd = hwnd; // Save the initial client rectangle ::GetClientRect(m_hwnd, &m_rcClient); ::GetClientRect(m_hwnd, &m_rcClientOriginal); // Set current client rectangle as minimum client rect ::GetClientRect(m_hwnd, &m_rcMinClient); // Set current window rectangle as minimum window rect ::GetWindowRect(m_hwnd, &m_rcMinWindow); ::MapWindowPoints(NULL, m_hwnd, (POINT*)&m_rcMinWindow, 2); // Allow NULL as pRules parameter if (pRules) { // Count the number of rules in the specified array const AutoSizerRule* pRulesIt = pRules; for (int cRules = 0; !pRulesIt->IsEnd(); ++pRulesIt) ++cRules; // Copy the rules m_Rules.resize(cRules); for (int i = 0; i < m_Rules.size(); ++i) { m_Rules[i] = pRules + i; m_Rules[i].ResolveIDs(this); m_Rules[i].SaveInitialOffsets(this); } // Update the positions of the controls according to the array of rules UpdatePositions(); } // Indicate success return true; } bool CAutoSizer::SetMinSize(int cx, int cy, bool bDlgUnits) { // Ensure that this is only called when attached to a window if (!m_hwnd) { _ASSERTE(m_hwnd); return false; } // Compute the difference between current minimum client and window rects m_rcMinWindow.left -= m_rcMinClient.left; m_rcMinWindow.top -= m_rcMinClient.top; m_rcMinWindow.right -= m_rcMinClient.right; m_rcMinWindow.bottom -= m_rcMinClient.bottom; // Save the new minimum client rect m_rcMinClient.left = 0; m_rcMinClient.top = 0; m_rcMinClient.right = cx; m_rcMinClient.bottom = cy; // Convert dialog units to pixels, if specified if (bDlgUnits) ::MapDialogRect(m_hwnd, &m_rcMinClient); // Compute the new minimum window rect m_rcMinWindow.left = m_rcMinClient.left + m_rcMinWindow.left; m_rcMinWindow.top = m_rcMinClient.top + m_rcMinWindow.top; m_rcMinWindow.right = m_rcMinClient.right + m_rcMinWindow.right; m_rcMinWindow.bottom = m_rcMinClient.bottom + m_rcMinWindow.bottom; // Adjust the window if new minimum is bigger than current size if (m_rcMinClient.right > m_rcClient.right || m_rcMinClient.bottom > m_rcClient.bottom) { // Get the current window rect RECT rcWindow; ::GetWindowRect(m_hwnd, &rcWindow); // Compute the new extents of the window int cxWindow = max(rcWindow.right - rcWindow.left, m_rcMinWindow.right - m_rcMinWindow.left); int cyWindow = max(rcWindow.bottom - rcWindow.top, m_rcMinWindow.bottom - m_rcMinWindow.top); // Set the new window size ::SetWindowPos(m_hwnd, NULL, 0, 0, cxWindow, cyWindow, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION); } // Indicate success return true; } ///////////////////////////////////////////////////////////////////////////// // Operations bool CAutoSizer::ProcessMessage(MSG* pMsg) { LRESULT lr = 0; return ProcessMessage(pMsg->message, pMsg->wParam, pMsg->lParam, &lr); } bool CAutoSizer::ProcessMessage(UINT uMsg, WPARAM wp, LPARAM lp, LRESULT* plr) { // Do nothing if the window has not been set if (!m_hwnd) return false; // Delegate messages to handler functions switch (uMsg) { case WM_DESTROY: OnDestroy(); break; case WM_GETMINMAXINFO: OnGetMinMaxInfo((MINMAXINFO*)lp); if (plr) *plr = 0; break; case WM_SIZE: OnSize(wp, LOWORD(lp), HIWORD(lp)); if (plr) *plr = 0; break; case WM_STYLECHANGED: OnStyleChanged(); if (plr) *plr = 0; break; default: return false; } // Currently always returning false return false; } bool CAutoSizer::AddRule(HWND hwndFollower, AutoSizer_Follower edgeFollower, HWND hwndLeader, AutoSizer_Leader edgeLeader, AutoSizer_Refresh refresh) { if (!::IsWindow(hwndFollower)) return false; if (!hwndLeader) hwndLeader = GetWindow(); // Create a rule structure with the new values AutoSizerRule rule; rule.m_idFollower = reinterpret_cast(hwndFollower); rule.m_idLeader = reinterpret_cast(hwndLeader); rule.m_eFollower = edgeFollower; rule.m_eLeader = edgeLeader; rule.m_eRefresh = refresh; // Ensure that the specified rule does not already exist in the array for (XRuleIt it = m_Rules.begin(); it != m_Rules.end(); ++it) if (*it == &rule) return false; // Create a new rule item XRule xRule(&rule); xRule.SaveInitialOffsets(this); // Find the first rule with the same follower for (it = m_Rules.begin(); it != m_Rules.end(); ++it) if (it->GetFollower() == hwndFollower) break; // Find the first rule NOT with the same follower for (it; it != m_Rules.end(); ++it) if (it->GetFollower() != hwndFollower) break; // Insert the new rule into the array m_Rules.insert(it, xRule); // Update the positions of the controls according to the new array of rules UpdatePositions(); // Indicate success return true; } bool CAutoSizer::RemoveRules(HWND hwndFollower) { // Get the current size of the rules array int cRules = m_Rules.size(); // Remove each rule for the specified follower window for (XRuleIt it = m_Rules.begin(); it != m_Rules.end();) { if (it->GetFollower() == hwndFollower) it = m_Rules.erase(it); else ++it; } // Indicate whether or not any rules were removed return cRules != m_Rules.size(); } ///////////////////////////////////////////////////////////////////////////// // Implementation void CAutoSizer::ComputeMinRects() { _ASSERTE(m_hwnd); } void CAutoSizer::UpdatePositions() { // Update the positions of the controls according to the array of rules HDWP hdwp = ::BeginDeferWindowPos(m_Rules.size()); HWND hwndPrevFollower = NULL; HWND hwndFollower; RECT rcFollower, rcNew; XRuleIt it = m_Rules.begin(); for (int i = 0; i <= m_Rules.size(); ++it, ++i) { // Get the window rectangle of the current follower control if (i == m_Rules.size() || it->GetFollower() != hwndPrevFollower) { // Apply the previous control's changes, if any if (hwndPrevFollower) { // Compute the old and new width and height int cxOld = rcFollower.right - rcFollower.left; int cyOld = rcFollower.bottom - rcFollower.top; int cxNew = rcNew.right - rcNew.left; int cyNew = rcNew.bottom - rcNew.top; // Determine the DeferWindowPos flags UINT nFlags = 0; if (cxOld == cxNew && cyOld == cyNew) nFlags |= SWP_NOSIZE; if (rcNew.left == rcFollower.left && rcNew.top == rcFollower.top) nFlags |= SWP_NOMOVE; // If the rectangle of the control has changed, reposition it if ((SWP_NOSIZE | SWP_NOMOVE) != nFlags) { // Move and/or size the control nFlags |= SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION; hdwp = ::DeferWindowPos(hdwp, hwndFollower, NULL, rcNew.left, rcNew.top, cxNew, cyNew, nFlags); } } // Exit loop now if beyond last rule in array if (i == m_Rules.size()) break; // Save the new follower HWND hwndPrevFollower = it->GetFollower(); // Get the window handle of the current follower control hwndFollower = hwndPrevFollower; // Get the window rectangle of the current follower control ::GetWindowRect(hwndFollower, &rcFollower); ::MapWindowPoints(NULL, m_hwnd, (POINT*)&rcFollower, 2); ::CopyRect(&rcNew, &rcFollower); } // Update the control's rectangle it->UpdateRectangle(this, rcNew); } ::EndDeferWindowPos(hdwp); ::UpdateWindow(m_hwnd); } ///////////////////////////////////////////////////////////////////////////// // Message Handlers void CAutoSizer::OnDestroy() { // Release the array m_Rules.clear(); // Reinitialize the object state m_hwnd = NULL; m_bRecomputeMinWindow = false; ::SetRectEmpty(&m_rcMinWindow); ::SetRectEmpty(&m_rcMinClient); ::SetRectEmpty(&m_rcClient); ::SetRectEmpty(&m_rcClientOriginal); } void CAutoSizer::OnGetMinMaxInfo(MINMAXINFO* pMMI) { pMMI->ptMinTrackSize.x = m_rcMinWindow.right - m_rcMinWindow.left; pMMI->ptMinTrackSize.y = m_rcMinWindow.bottom - m_rcMinWindow.top; } void CAutoSizer::OnSize(UINT nType, int cx, int cy) { if (!cx && !cy) { // Get the new client rectangle RECT rcClient; ::GetClientRect(GetWindow(), &rcClient); cx = rcClient.right - rcClient.left; cy = rcClient.bottom - rcClient.top; } // Recompute the minimum window rectangle, if flagged if (m_bRecomputeMinWindow) { // Reset the flag m_bRecomputeMinWindow = false; // Get the difference between the new client and window rects RECT rcWindow, rcClient; ::GetClientRect(m_hwnd, &rcClient); ::GetWindowRect(m_hwnd, &rcWindow); ::MapWindowPoints(NULL, m_hwnd, (POINT*)&rcWindow, 2); // Compute the difference between the new client and window rects int cxWindow = rcWindow.right - rcWindow.left; int cyWindow = rcWindow.bottom - rcWindow.top; rcWindow.left -= rcClient.left; rcWindow.top -= rcClient.top; rcWindow.right -= rcClient.right; rcWindow.bottom -= rcClient.bottom; // Compute the new minimum window rect from the minimum client rect m_rcMinWindow.left = m_rcMinClient.left + rcWindow.left; m_rcMinWindow.top = m_rcMinClient.top + rcWindow.top; m_rcMinWindow.right = m_rcMinClient.right + rcWindow.right; m_rcMinWindow.bottom = m_rcMinClient.bottom + rcWindow.bottom; // Determine if the window needs to be resized if smaller than minimums if (cx < m_rcMinClient.right || cy < m_rcMinClient.bottom) { ::SetWindowPos(m_hwnd, NULL, 0, 0, max(0, m_rcMinWindow.right - m_rcMinWindow.left), max(0, m_rcMinWindow.bottom - m_rcMinWindow.top), SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION); return; } } // Save the new client rectangle m_rcClient.right = cx; m_rcClient.bottom = cy; // Update the positions of the controls according to the array of rules UpdatePositions(); } void CAutoSizer::OnStyleChanged() { // Indicate that next WM_SIZE will recompute minimum window rect m_bRecomputeMinWindow = true; } ///////////////////////////////////////////////////////////////////////////// // CAutoSizer::XRule ///////////////////////////////////////////////////////////////////////////// // Operations bool CAutoSizer::XRule::SaveInitialOffsets(CAutoSizer* pSizer) { _ASSERTE(::IsWindow(GetFollower())); // Get the HWND of the follower control if (!GetFollower() || !::IsWindow(GetFollower())) return false; // Get the window rectangles of each control RECT rcFollower, rcLeader; const RECT* prcLeader; ::GetWindowRect(GetFollower(), &rcFollower); ::MapWindowPoints(NULL, pSizer->GetWindow(), (POINT*)&rcFollower, 2); if (GetLeader() == pSizer->GetWindow()) prcLeader = pSizer->GetOriginalClientRect(); else { prcLeader = &rcLeader; if (!GetLeader() || !::IsWindow(GetLeader())) return false; ::GetWindowRect(GetLeader(), &rcLeader); ::MapWindowPoints(NULL, pSizer->GetWindow(), (POINT*)&rcLeader, 2); } // Determine the leader coordinate to use for the offset calculation int nLeader = 0; switch (m_Rule.m_eLeader) { case AutoSizer_Lead_Left: nLeader = prcLeader->left; break; case AutoSizer_Lead_Right: nLeader = prcLeader->right; break; case AutoSizer_Lead_Top: nLeader = prcLeader->top; break; case AutoSizer_Lead_Bottom: nLeader = prcLeader->bottom; break; case AutoSizer_Lead_HCenter: case AutoSizer_Lead_VCenter: // There are no initial offsets to compute for centering rules break; default: _ASSERTE(false); return false; } // Determine the follower coordinate(s) to use for the offset calculation int cOffsets = 0; int nFollower[2] = {0, 0}; switch (m_Rule.m_eFollower) { case AutoSizer_Follow_Left: cOffsets = 1; nFollower[0] = rcFollower.left; break; case AutoSizer_Follow_Right: cOffsets = 1; nFollower[0] = rcFollower.right; break; case AutoSizer_Follow_LeftRight: cOffsets = 2; nFollower[0] = rcFollower.left; nFollower[1] = rcFollower.right; break; case AutoSizer_Follow_Top: cOffsets = 1; nFollower[0] = rcFollower.top; break; case AutoSizer_Follow_Bottom: cOffsets = 1; nFollower[0] = rcFollower.bottom; break; case AutoSizer_Follow_TopBottom: cOffsets = 2; nFollower[0] = rcFollower.top; nFollower[1] = rcFollower.bottom; break; // There are no initial offsets to compute for centering rules case AutoSizer_Follow_HCenter: case AutoSizer_Follow_VCenter: break; default: _ASSERTE(false); return false; } // Loop thru and compute each follower offset for (int i = 0; i < cOffsets; ++i) m_Offsets[i] = nFollower[i] - nLeader; // Indicate success return true; } bool CAutoSizer::XRule::UpdateRectangle(CAutoSizer* pSizer, RECT& rcNew) const { // Get the window rectangle of the leader control RECT rcLeader; const RECT* prcLeader; if (GetLeader() == pSizer->GetWindow()) prcLeader = pSizer->GetClientRect(); else { prcLeader = &rcLeader; _ASSERTE(::IsWindow(GetLeader())); if (!::IsWindow(GetLeader())) return false; ::GetWindowRect(GetLeader(), &rcLeader); ::MapWindowPoints(NULL, pSizer->GetWindow(), (POINT*)&rcLeader, 2); } // Determine the leader coordinate to use for the offset calculation int nLeader = 0; switch (m_Rule.m_eLeader) { case AutoSizer_Lead_Left: nLeader = prcLeader->left; break; case AutoSizer_Lead_Right: nLeader = prcLeader->right; break; case AutoSizer_Lead_Top: nLeader = prcLeader->top; break; case AutoSizer_Lead_Bottom: nLeader = prcLeader->bottom; break; case AutoSizer_Lead_HCenter: nLeader = prcLeader->left + (prcLeader->right - prcLeader->left) / 2; break; case AutoSizer_Lead_VCenter: nLeader = prcLeader->top + (prcLeader->bottom - prcLeader->top) / 2; break; default: _ASSERTE(false); return false; } // Adjust the rectangle of the follower control RECT rcOld; ::CopyRect(&rcOld, &rcNew); switch (m_Rule.m_eFollower) { case AutoSizer_Follow_Left: rcNew.left = nLeader + m_Offsets[0]; break; case AutoSizer_Follow_Right: rcNew.right = nLeader + m_Offsets[0]; break; case AutoSizer_Follow_LeftRight: rcNew.left = nLeader + m_Offsets[0]; rcNew.right = nLeader + m_Offsets[1]; break; case AutoSizer_Follow_Top: rcNew.top = nLeader + m_Offsets[0]; break; case AutoSizer_Follow_Bottom: rcNew.bottom = nLeader + m_Offsets[0]; break; case AutoSizer_Follow_TopBottom: rcNew.top = nLeader + m_Offsets[0]; rcNew.bottom = nLeader + m_Offsets[1]; break; case AutoSizer_Follow_HCenter: { int cxFollower = (rcNew.right - rcNew.left); rcNew.left = nLeader - (cxFollower / 2); rcNew.right = rcNew.left + cxFollower; break; } case AutoSizer_Follow_VCenter: { int cyFollower = (rcNew.bottom - rcNew.top); rcNew.top = nLeader - (cyFollower / 2); rcNew.bottom = rcNew.top + cyFollower; break; } default: _ASSERTE(false); return false; } // Compute the old and new width and height int cxOld = rcOld.right - rcOld.left; int cyOld = rcOld.bottom - rcOld.top; int cxNew = rcNew.right - rcNew.left; int cyNew = rcNew.bottom - rcNew.top; // Refresh the dialog window as specified, if needed if (m_Rule.m_eRefresh > AutoSizer_Refresh_NoRefresh) { if (cxOld != cxNew || cyOld != cyNew || rcNew.left != rcOld.left || rcNew.top != rcOld.top) { bool bEraseBk = AutoSizer_Refresh_BkRefresh == m_Rule.m_eRefresh; ::InvalidateRect(pSizer->GetWindow(), &rcNew, bEraseBk); } } // Indicate success return true; }