Win32 SDK C Tab Control Made Easy
2013-02-14 13:37
645 查看
Win32 SDK C Tab Control Made Easy
By DavidMacDermot, 18 Jun 2009
|
Download source - 6.56 KB
Introduction
Several years ago, I wanted to use the tab control in a Win32 SDK C project. The low-level implementation proved to be somewhat cumbersome. A search of the Web turned up a lot of examples based on MFC,but very little along the lines of plain old C. What I did find did not support keyboard navigation. After extensively using tab controls in VB.NET, I had an idea of what features would make for an easy-to-implement Win32 tab control:
Tab pages should be designed as separate dialogs. These could then be hidden and shown during tab selection.
All events received on controls should be handled in the parent dialog's callback procedure.
Keyboard navigation of the tab control, as well as tab pages, must work.
It must be fairly simple to define moving and sizing constraints within the dialog.
At the time I came up with a solution that worked fairly well and posted it here for the edification of the CodeProject community. Recently I revisited this project on account of some of the comments left here. I examined the code from the vantage point
of a few more years of experience and saw some room for improvement. This version, 3.0 offers much better handling of mouse clicks and keyboard input.
Usage
A look at TabCtrl.h revealsa struct and two method prototypes. I took a pseudo object-oriented approach to this problem in order to make it easy to implement more than one tab control simultaneously. I made extensive use of pointers to functions so that it would not be necessary to
add to or update prototyping in the TabCtrl.c module
or parent project.
In this version, I also include a handy macro for selecting tabs from an external event such as a button click on the parent dialog.
Collapse | Copy
Code
/***************************************************************************/ // Structs typedef struct TabControl { HWND hTab; HWND hVisiblePage; HWND* hTabPages; LPSTR *tabNames; int tabPageCount; BOOL blStretchTabs; // Function pointer to Parent Dialog Proc BOOL (CALLBACK *ParentProc)(HWND, UINT, WPARAM, LPARAM); // Function pointer to Tab Page Size Proc void (*TabPage_OnSize)(HWND hwnd, UINT state, int cx, int cy); // Pointers to shared functions BOOL (*Notify) (LPNMHDR); BOOL (*StretchTabPage) (HWND, INT); BOOL (*CenterTabPage) (HWND, INT); }TABCTRL, *LPTABCTRL; /***************************************************************************/ // Exported function prototypes void New_TabControl(LPTABCTRL, HWND, LPSTR*, LPSTR*, BOOL (CALLBACK *ParentProc)(HWND, UINT, WPARAM, LPARAM), VOID (*TabPage_OnSize)(HWND, UINT, int, int), BOOL fStretch); void TabControl_Destroy(LPTABCTRL); /****************************************************************************/ // Macros #define TabCtrl_SelectTab(hTab,iSel) { \ TabCtrl_SetCurSel(hTab,iSel); \ NMHDR nmh = { hTab, GetDlgCtrlID(hTab), TCN_SELCHANGE }; \ SendMessage(nmh.hwndFrom,WM_NOTIFY,(WPARAM)nmh.idFrom,(LPARAM)&nmh); }
Using this class is fairly straightforward. In the demo, I did the following:
Placed and sized two tab controls.
Created a dialog for each desired tab page. Note that dialogs should be borderless and sized to fit the tab control's client area.
Declared an instance of the
TabControl structfor each of my tab controls.
Collapse | Copy
Code
/** Global variables *******************************************************/ static HANDLE ghInstance; static SIZE gMinSize; static TABCTRL TabCtrl_1, TabCtrl_2;
I create instances of the tab control class in the
WM_INITDIALOGmessage handler using the
New_TabControl()function.
Some things to keep in mind:
The
tabnamesand
dlgnamesarrays
must be null terminated.
You supply the optional
TabCtrl1_TabPages_OnSize()function
discussed later in this article or
NULLif not needed.
The final argument,
fStretch, is
TRUEfor
a stretch-to-fit tab page and
FALSEif you want to center the tab page on the control.
Collapse | Copy
Code
InitHandles (hwnd); // this line must be first! //----This section must be second----// static LPSTR tabnames[]= {"Tab Page 1", "Tab Page 2", "Tab Page 3", "Tab Page 4", 0}; static LPSTR dlgnames[]= {MAKEINTRESOURCE(TAB_CONTROL_1_PAGE_1), MAKEINTRESOURCE(TAB_CONTROL_1_PAGE_2), MAKEINTRESOURCE(TAB_CONTROL_1_PAGE_3), MAKEINTRESOURCE(TAB_CONTROL_1_PAGE_4),0}; New_TabControl( &TabCtrl_1, // address of TabControl struct GetDlgItem(hwnd, TAB_CONTROL_1), // handle to tab control tabnames, // text for each tab dlgnames, // dialog ids of each tab page dialog &FormMain_DlgProc, //parent dialog proc &TabCtrl1_TabPages_OnSize, // optional address of size function or NULL TRUE); // stretch tab page to fit tab ctrl
I sort out the
WM_NOTIFYmessages for the tab control and route them to the class' internal
OnKeyDown()and
OnSelChanged()event
handlers via function pointers.
Collapse | Copy
Code
static BOOL FormMain_OnNotify(HWND hwnd, INT id, LPNMHDR pnm) { switch(id) { case TAB_CONTROL_1: return TabCtrl_1.Notify(pnm); case TAB_CONTROL_2: return TabCtrl_2.Notify(pnm); // // TODO: Add other control id case statements here. . . // } return FALSE; }
Sizing is handled on two levels. First, there is the sizing of the tab control and its associated tab pages, i.e. dialogs. Second, there is the handling of the size and position of the child controls within the tab pages. When I handle the
WM_SIZEmessage,
I supply typical sizing code for the tab controls. Now look at the
forloops beneath the
MoveWindow()statements.
I am using the functions
StretchTabPage()and
CenterTabPage()to
keep the tab pages within the client area of their respective tab controls. No messy sizing code here. :) I use a
Refreshmacro
one time only, right here in order to reduce flicker in the main dialog.
Collapse | Copy
Code
void FormMain_OnSize(HWND hwnd, UINT state, int cx, int cy) { RECT rc; GetClientRect(hwnd,&rc); MoveWindow(TabCtrl_1.hTab,0,0, (rc.right - rc.left)/2-4,rc.bottom - rc.top,FALSE); for(int i=0;i < TabCtrl_1.tabPageCount;i++) TabCtrl_1.StretchTabPage(TabCtrl_1.hTab,i); MoveWindow(TabCtrl_2.hTab,(rc.right - rc.left)/2,0, (rc.right - rc.left)/2-4,rc.bottom - rc.top,FALSE); for(int i=0;i < TabCtrl_2.tabPageCount;i++) TabCtrl_2.CenterTabPage(TabCtrl_2.hTab,i); Refresh(hwnd); }
The handling of the size and position of child controls within tab pages is done in a separate function. It is necessary to clone the
XXX_OnSize()function
-- i.e. message cracker function -- and pass the address of that function to the
New_TabControl()constructor that
we discussed earlier. All tab page
WM_SIZEmessages are then sent to this handler. I am using
if/
else ifstatements to separate out the various tab pages and controls. All sizing on this level is relative to the client area of the parent tab control.
Collapse | Copy
Code
void TabCtrl1_TabPages_OnSize(HWND hwnd, UINT state, int cx, int cy) { ///////////////////////////////////////////////////// // Sizing and positioning of Tab Page Widgets shall // be handled here so that they remain consistent with // the tab page. // Remember that the hwnd changes with each successive tab page RECT rc, chRc; int h, w; GetClientRect(hwnd, &rc); if(hwnd==TabCtrl_1.hTabPages[0]) { GetWindowRect(GetDlgItem(hwnd,CMD_CLICK_1),&chRc); h=chRc.bottom-chRc.top; w=chRc.right-chRc.left; MoveWindow(GetDlgItem(hwnd,CMD_CLICK_1), rc.left+(rc.right-rc.left-w) / 2, rc.top+(rc.bottom-rc.top)/4-h/2, chRc.right - chRc.left, chRc.bottom - chRc.top, FALSE); GetWindowRect(GetDlgItem(hwnd,CMD_CLICK_2),&chRc); h=chRc.bottom-chRc.top; w=chRc.right-chRc.left; MoveWindow(GetDlgItem(hwnd,CMD_CLICK_2), rc.left+(rc.right-rc.left-w)/2, rc.top+(rc.bottom-rc.top)/2-h/2, chRc.right - chRc.left,chRc.bottom - chRc.top, FALSE); GetWindowRect(GetDlgItem(hwnd,CMD_CLICK_3),&chRc); h=chRc.bottom-chRc.top; w=chRc.right-chRc.left; MoveWindow(GetDlgItem(hwnd,CMD_CLICK_3), rc.left+(rc.right-rc.left-w)/2, rc.top+(rc.bottom-rc.top)/4*3-h/2, chRc.right - chRc.left,chRc.bottom - chRc.top, FALSE); } else if(hwnd==TabCtrl_1.hTabPages[1]) }
There is one function for housekeeping,
TabControl_Destroy(), which releases the resources allocated to the tab
page dialogs.
Collapse | Copy
Code
void FormMain_OnClose(HWND hwnd) { PostQuitMessage(0);// turn off message loop TabControl_Destroy(&TabCtrl_1); TabControl_Destroy(&TabCtrl_2); EndDialog(hwnd, 0); }
Points of Interest
UsingGetClientRect()with the tab control does not always return the desired rectangle. It was necessary to calculate
the client area from the Window Rectangle of the tab control.
In order to tab around inside of a tab page -- in reality, a child dialog -- it is necessary to have a message loop running for that page. However, leaving an active message loop running when navigating between tab pages or tab controls produces all kinds
of weird behavior.
The solution is to create special-purpose message loop and keep track of the tab stops. I start the loop only when I enter the tab page via an arrow key and stop it when I exit that page after tabbing each of the tab stops.
Mouse clicks on a tab page or child control fire off a simulated
WM_KEYDOWNevent so that they might be handled in
a manner consistent with the key presses. When the mouse clicks to another tab page, we post
WM_SHOWWINDOWexplicitly
as an indicator to exit the loop.
Collapse | Copy
Code
static VOID TabPageMessageLoop(HWND hwnd) { MSG msg; int status; BOOL handled = FALSE; BOOL fFirstStop = FALSE; HWND hFirstStop; while ((status = GetMessage(&msg, NULL, 0, 0))) { if (-1 == status) // Exception { return; } else { //This message is explicitly posted from TabCtrl_OnSelChanged() to // indicate the closing of this page. Stop the Loop. if (WM_SHOWWINDOW == msg.message && FALSE == msg.wParam) return; //IsDialogMessage() dispatches WM_KEYDOWN to the tab page's child controls // so we'll sniff them out before they are translated and dispatched. if (WM_KEYDOWN == msg.message && VK_TAB == msg.wParam) { //Tab each tabstop in a tab page once and then return to // the tabCtl selected tab if (!fFirstStop) { fFirstStop = TRUE; hFirstStop = msg.hwnd; } else if (hFirstStop == msg.hwnd) { // Tab off the tab page HWND hTab = (HWND)GetWindowLong (GetParent(msg.hwnd), GWL_USERDATA); if(NULL == hTab) hTab = m_lptc->hTab; SetFocus(hTab); return; } } // Perform default dialog message processing using IsDialogM. . . handled = IsDialogMessage(hwnd, &msg); // Non dialog message handled in the standard way. if (!handled) { TranslateMessage(&msg); DispatchMessage(&msg); } } } // If we get here window is closing PostQuitMessage(0); return; }
Here, I navigate into a tab page with
VK_LEFT:
Collapse | Copy
Code
VOID TabCtrl_OnKeyDown(LPARAM lParam) { TC_KEYDOWN *tk = (TC_KEYDOWN *)lParam; int itemCount = TabCtrl_GetItemCount(tk->hdr.hwndFrom); int currentSel = TabCtrl_GetCurSel(tk->hdr.hwndFrom); if (itemCount <= 1) return; // Ignore if only one TabPage BOOL verticalTabs = GetWindowLong(m_lptc->hTab, GWL_STYLE) & TCS_VERTICAL; if (verticalTabs) { switch (tk->wVKey) { // // A bunch of case statements (snipped) // case VK_LEFT: //navigate within selected child tab page case VK_RIGHT: SetFocus(m_lptc->hTabPages[currentSel]); FirstTabstop_SetFocus(m_lptc->hTabPages[currentSel]); TabPageMessageLoop(m_lptc->hTabPages[currentSel]); break; default: return; } } // if(verticalTabs)
Here I enter by clicking a child control with the mouse. In addition to this, I forward all commands to the parent proc.
Collapse | Copy
Code
VOID TabPage_OnCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify) { //Forward all commands to the parent proc. //Note: We don't use SendMessage because on the receiving end, // that is: _OnCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify), // hwnd would = addressee and not the parent of the control. Intuition // favors the notion that hwnd is the parent of hwndCtl. FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, m_lptc->ParentProc); // If this WM_COMMAND message is a notification to parent window // i.e.: EN_SETFOCUS being sent when an edit control is initialized // do not engage the Message Loop or send any messages. if (codeNotify != 0) return; // Mouse clicks on a control should engage the Message Loop SetFocus(hwndCtl); FirstTabstop_SetFocus(hwnd); TabPageMessageLoop(hwnd); }
Here I enter by clicking a tab page with the mouse and simulate a keyboard entry.
Collapse | Copy
Code
VOID TabPage_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, INT x, INT y, UINT keyFlags) { // If Mouse click in tab page but not on control simulate keypress access // so that everything is handled in the same consistent way BOOL verticalTabs = GetWindowLong(m_lptc->hTab, GWL_STYLE) & TCS_VERTICAL; if (verticalTabs) { NMTCKEYDOWN nm = { m_lptc->hTab, GetDlgCtrlID(m_lptc->hTab), TCN_KEYDOWN, VK_LEFT, 0 }; FORWARD_WM_NOTIFY(nm.hdr.hwndFrom,nm.hdr.idFrom, &nm, SendMessage); } else { NMTCKEYDOWN nm = { m_lptc->hTab, GetDlgCtrlID(m_lptc->hTab), TCN_KEYDOWN, VK_DOWN, 0 }; FORWARD_WM_NOTIFY(nm.hdr.hwndFrom,nm.hdr.idFrom, &nm, SendMessage); } }
Finally, whenever there is more than one message loop active and a quit message is posted, that quit message must be re-posted to ensure that any child dialog process is discontinued. This is why I have the
PostQuitMessage(0)instruction
at the end of
TabPageMessageLoop().
History
July 5, 2006 version 1.0.0.0May 5, 2007 version 2.0.0.0: Some refactoring and small bug fixes
July 22, 2007 version 2.1: Refactored to make code more C++-friendly and updated article code snippets to reflect the current code.
June 12, 2009 version 3.0: Refactored to make tabbing more natural and handle mouse clicks in a manner consistent with keyboard input and updated article code snippets to reflect the current code.
License
This article, along with any associated source code and files, is licensed under The Code Project OpenLicense (CPOL)
转自:http://www.codeproject.com/Articles/14712/Win32-SDK-C-Tab-Control-Made-Easy
相关文章推荐
- 解决Win32 SDK编程添加list control控件程序无法运行的问题 . 和如何画进度条
- 解决Win32 SDK编程添加list control控件程序无法运行的问题
- 解决Win32 SDK编程添加list control控件程序无法运行的问题
- Win32 SDK程序创建一些控件(简单调用InitCommonControlsEx,并指定ICC_LISTVIEW_CLASSES控件就可以了)
- (Reporting Made Easy)The New ReportViewer Control in Visual Studio 2005 Provides Rich Capabilities to Fulfill All Your Reporting Needs
- WIN32 SDK 下子窗口VK_TAB键的焦点自动处理
- WIN32 SDK 下子窗口VK_TAB键的焦点自动处理
- Easy RadControl 之 RadTabControl(Silverlight)
- [Win32SDK基本]Static Control(1)Text Static Control 和 WM_CTLCOLORSTATIC
- [Win32SDK基本]Button Control(4)Radio Buttons
- [Win32SDK基本]Static Control(2)Image Static Control
- 标签控件TabControl的使用
- 给所有的Control添加发送键盘事件Tab事件,实现回车键自动跳转到下一个控件
- c#TabControl控件左边选项卡左边显示,文字横向显示
- P/Invoke C#使用Win32 SDK
- [ZT]我选择了WIN32 ASM和 WIN32 SDK
- 利用Tab Control创建属性页对话框
- MFC之标签控件Tab Control
- 编程读取其它进程中TabControl的内容
- Oracle Performance Regression Testing... made easy!