1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/notebook.cpp
3 // Purpose: implementation of wxNotebook
4 // Author: Vadim Zeitlin
7 // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
20 #include "wx/notebook.h"
23 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
24 #include "wx/string.h"
29 #include "wx/dcclient.h"
30 #include "wx/dcmemory.h"
31 #include "wx/control.h"
35 #include "wx/imaglist.h"
36 #include "wx/sysopt.h"
38 #include "wx/msw/private.h"
39 #include "wx/msw/dc.h"
42 #include "wx/msw/winundef.h"
45 #include "wx/msw/uxtheme.h"
48 // ----------------------------------------------------------------------------
50 // ----------------------------------------------------------------------------
52 // check that the page index is valid
53 #define IS_VALID_PAGE(nPage) ((nPage) < GetPageCount())
55 // you can set USE_NOTEBOOK_ANTIFLICKER to 0 for desktop Windows versions too
56 // to disable code whih results in flicker-less notebook redrawing at the
57 // expense of some extra GDI resource consumption
59 // notebooks are never resized under CE anyhow
60 #define USE_NOTEBOOK_ANTIFLICKER 0
62 #define USE_NOTEBOOK_ANTIFLICKER 1
65 // ----------------------------------------------------------------------------
67 // ----------------------------------------------------------------------------
69 // This is a work-around for missing defines in gcc-2.95 headers
71 #define TCS_RIGHT 0x0002
75 #define TCS_VERTICAL 0x0080
79 #define TCS_BOTTOM TCS_RIGHT
82 // ----------------------------------------------------------------------------
84 // ----------------------------------------------------------------------------
86 #if USE_NOTEBOOK_ANTIFLICKER
88 // the pointer to standard spin button wnd proc
89 static WXFARPROC gs_wndprocNotebookSpinBtn
= (WXFARPROC
)NULL
;
91 // the pointer to standard tab control wnd proc
92 static WXFARPROC gs_wndprocNotebook
= (WXFARPROC
)NULL
;
94 LRESULT APIENTRY _EXPORT
wxNotebookWndProc(HWND hwnd
,
99 #endif // USE_NOTEBOOK_ANTIFLICKER
101 // ----------------------------------------------------------------------------
103 // ----------------------------------------------------------------------------
105 static bool HasTroubleWithNonTopTabs()
107 const int verComCtl32
= wxApp::GetComCtl32Version();
109 // 600 is XP, 616 is Vista -- and both have a problem with tabs not on top
110 // (but don't just test for >= 600 as Microsoft might decide to fix it in
111 // later versions, who knows...)
112 return verComCtl32
>= 600 && verComCtl32
<= 616;
115 // ----------------------------------------------------------------------------
117 // ----------------------------------------------------------------------------
119 BEGIN_EVENT_TABLE(wxNotebook
, wxBookCtrlBase
)
120 EVT_SIZE(wxNotebook::OnSize
)
121 EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey
)
123 #if USE_NOTEBOOK_ANTIFLICKER
124 EVT_ERASE_BACKGROUND(wxNotebook::OnEraseBackground
)
125 EVT_PAINT(wxNotebook::OnPaint
)
126 #endif // USE_NOTEBOOK_ANTIFLICKER
129 // ============================================================================
131 // ============================================================================
133 // ----------------------------------------------------------------------------
134 // wxNotebook construction
135 // ----------------------------------------------------------------------------
137 // common part of all ctors
138 void wxNotebook::Init()
141 m_hbrBackground
= NULL
;
142 #endif // wxUSE_UXTHEME
144 #if USE_NOTEBOOK_ANTIFLICKER
145 m_hasSubclassedUpdown
= false;
146 m_doneUpdateHack
= false;
147 #endif // USE_NOTEBOOK_ANTIFLICKER
150 // default for dynamic class
151 wxNotebook::wxNotebook()
156 // the same arguments as for wxControl
157 wxNotebook::wxNotebook(wxWindow
*parent
,
162 const wxString
& name
)
166 Create(parent
, id
, pos
, size
, style
, name
);
170 bool wxNotebook::Create(wxWindow
*parent
,
175 const wxString
& name
)
177 if ( (style
& wxBK_ALIGN_MASK
) == wxBK_DEFAULT
)
179 #if defined(__POCKETPC__)
180 style
|= wxBK_BOTTOM
| wxNB_FLAT
;
187 // Not sure why, but without this style, there is no border
188 // around the notebook tabs.
189 if (style
& wxNB_FLAT
)
190 style
|= wxBORDER_SUNKEN
;
194 // ComCtl32 notebook tabs simply don't work unless they're on top if we
195 // have uxtheme, we can work around it later (after control creation), but
196 // if we have been compiled without uxtheme support, we have to clear those
198 if ( HasTroubleWithNonTopTabs() )
200 style
&= ~(wxBK_BOTTOM
| wxBK_LEFT
| wxBK_RIGHT
);
202 #endif //wxUSE_UXTHEME
204 #if defined(__WINE__) && wxUSE_UNICODE
205 LPCTSTR className
= L
"SysTabControl32";
207 LPCTSTR className
= WC_TABCONTROL
;
210 #if USE_NOTEBOOK_ANTIFLICKER
211 // SysTabCtl32 class has natively CS_HREDRAW and CS_VREDRAW enabled and it
212 // causes horrible flicker when resizing notebook, so get rid of it by
213 // using a class without these styles (but otherwise identical to it)
214 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE
) )
216 static ClassRegistrar s_clsNotebook
;
217 if ( !s_clsNotebook
.IsInitialized() )
219 // get a copy of standard class and modify it
222 if ( ::GetClassInfo(NULL
, WC_TABCONTROL
, &wc
) )
225 reinterpret_cast<WXFARPROC
>(wc
.lpfnWndProc
);
226 wc
.lpszClassName
= wxT("_wx_SysTabCtl32");
227 wc
.style
&= ~(CS_HREDRAW
| CS_VREDRAW
);
228 wc
.hInstance
= wxGetInstance();
229 wc
.lpfnWndProc
= wxNotebookWndProc
;
230 s_clsNotebook
.Register(wc
);
234 wxLogLastError(wxT("GetClassInfoEx(SysTabCtl32)"));
238 // use our custom class if available but fall back to the standard
239 // notebook if we failed to register it
240 if ( s_clsNotebook
.IsRegistered() )
242 // it's ok to use c_str() here as the static s_clsNotebook object
243 // has sufficiently long lifetime
244 className
= s_clsNotebook
.GetName().c_str();
247 #endif // USE_NOTEBOOK_ANTIFLICKER
249 if ( !CreateControl(parent
, id
, pos
, size
, style
| wxTAB_TRAVERSAL
,
250 wxDefaultValidator
, name
) )
253 if ( !MSWCreateControl(className
, wxEmptyString
, pos
, size
) )
256 // Inherit parent attributes and, unlike the default, also inherit the
257 // parent background colour in order to blend in with its background if
258 // it's set to a non-default value.
260 if ( parent
->InheritsBackgroundColour() && !UseBgCol() )
261 SetBackgroundColour(parent
->GetBackgroundColour());
264 if ( HasFlag(wxNB_NOPAGETHEME
) ||
265 wxSystemOptions::IsFalse(wxT("msw.notebook.themed-background")) )
267 SetBackgroundColour(GetThemeBackgroundColour());
269 else // use themed background by default
271 // create backing store
275 // comctl32.dll 6.0 doesn't support non-top tabs with visual styles (the
276 // control is simply not rendered correctly), so we disable themes
277 // if possible, otherwise we simply clear the styles.
278 if ( HasTroubleWithNonTopTabs() &&
279 (style
& (wxBK_BOTTOM
| wxBK_LEFT
| wxBK_RIGHT
)) )
281 // check if we use themes at all -- if we don't, we're still okay
282 if ( wxUxThemeEngine::GetIfActive() )
284 wxUxThemeEngine::GetIfActive()->SetWindowTheme(GetHwnd(), L
"", L
"");
286 // correct the background color for the new non-themed control
287 SetBackgroundColour(GetThemeBackgroundColour());
290 #endif // wxUSE_UXTHEME
292 // Undocumented hack to get flat notebook style
293 // In fact, we should probably only do this in some
294 // curcumstances, i.e. if we know we will have a border
295 // at the bottom (the tab control doesn't draw it itself)
296 #if defined(__POCKETPC__) || defined(__SMARTPHONE__)
297 if (HasFlag(wxNB_FLAT
))
299 SendMessage(GetHwnd(), CCM_SETVERSION
, COMCTL32_VERSION
, 0);
301 SetBackgroundColour(*wxWHITE
);
307 WXDWORD
wxNotebook::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
309 WXDWORD tabStyle
= wxControl::MSWGetStyle(style
, exstyle
);
311 tabStyle
|= WS_TABSTOP
| TCS_TABS
;
313 if ( style
& wxNB_MULTILINE
)
314 tabStyle
|= TCS_MULTILINE
;
315 if ( style
& wxNB_FIXEDWIDTH
)
316 tabStyle
|= TCS_FIXEDWIDTH
;
318 if ( style
& wxBK_BOTTOM
)
319 tabStyle
|= TCS_RIGHT
;
320 else if ( style
& wxBK_LEFT
)
321 tabStyle
|= TCS_VERTICAL
;
322 else if ( style
& wxBK_RIGHT
)
323 tabStyle
|= TCS_VERTICAL
| TCS_RIGHT
;
328 wxNotebook::~wxNotebook()
331 if ( m_hbrBackground
)
332 ::DeleteObject((HBRUSH
)m_hbrBackground
);
333 #endif // wxUSE_UXTHEME
336 // ----------------------------------------------------------------------------
337 // wxNotebook accessors
338 // ----------------------------------------------------------------------------
340 size_t wxNotebook::GetPageCount() const
343 wxASSERT( (int)m_pages
.Count() == TabCtrl_GetItemCount(GetHwnd()) );
345 return m_pages
.Count();
348 int wxNotebook::GetRowCount() const
350 return TabCtrl_GetRowCount(GetHwnd());
353 int wxNotebook::SetSelection(size_t nPage
)
355 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxNOT_FOUND
, wxT("notebook page out of range") );
357 if ( m_selection
== wxNOT_FOUND
|| nPage
!= (size_t)m_selection
)
359 if ( SendPageChangingEvent(nPage
) )
361 // program allows the page change
362 const int selectionOld
= m_selection
;
364 UpdateSelection(nPage
);
366 TabCtrl_SetCurSel(GetHwnd(), nPage
);
368 SendPageChangedEvent(selectionOld
, nPage
);
375 void wxNotebook::UpdateSelection(int selNew
)
377 if ( m_selection
!= wxNOT_FOUND
)
378 m_pages
[m_selection
]->Show(false);
380 if ( selNew
!= wxNOT_FOUND
)
382 wxNotebookPage
*pPage
= m_pages
[selNew
];
385 // In addition to showing the page, we also want to give focus to it to
386 // make it possible to work with it from keyboard easily. However there
387 // are two exceptions: first, don't touch the focus at all if the
388 // notebook itself is not currently shown.
389 if ( ::IsWindowVisible(GetHwnd()) )
391 // And second, don't give focus away if the tab control itself has
392 // it, as this is how the native property sheets behave: if you
393 // explicitly click on the tab label giving it focus, it will
394 // remain after switching to another page. But if the focus was
395 // inside the notebook page, it switches to the new page.
401 m_selection
= selNew
;
404 int wxNotebook::ChangeSelection(size_t nPage
)
406 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxNOT_FOUND
, wxT("notebook page out of range") );
408 const int selOld
= m_selection
;
410 if ( m_selection
== wxNOT_FOUND
|| nPage
!= (size_t)m_selection
)
412 TabCtrl_SetCurSel(GetHwnd(), nPage
);
414 UpdateSelection(nPage
);
420 bool wxNotebook::SetPageText(size_t nPage
, const wxString
& strText
)
422 wxCHECK_MSG( IS_VALID_PAGE(nPage
), false, wxT("notebook page out of range") );
425 tcItem
.mask
= TCIF_TEXT
;
426 tcItem
.pszText
= wxMSW_CONV_LPTSTR(strText
);
428 if ( !HasFlag(wxNB_MULTILINE
) )
429 return TabCtrl_SetItem(GetHwnd(), nPage
, &tcItem
) != 0;
431 // multiline - we need to set new page size if a line is added or removed
432 int rows
= GetRowCount();
433 bool ret
= TabCtrl_SetItem(GetHwnd(), nPage
, &tcItem
) != 0;
435 if ( ret
&& rows
!= GetRowCount() )
437 const wxRect r
= GetPageSize();
438 const size_t count
= m_pages
.Count();
439 for ( size_t page
= 0; page
< count
; page
++ )
440 m_pages
[page
]->SetSize(r
);
446 wxString
wxNotebook::GetPageText(size_t nPage
) const
448 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxEmptyString
, wxT("notebook page out of range") );
452 tcItem
.mask
= TCIF_TEXT
;
453 tcItem
.pszText
= buf
;
454 tcItem
.cchTextMax
= WXSIZEOF(buf
);
457 if ( TabCtrl_GetItem(GetHwnd(), nPage
, &tcItem
) )
458 str
= tcItem
.pszText
;
463 int wxNotebook::GetPageImage(size_t nPage
) const
465 wxCHECK_MSG( IS_VALID_PAGE(nPage
), wxNOT_FOUND
, wxT("notebook page out of range") );
468 tcItem
.mask
= TCIF_IMAGE
;
470 return TabCtrl_GetItem(GetHwnd(), nPage
, &tcItem
) ? tcItem
.iImage
474 bool wxNotebook::SetPageImage(size_t nPage
, int nImage
)
476 wxCHECK_MSG( IS_VALID_PAGE(nPage
), false, wxT("notebook page out of range") );
479 tcItem
.mask
= TCIF_IMAGE
;
480 tcItem
.iImage
= nImage
;
482 return TabCtrl_SetItem(GetHwnd(), nPage
, &tcItem
) != 0;
485 void wxNotebook::SetImageList(wxImageList
* imageList
)
487 wxNotebookBase::SetImageList(imageList
);
491 (void) TabCtrl_SetImageList(GetHwnd(), GetHimagelistOf(imageList
));
495 // ----------------------------------------------------------------------------
496 // wxNotebook size settings
497 // ----------------------------------------------------------------------------
499 wxRect
wxNotebook::GetPageSize() const
504 ::GetClientRect(GetHwnd(), &rc
);
506 // This check is to work around a bug in TabCtrl_AdjustRect which will
507 // cause a crash on win2k or on XP with themes disabled if either
508 // wxNB_MULTILINE is used or tabs are placed on a side, if the rectangle
511 // The value of 20 is chosen arbitrarily but seems to work
512 if ( rc
.right
> 20 && rc
.bottom
> 20 )
514 TabCtrl_AdjustRect(GetHwnd(), false, &rc
);
516 wxCopyRECTToRect(rc
, r
);
522 void wxNotebook::SetPageSize(const wxSize
& size
)
524 // transform the page size into the notebook size
531 TabCtrl_AdjustRect(GetHwnd(), true, &rc
);
534 SetSize(rc
.right
- rc
.left
, rc
.bottom
- rc
.top
);
537 void wxNotebook::SetPadding(const wxSize
& padding
)
539 TabCtrl_SetPadding(GetHwnd(), padding
.x
, padding
.y
);
542 // Windows-only at present. Also, you must use the wxNB_FIXEDWIDTH
544 void wxNotebook::SetTabSize(const wxSize
& sz
)
546 ::SendMessage(GetHwnd(), TCM_SETITEMSIZE
, 0, MAKELPARAM(sz
.x
, sz
.y
));
549 wxSize
wxNotebook::CalcSizeFromPage(const wxSize
& sizePage
) const
551 // we can't use TabCtrl_AdjustRect here because it only works for wxNB_TOP
552 wxSize sizeTotal
= sizePage
;
555 if ( GetPageCount() > 0 )
558 TabCtrl_GetItemRect(GetHwnd(), 0, &rect
);
559 tabSize
.x
= rect
.right
- rect
.left
;
560 tabSize
.y
= rect
.bottom
- rect
.top
;
563 const int rows
= GetRowCount();
565 // add an extra margin in both directions
566 const int MARGIN
= 8;
569 sizeTotal
.x
+= MARGIN
;
570 sizeTotal
.y
+= tabSize
.y
* rows
+ MARGIN
;
572 else // horizontal layout
574 sizeTotal
.x
+= tabSize
.x
* rows
+ MARGIN
;
575 sizeTotal
.y
+= MARGIN
;
581 void wxNotebook::AdjustPageSize(wxNotebookPage
*page
)
583 wxCHECK_RET( page
, wxT("NULL page in wxNotebook::AdjustPageSize") );
585 const wxRect r
= GetPageSize();
592 // ----------------------------------------------------------------------------
593 // wxNotebook operations
594 // ----------------------------------------------------------------------------
596 // remove one page from the notebook, without deleting
597 wxNotebookPage
*wxNotebook::DoRemovePage(size_t nPage
)
599 wxNotebookPage
*pageRemoved
= wxNotebookBase::DoRemovePage(nPage
);
603 // hide the removed page to maintain the invariant that only the
604 // selected page is visible and others are hidden:
605 pageRemoved
->Show(false);
607 TabCtrl_DeleteItem(GetHwnd(), nPage
);
609 if ( m_pages
.IsEmpty() )
611 // no selection any more, the notebook becamse empty
612 m_selection
= wxNOT_FOUND
;
614 else // notebook still not empty
616 int selNew
= TabCtrl_GetCurSel(GetHwnd());
617 if ( selNew
!= wxNOT_FOUND
)
619 // No selection change, just refresh the current selection.
620 // Because it could be that the slection index changed
621 // we need to update it.
622 // Note: this does not mean the selection it self changed.
623 m_selection
= selNew
;
624 m_pages
[m_selection
]->Refresh();
626 else if (int(nPage
) == m_selection
)
628 // The selection was deleted.
630 // Determine new selection.
631 if (m_selection
== int(GetPageCount()))
632 selNew
= m_selection
- 1;
634 selNew
= m_selection
;
636 // m_selection must be always valid so reset it before calling
638 m_selection
= wxNOT_FOUND
;
639 SetSelection(selNew
);
643 wxFAIL
; // Windows did not behave ok.
651 bool wxNotebook::DeleteAllPages()
653 size_t nPageCount
= GetPageCount();
655 for ( nPage
= 0; nPage
< nPageCount
; nPage
++ )
656 delete m_pages
[nPage
];
660 TabCtrl_DeleteAllItems(GetHwnd());
662 m_selection
= wxNOT_FOUND
;
664 InvalidateBestSize();
668 // same as AddPage() but does it at given position
669 bool wxNotebook::InsertPage(size_t nPage
,
670 wxNotebookPage
*pPage
,
671 const wxString
& strText
,
675 wxCHECK_MSG( pPage
!= NULL
, false, wxT("NULL page in wxNotebook::InsertPage") );
676 wxCHECK_MSG( IS_VALID_PAGE(nPage
) || nPage
== GetPageCount(), false,
677 wxT("invalid index in wxNotebook::InsertPage") );
679 wxASSERT_MSG( pPage
->GetParent() == this,
680 wxT("notebook pages must have notebook as parent") );
682 // add a new tab to the control
683 // ----------------------------
685 // init all fields to 0
687 wxZeroMemory(tcItem
);
689 // set the image, if any
692 tcItem
.mask
|= TCIF_IMAGE
;
693 tcItem
.iImage
= imageId
;
697 if ( !strText
.empty() )
699 tcItem
.mask
|= TCIF_TEXT
;
700 tcItem
.pszText
= wxMSW_CONV_LPTSTR(strText
);
703 // hide the page: unless it is selected, it shouldn't be shown (and if it
704 // is selected it will be shown later)
705 HWND hwnd
= GetWinHwnd(pPage
);
706 SetWindowLong(hwnd
, GWL_STYLE
, GetWindowLong(hwnd
, GWL_STYLE
) & ~WS_VISIBLE
);
708 // this updates internal flag too -- otherwise it would get out of sync
709 // with the real state
713 // fit the notebook page to the tab control's display area: this should be
714 // done before adding it to the notebook or TabCtrl_InsertItem() will
715 // change the notebooks size itself!
716 AdjustPageSize(pPage
);
718 // finally do insert it
719 if ( TabCtrl_InsertItem(GetHwnd(), nPage
, &tcItem
) == -1 )
721 wxLogError(wxT("Can't create the notebook page '%s'."), strText
.c_str());
726 // need to update the bg brush when the first page is added
727 // so the first panel gets the correct themed background
728 if ( m_pages
.empty() )
732 #endif // wxUSE_UXTHEME
735 // succeeded: save the pointer to the page
736 m_pages
.Insert(pPage
, nPage
);
738 // we may need to adjust the size again if the notebook size changed:
739 // normally this only happens for the first page we add (the tabs which
740 // hadn't been there before are now shown) but for a multiline notebook it
741 // can happen for any page at all as a new row could have been started
742 if ( m_pages
.GetCount() == 1 || HasFlag(wxNB_MULTILINE
) )
744 AdjustPageSize(pPage
);
746 // Additionally, force the layout of the notebook itself by posting a
747 // size event to it. If we don't do it, notebooks with pages on the
748 // left or the right side may fail to account for the fact that they
749 // are now big enough to fit all all of their pages on one row and
750 // still reserve space for the second row of tabs, see #1792.
751 const wxSize s
= GetSize();
752 ::PostMessage(GetHwnd(), WM_SIZE
, SIZE_RESTORED
, MAKELPARAM(s
.x
, s
.y
));
755 // now deal with the selection
756 // ---------------------------
758 // if the inserted page is before the selected one, we must update the
759 // index of the selected page
760 if ( int(nPage
) <= m_selection
)
762 // one extra page added
766 DoSetSelectionAfterInsertion(nPage
, bSelect
);
768 InvalidateBestSize();
773 int wxNotebook::HitTest(const wxPoint
& pt
, long *flags
) const
775 TC_HITTESTINFO hitTestInfo
;
776 hitTestInfo
.pt
.x
= pt
.x
;
777 hitTestInfo
.pt
.y
= pt
.y
;
778 int item
= TabCtrl_HitTest(GetHwnd(), &hitTestInfo
);
784 if ((hitTestInfo
.flags
& TCHT_NOWHERE
) == TCHT_NOWHERE
)
785 *flags
|= wxBK_HITTEST_NOWHERE
;
786 if ((hitTestInfo
.flags
& TCHT_ONITEM
) == TCHT_ONITEM
)
787 *flags
|= wxBK_HITTEST_ONITEM
;
788 if ((hitTestInfo
.flags
& TCHT_ONITEMICON
) == TCHT_ONITEMICON
)
789 *flags
|= wxBK_HITTEST_ONICON
;
790 if ((hitTestInfo
.flags
& TCHT_ONITEMLABEL
) == TCHT_ONITEMLABEL
)
791 *flags
|= wxBK_HITTEST_ONLABEL
;
792 if ( item
== wxNOT_FOUND
&& GetPageSize().Contains(pt
) )
793 *flags
|= wxBK_HITTEST_ONPAGE
;
799 // ----------------------------------------------------------------------------
800 // flicker-less notebook redraw
801 // ----------------------------------------------------------------------------
803 #if USE_NOTEBOOK_ANTIFLICKER
805 // wnd proc for the spin button
806 LRESULT APIENTRY _EXPORT
wxNotebookSpinBtnWndProc(HWND hwnd
,
811 if ( message
== WM_ERASEBKGND
)
814 return ::CallWindowProc(CASTWNDPROC gs_wndprocNotebookSpinBtn
,
815 hwnd
, message
, wParam
, lParam
);
818 LRESULT APIENTRY _EXPORT
wxNotebookWndProc(HWND hwnd
,
823 return ::CallWindowProc(CASTWNDPROC gs_wndprocNotebook
,
824 hwnd
, message
, wParam
, lParam
);
827 void wxNotebook::OnEraseBackground(wxEraseEvent
& WXUNUSED(event
))
832 void wxNotebook::OnPaint(wxPaintEvent
& WXUNUSED(event
))
837 ::GetClientRect(GetHwnd(), &rc
);
838 wxBitmap
bmp(rc
.right
, rc
.bottom
);
839 memdc
.SelectObject(bmp
);
841 const wxLayoutDirection dir
= dc
.GetLayoutDirection();
842 memdc
.SetLayoutDirection(dir
);
844 const HDC hdc
= GetHdcOf(memdc
);
846 // The drawing logic of the native tab control is absolutely impenetrable
847 // but observation shows that in the current Windows versions (XP and 7),
848 // the tab control always erases its entire background in its window proc
849 // when the tabs are top-aligned but does not do it when the tabs are in
850 // any other position.
852 // This means that we can't rely on our background colour being used for
853 // the blank area in the tab row because this doesn't work in the default
854 // top-aligned case, hence the hack with ExtFloodFill() below. But it also
855 // means that we still do need to erase the DC to account for the other
858 // Moreover, just in case some very old or very new (or even future,
859 // although it seems unlikely that this is ever going to change by now)
860 // version of Windows didn't do it like this, do both things in all cases
861 // instead of optimizing away the one of them which doesn't do anything for
862 // the effectively used tab orientation -- better safe than fast.
864 // Notice that we use our own background here, not the background used for
865 // the pages, because the tab row background must blend with the parent and
866 // so the background colour inherited from it (if any) must be used.
867 AutoHBRUSH
hbr(wxColourToRGB(GetBackgroundColour()));
869 ::FillRect(hdc
, &rc
, hbr
);
871 MSWDefWindowProc(WM_PAINT
, (WPARAM
)hdc
, 0);
873 // At least for the top-aligned tabs, our background colour was overwritten
874 // and so we now replace the default background with our colour. This is
875 // horribly inefficient, of course, but seems to be the only way to do it.
878 SelectInHDC
selectBrush(hdc
, hbr
);
880 // Find the point which must contain the default background colour:
881 // this is a hack, of course, but using this point "close" to the
882 // corner seems to work fine in practice.
886 switch ( GetWindowStyle() & wxBK_ALIGN_MASK
)
909 ::ExtFloodFill(hdc
, x
, y
, ::GetSysColor(COLOR_BTNFACE
), FLOODFILLSURFACE
);
912 // For some reason in RTL mode, source offset has to be -1, otherwise the
913 // right border (physical) remains unpainted.
914 const wxCoord ofs
= dir
== wxLayout_RightToLeft
? -1 : 0;
915 dc
.Blit(ofs
, 0, rc
.right
, rc
.bottom
, &memdc
, ofs
, 0);
918 #endif // USE_NOTEBOOK_ANTIFLICKER
920 // ----------------------------------------------------------------------------
921 // wxNotebook callbacks
922 // ----------------------------------------------------------------------------
924 void wxNotebook::OnSize(wxSizeEvent
& event
)
926 if ( GetPageCount() == 0 )
928 // Prevents droppings on resize, but does cause some flicker
929 // when there are no pages.
937 // Without this, we can sometimes get droppings at the edges
938 // of a notebook, for example a notebook in a splitter window.
939 // This needs to be reconciled with the RefreshRect calls
940 // at the end of this function, which weren't enough to prevent
943 wxSize sz
= GetClientSize();
945 // Refresh right side
946 wxRect
rect(sz
.x
-4, 0, 4, sz
.y
);
949 // Refresh bottom side
950 rect
= wxRect(0, sz
.y
-4, sz
.x
, 4);
954 rect
= wxRect(0, 0, 4, sz
.y
);
957 #endif // !__WXWINCE__
959 // fit all the notebook pages to the tab control's display area
962 rc
.left
= rc
.top
= 0;
963 GetSize((int *)&rc
.right
, (int *)&rc
.bottom
);
965 // save the total size, we'll use it below
966 int widthNbook
= rc
.right
- rc
.left
,
967 heightNbook
= rc
.bottom
- rc
.top
;
969 // there seems to be a bug in the implementation of TabCtrl_AdjustRect(): it
970 // returns completely false values for multiline tab controls after the tabs
971 // are added but before getting the first WM_SIZE (off by ~50 pixels, see
973 // http://sf.net/tracker/index.php?func=detail&aid=645323&group_id=9863&atid=109863
975 // and the only work around I could find was this ugly hack... without it
976 // simply toggling the "multiline" checkbox in the notebook sample resulted
977 // in a noticeable page displacement
978 if ( HasFlag(wxNB_MULTILINE
) )
980 // avoid an infinite recursion: we get another notification too!
981 static bool s_isInOnSize
= false;
986 SendMessage(GetHwnd(), WM_SIZE
, SIZE_RESTORED
,
987 MAKELPARAM(rc
.right
, rc
.bottom
));
988 s_isInOnSize
= false;
991 // The best size depends on the number of rows of tabs, which can
992 // change when the notepad is resized.
993 InvalidateBestSize();
997 // background bitmap size has changed, update the brush using it too
999 #endif // wxUSE_UXTHEME
1001 TabCtrl_AdjustRect(GetHwnd(), false, &rc
);
1003 int width
= rc
.right
- rc
.left
,
1004 height
= rc
.bottom
- rc
.top
;
1005 size_t nCount
= m_pages
.Count();
1006 for ( size_t nPage
= 0; nPage
< nCount
; nPage
++ ) {
1007 wxNotebookPage
*pPage
= m_pages
[nPage
];
1008 pPage
->SetSize(rc
.left
, rc
.top
, width
, height
);
1012 // unless we had already repainted everything, we now need to refresh
1013 if ( !HasFlag(wxFULL_REPAINT_ON_RESIZE
) )
1015 // invalidate areas not covered by pages
1016 RefreshRect(wxRect(0, 0, widthNbook
, rc
.top
), false);
1017 RefreshRect(wxRect(0, rc
.top
, rc
.left
, height
), false);
1018 RefreshRect(wxRect(0, rc
.bottom
, widthNbook
, heightNbook
- rc
.bottom
),
1020 RefreshRect(wxRect(rc
.right
, rc
.top
, widthNbook
- rc
.right
, height
),
1024 #if USE_NOTEBOOK_ANTIFLICKER
1025 // subclass the spin control used by the notebook to scroll pages to
1026 // prevent it from flickering on resize
1027 if ( !m_hasSubclassedUpdown
)
1029 // iterate over all child windows to find spin button
1030 for ( HWND child
= ::GetWindow(GetHwnd(), GW_CHILD
);
1032 child
= ::GetWindow(child
, GW_HWNDNEXT
) )
1034 wxWindow
*childWindow
= wxFindWinFromHandle((WXHWND
)child
);
1036 // see if it exists, if no wxWindow found then assume it's the spin
1040 // subclass the spin button to override WM_ERASEBKGND
1041 if ( !gs_wndprocNotebookSpinBtn
)
1042 gs_wndprocNotebookSpinBtn
= (WXFARPROC
)wxGetWindowProc(child
);
1044 wxSetWindowProc(child
, wxNotebookSpinBtnWndProc
);
1045 m_hasSubclassedUpdown
= true;
1051 // Probably because of the games we play above to avoid flicker sometimes
1052 // the text controls inside notebook pages are not shown correctly (they
1053 // don't have their borders) when the notebook is shown for the first time.
1054 // It's not really clear why does this happen and maybe the bug is in
1055 // wxTextCtrl itself and not here but updating the page when it's about to
1056 // be shown doesn't cost much and works around the problem so do it here
1058 if ( !m_doneUpdateHack
&& IsShownOnScreen() )
1060 m_doneUpdateHack
= true;
1061 wxWindow
* const page
= GetCurrentPage();
1065 #endif // USE_NOTEBOOK_ANTIFLICKER
1070 void wxNotebook::OnNavigationKey(wxNavigationKeyEvent
& event
)
1072 if ( event
.IsWindowChange() ) {
1074 AdvanceSelection(event
.GetDirection());
1077 // we get this event in 3 cases
1079 // a) one of our pages might have generated it because the user TABbed
1080 // out from it in which case we should propagate the event upwards and
1081 // our parent will take care of setting the focus to prev/next sibling
1085 // b) the parent panel wants to give the focus to us so that we
1086 // forward it to our selected page. We can't deal with this in
1087 // OnSetFocus() because we don't know which direction the focus came
1088 // from in this case and so can't choose between setting the focus to
1089 // first or last panel child
1093 // c) we ourselves (see MSWTranslateMessage) generated the event
1095 wxWindow
* const parent
= GetParent();
1097 // the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
1098 const bool isFromParent
= event
.GetEventObject() == (wxObject
*) parent
;
1099 const bool isFromSelf
= event
.GetEventObject() == (wxObject
*) this;
1100 const bool isForward
= event
.GetDirection();
1102 if ( isFromSelf
&& !isForward
)
1104 // focus is currently on notebook tab and should leave
1105 // it backwards (Shift-TAB)
1106 event
.SetCurrentFocus(this);
1107 parent
->HandleWindowEvent(event
);
1109 else if ( isFromParent
|| isFromSelf
)
1111 // no, it doesn't come from child, case (b) or (c): forward to a
1112 // page but only if entering notebook page (i.e. direction is
1113 // backwards (Shift-TAB) comething from out-of-notebook, or
1114 // direction is forward (TAB) from ourselves),
1115 if ( m_selection
!= wxNOT_FOUND
&&
1116 (!event
.GetDirection() || isFromSelf
) )
1118 // so that the page knows that the event comes from it's parent
1119 // and is being propagated downwards
1120 event
.SetEventObject(this);
1122 wxWindow
*page
= m_pages
[m_selection
];
1123 if ( !page
->HandleWindowEvent(event
) )
1127 //else: page manages focus inside it itself
1129 else // otherwise set the focus to the notebook itself
1136 // it comes from our child, case (a), pass to the parent, but only
1137 // if the direction is forwards. Otherwise set the focus to the
1138 // notebook itself. The notebook is always the 'first' control of a
1146 event
.SetCurrentFocus(this);
1147 parent
->HandleWindowEvent(event
);
1155 bool wxNotebook::DoDrawBackground(WXHDC hDC
, wxWindow
*child
)
1157 wxUxThemeHandle
theme(child
? child
: this, L
"TAB");
1161 // get the notebook client rect (we're not interested in drawing tabs
1163 wxRect r
= GetPageSize();
1168 wxCopyRectToRECT(r
, rc
);
1170 // map rect to the coords of the window we're drawing in
1172 ::MapWindowPoints(GetHwnd(), GetHwndOf(child
), (POINT
*)&rc
, 2);
1174 // we have the content area (page size), but we need to draw all of the
1175 // background for it to be aligned correctly
1176 wxUxThemeEngine::Get()->GetThemeBackgroundExtent
1185 wxUxThemeEngine::Get()->DrawThemeBackground
1198 WXHBRUSH
wxNotebook::QueryBgBitmap()
1200 wxRect r
= GetPageSize();
1204 WindowHDC
hDC(GetHwnd());
1205 MemoryHDC
hDCMem(hDC
);
1206 CompatibleBitmap
hBmp(hDC
, r
.x
+ r
.width
, r
.y
+ r
.height
);
1208 SelectInHDC
selectBmp(hDCMem
, hBmp
);
1210 if ( !DoDrawBackground((WXHDC
)(HDC
)hDCMem
) )
1213 return (WXHBRUSH
)::CreatePatternBrush(hBmp
);
1216 void wxNotebook::UpdateBgBrush()
1218 if ( m_hbrBackground
)
1219 ::DeleteObject((HBRUSH
)m_hbrBackground
);
1221 if ( !m_hasBgCol
&& wxUxThemeEngine::GetIfActive() )
1223 m_hbrBackground
= QueryBgBitmap();
1225 else // no themes or we've got user-defined solid colour
1227 m_hbrBackground
= NULL
;
1231 bool wxNotebook::MSWPrintChild(WXHDC hDC
, wxWindow
*child
)
1233 // solid background colour overrides themed background drawing
1234 if ( !UseBgCol() && DoDrawBackground(hDC
, child
) )
1237 // If we're using a solid colour (for example if we've switched off
1238 // theming for this notebook), paint it
1241 wxRect r
= GetPageSize();
1246 wxCopyRectToRECT(r
, rc
);
1248 // map rect to the coords of the window we're drawing in
1250 ::MapWindowPoints(GetHwnd(), GetHwndOf(child
), (POINT
*)&rc
, 2);
1252 wxBrush
brush(GetBackgroundColour());
1253 HBRUSH hbr
= GetHbrushOf(brush
);
1255 ::FillRect((HDC
) hDC
, &rc
, hbr
);
1260 return wxNotebookBase::MSWPrintChild(hDC
, child
);
1263 #endif // wxUSE_UXTHEME
1265 // Windows only: attempts to get colour for UX theme page background
1266 wxColour
wxNotebook::GetThemeBackgroundColour() const
1269 if (wxUxThemeEngine::Get())
1271 wxUxThemeHandle
hTheme((wxNotebook
*) this, L
"TAB");
1274 // This is total guesswork.
1275 // See PlatformSDK\Include\Tmschema.h for values.
1276 // JACS: can also use 9 (TABP_PANE)
1277 COLORREF themeColor
;
1278 bool success
= (S_OK
== wxUxThemeEngine::Get()->GetThemeColor(
1282 3821 /* FILLCOLORHINT */,
1285 return GetBackgroundColour();
1288 [DS] Workaround for WindowBlinds:
1289 Some themes return a near black theme color using FILLCOLORHINT,
1290 this makes notebook pages have an ugly black background and makes
1291 text (usually black) unreadable. Retry again with FILLCOLOR.
1293 This workaround potentially breaks appearance of some themes,
1294 but in practice it already fixes some themes.
1296 if (themeColor
== 1)
1298 wxUxThemeEngine::Get()->GetThemeColor(
1302 3802 /* FILLCOLOR */,
1306 wxColour colour
= wxRGBToColour(themeColor
);
1308 // Under Vista, the tab background colour is reported incorrectly.
1309 // So for the default theme at least, hard-code the colour to something
1310 // that will blend in.
1312 static int s_AeroStatus
= -1;
1313 if (s_AeroStatus
== -1)
1315 WCHAR szwThemeFile
[1024];
1316 WCHAR szwThemeColor
[256];
1317 if (S_OK
== wxUxThemeEngine::Get()->GetCurrentThemeName(szwThemeFile
, 1024, szwThemeColor
, 256, NULL
, 0))
1319 wxString
themeFile(szwThemeFile
), themeColor(szwThemeColor
);
1320 if (themeFile
.Find(wxT("Aero")) != -1 && themeColor
== wxT("NormalColor"))
1329 if (s_AeroStatus
== 1)
1330 colour
= wxColour(255, 255, 255);
1335 #endif // wxUSE_UXTHEME
1337 return GetBackgroundColour();
1340 // ----------------------------------------------------------------------------
1341 // wxNotebook base class virtuals
1342 // ----------------------------------------------------------------------------
1344 #if wxUSE_CONSTRAINTS
1346 // override these 2 functions to do nothing: everything is done in OnSize
1348 void wxNotebook::SetConstraintSizes(bool WXUNUSED(recurse
))
1350 // don't set the sizes of the pages - their correct size is not yet known
1351 wxControl::SetConstraintSizes(false);
1354 bool wxNotebook::DoPhase(int WXUNUSED(nPhase
))
1359 #endif // wxUSE_CONSTRAINTS
1361 // ----------------------------------------------------------------------------
1362 // wxNotebook Windows message handlers
1363 // ----------------------------------------------------------------------------
1365 bool wxNotebook::MSWOnScroll(int orientation
, WXWORD nSBCode
,
1366 WXWORD pos
, WXHWND control
)
1368 // don't generate EVT_SCROLLWIN events for the WM_SCROLLs coming from the
1373 return wxNotebookBase::MSWOnScroll(orientation
, nSBCode
, pos
, control
);
1376 bool wxNotebook::MSWOnNotify(int idCtrl
, WXLPARAM lParam
, WXLPARAM
* result
)
1378 wxBookCtrlEvent
event(wxEVT_NULL
, m_windowId
);
1380 NMHDR
* hdr
= (NMHDR
*)lParam
;
1381 switch ( hdr
->code
) {
1383 event
.SetEventType(wxEVT_NOTEBOOK_PAGE_CHANGED
);
1386 case TCN_SELCHANGING
:
1387 event
.SetEventType(wxEVT_NOTEBOOK_PAGE_CHANGING
);
1391 return wxControl::MSWOnNotify(idCtrl
, lParam
, result
);
1394 event
.SetSelection(TabCtrl_GetCurSel(GetHwnd()));
1395 event
.SetOldSelection(m_selection
);
1396 event
.SetEventObject(this);
1397 event
.SetInt(idCtrl
);
1399 // Change the selection before generating the event as its handler should
1400 // already see the new page selected.
1401 if ( hdr
->code
== TCN_SELCHANGE
)
1402 UpdateSelection(event
.GetSelection());
1404 bool processed
= HandleWindowEvent(event
);
1405 *result
= !event
.IsAllowed();
1409 #endif // wxUSE_NOTEBOOK