1 /////////////////////////////////////////////////////////////////////////////
\r
3 // This file is part of ResizableLib
\r
4 // http://sourceforge.net/projects/resizablelib
\r
6 // Copyright (C) 2000-2004 by Paolo Messina
\r
7 // http://www.geocities.com/ppescher - mailto:ppescher@hotmail.com
\r
9 // The contents of this file are subject to the Artistic License (the "License").
\r
10 // You may not use this file except in compliance with the License.
\r
11 // You may obtain a copy of the License at:
\r
12 // http://www.opensource.org/licenses/artistic-license.html
\r
14 // If you find this code useful, credits would be nice!
\r
16 /////////////////////////////////////////////////////////////////////////////
\r
20 * @brief Implementation of the CResizableLayout class.
\r
24 #include "ResizableLayout.h"
\r
25 #include "ResizableVersion.h"
\r
29 static char THIS_FILE[]=__FILE__;
\r
30 #define new DEBUG_NEW
\r
34 * @internal Constant used to detect clipping and refresh properties
\r
36 * @note In August 2002 Platform SDK, some guy at MS thought it was time
\r
37 * to add the missing symbol BS_TYPEMASK, but forgot its original
\r
38 * meaning and so now he's telling us not to use that symbol because
\r
39 * its value is likely to change in the future SDK releases, including
\r
40 * all the BS_* style bits in the mask, not just the button's type
\r
41 * as the symbol's name suggests.
\r
42 * @n So now we're forced to define another symbol, great!
\r
44 #define _BS_TYPEMASK 0x0000000FL
\r
47 * This function adds a new control to the layout manager and sets anchor
\r
48 * points for its top-left and bottom-right corners.
\r
50 * @param hWnd Window handle to the control to be added
\r
51 * @param anchorTopLeft Anchor point for the top-left corner
\r
52 * @param anchorBottomRight Anchor point for the bottom-right corner
\r
54 * @remarks Overlapping controls, like group boxes and the controls inside,
\r
55 * must be added from the outer controls to the inner ones, to let
\r
56 * the clipping routines work correctly.
\r
58 * @sa AddAnchorCallback RemoveAnchor
\r
60 void CResizableLayout::AddAnchor(HWND hWnd, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight)
\r
62 CWnd* pParent = GetResizableWnd();
\r
64 // child window must be valid
\r
65 ASSERT(::IsWindow(hWnd));
\r
66 // must be child of parent window
\r
67 ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));
\r
69 // get parent window's rect
\r
71 GetTotalClientRect(&rectParent);
\r
72 // and child control's rect
\r
74 ::GetWindowRect(hWnd, &rectChild);
\r
75 ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
\r
77 // adjust position, if client area has been scrolled
\r
78 rectChild.OffsetRect(-rectParent.TopLeft());
\r
80 // go calculate margins
\r
81 CSize marginTopLeft, marginBottomRight;
\r
83 // calculate margin for the top-left corner
\r
85 marginTopLeft.cx = rectChild.left - rectParent.Width() * anchorTopLeft.cx / 100;
\r
86 marginTopLeft.cy = rectChild.top - rectParent.Height() * anchorTopLeft.cy / 100;
\r
88 // calculate margin for the bottom-right corner
\r
90 marginBottomRight.cx = rectChild.right - rectParent.Width() * anchorBottomRight.cx / 100;
\r
91 marginBottomRight.cy = rectChild.bottom - rectParent.Height() * anchorBottomRight.cy / 100;
\r
93 // prepare the structure
\r
94 LAYOUTINFO layout(hWnd, anchorTopLeft, marginTopLeft,
\r
95 anchorBottomRight, marginBottomRight);
\r
97 // get control's window class
\r
98 GetClassName(hWnd, layout.sWndClass, MAX_PATH);
\r
100 // initialize resize properties (overridable)
\r
101 InitResizeProperties(layout);
\r
103 // must not be already there!
\r
104 // (this is probably due to a duplicate call to AddAnchor)
\r
106 ASSERT(!m_mapLayout.Lookup(hWnd, pos));
\r
108 // add to the list and the map
\r
109 pos = m_listLayout.AddTail(layout);
\r
110 m_mapLayout.SetAt(hWnd, pos);
\r
114 * This function adds a placeholder to the layout manager, that will be
\r
115 * dinamically set by a callback function whenever required.
\r
117 * @return The return value is an integer used to distinguish between
\r
118 * different placeholders in the callback implementation.
\r
120 * @remarks You must override @ref ArrangeLayoutCallback to provide layout
\r
123 * @sa AddAnchor ArrangeLayoutCallback ArrangeLayout
\r
125 UINT_PTR CResizableLayout::AddAnchorCallback()
\r
127 // one callback control cannot rely upon another callback control's
\r
128 // size and/or position (they're updated all together at the end)
\r
129 // it can however use a non-callback control, calling GetAnchorPosition()
\r
133 layout.nCallbackID = m_listLayoutCB.GetCount() + 1;
\r
134 m_listLayoutCB.AddTail(layout);
\r
135 return layout.nCallbackID;
\r
139 * This function is called for each placeholder added to the layout manager
\r
140 * and must be overridden to provide the necessary layout information.
\r
142 * @param layout Reference to a LAYOUTINFO structure to be filled with
\r
143 * layout information for the specified placeholder.
\r
144 * On input, nCallbackID is the identification number
\r
145 * returned by AddAnchorCallback. On output, anchor points and
\r
146 * the window handle must be set and valid.
\r
148 * @return The return value is @c TRUE if the layout information has been
\r
149 * provided successfully, @c FALSE to skip this placeholder.
\r
151 * @remarks When implementing this function, unknown placeholders should be
\r
152 * passed to the base class. Unhandled cases will fire an assertion
\r
153 * in the debug version.
\r
155 * @sa AddAnchorCallback ArrangeLayout LAYOUTINFO
\r
157 BOOL CResizableLayout::ArrangeLayoutCallback(LAYOUTINFO& layout) const
\r
159 UNREFERENCED_PARAMETER(layout);
\r
161 ASSERT(FALSE); // must be overridden, if callback is used
\r
163 return FALSE; // no useful output data
\r
167 * This function should be called in resizable window classes whenever the
\r
168 * controls layout should be updated, usually after a resize operation.
\r
170 * @remarks All the controls added to the layout are moved and resized at
\r
171 * once for performace reasons, so all the controls are in their
\r
172 * old position when AddAnchorCallback is called.
\r
173 * To know where a control will be placed use GetAnchorPosition.
\r
175 * @sa AddAnchor AddAnchorCallback ArrangeLayoutCallback GetAnchorPosition
\r
177 void CResizableLayout::ArrangeLayout() const
\r
182 CRect rectParent, rectChild;
\r
183 INT_PTR count = m_listLayout.GetCount();
\r
184 INT_PTR countCB = m_listLayoutCB.GetCount();
\r
186 if (count + countCB == 0)
\r
189 // get parent window's rect
\r
190 GetTotalClientRect(&rectParent);
\r
192 // reposition child windows
\r
193 HDWP hdwp = ::BeginDeferWindowPos (static_cast<int>(count + countCB));
\r
195 POSITION pos = m_listLayout.GetHeadPosition();
\r
196 while (pos != NULL)
\r
199 layout = m_listLayout.GetNext(pos);
\r
201 // calculate new child's position, size and flags for SetWindowPos
\r
202 CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
\r
204 // only if size or position changed
\r
205 if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
\r
207 hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
\r
208 rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
\r
212 // for callback items you may use GetAnchorPosition to know the
\r
213 // new position and size of a non-callback item after resizing
\r
215 pos = m_listLayoutCB.GetHeadPosition();
\r
216 while (pos != NULL)
\r
219 layout = m_listLayoutCB.GetNext(pos);
\r
220 // request layout data
\r
221 if (!ArrangeLayoutCallback(layout))
\r
224 // calculate new child's position, size and flags for SetWindowPos
\r
225 CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
\r
227 // only if size or position changed
\r
228 if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
\r
230 hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
\r
231 rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
\r
235 // finally move all the windows at once
\r
236 ::EndDeferWindowPos(hdwp);
\r
240 * @internal This function adds or removes a control window region
\r
241 * to or from the specified clipping region, according to its layout
\r
244 void CResizableLayout::ClipChildWindow(const LAYOUTINFO& layout,
\r
245 CRgn* pRegion) const
\r
247 // obtain window position
\r
249 ::GetWindowRect(layout.hWnd, &rect);
\r
250 #if (_WIN32_WINNT >= 0x0501)
\r
251 //! @todo decide when to clip client only or non-client too (themes?)
\r
252 //! (leave disabled meanwhile, until I find a good solution)
\r
253 //! @note wizard97 with watermark bitmap and themes won't look good!
\r
254 // if (real_WIN32_WINNT >= 0x501)
\r
255 // ::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
\r
257 ::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2);
\r
259 // use window region if any
\r
261 rgn.CreateRectRgn(0,0,0,0);
\r
262 switch (::GetWindowRgn(layout.hWnd, rgn))
\r
264 case COMPLEXREGION:
\r
266 rgn.OffsetRgn(rect.TopLeft());
\r
270 rgn.SetRectRgn(&rect);
\r
273 // get the clipping property
\r
274 BOOL bClipping = layout.properties.bAskClipping ?
\r
275 LikesClipping(layout) : layout.properties.bCachedLikesClipping;
\r
277 // modify region accordingly
\r
279 pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF);
\r
281 pRegion->CombineRgn(pRegion, &rgn, RGN_OR);
\r
285 * This function retrieves the clipping region for the current layout.
\r
286 * It can be used to draw directly inside the region, without applying
\r
287 * clipping as the ClipChildren function does.
\r
289 * @param pRegion Pointer to a CRegion object that holds the
\r
290 * calculated clipping region upon return
\r
292 * @deprecated For anti-flickering ClipChildren should be preferred
\r
293 * as it is more complete for platform compatibility.
\r
294 * It will probably become a private function.
\r
296 void CResizableLayout::GetClippingRegion(CRgn* pRegion) const
\r
298 CWnd* pWnd = GetResizableWnd();
\r
300 // System's default clipping area is screen's size,
\r
301 // not enough for max track size, for example:
\r
302 // if screen is 1024 x 768 and resizing border is 4 pixels,
\r
303 // maximized size is 1024+4*2=1032 x 768+4*2=776,
\r
304 // but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
\r
305 // So, if you resize the window to maximum size, the last 4 pixels
\r
306 // are clipped out by the default clipping region, that gets created
\r
307 // as soon as you call clipping functions (my guess).
\r
309 // reset clipping region to the whole client area
\r
311 pWnd->GetClientRect(&rect);
\r
312 pRegion->CreateRectRgnIndirect(&rect);
\r
314 // clip only anchored controls
\r
316 POSITION pos = m_listLayout.GetHeadPosition();
\r
317 while (pos != NULL)
\r
320 layout = m_listLayout.GetNext(pos);
\r
322 if (::IsWindowVisible(layout.hWnd))
\r
323 ClipChildWindow(layout, pRegion);
\r
325 pos = m_listLayoutCB.GetHeadPosition();
\r
326 while (pos != NULL)
\r
329 layout = m_listLayoutCB.GetNext(pos);
\r
331 if (!ArrangeLayoutCallback(layout))
\r
334 if (::IsWindowVisible(layout.hWnd))
\r
335 ClipChildWindow(layout, pRegion);
\r
337 //! @todo Has XP changed this??? It doesn't seem correct anymore!
\r
339 // fix for RTL layouts (1 pixel of horz offset)
\r
340 if (pWnd->GetExStyle() & WS_EX_LAYOUTRTL)
\r
341 pRegion->OffsetRgn(-1,0);
\r
345 //! @internal @brief Implements GetAncestor(pWnd->GetSafeHwnd(), GA_ROOT)
\r
346 inline CWnd* GetRootParentWnd(CWnd* pWnd)
\r
348 // GetAncestor API not present, emulate
\r
349 if (!(pWnd->GetStyle() & WS_CHILD))
\r
351 while (pWnd->GetStyle() & WS_CHILD)
\r
352 pWnd = pWnd->GetParent();
\r
357 * This function enables or restores clipping on the specified DC when
\r
358 * appropriate. It should be called whenever drawing on the window client
\r
359 * area to avoid flickering.
\r
361 * @param pDC Pointer to the target device context
\r
362 * @param bUndo Flag that specifies wether to restore the clipping region
\r
364 * @return The return value is @c TRUE if the clipping region has been
\r
365 * modified, @c FALSE if clipping was not necessary.
\r
367 * @remarks For anti-flickering to work, you should wrap your
\r
368 * @c WM_ERASEBKGND message handler inside a pair of calls to
\r
369 * this function, with the last parameter set to @c TRUE first
\r
370 * and to @c FALSE at the end.
\r
372 BOOL CResizableLayout::ClipChildren(CDC* pDC, BOOL bUndo)
\r
374 #if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
\r
375 // clipping not necessary when double-buffering enabled
\r
376 if (real_WIN32_WINNT >= 0x0501)
\r
378 CWnd *pWnd = GetRootParentWnd(GetResizableWnd());
\r
380 pWnd = GetResizableWnd();
\r
381 if (pWnd->GetExStyle() & WS_EX_COMPOSITED)
\r
386 HDC hDC = pDC->GetSafeHdc();
\r
387 HWND hWnd = GetResizableWnd()->GetSafeHwnd();
\r
389 m_nOldClipRgn = -1; // invalid region by default
\r
391 // Some controls (such as transparent toolbars and standard controls
\r
392 // with XP theme enabled) send a WM_ERASEBKGND msg to the parent
\r
393 // to draw themselves, in which case we must not enable clipping.
\r
395 // We check that the window associated with the DC is the
\r
396 // resizable window and not a child control.
\r
398 if (!bUndo && (hWnd == ::WindowFromDC(hDC)))
\r
400 // save old DC clipping region
\r
401 m_nOldClipRgn = ::GetClipRgn(hDC, m_hOldClipRgn);
\r
403 // clip out supported child windows
\r
405 GetClippingRegion(&rgnClip);
\r
406 ::ExtSelectClipRgn(hDC, rgnClip, RGN_AND);
\r
411 // restore old clipping region, only if modified and valid
\r
412 if (bUndo && m_nOldClipRgn >= 0)
\r
414 if (m_nOldClipRgn == 1)
\r
415 ::SelectClipRgn(hDC, m_hOldClipRgn);
\r
417 ::SelectClipRgn(hDC, NULL);
\r
426 * This function is used by this class, and should be used by derived
\r
427 * classes too, in place of the standard GetClientRect. It can be useful
\r
428 * for windows with scrollbars or expanding windows, to provide the true
\r
429 * client area, including even those parts which are not visible.
\r
431 * @param lpRect Pointer to the RECT structure that holds the result
\r
433 * @remarks Override this function to provide the client area the class uses
\r
434 * to perform layout calculations, both when adding controls and
\r
435 * when rearranging the layout.
\r
436 * @n The base implementation simply calls @c GetClientRect
\r
438 void CResizableLayout::GetTotalClientRect(LPRECT lpRect) const
\r
440 GetResizableWnd()->GetClientRect(lpRect);
\r
444 * This function is used to determine if a control needs to be painted when
\r
445 * it is moved or resized by the layout manager.
\r
447 * @param layout Reference to a @c LAYOUTINFO structure for the control
\r
448 * @param rectOld Reference to a @c RECT structure that holds the control
\r
449 * position and size before the layout update
\r
450 * @param rectNew Reference to a @c RECT structure that holds the control
\r
451 * position and size after the layout update
\r
453 * @return The return value is @c TRUE if the control should be freshly
\r
454 * painted after a layout update, @c FALSE if not necessary.
\r
456 * @remarks The default implementation tries to identify windows that
\r
457 * need refresh by their class name and window style.
\r
458 * @n Override this function if you need a different behavior or if
\r
459 * you have custom controls that fail to be identified.
\r
461 * @sa LikesClipping InitResizeProperties
\r
463 BOOL CResizableLayout::NeedsRefresh(const LAYOUTINFO& layout,
\r
464 const CRect& rectOld, const CRect& rectNew) const
\r
466 if (layout.bMsgSupport)
\r
468 REFRESHPROPERTY refresh;
\r
469 refresh.rcOld = rectOld;
\r
470 refresh.rcNew = rectNew;
\r
471 if (Send_NeedsRefresh(layout.hWnd, &refresh))
\r
472 return refresh.bNeedsRefresh;
\r
475 int nDiffWidth = (rectNew.Width() - rectOld.Width());
\r
476 int nDiffHeight = (rectNew.Height() - rectOld.Height());
\r
478 // is the same size?
\r
479 if (nDiffWidth == 0 && nDiffHeight == 0)
\r
482 // optimistic, no need to refresh
\r
483 BOOL bRefresh = FALSE;
\r
485 // window classes that need refresh when resized
\r
486 if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
\r
488 DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
\r
490 switch (style & SS_TYPEMASK)
\r
495 // word-wrapped text
\r
496 bRefresh = bRefresh || (nDiffWidth != 0);
\r
497 // vertically centered text
\r
498 if (style & SS_CENTERIMAGE)
\r
499 bRefresh = bRefresh || (nDiffHeight != 0);
\r
502 case SS_LEFTNOWORDWRAP:
\r
503 // text with ellipsis
\r
504 if (style & SS_ELLIPSISMASK)
\r
505 bRefresh = bRefresh || (nDiffWidth != 0);
\r
506 // vertically centered text
\r
507 if (style & SS_CENTERIMAGE)
\r
508 bRefresh = bRefresh || (nDiffHeight != 0);
\r
511 case SS_ENHMETAFILE:
\r
515 case SS_BLACKFRAME:
\r
517 case SS_WHITEFRAME:
\r
518 case SS_ETCHEDFRAME:
\r
526 // window classes that don't redraw client area correctly
\r
527 // when the hor scroll pos changes due to a resizing
\r
528 BOOL bHScroll = FALSE;
\r
529 if (0 == lstrcmp(layout.sWndClass, WC_LISTBOX))
\r
532 // fix for horizontally scrollable windows, if wider
\r
533 if (bHScroll && (nDiffWidth > 0))
\r
535 // get max scroll position
\r
537 info.cbSize = sizeof(SCROLLINFO);
\r
538 info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
\r
539 if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info))
\r
541 // subtract the page size
\r
542 info.nMax -= __max(info.nPage - 1, 0);
\r
545 // resizing will cause the text to scroll on the right
\r
546 // because the scrollbar is going beyond the right limit
\r
547 if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax))
\r
549 // needs repainting, due to horiz scrolling
\r
558 * This function is used to determine if a control can be safely clipped
\r
559 * out of the parent window client area when it is repainted, usually
\r
560 * after a resize operation.
\r
562 * @param layout Reference to a @c LAYOUTINFO structure for the control
\r
564 * @return The return value is @c TRUE if clipping is supported by the
\r
565 * control, @c FALSE otherwise.
\r
567 * @remarks The default implementation tries to identify @a clippable
\r
568 * windows by their class name and window style.
\r
569 * @n Override this function if you need a different behavior or if
\r
570 * you have custom controls that fail to be identified.
\r
572 * @sa NeedsRefresh InitResizeProperties
\r
574 BOOL CResizableLayout::LikesClipping(const LAYOUTINFO& layout) const
\r
576 if (layout.bMsgSupport)
\r
578 CLIPPINGPROPERTY clipping;
\r
579 if (Send_LikesClipping(layout.hWnd, &clipping))
\r
580 return clipping.bLikesClipping;
\r
583 DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
\r
585 // skip windows that wants background repainted
\r
586 if (0 == lstrcmp(layout.sWndClass, WC_BUTTON))
\r
589 switch (style & _BS_TYPEMASK)
\r
595 // ownerdraw buttons must return correct hittest code
\r
596 // to notify their transparency to the system and this library
\r
597 // or they could use the registered message (more reliable)
\r
598 ::GetWindowRect(layout.hWnd, &rect);
\r
599 ::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
\r
600 if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
\r
601 WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
\r
607 else if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
\r
609 switch (style & SS_TYPEMASK)
\r
614 case SS_LEFTNOWORDWRAP:
\r
620 case SS_ETCHEDHORZ:
\r
621 case SS_ETCHEDVERT:
\r
629 case SS_ENHMETAFILE:
\r
630 if (style & SS_CENTERIMAGE)
\r
640 // assume the others like clipping
\r
646 * @internal This function calculates the new size and position of a
\r
647 * control in the layout and flags for @c SetWindowPos
\r
649 void CResizableLayout::CalcNewChildPosition(const LAYOUTINFO& layout,
\r
650 const CRect &rectParent, CRect &rectChild, UINT& uFlags) const
\r
652 CWnd* pParent = GetResizableWnd();
\r
654 ::GetWindowRect(layout.hWnd, &rectChild);
\r
655 ::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
\r
659 // calculate new top-left corner
\r
660 rectNew.left = layout.marginTopLeft.cx + rectParent.Width() * layout.anchorTopLeft.cx / 100;
\r
661 rectNew.top = layout.marginTopLeft.cy + rectParent.Height() * layout.anchorTopLeft.cy / 100;
\r
663 // calculate new bottom-right corner
\r
664 rectNew.right = layout.marginBottomRight.cx + rectParent.Width() * layout.anchorBottomRight.cx / 100;
\r
665 rectNew.bottom = layout.marginBottomRight.cy + rectParent.Height() * layout.anchorBottomRight.cy / 100;
\r
667 // adjust position, if client area has been scrolled
\r
668 rectNew.OffsetRect(rectParent.TopLeft());
\r
670 // get the refresh property
\r
671 BOOL bRefresh = layout.properties.bAskRefresh ?
\r
672 NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh;
\r
675 uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
\r
677 uFlags |= SWP_NOCOPYBITS;
\r
678 if (rectNew.TopLeft() == rectChild.TopLeft())
\r
679 uFlags |= SWP_NOMOVE;
\r
680 if (rectNew.Size() == rectChild.Size())
\r
681 uFlags |= SWP_NOSIZE;
\r
684 rectChild = rectNew;
\r
688 * This function calculates the top, left, bottom, right margins for a
\r
689 * given size of the specified control.
\r
691 * @param hWnd Window handle to a control in the layout
\r
692 * @param sizeChild Size of the control to use in calculations
\r
693 * @param rectMargins Holds the calculated margins
\r
695 * @return The return value is @c TRUE if successful, @c FALSE otherwise
\r
697 * @remarks This function can be used to infer the parent window size
\r
698 * from the size of one of its child controls.
\r
699 * It is used to implement cascading of size constraints.
\r
701 BOOL CResizableLayout::GetAnchorMargins(HWND hWnd, const CSize &sizeChild, CRect &rectMargins) const
\r
704 if (!m_mapLayout.Lookup(hWnd, pos))
\r
707 const LAYOUTINFO& layout = m_listLayout.GetAt(pos);
\r
709 // augmented size, relative to anchor points
\r
710 CSize size = sizeChild + layout.marginTopLeft - layout.marginBottomRight;
\r
712 // percent of parent size occupied by this control
\r
713 CSize percent(layout.anchorBottomRight.cx - layout.anchorTopLeft.cx,
\r
714 layout.anchorBottomRight.cy - layout.anchorTopLeft.cy);
\r
716 // calculate total margins
\r
717 rectMargins.left = size.cx * layout.anchorTopLeft.cx / percent.cx + layout.marginTopLeft.cx;
\r
718 rectMargins.top = size.cy * layout.anchorTopLeft.cy / percent.cy + layout.marginTopLeft.cy;
\r
719 rectMargins.right = size.cx * (100 - layout.anchorBottomRight.cx) / percent.cx - layout.marginBottomRight.cx;
\r
720 rectMargins.bottom = size.cy * (100 - layout.anchorBottomRight.cy) / percent.cy - layout.marginBottomRight.cy;
\r
726 * This function is used to set the initial resize properties of a control
\r
727 * in the layout, that are stored in the @c properties member of the
\r
728 * related @c LAYOUTINFO structure.
\r
730 * @param layout Reference to the @c LAYOUTINFO structure to be set
\r
732 * @remarks The various flags are used to specify whether the resize
\r
733 * properties (clipping, refresh) can change at run-time, and a new
\r
734 * call to the property querying functions is needed at every
\r
735 * layout update, or they are static properties, and the cached
\r
736 * value is used whenever necessary.
\r
737 * @n The default implementation sends a registered message to the
\r
738 * control, giving it the opportunity to specify its resize
\r
739 * properties, which takes precedence if the message is supported.
\r
740 * It then sets the @a clipping property as static, calling
\r
741 * @c LikesClipping only once, and the @a refresh property as
\r
742 * dynamic, causing @c NeedsRefresh to be called every time.
\r
743 * @n This should be right for most situations, as the need for
\r
744 * @a refresh usually depends on the size fo a control, while the
\r
745 * support for @a clipping is usually linked to the specific type
\r
746 * of control, which is unlikely to change at run-time, but you can
\r
747 * still override this function if a different beahvior is needed.
\r
749 * @sa LikesClipping NeedsRefresh LAYOUTINFO RESIZEPROPERTIES
\r
751 void CResizableLayout::InitResizeProperties(LAYOUTINFO &layout) const
\r
753 // check if custom window supports this library
\r
754 // (properties must be correctly set by the window)
\r
755 layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties);
\r
757 // default properties
\r
758 if (!layout.bMsgSupport)
\r
760 // clipping property is assumed as static
\r
761 layout.properties.bAskClipping = FALSE;
\r
762 layout.properties.bCachedLikesClipping = LikesClipping(layout);
\r
763 // refresh property is assumed as dynamic
\r
764 layout.properties.bAskRefresh = TRUE;
\r
769 * This function modifies a window to enable resizing functionality.
\r
770 * This affects the window style, size, system menu and appearance.
\r
772 * @param lpCreateStruct Pointer to a @c CREATESTRUCT structure, usually
\r
773 * passed by the system to the window procedure in a @c WM_CREATE
\r
774 * or @c WM_NCCREATE
\r
776 * @remarks The function is intended to be called only inside a @c WM_CREATE
\r
777 * or @c WM_NCCREATE message handler.
\r
779 void CResizableLayout::MakeResizable(LPCREATESTRUCT lpCreateStruct)
\r
781 if (lpCreateStruct->style & WS_CHILD)
\r
784 CWnd* pWnd = GetResizableWnd();
\r
786 #if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
\r
787 // enable double-buffering on supported platforms
\r
788 pWnd->ModifyStyleEx(0, WS_EX_COMPOSITED);
\r
791 if (!(lpCreateStruct->style & WS_THICKFRAME))
\r
793 // set resizable style
\r
794 pWnd->ModifyStyle(DS_MODALFRAME, WS_THICKFRAME);
\r
795 // keep client area
\r
796 CRect rect(CPoint(lpCreateStruct->x, lpCreateStruct->y),
\r
797 CSize(lpCreateStruct->cx, lpCreateStruct->cy));
\r
798 pWnd->SendMessage(WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
\r
799 // adjust size to reflect new style
\r
800 ::AdjustWindowRectEx(&rect, pWnd->GetStyle(),
\r
801 ::IsMenu(pWnd->GetMenu()->GetSafeHmenu()), pWnd->GetExStyle());
\r
802 pWnd->SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
\r
803 SWP_NOSENDCHANGING|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREPOSITION);
\r
804 // update dimensions
\r
805 lpCreateStruct->cx = rect.Width();
\r
806 lpCreateStruct->cy = rect.Height();
\r
811 * This function should be called inside the parent window @c WM_NCCALCSIZE
\r
812 * message handler to help eliminate flickering.
\r
814 * @param bAfterDefault Flag that specifies wether the call is made before
\r
815 * or after the default handler
\r
816 * @param lpncsp Pointer to the @c NCCALCSIZE_PARAMS structure that is
\r
817 * passed to the message handler
\r
818 * @param lResult Reference to the result of the message handler.
\r
819 * It contains the default handler result on input and the value to
\r
820 * return from the window procedure on output.
\r
822 * @remarks This function fixes the annoying flickering effect that is
\r
823 * visible when resizing the top or left edges of the window
\r
824 * (at least on a "left to right" Windows localized version).
\r
826 void CResizableLayout::HandleNcCalcSize(BOOL bAfterDefault, LPNCCALCSIZE_PARAMS lpncsp, LRESULT &lResult)
\r
828 // prevent useless complication when size is not changing
\r
829 // prevent recursion when resetting the window region (see below)
\r
830 if ((lpncsp->lppos->flags & SWP_NOSIZE)
\r
831 #if (_WIN32_WINNT >= 0x0501)
\r
837 if (!bAfterDefault)
\r
839 // save a copy before default handler gets called
\r
840 m_rectClientBefore = lpncsp->rgrc[2];
\r
842 else // after default WM_NCCALCSIZE msg processing
\r
846 // default handler already uses an advanced validation policy, give up
\r
849 // default calculated client rect
\r
850 RECT &rectClientAfter = lpncsp->rgrc[0];
\r
852 // intersection between old and new client area is to be preserved
\r
853 // set source and destination rects to this intersection
\r
854 RECT &rectPreserve = lpncsp->rgrc[1];
\r
855 ::IntersectRect(&rectPreserve, &rectClientAfter, &m_rectClientBefore);
\r
856 lpncsp->rgrc[2] = rectPreserve;
\r
858 lResult = WVR_VALIDRECTS;
\r
860 // FIX: window region must be updated before the result of the
\r
861 // WM_NCCALCSIZE message gets processed by the system,
\r
862 // otherwise the old window region will clip the client
\r
863 // area during the preservation process.
\r
864 // This is especially evident on WinXP when the non-client
\r
865 // area is rendered with Visual Styles enabled and the
\r
866 // windows have a non rectangular region.
\r
867 #if (_WIN32_WINNT >= 0x0501)
\r
868 //! @todo change rt check to only if themed frame. what about custom wnd region?
\r
869 if (real_WIN32_WINNT >= 0x0501)
\r
871 CWnd* pWnd = GetResizableWnd();
\r
872 DWORD dwStyle = pWnd->GetStyle();
\r
873 if ((dwStyle & (WS_CAPTION|WS_MAXIMIZE)) == WS_CAPTION)
\r
875 m_bNoRecursion = TRUE;
\r
876 pWnd->SetWindowRgn(NULL, FALSE);
\r
877 m_bNoRecursion = FALSE;
\r