ResizableLayout.cpp
上传用户:xjjlds
上传日期:2015-12-05
资源大小:22823k
文件大小:15k
源码类别:

多媒体编程

开发平台:

Visual C++

  1. // ResizableLayout.cpp: implementation of the CResizableLayout class.
  2. //
  3. /////////////////////////////////////////////////////////////////////////////
  4. //
  5. // Copyright (C) 2000-2002 by Paolo Messina
  6. // (http://www.geocities.com/ppescher - ppescher@yahoo.com)
  7. //
  8. // The contents of this file are subject to the Artistic License (the "License").
  9. // You may not use this file except in compliance with the License. 
  10. // You may obtain a copy of the License at:
  11. // http://www.opensource.org/licenses/artistic-license.html
  12. //
  13. // If you find this code useful, credits would be nice!
  14. //
  15. /////////////////////////////////////////////////////////////////////////////
  16. #include "stdafx.h"
  17. #include "commctrl.h"
  18. #include "ResizableLayout.h"
  19. #include "ResizableMsgSupport.inl"
  20. #ifdef _DEBUG
  21. #undef THIS_FILE
  22. static char THIS_FILE[]=__FILE__;
  23. #define new DEBUG_NEW
  24. #endif
  25. //////////////////////////////////////////////////////////////////////
  26. // Construction/Destruction
  27. //////////////////////////////////////////////////////////////////////
  28. // In August 2002 Platform SDK, some guy at MS thought it was time to
  29. // add the missing symbol BS_TYPEMASK, but forgot its original meaning
  30. // and so now he's telling us not to use that symbol because its
  31. // value is likely to change in the future SDK releases, including all
  32. // the BS_* style bits in the mask, not just the button's type as the
  33. // symbol's name suggests. So now we're forced to use another symbol!
  34. #define _BS_TYPEMASK 0x0000000FL
  35. void CResizableLayout::AddAnchor(HWND hWnd, CSize sizeTypeTL, CSize sizeTypeBR)
  36. {
  37. CWnd* pParent = GetResizableWnd();
  38. // child window must be valid
  39. ASSERT(::IsWindow(hWnd));
  40. // must be child of parent window
  41. // ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));
  42. // top-left anchor must be valid
  43. ASSERT(sizeTypeTL != NOANCHOR);
  44. // get control's window class
  45. CString sClassName;
  46. GetClassName(hWnd, sClassName.GetBufferSetLength(MAX_PATH), MAX_PATH);
  47. sClassName.ReleaseBuffer();
  48. // get parent window's rect
  49. CRect rectParent;
  50. GetTotalClientRect(&rectParent);
  51. // and child control's rect
  52. CRect rectChild;
  53. ::GetWindowRect(hWnd, &rectChild);
  54. ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
  55. // adjust position, if client area has been scrolled
  56. rectChild.OffsetRect(-rectParent.TopLeft());
  57. // go calculate margins
  58. CSize sizeMarginTL, sizeMarginBR;
  59. if (sizeTypeBR == NOANCHOR)
  60. sizeTypeBR = sizeTypeTL;
  61. // calculate margin for the top-left corner
  62. sizeMarginTL.cx = rectChild.left - rectParent.Width() * sizeTypeTL.cx / 100;
  63. sizeMarginTL.cy = rectChild.top - rectParent.Height() * sizeTypeTL.cy / 100;
  64. // calculate margin for the bottom-right corner
  65. sizeMarginBR.cx = rectChild.right - rectParent.Width() * sizeTypeBR.cx / 100;
  66. sizeMarginBR.cy = rectChild.bottom - rectParent.Height() * sizeTypeBR.cy / 100;
  67. // prepare the structure
  68. LayoutInfo layout(hWnd, sizeTypeTL, sizeMarginTL,
  69. sizeTypeBR, sizeMarginBR, sClassName);
  70. // initialize resize properties (overridable)
  71. InitResizeProperties(layout);
  72. // must not be already there!
  73. // (this is probably due to a duplicate call to AddAnchor)
  74. POSITION pos;
  75. ASSERT(!m_mapLayout.Lookup(hWnd, pos));
  76. // add to the list and the map
  77. pos = m_listLayout.AddTail(layout);
  78. m_mapLayout.SetAt(hWnd, pos);
  79. }
  80. void CResizableLayout::AddAnchorCallback(UINT nCallbackID)
  81. {
  82. // one callback control cannot rely upon another callback control's
  83. // size and/or position (they're updated all together at the end)
  84. // it can however use a non-callback control, which is updated before
  85. // add to the list
  86. LayoutInfo layout;
  87. layout.nCallbackID = nCallbackID;
  88. m_listLayoutCB.AddTail(layout);
  89. }
  90. BOOL CResizableLayout::ArrangeLayoutCallback(CResizableLayout::LayoutInfo& /*layout*/)
  91. {
  92. ASSERT(FALSE);
  93. // must be overridden, if callback is used
  94. return FALSE; // no output data
  95. }
  96. void CResizableLayout::ArrangeLayout()
  97. {
  98. // common vars
  99. UINT uFlags;
  100. LayoutInfo layout;
  101. CRect rectParent, rectChild;
  102. GetTotalClientRect(&rectParent); // get parent window's rect
  103. int count = m_listLayout.GetCount();
  104. int countCB = m_listLayoutCB.GetCount();
  105. // reposition child windows
  106. HDWP hdwp = ::BeginDeferWindowPos(count + countCB);
  107. POSITION pos = m_listLayout.GetHeadPosition();
  108. while (pos != NULL)
  109. {
  110. // get layout info
  111. layout = m_listLayout.GetNext(pos);
  112. // calculate new child's position, size and flags for SetWindowPos
  113. CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
  114. // only if size or position changed
  115. if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
  116. {
  117. hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
  118. rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
  119. }
  120. }
  121. // for callback items you may use GetAnchorPosition to know the
  122. // new position and size of a non-callback item after resizing
  123. pos = m_listLayoutCB.GetHeadPosition();
  124. while (pos != NULL)
  125. {
  126. // get layout info
  127. layout = m_listLayoutCB.GetNext(pos);
  128. // request layout data
  129. if (!ArrangeLayoutCallback(layout))
  130. continue;
  131. // calculate new child's position, size and flags for SetWindowPos
  132. CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
  133. // only if size or position changed
  134. if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
  135. {
  136. hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
  137. rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
  138. }
  139. }
  140. // finally move all the windows at once
  141. ::EndDeferWindowPos(hdwp);
  142. }
  143. void CResizableLayout::ClipChildWindow(const CResizableLayout::LayoutInfo& layout,
  144.    CRgn* pRegion)
  145. {
  146. // obtain window position
  147. CRect rect;
  148. ::GetWindowRect(layout.hWnd, &rect);
  149. ::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2);
  150. // use window region if any
  151. CRgn rgn;
  152. rgn.CreateRectRgn(0,0,0,0);
  153. switch (::GetWindowRgn(layout.hWnd, rgn))
  154. {
  155. case COMPLEXREGION:
  156. case SIMPLEREGION:
  157. rgn.OffsetRgn(rect.TopLeft());
  158. break;
  159. default:
  160. rgn.SetRectRgn(&rect);
  161. }
  162. // get the clipping property
  163. BOOL bClipping = layout.properties.bAskClipping ?
  164. LikesClipping(layout) : layout.properties.bCachedLikesClipping;
  165. // modify region accordingly
  166. if (bClipping)
  167. pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF);
  168. else
  169. pRegion->CombineRgn(pRegion, &rgn, RGN_OR);
  170. }
  171. void CResizableLayout::GetClippingRegion(CRgn* pRegion)
  172. {
  173. CWnd* pWnd = GetResizableWnd();
  174. // System's default clipping area is screen's size,
  175. // not enough for max track size, for example:
  176. // if screen is 1024 x 768 and resizing border is 4 pixels,
  177. // maximized size is 1024+4*2=1032 x 768+4*2=776,
  178. // but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
  179. // So, if you resize the window to maximum size, the last 4 pixels
  180. // are clipped out by the default clipping region, that gets created
  181. // as soon as you call clipping functions (my guess).
  182. // reset clipping region to the whole client area
  183. CRect rect;
  184. pWnd->GetClientRect(&rect);
  185. pRegion->CreateRectRgnIndirect(&rect);
  186. // clip only anchored controls
  187. LayoutInfo layout;
  188. POSITION pos = m_listLayout.GetHeadPosition();
  189. while (pos != NULL)
  190. {
  191. // get layout info
  192. layout = m_listLayout.GetNext(pos);
  193. if (::IsWindowVisible(layout.hWnd))
  194. ClipChildWindow(layout, pRegion);
  195. }
  196. pos = m_listLayoutCB.GetHeadPosition();
  197. while (pos != NULL)
  198. {
  199. // get layout info
  200. layout = m_listLayoutCB.GetNext(pos);
  201. // request data
  202. if (!ArrangeLayoutCallback(layout))
  203. continue;
  204. if (::IsWindowVisible(layout.hWnd))
  205. ClipChildWindow(layout, pRegion);
  206. }
  207. // fix for RTL layouts (1 pixel of horz offset)
  208. if (pWnd->GetExStyle() & 0x00400000L/*WS_EX_LAYOUTRTL*/)
  209. pRegion->OffsetRgn(-1,0);
  210. }
  211. void CResizableLayout::EraseBackground(CDC* pDC)
  212. {
  213. HWND hWnd = GetResizableWnd()->GetSafeHwnd();
  214. // retrieve the background brush
  215. HBRUSH hBrush = NULL;
  216. // is this a dialog box?
  217. // (using class atom is quickier than using the class name)
  218. ATOM atomWndClass = (ATOM)::GetClassLong(hWnd, GCW_ATOM);
  219. if (atomWndClass == (ATOM)0x8002)
  220. {
  221. // send a message to the dialog box
  222. hBrush = (HBRUSH)::SendMessage(hWnd, WM_CTLCOLORDLG,
  223. (WPARAM)pDC->GetSafeHdc(), (LPARAM)hWnd);
  224. }
  225. else
  226. {
  227. // take the background brush from the window's class
  228. hBrush = (HBRUSH)::GetClassLongPtr(hWnd, GCL_HBRBACKGROUND);
  229. }
  230. // fill the clipped background
  231. CRgn rgn;
  232. GetClippingRegion(&rgn);
  233. ::FillRgn(pDC->GetSafeHdc(), rgn, hBrush);
  234. }
  235. // support legacy code (will disappear in future versions)
  236. void CResizableLayout::ClipChildren(CDC* pDC)
  237. {
  238. CRgn rgn;
  239. GetClippingRegion(&rgn);
  240. // the clipping region is in device units
  241. rgn.OffsetRgn(-pDC->GetWindowOrg());
  242. pDC->SelectClipRgn(&rgn);
  243. }
  244. void CResizableLayout::GetTotalClientRect(LPRECT lpRect)
  245. {
  246. GetResizableWnd()->GetClientRect(lpRect);
  247. }
  248. BOOL CResizableLayout::NeedsRefresh(const CResizableLayout::LayoutInfo& layout,
  249. const CRect& rectOld, const CRect& rectNew)
  250. {
  251. if (layout.bMsgSupport)
  252. {
  253. REFRESHPROPERTY refresh;
  254. refresh.rcOld = rectOld;
  255. refresh.rcNew = rectNew;
  256. if (Send_NeedsRefresh(layout.hWnd, &refresh))
  257. return refresh.bNeedsRefresh;
  258. }
  259. int nDiffWidth = (rectNew.Width() - rectOld.Width());
  260. int nDiffHeight = (rectNew.Height() - rectOld.Height());
  261. // is the same size?
  262. if (nDiffWidth == 0 && nDiffHeight == 0)
  263. return FALSE;
  264. // optimistic, no need to refresh
  265. BOOL bRefresh = FALSE;
  266. // window classes that need refresh when resized
  267. if (layout.sWndClass == WC_STATIC)
  268. {
  269. DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
  270. switch (style & SS_TYPEMASK)
  271. {
  272. case SS_LEFT:
  273. case SS_CENTER:
  274. case SS_RIGHT:
  275. // word-wrapped text
  276. bRefresh = bRefresh || (nDiffWidth != 0);
  277. // vertically centered text
  278. if (style & SS_CENTERIMAGE)
  279. bRefresh = bRefresh || (nDiffHeight != 0);
  280. break;
  281. case SS_LEFTNOWORDWRAP:
  282. // text with ellipsis
  283. if (style & SS_ELLIPSISMASK)
  284. bRefresh = bRefresh || (nDiffWidth != 0);
  285. // vertically centered text
  286. if (style & SS_CENTERIMAGE)
  287. bRefresh = bRefresh || (nDiffHeight != 0);
  288. break;
  289. case SS_ENHMETAFILE:
  290. case SS_BITMAP:
  291. case SS_ICON:
  292. // images
  293. case SS_BLACKFRAME:
  294. case SS_GRAYFRAME:
  295. case SS_WHITEFRAME:
  296. case SS_ETCHEDFRAME:
  297. // and frames
  298. bRefresh = TRUE;
  299. break;
  300. }
  301. }
  302. // window classes that don't redraw client area correctly
  303. // when the hor scroll pos changes due to a resizing
  304. BOOL bHScroll = FALSE;
  305. if (layout.sWndClass == WC_LISTBOX)
  306. bHScroll = TRUE;
  307. // fix for horizontally scrollable windows
  308. if (bHScroll && (nDiffWidth > 0))
  309. {
  310. // get max scroll position
  311. SCROLLINFO info;
  312. info.cbSize = sizeof(SCROLLINFO);
  313. info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
  314. if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info))
  315. {
  316. // subtract the page size
  317. info.nMax -= __max(info.nPage-1,0);
  318. }
  319. // resizing will cause the text to scroll on the right
  320. // because the scrollbar is going beyond the right limit
  321. if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax))
  322. {
  323. // needs repainting, due to horiz scrolling
  324. bRefresh = TRUE;
  325. }
  326. }
  327. return bRefresh;
  328. }
  329. BOOL CResizableLayout::LikesClipping(const CResizableLayout::LayoutInfo& layout)
  330. {
  331. if (layout.bMsgSupport)
  332. {
  333. CLIPPINGPROPERTY clipping;
  334. if (Send_LikesClipping(layout.hWnd, &clipping))
  335. return clipping.bLikesClipping;
  336. }
  337. DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
  338. // skip windows that wants background repainted
  339. if (layout.sWndClass == TOOLBARCLASSNAME && (style & TBSTYLE_TRANSPARENT))
  340. return FALSE;
  341. else if (layout.sWndClass == WC_BUTTON)
  342. {
  343. CRect rect;
  344. switch (style & _BS_TYPEMASK)
  345. {
  346. case BS_GROUPBOX:
  347. return FALSE;
  348. case BS_OWNERDRAW:
  349. // ownerdraw buttons must return correct hittest code
  350. // to notify their transparency to the system and this library
  351. ::GetWindowRect(layout.hWnd, &rect);
  352. if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
  353. WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
  354. return FALSE;
  355. break;
  356. }
  357. return TRUE;
  358. }
  359. else if (layout.sWndClass == WC_STATIC)
  360. {
  361. switch (style & SS_TYPEMASK)
  362. {
  363. case SS_LEFT:
  364. case SS_CENTER:
  365. case SS_RIGHT:
  366. case SS_SIMPLE:
  367. case SS_LEFTNOWORDWRAP:
  368. // text
  369. case SS_BLACKRECT:
  370. case SS_GRAYRECT:
  371. case SS_WHITERECT:
  372. // filled rects
  373. case SS_ETCHEDHORZ:
  374. case SS_ETCHEDVERT:
  375. // etched lines
  376. case SS_BITMAP:
  377. // bitmaps
  378. return TRUE;
  379. break;
  380. case SS_ICON:
  381. case SS_ENHMETAFILE:
  382. if (style & SS_CENTERIMAGE)
  383. return FALSE;
  384. return TRUE;
  385. break;
  386. default:
  387. return FALSE;
  388. }
  389. }
  390. // assume the others like clipping
  391. return TRUE;
  392. }
  393. void CResizableLayout::CalcNewChildPosition(const CResizableLayout::LayoutInfo& layout,
  394. const CRect &rectParent, CRect &rectChild, UINT& uFlags)
  395. {
  396. CWnd* pParent = GetResizableWnd();
  397. ::GetWindowRect(layout.hWnd, &rectChild);
  398. ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
  399. CRect rectNew;
  400. // calculate new top-left corner
  401. rectNew.left = layout.sizeMarginTL.cx + rectParent.Width() * layout.sizeTypeTL.cx / 100;
  402. rectNew.top = layout.sizeMarginTL.cy + rectParent.Height() * layout.sizeTypeTL.cy / 100;
  403. // calculate new bottom-right corner
  404. rectNew.right = layout.sizeMarginBR.cx + rectParent.Width() * layout.sizeTypeBR.cx / 100;
  405. rectNew.bottom = layout.sizeMarginBR.cy + rectParent.Height() * layout.sizeTypeBR.cy / 100;
  406. // adjust position, if client area has been scrolled
  407. rectNew.OffsetRect(rectParent.TopLeft());
  408. // get the refresh property
  409. BOOL bRefresh = layout.properties.bAskRefresh ?
  410. NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh;
  411. // set flags 
  412. uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
  413. if (bRefresh)
  414. uFlags |= SWP_NOCOPYBITS;
  415. if (rectNew.TopLeft() == rectChild.TopLeft())
  416. uFlags |= SWP_NOMOVE;
  417. if (rectNew.Size() == rectChild.Size())
  418. uFlags |= SWP_NOSIZE;
  419. // update rect
  420. rectChild = rectNew;
  421. }
  422. void CResizableLayout::InitResizeProperties(CResizableLayout::LayoutInfo &layout)
  423. {
  424. // check if custom window supports this library
  425. // (properties must be correctly set by the window)
  426. layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties);
  427. // default properties
  428. if (!layout.bMsgSupport)
  429. {
  430. // clipping property is assumed as static
  431. layout.properties.bAskClipping = FALSE;
  432. layout.properties.bCachedLikesClipping = LikesClipping(layout);
  433. // refresh property is assumed as dynamic
  434. layout.properties.bAskRefresh = TRUE;
  435. }
  436. }