ResizableSheetEx.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. // ResizableSheetEx.cpp : implementation file
  2. //
  3. /////////////////////////////////////////////////////////////////////////////
  4. //
  5. // This file is part of ResizableLib
  6. // https://github.com/ppescher/resizablelib
  7. //
  8. // Copyright (C) 2000-2015 by Paolo Messina
  9. // mailto:ppescher@hotmail.com
  10. //
  11. // The contents of this file are subject to the Artistic License 2.0
  12. // http://opensource.org/licenses/Artistic-2.0
  13. //
  14. // If you find this code useful, credits would be nice!
  15. //
  16. /////////////////////////////////////////////////////////////////////////////
  17. #include "stdafx.h"
  18. #include "ResizableSheetEx.h"
  19. #ifdef _DEBUG
  20. #define new DEBUG_NEW
  21. #undef THIS_FILE
  22. static char THIS_FILE[] = __FILE__;
  23. #endif
  24. /////////////////////////////////////////////////////////////////////////////
  25. // CResizableSheetEx
  26. IMPLEMENT_DYNAMIC(CResizableSheetEx, CPropertySheetEx)
  27. inline void CResizableSheetEx::PrivateConstruct()
  28. {
  29. m_bEnableSaveRestore = FALSE;
  30. m_bSavePage = FALSE;
  31. m_dwGripTempState = 1;
  32. m_bLayoutDone = FALSE;
  33. m_bRectOnly = FALSE;
  34. m_nCallbackID = 0;
  35. }
  36. inline BOOL CResizableSheetEx::IsWizard() const
  37. {
  38. return (m_psh.dwFlags & PSH_WIZARD);
  39. }
  40. inline BOOL CResizableSheetEx::IsWizard97() const
  41. {
  42. return (m_psh.dwFlags & (PSH_IE4WIZARD97 | PSH_IE5WIZARD97));
  43. }
  44. CResizableSheetEx::CResizableSheetEx()
  45. {
  46. PrivateConstruct();
  47. }
  48. CResizableSheetEx::CResizableSheetEx(UINT nIDCaption, CWnd* pParentWnd,
  49. UINT iSelectPage, HBITMAP hbmWatermark, HPALETTE hpalWatermark,
  50. HBITMAP hbmHeader)
  51. : CPropertySheetEx(nIDCaption, pParentWnd, iSelectPage,
  52. hbmWatermark, hpalWatermark, hbmHeader)
  53. {
  54. PrivateConstruct();
  55. }
  56. CResizableSheetEx::CResizableSheetEx(LPCTSTR pszCaption, CWnd* pParentWnd,
  57. UINT iSelectPage, HBITMAP hbmWatermark, HPALETTE hpalWatermark,
  58. HBITMAP hbmHeader)
  59. : CPropertySheetEx(pszCaption, pParentWnd, iSelectPage,
  60. hbmWatermark, hpalWatermark, hbmHeader)
  61. {
  62. PrivateConstruct();
  63. }
  64. CResizableSheetEx::~CResizableSheetEx()
  65. {
  66. }
  67. BEGIN_MESSAGE_MAP(CResizableSheetEx, CPropertySheetEx)
  68. //{{AFX_MSG_MAP(CResizableSheetEx)
  69. ON_WM_GETMINMAXINFO()
  70. ON_WM_SIZE()
  71. ON_WM_DESTROY()
  72. ON_WM_ERASEBKGND()
  73. ON_WM_NCCREATE()
  74. //}}AFX_MSG_MAP
  75. ON_NOTIFY_REFLECT_EX(PSN_SETACTIVE, OnPageChanging)
  76. ON_REGISTERED_MESSAGE(WMU_RESIZESUPPORT, OnResizeSupport)
  77. END_MESSAGE_MAP()
  78. /////////////////////////////////////////////////////////////////////////////
  79. // CResizableSheetEx message handlers
  80. BOOL CResizableSheetEx::OnNcCreate(LPCREATESTRUCT lpCreateStruct)
  81. {
  82. if (!CPropertySheetEx::OnNcCreate(lpCreateStruct))
  83. return FALSE;
  84. // child dialogs don't want resizable border or size grip,
  85. // nor they can handle the min/max size constraints
  86. BOOL bChild = lpCreateStruct->style & WS_CHILD;
  87. // create and init the size-grip
  88. if (!CreateSizeGrip(!bChild))
  89. return FALSE;
  90. MakeResizable(lpCreateStruct);
  91. return TRUE;
  92. }
  93. BOOL CResizableSheetEx::OnInitDialog()
  94. {
  95. BOOL bResult = CPropertySheetEx::OnInitDialog();
  96. // initialize layout
  97. PresetLayout();
  98. m_bLayoutDone = TRUE;
  99. return bResult;
  100. }
  101. LRESULT CResizableSheetEx::OnResizeSupport(WPARAM wParam, LPARAM lParam)
  102. {
  103. switch (wParam)
  104. {
  105. case RSZSUP_SHEETPAGEEXHACK:
  106. {
  107. // a window object must be still associated to the page handle
  108. // but MFC subclassing has been turned off to allow the system
  109. // to subclass it first, so we can catch all the messages
  110. CWnd* pWnd = CWnd::FromHandlePermanent((HWND)lParam);
  111. if (pWnd == NULL)
  112. return 0;
  113. // suclass the window again and refresh page and sheet
  114. pWnd->SubclassWindow(pWnd->Detach());
  115. RefreshLayout();
  116. pWnd->SendMessage(WM_SIZE);
  117. Invalidate();
  118. UnlockWindowUpdate();
  119. if (pWnd->IsWindowVisible())
  120. {
  121. // send lost PSN_SETACTIVE notification message
  122. CPropertyPage* pPage = DYNAMIC_DOWNCAST(CPropertyPage, pWnd);
  123. if (pPage != NULL)
  124. SetActivePage(pPage);
  125. }
  126. }
  127. break;
  128. default:
  129. return FALSE;
  130. }
  131. return TRUE;
  132. }
  133. void CResizableSheetEx::OnDestroy()
  134. {
  135. if (m_bEnableSaveRestore)
  136. {
  137. SaveWindowRect(m_sSection, m_bRectOnly);
  138. if (m_bSavePage)
  139. SavePage(m_sSection);
  140. }
  141. // reset instance data
  142. RemoveAllAnchors();
  143. ResetAllRects();
  144. PrivateConstruct();
  145. CPropertySheetEx::OnDestroy();
  146. }
  147. // maps an index to a button ID and vice-versa
  148. static UINT _propButtons[] =
  149. {
  150. IDOK, IDCANCEL, ID_APPLY_NOW, IDHELP,
  151. ID_WIZBACK, ID_WIZNEXT, ID_WIZFINISH
  152. };
  153. const int _propButtonsCount = sizeof(_propButtons)/sizeof(UINT);
  154. // horizontal line in wizard mode
  155. #define ID_WIZLINE ID_WIZFINISH+1
  156. #define ID_WIZLINEHDR ID_WIZFINISH+2
  157. void CResizableSheetEx::PresetLayout()
  158. {
  159. // set the initial size as the min track size
  160. CRect rc;
  161. GetWindowRect(&rc);
  162. SetMinTrackSize(rc.Size());
  163. // use *total* parent size to have correct margins
  164. CRect rectPage, rectSheet;
  165. GetTotalClientRect(&rectSheet);
  166. // get page area
  167. if (IsWizard() || IsWizard97())
  168. {
  169. HWND hPage = PropSheet_GetCurrentPageHwnd(m_hWnd);
  170. ::GetWindowRect(hPage, &rectPage);
  171. }
  172. else
  173. {
  174. GetTabControl()->GetWindowRect(&rectPage);
  175. }
  176. ::MapWindowPoints(NULL, m_hWnd, (LPPOINT)&rectPage, 2);
  177. // calculate margins
  178. CRect rect;
  179. int cxDiff = rectSheet.right - rectPage.right;
  180. int cyDiff = 0;
  181. // try all possible buttons
  182. for (int i = 0; i < _propButtonsCount; i++)
  183. {
  184. CWnd* pWnd = GetDlgItem(_propButtons[i]);
  185. if (NULL != pWnd)
  186. {
  187. // move buttons if necessary
  188. if (GetStyle() & WS_CHILD)
  189. {
  190. pWnd->GetWindowRect(&rect);
  191. ::MapWindowPoints(NULL, m_hWnd, (LPPOINT)&rect, 2);
  192. cyDiff = rectSheet.bottom - rect.bottom;
  193. rect.OffsetRect(cxDiff, cyDiff);
  194. pWnd->MoveWindow(&rect);
  195. }
  196. // add buttons to the layout manager
  197. AddAnchor(_propButtons[i], BOTTOM_RIGHT);
  198. }
  199. }
  200. // setup pages area
  201. if (IsWizard() || IsWizard97())
  202. {
  203. // move line and pages if necessary
  204. if (GetStyle() & WS_CHILD)
  205. {
  206. GetDlgItem(ID_WIZLINE)->GetWindowRect(&rect);
  207. ::MapWindowPoints(NULL, m_hWnd, (LPPOINT)&rect, 2);
  208. rect.OffsetRect(0, cyDiff);
  209. rect.InflateRect(cxDiff, 0);
  210. GetDlgItem(ID_WIZLINE)->MoveWindow(&rect);
  211. rectPage.bottom += cyDiff;
  212. rectPage.left = 0;
  213. rectPage.top = 0;
  214. rectPage.right = rectSheet.right;
  215. }
  216. AddAnchor(ID_WIZLINE, BOTTOM_LEFT, BOTTOM_RIGHT);
  217. if (IsWizard97()) // add header line for wizard97 dialogs
  218. AddAnchor(ID_WIZLINEHDR, TOP_LEFT, TOP_RIGHT);
  219. // hide tab control
  220. GetTabControl()->ShowWindow(SW_HIDE);
  221. // pre-calculate margins
  222. m_sizePageTL = rectPage.TopLeft() - rectSheet.TopLeft();
  223. m_sizePageBR = rectPage.BottomRight() - rectSheet.BottomRight();
  224. }
  225. else
  226. {
  227. // grow tab to the available sheet space
  228. if (cyDiff > 0)
  229. rectSheet.bottom = rectPage.bottom + cyDiff;
  230. if (GetStyle() & WS_CHILD)
  231. GetTabControl()->MoveWindow(&rectSheet);
  232. AddAnchor(AFX_IDC_TAB_CONTROL, TOP_LEFT, BOTTOM_RIGHT);
  233. }
  234. // add a callback for active page (which can change at run-time)
  235. m_nCallbackID = AddAnchorCallback();
  236. // prevent flickering
  237. GetTabControl()->ModifyStyle(0, WS_CLIPSIBLINGS);
  238. }
  239. BOOL CResizableSheetEx::ArrangeLayoutCallback(LAYOUTINFO &layout) const
  240. {
  241. if (layout.nCallbackID != m_nCallbackID) // we only added 1 callback
  242. return CResizableLayout::ArrangeLayoutCallback(layout);
  243. // set layout info for active page
  244. layout.hWnd = (HWND)::SendMessage(GetSafeHwnd(), PSM_GETCURRENTPAGEHWND, 0, 0);
  245. if (!::IsWindow(layout.hWnd))
  246. return FALSE;
  247. // set margins
  248. if (IsWizard()) // wizard mode
  249. {
  250. // use pre-calculated margins
  251. layout.marginTopLeft = m_sizePageTL;
  252. layout.marginBottomRight = m_sizePageBR;
  253. }
  254. else if (IsWizard97()) // wizard 97
  255. {
  256. // use pre-calculated margins
  257. layout.marginTopLeft = m_sizePageTL;
  258. layout.marginBottomRight = m_sizePageBR;
  259. if (!(GetActivePage()->m_psp.dwFlags & PSP_HIDEHEADER))
  260. {
  261. // add header vertical offset
  262. CRect rectLine, rectSheet;
  263. GetTotalClientRect(&rectSheet);
  264. GetAnchorPosition(ID_WIZLINEHDR, rectSheet, rectLine);
  265. layout.marginTopLeft.cy = rectLine.bottom;
  266. }
  267. }
  268. else // tab mode
  269. {
  270. CTabCtrl* pTab = GetTabControl();
  271. ASSERT(pTab != NULL);
  272. // get tab position after resizing and calc page rect
  273. CRect rectPage, rectSheet;
  274. GetTotalClientRect(&rectSheet);
  275. if (!GetAnchorPosition(pTab->m_hWnd, rectSheet, rectPage))
  276. return FALSE; // no page yet
  277. // temporarily resize the tab control to calc page size
  278. CRect rectSave;
  279. pTab->GetWindowRect(rectSave);
  280. ::MapWindowPoints(NULL, m_hWnd, (LPPOINT)&rectSave, 2);
  281. pTab->SetRedraw(FALSE);
  282. pTab->MoveWindow(rectPage, FALSE);
  283. pTab->AdjustRect(FALSE, &rectPage);
  284. pTab->MoveWindow(rectSave, FALSE);
  285. pTab->SetRedraw(TRUE);
  286. // set margins
  287. layout.marginTopLeft = rectPage.TopLeft() - rectSheet.TopLeft();
  288. layout.marginBottomRight = rectPage.BottomRight() - rectSheet.BottomRight();
  289. }
  290. // set anchor types
  291. layout.anchorTopLeft = TOP_LEFT;
  292. layout.anchorBottomRight = BOTTOM_RIGHT;
  293. // use this layout info
  294. return TRUE;
  295. }
  296. void CResizableSheetEx::OnSize(UINT nType, int cx, int cy)
  297. {
  298. CWnd::OnSize(nType, cx, cy);
  299. if (nType == SIZE_MAXHIDE || nType == SIZE_MAXSHOW)
  300. return; // arrangement not needed
  301. if (nType == SIZE_MAXIMIZED)
  302. HideSizeGrip(&m_dwGripTempState);
  303. else
  304. ShowSizeGrip(&m_dwGripTempState);
  305. // update grip and layout
  306. UpdateSizeGrip();
  307. ArrangeLayout();
  308. if (IsWizard97())
  309. {
  310. // refresh header area
  311. CRect rect;
  312. GetHeaderRect(rect);
  313. InvalidateRect(rect, FALSE);
  314. }
  315. }
  316. BOOL CResizableSheetEx::OnPageChanging(NMHDR* /*pNotifyStruct*/, LRESULT* /*pResult*/)
  317. {
  318. // update new wizard page
  319. // active page changes after this notification
  320. PostMessage(WM_SIZE);
  321. return FALSE; // continue routing
  322. }
  323. BOOL CResizableSheetEx::OnEraseBkgnd(CDC* pDC)
  324. {
  325. if (ClipChildren(pDC, FALSE))
  326. {
  327. // when clipping, remove header from clipping area
  328. if (IsWizard97())
  329. {
  330. // clip header area out
  331. CRect rect;
  332. GetHeaderRect(rect);
  333. pDC->ExcludeClipRect(rect);
  334. }
  335. }
  336. BOOL bRet = CPropertySheetEx::OnEraseBkgnd(pDC);
  337. ClipChildren(pDC, TRUE);
  338. return bRet;
  339. }
  340. BOOL CResizableSheetEx::CalcSizeExtra(HWND /*hWndChild*/, CSize sizeChild, CSize &sizeExtra)
  341. {
  342. CTabCtrl* pTab = GetTabControl();
  343. if (!pTab)
  344. return FALSE;
  345. // get margins of tabcontrol
  346. CRect rectMargins;
  347. if (!GetAnchorMargins(pTab->m_hWnd, sizeChild, rectMargins))
  348. return FALSE;
  349. // get margin caused by tabcontrol
  350. CRect rectTabMargins(0,0,0,0);
  351. // get tab position after resizing and calc page rect
  352. CRect rectPage, rectSheet;
  353. GetTotalClientRect(&rectSheet);
  354. if (!GetAnchorPosition(pTab->m_hWnd, rectSheet, rectPage))
  355. return FALSE; // no page yet
  356. // temporarily resize the tab control to calc page size
  357. CRect rectSave;
  358. pTab->GetWindowRect(rectSave);
  359. ::MapWindowPoints(NULL, m_hWnd, (LPPOINT)&rectSave, 2);
  360. pTab->SetRedraw(FALSE);
  361. pTab->MoveWindow(rectPage, FALSE);
  362. pTab->AdjustRect(TRUE, &rectTabMargins);
  363. pTab->MoveWindow(rectSave, FALSE);
  364. pTab->SetRedraw(TRUE);
  365. // add non-client size
  366. ::AdjustWindowRectEx(&rectTabMargins, GetStyle(), !(GetStyle() & WS_CHILD) &&
  367. ::IsMenu(GetMenu()->GetSafeHmenu()), GetExStyle());
  368. // compute extra size
  369. sizeExtra = rectMargins.TopLeft() + rectMargins.BottomRight() +
  370. rectTabMargins.Size();
  371. return TRUE;
  372. }
  373. void CResizableSheetEx::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
  374. {
  375. MinMaxInfo(lpMMI);
  376. CTabCtrl* pTab = GetTabControl();
  377. if (!pTab)
  378. return;
  379. int nCount = GetPageCount();
  380. for (int idx = 0; idx < nCount; ++idx)
  381. {
  382. if (IsWizard()) // wizard mode
  383. {
  384. // use pre-calculated margins
  385. CRect rectExtra(-CPoint(m_sizePageTL), -CPoint(m_sizePageBR));
  386. // add non-client size
  387. ::AdjustWindowRectEx(&rectExtra, GetStyle(), !(GetStyle() & WS_CHILD) &&
  388. ::IsMenu(GetMenu()->GetSafeHmenu()), GetExStyle());
  389. ChainMinMaxInfo(lpMMI, *GetPage(idx), rectExtra.Size());
  390. }
  391. else if (IsWizard97()) // wizard 97
  392. {
  393. // use pre-calculated margins
  394. CRect rectExtra(-CPoint(m_sizePageTL), -CPoint(m_sizePageBR));
  395. if (!(GetPage(idx)->m_psp.dwFlags & PSP_HIDEHEADER))
  396. {
  397. // add header vertical offset
  398. CRect rectLine, rectSheet;
  399. GetTotalClientRect(&rectSheet);
  400. GetAnchorPosition(ID_WIZLINEHDR, rectSheet, rectLine);
  401. rectExtra.top = -rectLine.bottom;
  402. }
  403. // add non-client size
  404. ::AdjustWindowRectEx(&rectExtra, GetStyle(), !(GetStyle() & WS_CHILD) &&
  405. ::IsMenu(GetMenu()->GetSafeHmenu()), GetExStyle());
  406. ChainMinMaxInfo(lpMMI, *GetPage(idx), rectExtra.Size());
  407. }
  408. else // tab mode
  409. {
  410. ChainMinMaxInfoCB(lpMMI, *GetPage(idx));
  411. }
  412. }
  413. }
  414. // protected members
  415. void CResizableSheetEx::GetHeaderRect(LPRECT lpRect)
  416. {
  417. CWnd* pWizLineHdr = GetDlgItem(ID_WIZLINEHDR);
  418. if (pWizLineHdr != NULL && pWizLineHdr->IsWindowVisible())
  419. {
  420. pWizLineHdr->GetWindowRect(lpRect);
  421. ::MapWindowPoints(NULL, m_hWnd, (LPPOINT)lpRect, 2);
  422. LONG bottom = lpRect->top;
  423. GetClientRect(lpRect);
  424. lpRect->bottom = bottom;
  425. }
  426. else
  427. ::SetRectEmpty(lpRect);
  428. }
  429. int CResizableSheetEx::GetMinWidth()
  430. {
  431. CRect rectWnd, rectSheet;
  432. GetTotalClientRect(&rectSheet);
  433. int max = 0, min = rectSheet.Width();
  434. // search for leftmost and rightmost button margins
  435. for (int i = 0; i < 7; i++)
  436. {
  437. CWnd* pWnd = GetDlgItem(_propButtons[i]);
  438. // exclude not present or hidden buttons
  439. if (pWnd == NULL || !(pWnd->GetStyle() & WS_VISIBLE))
  440. continue;
  441. // left position is relative to the right border
  442. // of the parent window (negative value)
  443. pWnd->GetWindowRect(&rectWnd);
  444. ::MapWindowPoints(NULL, m_hWnd, (LPPOINT)&rectWnd, 2);
  445. int left = rectSheet.right - rectWnd.left;
  446. int right = rectSheet.right - rectWnd.right;
  447. if (left > max)
  448. max = left;
  449. if (right < min)
  450. min = right;
  451. }
  452. // sizing border width
  453. int border = GetSystemMetrics(SM_CXSIZEFRAME);
  454. // compute total width
  455. return max + min + 2*border;
  456. }
  457. // NOTE: this must be called after all the other settings
  458. // to have the window and its controls displayed properly
  459. void CResizableSheetEx::EnableSaveRestore(LPCTSTR pszSection, BOOL bRectOnly, BOOL bWithPage)
  460. {
  461. m_sSection = pszSection;
  462. m_bSavePage = bWithPage;
  463. m_bEnableSaveRestore = TRUE;
  464. m_bRectOnly = bRectOnly;
  465. // restore immediately
  466. LoadWindowRect(pszSection, bRectOnly);
  467. if (bWithPage)
  468. {
  469. LoadPage(pszSection);
  470. ArrangeLayout(); // needs refresh
  471. }
  472. }
  473. void CResizableSheetEx::RefreshLayout()
  474. {
  475. SendMessage(WM_SIZE);
  476. }
  477. LRESULT CResizableSheetEx::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
  478. {
  479. if (message != WM_NCCALCSIZE || wParam == 0 || !m_bLayoutDone)
  480. return CPropertySheetEx::WindowProc(message, wParam, lParam);
  481. // specifying valid rects needs controls already anchored
  482. LRESULT lResult = 0;
  483. HandleNcCalcSize(FALSE, (LPNCCALCSIZE_PARAMS)lParam, lResult);
  484. lResult = CPropertySheetEx::WindowProc(message, wParam, lParam);
  485. HandleNcCalcSize(TRUE, (LPNCCALCSIZE_PARAMS)lParam, lResult);
  486. return lResult;
  487. }