-
Notifications
You must be signed in to change notification settings - Fork 1
/
ResizableLayout.cpp
882 lines (777 loc) · 28.3 KB
/
ResizableLayout.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
/////////////////////////////////////////////////////////////////////////////
//
// This file is part of ResizableLib
// http://sourceforge.net/projects/resizablelib
//
// Copyright (C) 2000-2004 by Paolo Messina
// http://www.geocities.com/ppescher - mailto:ppescher@hotmail.com
//
// The contents of this file are subject to the Artistic License (the "License").
// You may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
// http://www.opensource.org/licenses/artistic-license.html
//
// If you find this code useful, credits would be nice!
//
/////////////////////////////////////////////////////////////////////////////
/*!
* @file
* @brief Implementation of the CResizableLayout class.
*/
#include "stdafx.h"
#include "ResizableLayout.h"
#include "ResizableVersion.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
/*!
* @internal Constant used to detect clipping and refresh properties
*
* @note In August 2002 Platform SDK, some guy at MS thought it was time
* to add the missing symbol BS_TYPEMASK, but forgot its original
* meaning and so now he's telling us not to use that symbol because
* its value is likely to change in the future SDK releases, including
* all the BS_* style bits in the mask, not just the button's type
* as the symbol's name suggests.
* @n So now we're forced to define another symbol, great!
*/
#define _BS_TYPEMASK 0x0000000FL
/*!
* This function adds a new control to the layout manager and sets anchor
* points for its top-left and bottom-right corners.
*
* @param hWnd Window handle to the control to be added
* @param anchorTopLeft Anchor point for the top-left corner
* @param anchorBottomRight Anchor point for the bottom-right corner
*
* @remarks Overlapping controls, like group boxes and the controls inside,
* must be added from the outer controls to the inner ones, to let
* the clipping routines work correctly.
*
* @sa AddAnchorCallback RemoveAnchor
*/
void CResizableLayout::AddAnchor(HWND hWnd, ANCHOR anchorTopLeft, ANCHOR anchorBottomRight)
{
CWnd* pParent = GetResizableWnd();
// child window must be valid
ASSERT(::IsWindow(hWnd));
// must be child of parent window
ASSERT(::IsChild(pParent->GetSafeHwnd(), hWnd));
// get parent window's rect
CRect rectParent;
GetTotalClientRect(&rectParent);
// and child control's rect
CRect rectChild;
::GetWindowRect(hWnd, &rectChild);
::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
// adjust position, if client area has been scrolled
rectChild.OffsetRect(-rectParent.TopLeft());
// go calculate margins
CSize marginTopLeft, marginBottomRight;
// calculate margin for the top-left corner
marginTopLeft.cx = rectChild.left - rectParent.Width() * anchorTopLeft.cx / 100;
marginTopLeft.cy = rectChild.top - rectParent.Height() * anchorTopLeft.cy / 100;
// calculate margin for the bottom-right corner
marginBottomRight.cx = rectChild.right - rectParent.Width() * anchorBottomRight.cx / 100;
marginBottomRight.cy = rectChild.bottom - rectParent.Height() * anchorBottomRight.cy / 100;
// prepare the structure
LAYOUTINFO layout(hWnd, anchorTopLeft, marginTopLeft,
anchorBottomRight, marginBottomRight);
// get control's window class
GetClassName(hWnd, layout.sWndClass, MAX_PATH);
// initialize resize properties (overridable)
InitResizeProperties(layout);
// must not be already there!
// (this is probably due to a duplicate call to AddAnchor)
POSITION pos;
ASSERT(!m_mapLayout.Lookup(hWnd, pos));
// add to the list and the map
pos = m_listLayout.AddTail(layout);
m_mapLayout.SetAt(hWnd, pos);
}
/*!
* This function adds a placeholder to the layout manager, that will be
* dinamically set by a callback function whenever required.
*
* @return The return value is an integer used to distinguish between
* different placeholders in the callback implementation.
*
* @remarks You must override @ref ArrangeLayoutCallback to provide layout
* information.
*
* @sa AddAnchor ArrangeLayoutCallback ArrangeLayout
*/
UINT CResizableLayout::AddAnchorCallback()
{
// one callback control cannot rely upon another callback control's
// size and/or position (they're updated all together at the end)
// it can however use a non-callback control, calling GetAnchorPosition()
// add to the list
LAYOUTINFO layout;
layout.nCallbackID = m_listLayoutCB.GetCount() + 1;
m_listLayoutCB.AddTail(layout);
return layout.nCallbackID;
}
/*!
* This function is called for each placeholder added to the layout manager
* and must be overridden to provide the necessary layout information.
*
* @param layout Reference to a LAYOUTINFO structure to be filled with
* layout information for the specified placeholder.
* On input, nCallbackID is the identification number
* returned by AddAnchorCallback. On output, anchor points and
* the window handle must be set and valid.
*
* @return The return value is @c TRUE if the layout information has been
* provided successfully, @c FALSE to skip this placeholder.
*
* @remarks When implementing this function, unknown placeholders should be
* passed to the base class. Unhandled cases will fire an assertion
* in the debug version.
*
* @sa AddAnchorCallback ArrangeLayout LAYOUTINFO
*/
BOOL CResizableLayout::ArrangeLayoutCallback(LAYOUTINFO& layout) const
{
UNREFERENCED_PARAMETER(layout);
ASSERT(FALSE); // must be overridden, if callback is used
return FALSE; // no useful output data
}
/*!
* This function should be called in resizable window classes whenever the
* controls layout should be updated, usually after a resize operation.
*
* @remarks All the controls added to the layout are moved and resized at
* once for performace reasons, so all the controls are in their
* old position when AddAnchorCallback is called.
* To know where a control will be placed use GetAnchorPosition.
*
* @sa AddAnchor AddAnchorCallback ArrangeLayoutCallback GetAnchorPosition
*/
void CResizableLayout::ArrangeLayout() const
{
// common vars
UINT uFlags;
LAYOUTINFO layout;
CRect rectParent, rectChild;
int count = m_listLayout.GetCount();
int countCB = m_listLayoutCB.GetCount();
if (count + countCB == 0)
return;
// get parent window's rect
GetTotalClientRect(&rectParent);
// reposition child windows
HDWP hdwp = ::BeginDeferWindowPos(count + countCB);
POSITION pos = m_listLayout.GetHeadPosition();
while (pos != NULL)
{
// get layout info
layout = m_listLayout.GetNext(pos);
// calculate new child's position, size and flags for SetWindowPos
CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
// only if size or position changed
if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
{
hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
}
}
// for callback items you may use GetAnchorPosition to know the
// new position and size of a non-callback item after resizing
pos = m_listLayoutCB.GetHeadPosition();
while (pos != NULL)
{
// get layout info
layout = m_listLayoutCB.GetNext(pos);
// request layout data
if (!ArrangeLayoutCallback(layout))
continue;
// calculate new child's position, size and flags for SetWindowPos
CalcNewChildPosition(layout, rectParent, rectChild, uFlags);
// only if size or position changed
if ((uFlags & (SWP_NOMOVE|SWP_NOSIZE)) != (SWP_NOMOVE|SWP_NOSIZE))
{
hdwp = ::DeferWindowPos(hdwp, layout.hWnd, NULL, rectChild.left,
rectChild.top, rectChild.Width(), rectChild.Height(), uFlags);
}
}
// finally move all the windows at once
::EndDeferWindowPos(hdwp);
}
/*!
* @internal This function adds or removes a control window region
* to or from the specified clipping region, according to its layout
* properties.
*/
void CResizableLayout::ClipChildWindow(const LAYOUTINFO& layout,
CRgn* pRegion) const
{
// obtain window position
CRect rect;
::GetWindowRect(layout.hWnd, &rect);
#if (_WIN32_WINNT >= 0x0501)
//! @todo decide when to clip client only or non-client too (themes?)
//! (leave disabled meanwhile, until I find a good solution)
//! @note wizard97 with watermark bitmap and themes won't look good!
// if (real_WIN32_WINNT >= 0x501)
// ::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
#endif
::MapWindowPoints(NULL, GetResizableWnd()->m_hWnd, (LPPOINT)&rect, 2);
// use window region if any
CRgn rgn;
rgn.CreateRectRgn(0,0,0,0);
switch (::GetWindowRgn(layout.hWnd, rgn))
{
case COMPLEXREGION:
case SIMPLEREGION:
rgn.OffsetRgn(rect.TopLeft());
break;
default:
rgn.SetRectRgn(&rect);
}
// get the clipping property
BOOL bClipping = layout.properties.bAskClipping ?
LikesClipping(layout) : layout.properties.bCachedLikesClipping;
// modify region accordingly
if (bClipping)
pRegion->CombineRgn(pRegion, &rgn, RGN_DIFF);
else
pRegion->CombineRgn(pRegion, &rgn, RGN_OR);
}
/*!
* This function retrieves the clipping region for the current layout.
* It can be used to draw directly inside the region, without applying
* clipping as the ClipChildren function does.
*
* @param pRegion Pointer to a CRegion object that holds the
* calculated clipping region upon return
*
* @deprecated For anti-flickering ClipChildren should be preferred
* as it is more complete for platform compatibility.
* It will probably become a private function.
*/
void CResizableLayout::GetClippingRegion(CRgn* pRegion) const
{
CWnd* pWnd = GetResizableWnd();
// System's default clipping area is screen's size,
// not enough for max track size, for example:
// if screen is 1024 x 768 and resizing border is 4 pixels,
// maximized size is 1024+4*2=1032 x 768+4*2=776,
// but max track size is 4 pixels bigger 1036 x 780 (don't ask me why!)
// So, if you resize the window to maximum size, the last 4 pixels
// are clipped out by the default clipping region, that gets created
// as soon as you call clipping functions (my guess).
// reset clipping region to the whole client area
CRect rect;
pWnd->GetClientRect(&rect);
pRegion->CreateRectRgnIndirect(&rect);
// clip only anchored controls
LAYOUTINFO layout;
POSITION pos = m_listLayout.GetHeadPosition();
while (pos != NULL)
{
// get layout info
layout = m_listLayout.GetNext(pos);
if (::IsWindowVisible(layout.hWnd))
ClipChildWindow(layout, pRegion);
}
pos = m_listLayoutCB.GetHeadPosition();
while (pos != NULL)
{
// get layout info
layout = m_listLayoutCB.GetNext(pos);
// request data
if (!ArrangeLayoutCallback(layout))
continue;
if (::IsWindowVisible(layout.hWnd))
ClipChildWindow(layout, pRegion);
}
//! @todo Has XP changed this??? It doesn't seem correct anymore!
/*
// fix for RTL layouts (1 pixel of horz offset)
if (pWnd->GetExStyle() & WS_EX_LAYOUTRTL)
pRegion->OffsetRgn(-1,0);
*/
}
//! @internal @brief Implements GetAncestor(pWnd->GetSafeHwnd(), GA_ROOT)
inline CWnd* GetRootParentWnd(CWnd* pWnd)
{
// GetAncestor API not present, emulate
if (!(pWnd->GetStyle() & WS_CHILD))
return NULL;
while (pWnd->GetStyle() & WS_CHILD)
pWnd = pWnd->GetParent();
return pWnd;
}
/*!
* This function enables or restores clipping on the specified DC when
* appropriate. It should be called whenever drawing on the window client
* area to avoid flickering.
*
* @param pDC Pointer to the target device context
* @param bUndo Flag that specifies wether to restore the clipping region
*
* @return The return value is @c TRUE if the clipping region has been
* modified, @c FALSE if clipping was not necessary.
*
* @remarks For anti-flickering to work, you should wrap your
* @c WM_ERASEBKGND message handler inside a pair of calls to
* this function, with the last parameter set to @c TRUE first
* and to @c FALSE at the end.
*/
BOOL CResizableLayout::ClipChildren(CDC* pDC, BOOL bUndo)
{
#if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
// clipping not necessary when double-buffering enabled
if (real_WIN32_WINNT >= 0x0501)
{
CWnd *pWnd = GetRootParentWnd(GetResizableWnd());
if (pWnd == NULL)
pWnd = GetResizableWnd();
if (pWnd->GetExStyle() & WS_EX_COMPOSITED)
return FALSE;
}
#endif
HDC hDC = pDC->GetSafeHdc();
HWND hWnd = GetResizableWnd()->GetSafeHwnd();
m_nOldClipRgn = -1; // invalid region by default
// Some controls (such as transparent toolbars and standard controls
// with XP theme enabled) send a WM_ERASEBKGND msg to the parent
// to draw themselves, in which case we must not enable clipping.
// We check that the window associated with the DC is the
// resizable window and not a child control.
if (!bUndo && (hWnd == ::WindowFromDC(hDC)))
{
// save old DC clipping region
m_nOldClipRgn = ::GetClipRgn(hDC, m_hOldClipRgn);
// clip out supported child windows
CRgn rgnClip;
GetClippingRegion(&rgnClip);
::ExtSelectClipRgn(hDC, rgnClip, RGN_AND);
return TRUE;
}
// restore old clipping region, only if modified and valid
if (bUndo && m_nOldClipRgn >= 0)
{
if (m_nOldClipRgn == 1)
::SelectClipRgn(hDC, m_hOldClipRgn);
else
::SelectClipRgn(hDC, NULL);
return TRUE;
}
return FALSE;
}
/*!
* This function is used by this class, and should be used by derived
* classes too, in place of the standard GetClientRect. It can be useful
* for windows with scrollbars or expanding windows, to provide the true
* client area, including even those parts which are not visible.
*
* @param lpRect Pointer to the RECT structure that holds the result
*
* @remarks Override this function to provide the client area the class uses
* to perform layout calculations, both when adding controls and
* when rearranging the layout.
* @n The base implementation simply calls @c GetClientRect
*/
void CResizableLayout::GetTotalClientRect(LPRECT lpRect) const
{
GetResizableWnd()->GetClientRect(lpRect);
}
/*!
* This function is used to determine if a control needs to be painted when
* it is moved or resized by the layout manager.
*
* @param layout Reference to a @c LAYOUTINFO structure for the control
* @param rectOld Reference to a @c RECT structure that holds the control
* position and size before the layout update
* @param rectNew Reference to a @c RECT structure that holds the control
* position and size after the layout update
*
* @return The return value is @c TRUE if the control should be freshly
* painted after a layout update, @c FALSE if not necessary.
*
* @remarks The default implementation tries to identify windows that
* need refresh by their class name and window style.
* @n Override this function if you need a different behavior or if
* you have custom controls that fail to be identified.
*
* @sa LikesClipping InitResizeProperties
*/
BOOL CResizableLayout::NeedsRefresh(const LAYOUTINFO& layout,
const CRect& rectOld, const CRect& rectNew) const
{
if (layout.bMsgSupport)
{
REFRESHPROPERTY refresh;
refresh.rcOld = rectOld;
refresh.rcNew = rectNew;
if (Send_NeedsRefresh(layout.hWnd, &refresh))
return refresh.bNeedsRefresh;
}
int nDiffWidth = (rectNew.Width() - rectOld.Width());
int nDiffHeight = (rectNew.Height() - rectOld.Height());
// is the same size?
if (nDiffWidth == 0 && nDiffHeight == 0)
return FALSE;
// optimistic, no need to refresh
BOOL bRefresh = FALSE;
// window classes that need refresh when resized
if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
{
DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
switch (style & SS_TYPEMASK)
{
case SS_LEFT:
case SS_CENTER:
case SS_RIGHT:
// word-wrapped text
bRefresh = bRefresh || (nDiffWidth != 0);
// vertically centered text
if (style & SS_CENTERIMAGE)
bRefresh = bRefresh || (nDiffHeight != 0);
break;
case SS_LEFTNOWORDWRAP:
// text with ellipsis
if (style & SS_ELLIPSISMASK)
bRefresh = bRefresh || (nDiffWidth != 0);
// vertically centered text
if (style & SS_CENTERIMAGE)
bRefresh = bRefresh || (nDiffHeight != 0);
break;
case SS_ENHMETAFILE:
case SS_BITMAP:
case SS_ICON:
// images
case SS_BLACKFRAME:
case SS_GRAYFRAME:
case SS_WHITEFRAME:
case SS_ETCHEDFRAME:
// and frames
bRefresh = TRUE;
break;
}
return bRefresh;
}
// window classes that don't redraw client area correctly
// when the hor scroll pos changes due to a resizing
BOOL bHScroll = FALSE;
if (0 == lstrcmp(layout.sWndClass, WC_LISTBOX))
bHScroll = TRUE;
// fix for horizontally scrollable windows, if wider
if (bHScroll && (nDiffWidth > 0))
{
// get max scroll position
SCROLLINFO info;
info.cbSize = sizeof(SCROLLINFO);
info.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
if (::GetScrollInfo(layout.hWnd, SB_HORZ, &info))
{
// subtract the page size
info.nMax -= __max(info.nPage - 1, 0);
}
// resizing will cause the text to scroll on the right
// because the scrollbar is going beyond the right limit
if ((info.nMax > 0) && (info.nPos + nDiffWidth > info.nMax))
{
// needs repainting, due to horiz scrolling
bRefresh = TRUE;
}
}
return bRefresh;
}
/*!
* This function is used to determine if a control can be safely clipped
* out of the parent window client area when it is repainted, usually
* after a resize operation.
*
* @param layout Reference to a @c LAYOUTINFO structure for the control
*
* @return The return value is @c TRUE if clipping is supported by the
* control, @c FALSE otherwise.
*
* @remarks The default implementation tries to identify @a clippable
* windows by their class name and window style.
* @n Override this function if you need a different behavior or if
* you have custom controls that fail to be identified.
*
* @sa NeedsRefresh InitResizeProperties
*/
BOOL CResizableLayout::LikesClipping(const LAYOUTINFO& layout) const
{
if (layout.bMsgSupport)
{
CLIPPINGPROPERTY clipping;
if (Send_LikesClipping(layout.hWnd, &clipping))
return clipping.bLikesClipping;
}
DWORD style = ::GetWindowLong(layout.hWnd, GWL_STYLE);
// skip windows that wants background repainted
if (0 == lstrcmp(layout.sWndClass, WC_BUTTON))
{
CRect rect;
switch (style & _BS_TYPEMASK)
{
case BS_GROUPBOX:
return FALSE;
case BS_OWNERDRAW:
// ownerdraw buttons must return correct hittest code
// to notify their transparency to the system and this library
// or they could use the registered message (more reliable)
::GetWindowRect(layout.hWnd, &rect);
::SendMessage(layout.hWnd, WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
if ( HTTRANSPARENT == ::SendMessage(layout.hWnd,
WM_NCHITTEST, 0, MAKELPARAM(rect.left, rect.top)) )
return FALSE;
break;
}
return TRUE;
}
else if (0 == lstrcmp(layout.sWndClass, WC_STATIC))
{
switch (style & SS_TYPEMASK)
{
case SS_LEFT:
case SS_CENTER:
case SS_RIGHT:
case SS_LEFTNOWORDWRAP:
// text
case SS_BLACKRECT:
case SS_GRAYRECT:
case SS_WHITERECT:
// filled rects
case SS_ETCHEDHORZ:
case SS_ETCHEDVERT:
// etched lines
case SS_BITMAP:
// bitmaps
return TRUE;
break;
case SS_ICON:
case SS_ENHMETAFILE:
if (style & SS_CENTERIMAGE)
return FALSE;
return TRUE;
break;
default:
return FALSE;
}
}
// assume the others like clipping
return TRUE;
}
/*!
* @internal This function calculates the new size and position of a
* control in the layout and flags for @c SetWindowPos
*/
void CResizableLayout::CalcNewChildPosition(const LAYOUTINFO& layout,
const CRect &rectParent, CRect &rectChild, UINT& uFlags) const
{
CWnd* pParent = GetResizableWnd();
::GetWindowRect(layout.hWnd, &rectChild);
::MapWindowPoints(NULL, pParent->m_hWnd, (LPPOINT)&rectChild, 2);
CRect rectNew;
// calculate new top-left corner
rectNew.left = layout.marginTopLeft.cx + rectParent.Width() * layout.anchorTopLeft.cx / 100;
rectNew.top = layout.marginTopLeft.cy + rectParent.Height() * layout.anchorTopLeft.cy / 100;
// calculate new bottom-right corner
rectNew.right = layout.marginBottomRight.cx + rectParent.Width() * layout.anchorBottomRight.cx / 100;
rectNew.bottom = layout.marginBottomRight.cy + rectParent.Height() * layout.anchorBottomRight.cy / 100;
// adjust position, if client area has been scrolled
rectNew.OffsetRect(rectParent.TopLeft());
// get the refresh property
BOOL bRefresh = layout.properties.bAskRefresh ?
NeedsRefresh(layout, rectChild, rectNew) : layout.properties.bCachedNeedsRefresh;
// set flags
uFlags = SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREPOSITION;
if (bRefresh)
uFlags |= SWP_NOCOPYBITS;
if (rectNew.TopLeft() == rectChild.TopLeft())
uFlags |= SWP_NOMOVE;
if (rectNew.Size() == rectChild.Size())
uFlags |= SWP_NOSIZE;
// update rect
rectChild = rectNew;
}
/*!
* This function calculates the top, left, bottom, right margins for a
* given size of the specified control.
*
* @param hWnd Window handle to a control in the layout
* @param sizeChild Size of the control to use in calculations
* @param rectMargins Holds the calculated margins
*
* @return The return value is @c TRUE if successful, @c FALSE otherwise
*
* @remarks This function can be used to infer the parent window size
* from the size of one of its child controls.
* It is used to implement cascading of size constraints.
*/
BOOL CResizableLayout::GetAnchorMargins(HWND hWnd, const CSize &sizeChild, CRect &rectMargins) const
{
POSITION pos;
if (!m_mapLayout.Lookup(hWnd, pos))
return FALSE;
const LAYOUTINFO& layout = m_listLayout.GetAt(pos);
// augmented size, relative to anchor points
CSize size = sizeChild + layout.marginTopLeft - layout.marginBottomRight;
// percent of parent size occupied by this control
CSize percent(layout.anchorBottomRight.cx - layout.anchorTopLeft.cx,
layout.anchorBottomRight.cy - layout.anchorTopLeft.cy);
// calculate total margins
rectMargins.left = size.cx * layout.anchorTopLeft.cx / percent.cx + layout.marginTopLeft.cx;
rectMargins.top = size.cy * layout.anchorTopLeft.cy / percent.cy + layout.marginTopLeft.cy;
rectMargins.right = size.cx * (100 - layout.anchorBottomRight.cx) / percent.cx - layout.marginBottomRight.cx;
rectMargins.bottom = size.cy * (100 - layout.anchorBottomRight.cy) / percent.cy - layout.marginBottomRight.cy;
return TRUE;
}
/*!
* This function is used to set the initial resize properties of a control
* in the layout, that are stored in the @c properties member of the
* related @c LAYOUTINFO structure.
*
* @param layout Reference to the @c LAYOUTINFO structure to be set
*
* @remarks The various flags are used to specify whether the resize
* properties (clipping, refresh) can change at run-time, and a new
* call to the property querying functions is needed at every
* layout update, or they are static properties, and the cached
* value is used whenever necessary.
* @n The default implementation sends a registered message to the
* control, giving it the opportunity to specify its resize
* properties, which takes precedence if the message is supported.
* It then sets the @a clipping property as static, calling
* @c LikesClipping only once, and the @a refresh property as
* dynamic, causing @c NeedsRefresh to be called every time.
* @n This should be right for most situations, as the need for
* @a refresh usually depends on the size fo a control, while the
* support for @a clipping is usually linked to the specific type
* of control, which is unlikely to change at run-time, but you can
* still override this function if a different beahvior is needed.
*
* @sa LikesClipping NeedsRefresh LAYOUTINFO RESIZEPROPERTIES
*/
void CResizableLayout::InitResizeProperties(LAYOUTINFO &layout) const
{
// check if custom window supports this library
// (properties must be correctly set by the window)
layout.bMsgSupport = Send_QueryProperties(layout.hWnd, &layout.properties);
// default properties
if (!layout.bMsgSupport)
{
// clipping property is assumed as static
layout.properties.bAskClipping = FALSE;
layout.properties.bCachedLikesClipping = LikesClipping(layout);
// refresh property is assumed as dynamic
layout.properties.bAskRefresh = TRUE;
}
}
/*!
* This function modifies a window to enable resizing functionality.
* This affects the window style, size, system menu and appearance.
*
* @param lpCreateStruct Pointer to a @c CREATESTRUCT structure, usually
* passed by the system to the window procedure in a @c WM_CREATE
* or @c WM_NCCREATE
*
* @remarks The function is intended to be called only inside a @c WM_CREATE
* or @c WM_NCCREATE message handler.
*/
void CResizableLayout::MakeResizable(LPCREATESTRUCT lpCreateStruct)
{
if (lpCreateStruct->style & WS_CHILD)
return;
CWnd* pWnd = GetResizableWnd();
#if (_WIN32_WINNT >= 0x0501 && !defined(RSZLIB_NO_XP_DOUBLE_BUFFER))
// enable double-buffering on supported platforms
pWnd->ModifyStyleEx(0, WS_EX_COMPOSITED);
#endif
if (!(lpCreateStruct->style & WS_THICKFRAME))
{
// set resizable style
pWnd->ModifyStyle(DS_MODALFRAME, WS_THICKFRAME);
// keep client area
CRect rect(CPoint(lpCreateStruct->x, lpCreateStruct->y),
CSize(lpCreateStruct->cx, lpCreateStruct->cy));
pWnd->SendMessage(WM_NCCALCSIZE, FALSE, (LPARAM)&rect);
// adjust size to reflect new style
::AdjustWindowRectEx(&rect, pWnd->GetStyle(),
::IsMenu(pWnd->GetMenu()->GetSafeHmenu()), pWnd->GetExStyle());
pWnd->SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
SWP_NOSENDCHANGING|SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOREPOSITION);
// update dimensions
lpCreateStruct->cx = rect.Width();
lpCreateStruct->cy = rect.Height();
}
}
/*!
* This function should be called inside the parent window @c WM_NCCALCSIZE
* message handler to help eliminate flickering.
*
* @param bAfterDefault Flag that specifies wether the call is made before
* or after the default handler
* @param lpncsp Pointer to the @c NCCALCSIZE_PARAMS structure that is
* passed to the message handler
* @param lResult Reference to the result of the message handler.
* It contains the default handler result on input and the value to
* return from the window procedure on output.
*
* @remarks This function fixes the annoying flickering effect that is
* visible when resizing the top or left edges of the window
* (at least on a "left to right" Windows localized version).
*/
void CResizableLayout::HandleNcCalcSize(BOOL bAfterDefault, LPNCCALCSIZE_PARAMS lpncsp, LRESULT &lResult)
{
// prevent useless complication when size is not changing
// prevent recursion when resetting the window region (see below)
if ((lpncsp->lppos->flags & SWP_NOSIZE)
#if (_WIN32_WINNT >= 0x0501)
|| m_bNoRecursion
#endif
)
return;
if (!bAfterDefault)
{
// save a copy before default handler gets called
m_rectClientBefore = lpncsp->rgrc[2];
}
else // after default WM_NCCALCSIZE msg processing
{
if (lResult != 0)
{
// default handler already uses an advanced validation policy, give up
return;
}
// default calculated client rect
RECT &rectClientAfter = lpncsp->rgrc[0];
// intersection between old and new client area is to be preserved
// set source and destination rects to this intersection
RECT &rectPreserve = lpncsp->rgrc[1];
::IntersectRect(&rectPreserve, &rectClientAfter, &m_rectClientBefore);
lpncsp->rgrc[2] = rectPreserve;
lResult = WVR_VALIDRECTS;
// FIX: window region must be updated before the result of the
// WM_NCCALCSIZE message gets processed by the system,
// otherwise the old window region will clip the client
// area during the preservation process.
// This is especially evident on WinXP when the non-client
// area is rendered with Visual Styles enabled and the
// windows have a non rectangular region.
#if (_WIN32_WINNT >= 0x0501)
//! @todo change rt check to only if themed frame. what about custom wnd region?
if (real_WIN32_WINNT >= 0x0501)
{
CWnd* pWnd = GetResizableWnd();
DWORD dwStyle = pWnd->GetStyle();
if ((dwStyle & (WS_CAPTION|WS_MAXIMIZE)) == WS_CAPTION)
{
m_bNoRecursion = TRUE;
pWnd->SetWindowRgn(NULL, FALSE);
m_bNoRecursion = FALSE;
}
}
#endif
}
}