1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/notebook.cpp
3 // Purpose: implementation of wxNotebook
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
12 // For compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
21 #include "wx/notebook.h"
24 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
25 #include "wx/string.h"
30 #include "wx/dcclient.h"
31 #include "wx/dcmemory.h"
32 #include "wx/control.h"
36 #include "wx/imaglist.h"
37 #include "wx/sysopt.h"
39 #include "wx/msw/private.h"
40 #include "wx/msw/dc.h"
43 #include "wx/msw/winundef.h"
46 #include "wx/msw/uxtheme.h"
49 // ----------------------------------------------------------------------------
51 // ----------------------------------------------------------------------------
53 // check that the page index is valid
54 #define IS_VALID_PAGE(nPage) ((nPage) < GetPageCount())
56 // you can set USE_NOTEBOOK_ANTIFLICKER to 0 for desktop Windows versions too
57 // to disable code whih results in flicker-less notebook redrawing at the
58 // expense of some extra GDI resource consumption
60 // notebooks are never resized under CE anyhow
61 #define USE_NOTEBOOK_ANTIFLICKER 0
63 #define USE_NOTEBOOK_ANTIFLICKER 1
66 // ----------------------------------------------------------------------------
68 // ----------------------------------------------------------------------------
70 // This is a work-around for missing defines in gcc-2.95 headers
72 #define TCS_RIGHT 0x0002
76 #define TCS_VERTICAL 0x0080
80 #define TCS_BOTTOM TCS_RIGHT
83 // ----------------------------------------------------------------------------
85 // ----------------------------------------------------------------------------
87 #if USE_NOTEBOOK_ANTIFLICKER
89 // the pointer to standard spin button wnd proc
90 static WXFARPROC gs_wndprocNotebookSpinBtn
= (WXFARPROC
)NULL
;
92 // the pointer to standard tab control wnd proc
93 static WXFARPROC gs_wndprocNotebook
= (WXFARPROC
)NULL
;
95 LRESULT APIENTRY _EXPORT
wxNotebookWndProc(HWND hwnd
,
100 #endif // USE_NOTEBOOK_ANTIFLICKER
102 // ----------------------------------------------------------------------------
104 // ----------------------------------------------------------------------------
106 static bool HasTroubleWithNonTopTabs()
108 const int verComCtl32
= wxApp::GetComCtl32Version();
110 // 600 is XP, 616 is Vista -- and both have a problem with tabs not on top
111 // (but don't just test for >= 600 as Microsoft might decide to fix it in
112 // later versions, who knows...)
113 return verComCtl32
>= 600 && verComCtl32
<= 616;
116 // ----------------------------------------------------------------------------
118 // ----------------------------------------------------------------------------
120 BEGIN_EVENT_TABLE(wxNotebook
, wxBookCtrlBase
)
121 EVT_SIZE(wxNotebook::OnSize
)
122 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey
)
124 #if USE_NOTEBOOK_ANTIFLICKER
125 EVT_ERASE_BACKGROUND(wxNotebook::OnEraseBackground
)
126 EVT_PAINT(wxNotebook::OnPaint
)
127 #endif // USE_NOTEBOOK_ANTIFLICKER
130 // ============================================================================
132 // ============================================================================
134 // ----------------------------------------------------------------------------
135 // wxNotebook construction
136 // ----------------------------------------------------------------------------
138 // common part of all ctors
139 void wxNotebook::Init()
142 m_hbrBackground
= NULL
;
143 #endif // wxUSE_UXTHEME
145 #if USE_NOTEBOOK_ANTIFLICKER
146 m_hasSubclassedUpdown
= false;
147 m_doneUpdateHack
= false;
148 #endif // USE_NOTEBOOK_ANTIFLICKER
151 // default for dynamic class
152 wxNotebook::wxNotebook()
157 // the same arguments as for wxControl
158 wxNotebook::wxNotebook(wxWindow
*parent
,
163 const wxString
& name
)
167 Create(parent
, id
, pos
, size
, style
, name
);
171 bool wxNotebook::Create(wxWindow
*parent
,
176 const wxString
& name
)
178 if ( (style
& wxBK_ALIGN_MASK
) == wxBK_DEFAULT
)
180 #if defined(__POCKETPC__)
181 style
|= wxBK_BOTTOM
| wxNB_FLAT
;
188 // Not sure why, but without this style, there is no border
189 // around the notebook tabs.
190 if (style
& wxNB_FLAT
)
191 style
|= wxBORDER_SUNKEN
;
195 // ComCtl32 notebook tabs simply don't work unless they're on top if we
196 // have uxtheme, we can work around it later (after control creation), but
197 // if we have been compiled without uxtheme support, we have to clear those
199 if ( HasTroubleWithNonTopTabs() )
201 style
&= ~(wxBK_BOTTOM
| wxBK_LEFT
| wxBK_RIGHT
);
203 #endif //wxUSE_UXTHEME
205 #if defined(__WINE__) && wxUSE_UNICODE
206 LPCTSTR className
= L
"SysTabControl32";
208 LPCTSTR className
= WC_TABCONTROL
;
211 #if USE_NOTEBOOK_ANTIFLICKER
212 // SysTabCtl32 class has natively CS_HREDRAW and CS_VREDRAW enabled and it
213 // causes horrible flicker when resizing notebook, so get rid of it by
214 // using a class without these styles (but otherwise identical to it)
215 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE
) )
217 static ClassRegistrar s_clsNotebook
;
218 if ( !s_clsNotebook
.IsInitialized() )
220 // get a copy of standard class and modify it
223 if ( ::GetClassInfo(NULL
, WC_TABCONTROL
, &wc
) )
226 reinterpret_cast<WXFARPROC
>(wc
.lpfnWndProc
);
227 wc
.lpszClassName
= wxT("_wx_SysTabCtl32");
228 wc
.style
&= ~(CS_HREDRAW
| CS_VREDRAW
);
229 wc
.hInstance
= wxGetInstance();
230 wc
.lpfnWndProc
= wxNotebookWndProc
;
231 s_clsNotebook
.Register(wc
);
235 wxLogLastError(wxT("GetClassInfoEx(SysTabCtl32)"));
239 // use our custom class if available but fall back to the standard
240 // notebook if we failed to register it
241 if ( s_clsNotebook
.IsRegistered() )
243 // it's ok to use c_str() here as the static s_clsNotebook object
244 // has sufficiently long lifetime
245 className
= s_clsNotebook
.GetName().c_str();
248 #endif // USE_NOTEBOOK_ANTIFLICKER
250 if ( !CreateControl(parent
, id
, pos
, size
, style
| wxTAB_TRAVERSAL
,
251 wxDefaultValidator
, name
) )
254 if ( !MSWCreateControl(className
, wxEmptyString
, pos
, size
) )
257 // Inherit parent attributes and, unlike the default, also inherit the
258 // parent background colour in order to blend in with its background if
259 // it's set to a non-default value.
261 if ( parent
->InheritsBackgroundColour() && !UseBgCol() )
262 SetBackgroundColour(parent
->GetBackgroundColour());
265 if ( HasFlag(wxNB_NOPAGETHEME
) ||
266 wxSystemOptions::IsFalse(wxT("msw.notebook.themed-background")) )
268 SetBackgroundColour(GetThemeBackgroundColour());
270 else // use themed background by default
272 // create backing store
276 // comctl32.dll 6.0 doesn't support non-top tabs with visual styles (the
277 // control is simply not rendered correctly), so we disable themes
278 // if possible, otherwise we simply clear the styles.
279 if ( HasTroubleWithNonTopTabs() &&
280 (style
& (wxBK_BOTTOM
| wxBK_LEFT
| wxBK_RIGHT
)) )
282 // check if we use themes at all -- if we don't, we're still okay
283 if ( wxUxThemeEngine::GetIfActive() )
285 wxUxThemeEngine::GetIfActive()->SetWindowTheme(GetHwnd(), L
"", L
"");
287 // correct the background color for the new non-themed control
288 SetBackgroundColour(GetThemeBackgroundColour());
291 #endif // wxUSE_UXTHEME
293 // Undocumented hack to get flat notebook style
294 // In fact, we should probably only do this in some
295 // curcumstances, i.e. if we know we will have a border
296 // at the bottom (the tab control doesn't draw it itself)
297 #if defined(__POCKETPC__) || defined(__SMARTPHONE__)
298 if (HasFlag(wxNB_FLAT
))
300 SendMessage(GetHwnd(), CCM_SETVERSION
, COMCTL32_VERSION
, 0);
302 SetBackgroundColour(*wxWHITE
);
308 WXDWORD
wxNotebook::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
310 WXDWORD tabStyle
= wxControl::MSWGetStyle(style
, exstyle
);
312 tabStyle
|= WS_TABSTOP
| TCS_TABS
;
314 if ( style
& wxNB_MULTILINE
)
315 tabStyle
|= TCS_MULTILINE
;
316 if ( style
& wxNB_FIXEDWIDTH
)
317 tabStyle
|= TCS_FIXEDWIDTH
;
319 if ( style
& wxBK_BOTTOM
)
320 tabStyle
|= TCS_RIGHT
;
321 else if ( style
& wxBK_LEFT
)
322 tabStyle
|= TCS_VERTICAL
;
323 else if ( style
& wxBK_RIGHT
)
324 tabStyle
|= TCS_VERTICAL
| TCS_RIGHT
;
329 wxNotebook::~wxNotebook()
332 if ( m_hbrBackground
)
333 ::DeleteObject((HBRUSH
)m_hbrBackground
);
334 #endif // wxUSE_UXTHEME
337 // ----------------------------------------------------------------------------
338 // wxNotebook accessors
339 // ----------------------------------------------------------------------------
341 size_t wxNotebook::GetPageCount() const
344 wxASSERT( (int)m_pages
.Count() == TabCtrl_GetItemCount(GetHwnd()) );
346 return m_pages
.Count();
349 int wxNotebook::GetRowCount() const
351 return TabCtrl_GetRowCount(GetHwnd());
354 int wxNotebook::SetSelection(size_t nPage
)
356 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxNOT_FOUND
, wxT("notebook page out of range") );
358 if ( m_selection
== wxNOT_FOUND
|| nPage
!= (size_t)m_selection
)
360 if ( SendPageChangingEvent(nPage
) )
362 // program allows the page change
363 const int selectionOld
= m_selection
;
365 UpdateSelection(nPage
);
367 TabCtrl_SetCurSel(GetHwnd(), nPage
);
369 SendPageChangedEvent(selectionOld
, nPage
);
376 void wxNotebook::UpdateSelection(int selNew
)
378 if ( m_selection
!= wxNOT_FOUND
)
379 m_pages
[m_selection
]->Show(false);
381 if ( selNew
!= wxNOT_FOUND
)
383 wxNotebookPage
*pPage
= m_pages
[selNew
];
386 // In addition to showing the page, we also want to give focus to it to
387 // make it possible to work with it from keyboard easily. However there
388 // are two exceptions: first, don't touch the focus at all if the
389 // notebook itself is not currently shown.
390 if ( ::IsWindowVisible(GetHwnd()) )
392 // And second, don't give focus away if the tab control itself has
393 // it, as this is how the native property sheets behave: if you
394 // explicitly click on the tab label giving it focus, it will
395 // remain after switching to another page. But if the focus was
396 // inside the notebook page, it switches to the new page.
402 m_selection
= selNew
;
405 int wxNotebook::ChangeSelection(size_t nPage
)
407 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxNOT_FOUND
, wxT("notebook page out of range") );
409 const int selOld
= m_selection
;
411 if ( m_selection
== wxNOT_FOUND
|| nPage
!= (size_t)m_selection
)
413 TabCtrl_SetCurSel(GetHwnd(), nPage
);
415 UpdateSelection(nPage
);
421 bool wxNotebook::SetPageText(size_t nPage
, const wxString
& strText
)
423 wxCHECK_MSG( IS_VALID_PAGE(nPage
), false, wxT("notebook page out of range") );
426 tcItem
.mask
= TCIF_TEXT
;
427 tcItem
.pszText
= wxMSW_CONV_LPTSTR(strText
);
429 if ( !HasFlag(wxNB_MULTILINE
) )
430 return TabCtrl_SetItem(GetHwnd(), nPage
, &tcItem
) != 0;
432 // multiline - we need to set new page size if a line is added or removed
433 int rows
= GetRowCount();
434 bool ret
= TabCtrl_SetItem(GetHwnd(), nPage
, &tcItem
) != 0;
436 if ( ret
&& rows
!= GetRowCount() )
438 const wxRect r
= GetPageSize();
439 const size_t count
= m_pages
.Count();
440 for ( size_t page
= 0; page
< count
; page
++ )
441 m_pages
[page
]->SetSize(r
);
447 wxString
wxNotebook::GetPageText(size_t nPage
) const
449 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxEmptyString
, wxT("notebook page out of range") );
453 tcItem
.mask
= TCIF_TEXT
;
454 tcItem
.pszText
= buf
;
455 tcItem
.cchTextMax
= WXSIZEOF(buf
);
458 if ( TabCtrl_GetItem(GetHwnd(), nPage
, &tcItem
) )
459 str
= tcItem
.pszText
;
464 int wxNotebook::GetPageImage(size_t nPage
) const
466 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxNOT_FOUND
, wxT("notebook page out of range") );
469 tcItem
.mask
= TCIF_IMAGE
;
471 return TabCtrl_GetItem(GetHwnd(), nPage
, &tcItem
) ? tcItem
.iImage
475 bool wxNotebook::SetPageImage(size_t nPage
, int nImage
)
477 wxCHECK_MSG( IS_VALID_PAGE(nPage
), false, wxT("notebook page out of range") );
480 tcItem
.mask
= TCIF_IMAGE
;
481 tcItem
.iImage
= nImage
;
483 return TabCtrl_SetItem(GetHwnd(), nPage
, &tcItem
) != 0;
486 void wxNotebook::SetImageList(wxImageList
* imageList
)
488 wxNotebookBase::SetImageList(imageList
);
492 (void) TabCtrl_SetImageList(GetHwnd(), GetHimagelistOf(imageList
));
496 // ----------------------------------------------------------------------------
497 // wxNotebook size settings
498 // ----------------------------------------------------------------------------
500 wxRect
wxNotebook::GetPageSize() const
505 ::GetClientRect(GetHwnd(), &rc
);
507 // This check is to work around a bug in TabCtrl_AdjustRect which will
508 // cause a crash on win2k or on XP with themes disabled if either
509 // wxNB_MULTILINE is used or tabs are placed on a side, if the rectangle
512 // The value of 20 is chosen arbitrarily but seems to work
513 if ( rc
.right
> 20 && rc
.bottom
> 20 )
515 TabCtrl_AdjustRect(GetHwnd(), false, &rc
);
517 wxCopyRECTToRect(rc
, r
);
523 void wxNotebook::SetPageSize(const wxSize
& size
)
525 // transform the page size into the notebook size
532 TabCtrl_AdjustRect(GetHwnd(), true, &rc
);
535 SetSize(rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
538 void wxNotebook::SetPadding(const wxSize
& padding
)
540 TabCtrl_SetPadding(GetHwnd(), padding
.x
, padding
.y
);
543 // Windows-only at present. Also, you must use the wxNB_FIXEDWIDTH
545 void wxNotebook::SetTabSize(const wxSize
& sz
)
547 ::SendMessage(GetHwnd(), TCM_SETITEMSIZE
, 0, MAKELPARAM(sz
.x
, sz
.y
));
550 wxSize
wxNotebook::CalcSizeFromPage(const wxSize
& sizePage
) const
552 // we can't use TabCtrl_AdjustRect here because it only works for wxNB_TOP
553 wxSize sizeTotal
= sizePage
;
556 if ( GetPageCount() > 0 )
559 TabCtrl_GetItemRect(GetHwnd(), 0, &rect
);
560 tabSize
.x
= rect
.right
- rect
.left
;
561 tabSize
.y
= rect
.bottom
- rect
.top
;
564 const int rows
= GetRowCount();
566 // add an extra margin in both directions
567 const int MARGIN
= 8;
570 sizeTotal
.x
+= MARGIN
;
571 sizeTotal
.y
+= tabSize
.y
* rows
+ MARGIN
;
573 else // horizontal layout
575 sizeTotal
.x
+= tabSize
.x
* rows
+ MARGIN
;
576 sizeTotal
.y
+= MARGIN
;
582 void wxNotebook::AdjustPageSize(wxNotebookPage
*page
)
584 wxCHECK_RET( page
, wxT("NULL page in wxNotebook::AdjustPageSize") );
586 const wxRect r
= GetPageSize();
593 // ----------------------------------------------------------------------------
594 // wxNotebook operations
595 // ----------------------------------------------------------------------------
597 // remove one page from the notebook, without deleting
598 wxNotebookPage
*wxNotebook::DoRemovePage(size_t nPage
)
600 wxNotebookPage
*pageRemoved
= wxNotebookBase::DoRemovePage(nPage
);
604 // hide the removed page to maintain the invariant that only the
605 // selected page is visible and others are hidden:
606 pageRemoved
->Show(false);
608 TabCtrl_DeleteItem(GetHwnd(), nPage
);
610 if ( m_pages
.IsEmpty() )
612 // no selection any more, the notebook becamse empty
613 m_selection
= wxNOT_FOUND
;
615 else // notebook still not empty
617 int selNew
= TabCtrl_GetCurSel(GetHwnd());
618 if ( selNew
!= wxNOT_FOUND
)
620 // No selection change, just refresh the current selection.
621 // Because it could be that the slection index changed
622 // we need to update it.
623 // Note: this does not mean the selection it self changed.
624 m_selection
= selNew
;
625 m_pages
[m_selection
]->Refresh();
627 else if (int(nPage
) == m_selection
)
629 // The selection was deleted.
631 // Determine new selection.
632 if (m_selection
== int(GetPageCount()))
633 selNew
= m_selection
- 1;
635 selNew
= m_selection
;
637 // m_selection must be always valid so reset it before calling
639 m_selection
= wxNOT_FOUND
;
640 SetSelection(selNew
);
644 wxFAIL
; // Windows did not behave ok.
652 bool wxNotebook::DeleteAllPages()
654 size_t nPageCount
= GetPageCount();
656 for ( nPage
= 0; nPage
< nPageCount
; nPage
++ )
657 delete m_pages
[nPage
];
661 TabCtrl_DeleteAllItems(GetHwnd());
663 m_selection
= wxNOT_FOUND
;
665 InvalidateBestSize();
669 // same as AddPage() but does it at given position
670 bool wxNotebook::InsertPage(size_t nPage
,
671 wxNotebookPage
*pPage
,
672 const wxString
& strText
,
676 wxCHECK_MSG( pPage
!= NULL
, false, wxT("NULL page in wxNotebook::InsertPage") );
677 wxCHECK_MSG( IS_VALID_PAGE(nPage
) || nPage
== GetPageCount(), false,
678 wxT("invalid index in wxNotebook::InsertPage") );
680 wxASSERT_MSG( pPage
->GetParent() == this,
681 wxT("notebook pages must have notebook as parent") );
683 // add a new tab to the control
684 // ----------------------------
686 // init all fields to 0
688 wxZeroMemory(tcItem
);
690 // set the image, if any
693 tcItem
.mask
|= TCIF_IMAGE
;
694 tcItem
.iImage
= imageId
;
698 if ( !strText
.empty() )
700 tcItem
.mask
|= TCIF_TEXT
;
701 tcItem
.pszText
= wxMSW_CONV_LPTSTR(strText
);
704 // hide the page: unless it is selected, it shouldn't be shown (and if it
705 // is selected it will be shown later)
706 HWND hwnd
= GetWinHwnd(pPage
);
707 SetWindowLong(hwnd
, GWL_STYLE
, GetWindowLong(hwnd
, GWL_STYLE
) & ~WS_VISIBLE
);
709 // this updates internal flag too -- otherwise it would get out of sync
710 // with the real state
714 // fit the notebook page to the tab control's display area: this should be
715 // done before adding it to the notebook or TabCtrl_InsertItem() will
716 // change the notebooks size itself!
717 AdjustPageSize(pPage
);
719 // finally do insert it
720 if ( TabCtrl_InsertItem(GetHwnd(), nPage
, &tcItem
) == -1 )
722 wxLogError(wxT("Can't create the notebook page '%s'."), strText
.c_str());
727 // need to update the bg brush when the first page is added
728 // so the first panel gets the correct themed background
729 if ( m_pages
.empty() )
733 #endif // wxUSE_UXTHEME
736 // succeeded: save the pointer to the page
737 m_pages
.Insert(pPage
, nPage
);
739 // we may need to adjust the size again if the notebook size changed:
740 // normally this only happens for the first page we add (the tabs which
741 // hadn't been there before are now shown) but for a multiline notebook it
742 // can happen for any page at all as a new row could have been started
743 if ( m_pages
.GetCount() == 1 || HasFlag(wxNB_MULTILINE
) )
745 AdjustPageSize(pPage
);
747 // Additionally, force the layout of the notebook itself by posting a
748 // size event to it. If we don't do it, notebooks with pages on the
749 // left or the right side may fail to account for the fact that they
750 // are now big enough to fit all all of their pages on one row and
751 // still reserve space for the second row of tabs, see #1792.
752 const wxSize s
= GetSize();
753 ::PostMessage(GetHwnd(), WM_SIZE
, SIZE_RESTORED
, MAKELPARAM(s
.x
, s
.y
));
756 // now deal with the selection
757 // ---------------------------
759 // if the inserted page is before the selected one, we must update the
760 // index of the selected page
761 if ( int(nPage
) <= m_selection
)
763 // one extra page added
767 DoSetSelectionAfterInsertion(nPage
, bSelect
);
769 InvalidateBestSize();
774 int wxNotebook::HitTest(const wxPoint
& pt
, long *flags
) const
776 TC_HITTESTINFO hitTestInfo
;
777 hitTestInfo
.pt
.x
= pt
.x
;
778 hitTestInfo
.pt
.y
= pt
.y
;
779 int item
= TabCtrl_HitTest(GetHwnd(), &hitTestInfo
);
785 if ((hitTestInfo
.flags
& TCHT_NOWHERE
) == TCHT_NOWHERE
)
786 *flags
|= wxBK_HITTEST_NOWHERE
;
787 if ((hitTestInfo
.flags
& TCHT_ONITEM
) == TCHT_ONITEM
)
788 *flags
|= wxBK_HITTEST_ONITEM
;
789 if ((hitTestInfo
.flags
& TCHT_ONITEMICON
) == TCHT_ONITEMICON
)
790 *flags
|= wxBK_HITTEST_ONICON
;
791 if ((hitTestInfo
.flags
& TCHT_ONITEMLABEL
) == TCHT_ONITEMLABEL
)
792 *flags
|= wxBK_HITTEST_ONLABEL
;
793 if ( item
== wxNOT_FOUND
&& GetPageSize().Contains(pt
) )
794 *flags
|= wxBK_HITTEST_ONPAGE
;
800 // ----------------------------------------------------------------------------
801 // flicker-less notebook redraw
802 // ----------------------------------------------------------------------------
804 #if USE_NOTEBOOK_ANTIFLICKER
806 // wnd proc for the spin button
807 LRESULT APIENTRY _EXPORT
wxNotebookSpinBtnWndProc(HWND hwnd
,
812 if ( message
== WM_ERASEBKGND
)
815 return ::CallWindowProc(CASTWNDPROC gs_wndprocNotebookSpinBtn
,
816 hwnd
, message
, wParam
, lParam
);
819 LRESULT APIENTRY _EXPORT
wxNotebookWndProc(HWND hwnd
,
824 return ::CallWindowProc(CASTWNDPROC gs_wndprocNotebook
,
825 hwnd
, message
, wParam
, lParam
);
828 void wxNotebook::OnEraseBackground(wxEraseEvent
& WXUNUSED(event
))
833 void wxNotebook::OnPaint(wxPaintEvent
& WXUNUSED(event
))
838 ::GetClientRect(GetHwnd(), &rc
);
839 wxBitmap
bmp(rc
.right
, rc
.bottom
);
840 memdc
.SelectObject(bmp
);
842 const wxLayoutDirection dir
= dc
.GetLayoutDirection();
843 memdc
.SetLayoutDirection(dir
);
845 const HDC hdc
= GetHdcOf(memdc
);
847 // The drawing logic of the native tab control is absolutely impenetrable
848 // but observation shows that in the current Windows versions (XP and 7),
849 // the tab control always erases its entire background in its window proc
850 // when the tabs are top-aligned but does not do it when the tabs are in
851 // any other position.
853 // This means that we can't rely on our background colour being used for
854 // the blank area in the tab row because this doesn't work in the default
855 // top-aligned case, hence the hack with ExtFloodFill() below. But it also
856 // means that we still do need to erase the DC to account for the other
859 // Moreover, just in case some very old or very new (or even future,
860 // although it seems unlikely that this is ever going to change by now)
861 // version of Windows didn't do it like this, do both things in all cases
862 // instead of optimizing away the one of them which doesn't do anything for
863 // the effectively used tab orientation -- better safe than fast.
865 // Notice that we use our own background here, not the background used for
866 // the pages, because the tab row background must blend with the parent and
867 // so the background colour inherited from it (if any) must be used.
868 AutoHBRUSH
hbr(wxColourToRGB(GetBackgroundColour()));
870 ::FillRect(hdc
, &rc
, hbr
);
872 MSWDefWindowProc(WM_PAINT
, (WPARAM
)hdc
, 0);
874 // At least for the top-aligned tabs, our background colour was overwritten
875 // and so we now replace the default background with our colour. This is
876 // horribly inefficient, of course, but seems to be the only way to do it.
879 SelectInHDC
selectBrush(hdc
, hbr
);
881 // Find the point which must contain the default background colour:
882 // this is a hack, of course, but using this point "close" to the
883 // corner seems to work fine in practice.
887 switch ( GetWindowStyle() & wxBK_ALIGN_MASK
)
910 ::ExtFloodFill(hdc
, x
, y
, ::GetSysColor(COLOR_BTNFACE
), FLOODFILLSURFACE
);
913 // For some reason in RTL mode, source offset has to be -1, otherwise the
914 // right border (physical) remains unpainted.
915 const wxCoord ofs
= dir
== wxLayout_RightToLeft
? -1 : 0;
916 dc
.Blit(ofs
, 0, rc
.right
, rc
.bottom
, &memdc
, ofs
, 0);
919 #endif // USE_NOTEBOOK_ANTIFLICKER
921 // ----------------------------------------------------------------------------
922 // wxNotebook callbacks
923 // ----------------------------------------------------------------------------
925 void wxNotebook::OnSize(wxSizeEvent
& event
)
927 if ( GetPageCount() == 0 )
929 // Prevents droppings on resize, but does cause some flicker
930 // when there are no pages.
938 // Without this, we can sometimes get droppings at the edges
939 // of a notebook, for example a notebook in a splitter window.
940 // This needs to be reconciled with the RefreshRect calls
941 // at the end of this function, which weren't enough to prevent
944 wxSize sz
= GetClientSize();
946 // Refresh right side
947 wxRect
rect(sz
.x
-4, 0, 4, sz
.y
);
950 // Refresh bottom side
951 rect
= wxRect(0, sz
.y
-4, sz
.x
, 4);
955 rect
= wxRect(0, 0, 4, sz
.y
);
958 #endif // !__WXWINCE__
960 // fit all the notebook pages to the tab control's display area
963 rc
.left
= rc
.top
= 0;
964 GetSize((int *)&rc
.right
, (int *)&rc
.bottom
);
966 // save the total size, we'll use it below
967 int widthNbook
= rc
.right
- rc
.left
,
968 heightNbook
= rc
.bottom
- rc
.top
;
970 // there seems to be a bug in the implementation of TabCtrl_AdjustRect(): it
971 // returns completely false values for multiline tab controls after the tabs
972 // are added but before getting the first WM_SIZE (off by ~50 pixels, see
974 // http://sf.net/tracker/index.php?func=detail&aid=645323&group_id=9863&atid=109863
976 // and the only work around I could find was this ugly hack... without it
977 // simply toggling the "multiline" checkbox in the notebook sample resulted
978 // in a noticeable page displacement
979 if ( HasFlag(wxNB_MULTILINE
) )
981 // avoid an infinite recursion: we get another notification too!
982 static bool s_isInOnSize
= false;
987 SendMessage(GetHwnd(), WM_SIZE
, SIZE_RESTORED
,
988 MAKELPARAM(rc
.right
, rc
.bottom
));
989 s_isInOnSize
= false;
992 // The best size depends on the number of rows of tabs, which can
993 // change when the notepad is resized.
994 InvalidateBestSize();
998 // background bitmap size has changed, update the brush using it too
1000 #endif // wxUSE_UXTHEME
1002 TabCtrl_AdjustRect(GetHwnd(), false, &rc
);
1004 int width
= rc
.right
- rc
.left
,
1005 height
= rc
.bottom
- rc
.top
;
1006 size_t nCount
= m_pages
.Count();
1007 for ( size_t nPage
= 0; nPage
< nCount
; nPage
++ ) {
1008 wxNotebookPage
*pPage
= m_pages
[nPage
];
1009 pPage
->SetSize(rc
.left
, rc
.top
, width
, height
);
1013 // unless we had already repainted everything, we now need to refresh
1014 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE
) )
1016 // invalidate areas not covered by pages
1017 RefreshRect(wxRect(0, 0, widthNbook
, rc
.top
), false);
1018 RefreshRect(wxRect(0, rc
.top
, rc
.left
, height
), false);
1019 RefreshRect(wxRect(0, rc
.bottom
, widthNbook
, heightNbook
- rc
.bottom
),
1021 RefreshRect(wxRect(rc
.right
, rc
.top
, widthNbook
- rc
.right
, height
),
1025 #if USE_NOTEBOOK_ANTIFLICKER
1026 // subclass the spin control used by the notebook to scroll pages to
1027 // prevent it from flickering on resize
1028 if ( !m_hasSubclassedUpdown
)
1030 // iterate over all child windows to find spin button
1031 for ( HWND child
= ::GetWindow(GetHwnd(), GW_CHILD
);
1033 child
= ::GetWindow(child
, GW_HWNDNEXT
) )
1035 wxWindow
*childWindow
= wxFindWinFromHandle((WXHWND
)child
);
1037 // see if it exists, if no wxWindow found then assume it's the spin
1041 // subclass the spin button to override WM_ERASEBKGND
1042 if ( !gs_wndprocNotebookSpinBtn
)
1043 gs_wndprocNotebookSpinBtn
= (WXFARPROC
)wxGetWindowProc(child
);
1045 wxSetWindowProc(child
, wxNotebookSpinBtnWndProc
);
1046 m_hasSubclassedUpdown
= true;
1052 // Probably because of the games we play above to avoid flicker sometimes
1053 // the text controls inside notebook pages are not shown correctly (they
1054 // don't have their borders) when the notebook is shown for the first time.
1055 // It's not really clear why does this happen and maybe the bug is in
1056 // wxTextCtrl itself and not here but updating the page when it's about to
1057 // be shown doesn't cost much and works around the problem so do it here
1059 if ( !m_doneUpdateHack
&& IsShownOnScreen() )
1061 m_doneUpdateHack
= true;
1062 wxWindow
* const page
= GetCurrentPage();
1066 #endif // USE_NOTEBOOK_ANTIFLICKER
1071 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent
& event
)
1073 if ( event
.IsWindowChange() ) {
1075 AdvanceSelection(event
.GetDirection());
1078 // we get this event in 3 cases
1080 // a) one of our pages might have generated it because the user TABbed
1081 // out from it in which case we should propagate the event upwards and
1082 // our parent will take care of setting the focus to prev/next sibling
1086 // b) the parent panel wants to give the focus to us so that we
1087 // forward it to our selected page. We can't deal with this in
1088 // OnSetFocus() because we don't know which direction the focus came
1089 // from in this case and so can't choose between setting the focus to
1090 // first or last panel child
1094 // c) we ourselves (see MSWTranslateMessage) generated the event
1096 wxWindow
* const parent
= GetParent();
1098 // the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
1099 const bool isFromParent
= event
.GetEventObject() == (wxObject
*) parent
;
1100 const bool isFromSelf
= event
.GetEventObject() == (wxObject
*) this;
1101 const bool isForward
= event
.GetDirection();
1103 if ( isFromSelf
&& !isForward
)
1105 // focus is currently on notebook tab and should leave
1106 // it backwards (Shift-TAB)
1107 event
.SetCurrentFocus(this);
1108 parent
->HandleWindowEvent(event
);
1110 else if ( isFromParent
|| isFromSelf
)
1112 // no, it doesn't come from child, case (b) or (c): forward to a
1113 // page but only if entering notebook page (i.e. direction is
1114 // backwards (Shift-TAB) comething from out-of-notebook, or
1115 // direction is forward (TAB) from ourselves),
1116 if ( m_selection
!= wxNOT_FOUND
&&
1117 (!event
.GetDirection() || isFromSelf
) )
1119 // so that the page knows that the event comes from it's parent
1120 // and is being propagated downwards
1121 event
.SetEventObject(this);
1123 wxWindow
*page
= m_pages
[m_selection
];
1124 if ( !page
->HandleWindowEvent(event
) )
1128 //else: page manages focus inside it itself
1130 else // otherwise set the focus to the notebook itself
1137 // it comes from our child, case (a), pass to the parent, but only
1138 // if the direction is forwards. Otherwise set the focus to the
1139 // notebook itself. The notebook is always the 'first' control of a
1147 event
.SetCurrentFocus(this);
1148 parent
->HandleWindowEvent(event
);
1156 bool wxNotebook::DoDrawBackground(WXHDC hDC
, wxWindow
*child
)
1158 wxUxThemeHandle
theme(child
? child
: this, L
"TAB");
1162 // get the notebook client rect (we're not interested in drawing tabs
1164 wxRect r
= GetPageSize();
1169 wxCopyRectToRECT(r
, rc
);
1171 // map rect to the coords of the window we're drawing in
1173 ::MapWindowPoints(GetHwnd(), GetHwndOf(child
), (POINT
*)&rc
, 2);
1175 // we have the content area (page size), but we need to draw all of the
1176 // background for it to be aligned correctly
1177 wxUxThemeEngine::Get()->GetThemeBackgroundExtent
1186 wxUxThemeEngine::Get()->DrawThemeBackground
1199 WXHBRUSH
wxNotebook::QueryBgBitmap()
1201 wxRect r
= GetPageSize();
1205 WindowHDC
hDC(GetHwnd());
1206 MemoryHDC
hDCMem(hDC
);
1207 CompatibleBitmap
hBmp(hDC
, r
.x
+ r
.width
, r
.y
+ r
.height
);
1209 SelectInHDC
selectBmp(hDCMem
, hBmp
);
1211 if ( !DoDrawBackground((WXHDC
)(HDC
)hDCMem
) )
1214 return (WXHBRUSH
)::CreatePatternBrush(hBmp
);
1217 void wxNotebook::UpdateBgBrush()
1219 if ( m_hbrBackground
)
1220 ::DeleteObject((HBRUSH
)m_hbrBackground
);
1222 if ( !m_hasBgCol
&& wxUxThemeEngine::GetIfActive() )
1224 m_hbrBackground
= QueryBgBitmap();
1226 else // no themes or we've got user-defined solid colour
1228 m_hbrBackground
= NULL
;
1232 bool wxNotebook::MSWPrintChild(WXHDC hDC
, wxWindow
*child
)
1234 // solid background colour overrides themed background drawing
1235 if ( !UseBgCol() && DoDrawBackground(hDC
, child
) )
1238 // If we're using a solid colour (for example if we've switched off
1239 // theming for this notebook), paint it
1242 wxRect r
= GetPageSize();
1247 wxCopyRectToRECT(r
, rc
);
1249 // map rect to the coords of the window we're drawing in
1251 ::MapWindowPoints(GetHwnd(), GetHwndOf(child
), (POINT
*)&rc
, 2);
1253 wxBrush
brush(GetBackgroundColour());
1254 HBRUSH hbr
= GetHbrushOf(brush
);
1256 ::FillRect((HDC
) hDC
, &rc
, hbr
);
1261 return wxNotebookBase::MSWPrintChild(hDC
, child
);
1264 #endif // wxUSE_UXTHEME
1266 // Windows only: attempts to get colour for UX theme page background
1267 wxColour
wxNotebook::GetThemeBackgroundColour() const
1270 if (wxUxThemeEngine::Get())
1272 wxUxThemeHandle
hTheme((wxNotebook
*) this, L
"TAB");
1275 // This is total guesswork.
1276 // See PlatformSDK\Include\Tmschema.h for values.
1277 // JACS: can also use 9 (TABP_PANE)
1278 COLORREF themeColor
;
1279 bool success
= (S_OK
== wxUxThemeEngine::Get()->GetThemeColor(
1283 3821 /* FILLCOLORHINT */,
1286 return GetBackgroundColour();
1289 [DS] Workaround for WindowBlinds:
1290 Some themes return a near black theme color using FILLCOLORHINT,
1291 this makes notebook pages have an ugly black background and makes
1292 text (usually black) unreadable. Retry again with FILLCOLOR.
1294 This workaround potentially breaks appearance of some themes,
1295 but in practice it already fixes some themes.
1297 if (themeColor
== 1)
1299 wxUxThemeEngine::Get()->GetThemeColor(
1303 3802 /* FILLCOLOR */,
1307 wxColour colour
= wxRGBToColour(themeColor
);
1309 // Under Vista, the tab background colour is reported incorrectly.
1310 // So for the default theme at least, hard-code the colour to something
1311 // that will blend in.
1313 static int s_AeroStatus
= -1;
1314 if (s_AeroStatus
== -1)
1316 WCHAR szwThemeFile
[1024];
1317 WCHAR szwThemeColor
[256];
1318 if (S_OK
== wxUxThemeEngine::Get()->GetCurrentThemeName(szwThemeFile
, 1024, szwThemeColor
, 256, NULL
, 0))
1320 wxString
themeFile(szwThemeFile
), themeColor(szwThemeColor
);
1321 if (themeFile
.Find(wxT("Aero")) != -1 && themeColor
== wxT("NormalColor"))
1330 if (s_AeroStatus
== 1)
1331 colour
= wxColour(255, 255, 255);
1336 #endif // wxUSE_UXTHEME
1338 return GetBackgroundColour();
1341 // ----------------------------------------------------------------------------
1342 // wxNotebook base class virtuals
1343 // ----------------------------------------------------------------------------
1345 #if wxUSE_CONSTRAINTS
1347 // override these 2 functions to do nothing: everything is done in OnSize
1349 void wxNotebook::SetConstraintSizes(bool WXUNUSED(recurse
))
1351 // don't set the sizes of the pages - their correct size is not yet known
1352 wxControl::SetConstraintSizes(false);
1355 bool wxNotebook::DoPhase(int WXUNUSED(nPhase
))
1360 #endif // wxUSE_CONSTRAINTS
1362 // ----------------------------------------------------------------------------
1363 // wxNotebook Windows message handlers
1364 // ----------------------------------------------------------------------------
1366 bool wxNotebook::MSWOnScroll(int orientation
, WXWORD nSBCode
,
1367 WXWORD pos
, WXHWND control
)
1369 // don't generate EVT_SCROLLWIN events for the WM_SCROLLs coming from the
1374 return wxNotebookBase::MSWOnScroll(orientation
, nSBCode
, pos
, control
);
1377 bool wxNotebook::MSWOnNotify(int idCtrl
, WXLPARAM lParam
, WXLPARAM
* result
)
1379 wxBookCtrlEvent
event(wxEVT_NULL
, m_windowId
);
1381 NMHDR
* hdr
= (NMHDR
*)lParam
;
1382 switch ( hdr
->code
) {
1384 event
.SetEventType(wxEVT_NOTEBOOK_PAGE_CHANGED
);
1387 case TCN_SELCHANGING
:
1388 event
.SetEventType(wxEVT_NOTEBOOK_PAGE_CHANGING
);
1392 return wxControl::MSWOnNotify(idCtrl
, lParam
, result
);
1395 event
.SetSelection(TabCtrl_GetCurSel(GetHwnd()));
1396 event
.SetOldSelection(m_selection
);
1397 event
.SetEventObject(this);
1398 event
.SetInt(idCtrl
);
1400 // Change the selection before generating the event as its handler should
1401 // already see the new page selected.
1402 if ( hdr
->code
== TCN_SELCHANGE
)
1403 UpdateSelection(event
.GetSelection());
1405 bool processed
= HandleWindowEvent(event
);
1406 *result
= !event
.IsAllowed();
1410 #endif // wxUSE_NOTEBOOK