How to handle results from a PropertySheet? - winapi

I find different examples in the Internet of how to program a PropertySheet in WinAPI, but they are not complete.
The code I am using is shown below. I have a PropertySheet with 3 Tabs, each one with a Dialog.
The different Dialogs are called, when I click the Tabs, so far it is working.
However, when I leave the PropertySheet pressing OK button, how do I get the contents in the Textboxes etc. of each Dialog?
Normally I used to do that in the DialogProc when WM_COMMAND/IDOK was received using:
GetDlgItemText( hDlg,IDC_TEXTBOX1, buf, 100);
But in the PropertySheet there is only one OK Button for all dialogs, no WM_COMMAND/IDOK is received in the DialogProc.
What shall I do?
Resource_file:
IDD_DIALOG_1 DIALOGEX 0, 0, 385, 186
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
BEGIN
LTEXT "param",IDC_STATIC,6,23,39,10
EDITTEXT IDC_TEXTBOX1,48,20,237,15
END
C Source:
LRESULT CALLBACK
Dialog1(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
char buf[500];
char* ptr;
int p; // =lParam, rin of edited person
int f;
switch (message)
{
case WM_INITDIALOG:
{
SetDlgItemText(hDlg, IDC_TEXTBOX1, "something");
return 0;
}
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDOK: // never reached (OK Button belongs to the PropertySheet!)
}
}
}
return FALSE;
} /* Dialog1 */
INT_PTR DoPropertySheet(HWND hwndOwner, LPARAM p)
{
PROPSHEETPAGE psp[3];
PROPSHEETHEADER psh;
memset(psp,0,sizeof(psp));
for(int i=0;i<3; i++)
{
psp[i].dwSize = sizeof(PROPSHEETPAGE);
psp[i].dwFlags = PSP_USETITLE;
psp[i].hInstance = hInstance;
psp[i].lParam = p;
}
psp[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG_1);
psp[0].pfnDlgProc = (DLGPROC)Dialog1;
psp[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG_2);
psp[1].pfnDlgProc = (DLGPROC)Dialog2;
psp[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG_3);
psp[2].pfnDlgProc = (DLGPROC)Dialog3;
psh.dwSize = sizeof(PROPSHEETHEADER);
psh.dwFlags = PSH_PROPSHEETPAGE | PSH_NOAPPLYNOW;
psh.hwndParent = hwndOwner;
psh.hInstance = hInstance;
psh.pszIcon = 0;
psh.nPages = sizeof(psp) / sizeof(PROPSHEETPAGE);
psh.nStartPage = 0;
psh.ppsp = (LPCPROPSHEETPAGE) &psp;
psh.pfnCallback = NULL;
if (PropertySheet(&psh)) // 0:cancel, otherwise:1
{
//get contens of propertySheet here?? how??
}
return 0;
}

when user press OK or Apply all your pages got PSN_APPLY notification code. so you need look for WM_NOTIFY with PSN_APPLY code
when user press cancel you got PSN_RESET notification
INT_PTR CALLBACK PPDlgProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam)
{
union {
LPARAM lp;
NMHDR* hdr;
PSHNOTIFY* psn;
};
switch (umsg)
{
case WM_NOTIFY:
lp = lParam;
switch (hdr->code)
{
case PSN_APPLY:
DbgPrint("apply");
break;
case PSN_RESET:
DbgPrint("cancel\n");
break;
}
break;
}
return 0;
}

Related

How do I force a redraw of WIN32 scrolled window while holding scrollbar down

We've noticed an issue with ScrollWindowEx on our Windows app. Because there are a lot of child windows, if you hold down the scrollbar and move around, it causes a huge stall in DesktopWindowManager.
This is documented here
Why is my MFC application hanging after interacting with both scroll-bars?
So, I've taken the call to ScrollWindowEx out and now I can move my scrollbar with no issues, however the window and all its children only get drawn at the new position when I let go of the scrollbar.
It seems that my entire program stalls and gets stuck in the message loop when the scroll bar is held down. This isnt a problem if we can force the window that the scrollbar is attached to to update. But how do I do that ?
I've tried this
UpdateWindow(pPane);
RedrawWindow(pPane,NULL,NULL,RDW_ALLCHILDREN|RDW_INVALIDATE|RDW_ERASE | RDW_INTERNALPAINT | RDW_UPDATENOW);
And it does update and redraw - you can see the glitching - BUT it redraws it at the position that it was last left at. Not where the new position of the scroll bar would set it.
Im calling
SetScrollInfo(...)
with the correct parameters, otherwise it wouldn't work normally
How can I get it to redraw the window and its children with the correct parameters, whilst the scroll bar is held down?
This is my scroll message handler
case WM_VSCROLL:
{
int iVScrollPos;
SCROLLINFO vsi;
ZeroMemory(&vsi, sizeof(SCROLLINFO));
vsi.cbSize=sizeof(SCROLLINFO);
vsi.fMask=SIF_ALL;
GetScrollInfo(hWnd,SB_VERT,&vsi);
iVScrollPos=vsi.nPos;
switch(LOWORD(wParam))
{
case SB_LINEUP:
if(vsi.nPos>vsi.nMin)
vsi.nPos=vsi.nPos-10;
break;
case SB_PAGEUP:
vsi.nPos = vsi.nPos - vsi.nPage;
break;
case SB_LINEDOWN:
if(vsi.nPos<vsi.nMax)
vsi.nPos=vsi.nPos+10;
break;
case SB_PAGEDOWN:
vsi.nPos = vsi.nPos + vsi.nPage;
break;
case SB_THUMBTRACK:
vsi.nPos=vsi.nTrackPos;
break;
}
vsi.nMin=0;
vsi.nMax=pOW->dialogysize;
vsi.nPage=sy;
SetScrollInfo(hWnd,SB_VERT,&vsi,TRUE);
GetScrollInfo(hWnd, SB_VERT, &vsi);
if(vsi.nPos != iVScrollPos)
{
RedrawWindow(hWnd,NULL,NULL,RDW_ALLCHILDREN|RDW_INVALIDATE|RDW_ERASE | RDW_INTERNALPAINT | RDW_UPDATENOW);
}
The window redraws as you move the scroll bar, but in the same place it was when you held the mouse down on the scrollbar. It onlyupdates position when you let go of the mouse button.
Thanks
Shaun
EDIT - Ive created a sample that reproduces the behaviour. Note we have a separate window class to hold the multiple edit windows. I havent handled the WM_SIZE and WM_MOVE messages in this demo but the code should work with the window as it is.
IF USESCROLL is defined in and you move the scrollbar up and down it casues a lockup in DesktopWindowManager
IF USESCROLL is not commented in, then the trackPos in the structure still gets updated and I call reDrawWindow, but the child edit windows dont move as I scroll the scrollbar.
As I dont want to have the lockup, how can I get them to move please ?
Thanks again
Shaun
#include <windows.h>
#include <stdio.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK PaneProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "Class";
RegisterClass(&wc);
wc.lpfnWndProc = PaneProc;
wc.hInstance = hInstance;
wc.lpszClassName = "Pane";
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
(LPCSTR)"Class", // Window class
(LPCSTR)"Test", // Window text
WS_OVERLAPPEDWINDOW| WS_BORDER | WS_CAPTION | WS_CLIPSIBLINGS | WS_THICKFRAME | WS_SYSMENU, // Window style
200,200,400,400,
NULL,NULL,hInstance,NULL);
if (hwnd == NULL)
{
return 0;
}
HWND hwndPane = CreateWindowEx(
0, // Optional window styles.
(LPCSTR)"Pane", // Window class
(LPCSTR)"Test", // Window text
WS_OVERLAPPEDWINDOW|WS_VISIBLE|WS_VSCROLL, // Window style
220,220,400,400,
hwnd,NULL,hInstance,NULL);
if (hwndPane == NULL)
{
return 0;
}
for(int i=0;i<200;i++)
{
HWND eb = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("Edit"), NULL,ES_AUTOHSCROLL | WS_CHILD| WS_VISIBLE, 16, 16+24*i, 64, 24, hwndPane, (HMENU)i+10000, hInstance, NULL);
char tmp[64];
sprintf(tmp,"%d",i);
SetWindowText(eb,tmp);
}
ShowWindow(hwnd, nCmdShow);
ShowWindow(hwndPane, nCmdShow);
// Run the message loop.
while(1)
{
MSG msg = { };
while (PeekMessage(&msg, NULL, 0,0,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Sleep(10);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
LRESULT CALLBACK PaneProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
int iVScrollPos;
SCROLLINFO vsi;
ZeroMemory(&vsi, sizeof(SCROLLINFO));
vsi.cbSize=sizeof(SCROLLINFO);
vsi.fMask=SIF_ALL;
GetScrollInfo(hwnd,SB_VERT,&vsi);
iVScrollPos=vsi.nPos;
vsi.nMin=0;
vsi.nMax=16+24*200+40;
vsi.nPage=400;
SetScrollInfo(hwnd,SB_VERT,&vsi,TRUE);
GetScrollInfo(hwnd, SB_VERT, &vsi);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_VSCROLL:
{
int iVScrollPos;
SCROLLINFO vsi;
ZeroMemory(&vsi, sizeof(SCROLLINFO));
vsi.cbSize=sizeof(SCROLLINFO);
vsi.fMask=SIF_ALL;
GetScrollInfo(hwnd,SB_VERT,&vsi);
iVScrollPos=vsi.nPos;
switch(LOWORD(wParam))
{
case SB_THUMBTRACK:
vsi.nPos=vsi.nTrackPos;
break;
}
vsi.nMin=0;
vsi.nMax=16+24*200+40;
vsi.nPage=400;
SetScrollInfo(hwnd,SB_VERT,&vsi,TRUE);
GetScrollInfo(hwnd, SB_VERT, &vsi);
if(vsi.nPos != iVScrollPos)
{
float ScrollAmtY=-(vsi.nPos - iVScrollPos);
#define USESCROLL
#ifdef USESCROLL
int ok=ScrollWindowEx(hwnd ,0,ScrollAmtY,NULL,NULL,NULL,NULL,SW_INVALIDATE|SW_ERASE|SW_SCROLLCHILDREN);
#else
UpdateWindow(hwnd);
RedrawWindow(hwnd,NULL,NULL,RDW_ALLCHILDREN|RDW_INVALIDATE|RDW_ERASE | RDW_INTERNALPAINT | RDW_UPDATENOW);
#endif
}
return 0;
}
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Direct2D: moving window turns gray

I've started Direct2D from a very simple example.
Acquire the factory and ID2D1HwndRenderTarget, then handle WM_PAINT message to draw just a background with a solid color using "Clear" function.
It's work fine, until I start to move the window. When the window is moving it turns gray like nothing is drawing. I've tried to draw an ellipse, and the result is the same.
How could one present the window content with the window moving?
P.S. In case the code is needed
#include <Windows.h>
#include <d2d1_1.h>
#pragma comment(lib,"d2d1")
ID2D1Factory * d2factory_ptr = NULL;
ID2D1HwndRenderTarget * renderTarget_ptr = NULL;
LRESULT CALLBACK mainWinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(
HINSTANCE hInstance
, HINSTANCE prevInstance
, LPWSTR cmd
, int nCmdShow
) {
WNDCLASSEX wndClassStruct;
ZeroMemory(&wndClassStruct, sizeof(WNDCLASSEX));
wndClassStruct.cbSize = sizeof(WNDCLASSEX);
wndClassStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
wndClassStruct.style = CS_HREDRAW | CS_VREDRAW;
wndClassStruct.hInstance = hInstance;
wndClassStruct.lpfnWndProc = mainWinProc;
wndClassStruct.lpszClassName = TEXT("MainWnd");
RegisterClassEx(&wndClassStruct);
RECT windowRect = { 0,0,640,480};
AdjustWindowRectEx(&windowRect, WS_OVERLAPPEDWINDOW, 0, WS_EX_APPWINDOW);
HWND hWnd = CreateWindowEx(WS_EX_APPWINDOW, TEXT("MainWnd"), TEXT("Direct 2D Test Window"), WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_VISIBLE, CW_USEDEFAULT, 0, windowRect.right-windowRect.left, windowRect.bottom-windowRect.top, NULL, NULL, hInstance, 0);
{
D2D1_FACTORY_OPTIONS fo;
ZeroMemory(&fo, sizeof(D2D1_FACTORY_OPTIONS));
IID const factoryIID = IID_ID2D1Factory1;
HRESULT res = S_OK;
if (S_OK != (res = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factoryIID, &fo, &d2factory_ptr))) {
return 0;
}
RECT clientRect;
GetClientRect(hWnd, &clientRect);
D2D1_RENDER_TARGET_PROPERTIES renderTargetProps;
ZeroMemory(&renderTargetProps, sizeof(D2D1_RENDER_TARGET_PROPERTIES));
renderTargetProps.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
renderTargetProps.pixelFormat = (D2D1_PIXEL_FORMAT) { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
renderTargetProps.dpiX = 0;
renderTargetProps.dpiY = 0;
renderTargetProps.usage = D2D1_RENDER_TARGET_USAGE_FORCE_BITMAP_REMOTING;
renderTargetProps.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;
D2D1_HWND_RENDER_TARGET_PROPERTIES hwndRenderProps;
ZeroMemory(&hwndRenderProps, sizeof(D2D1_HWND_RENDER_TARGET_PROPERTIES));
hwndRenderProps.hwnd = hWnd;
hwndRenderProps.pixelSize = (D2D1_SIZE_U) { clientRect.right - clientRect.left, clientRect.bottom - clientRect.top };
hwndRenderProps.presentOptions = D2D1_PRESENT_OPTIONS_NONE;
if (S_OK != (res = d2factory_ptr->lpVtbl->CreateHwndRenderTarget(d2factory_ptr, &renderTargetProps, &hwndRenderProps, &renderTarget_ptr))) {
return 0;
}
}
ShowWindow(hWnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DestroyWindow(hWnd);
if(NULL != renderTarget_ptr)
renderTarget_ptr->lpVtbl->Base.Base.Base.Release((IUnknown*)renderTarget_ptr);
if (NULL != d2factory_ptr)
d2factory_ptr->lpVtbl->Base.Release((IUnknown*)d2factory_ptr);
return 0;
}
LRESULT onPaintMainWindow() {
ID2D1RenderTargetVtbl renderTargetFuncs = renderTarget_ptr->lpVtbl->Base;
ID2D1RenderTarget * This = (ID2D1RenderTarget*)renderTarget_ptr;
D2D1_TAG tag1, tag2;
D2D1_COLOR_F backgroundClr = (D2D1_COLOR_F) { 0.0, 0.5, 1.0, 1.0 };
renderTargetFuncs.BeginDraw(This);
renderTargetFuncs.Clear(This, &backgroundClr);
renderTargetFuncs.EndDraw(This, &tag1, &tag2);
return 0;
}
LRESULT CALLBACK mainWinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (WM_PAINT == uMsg)
return onPaintMainWindow();
if (WM_DESTROY == uMsg) {
PostQuitMessage(0); return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
Configure your WNDCLASSEX to not have a background brush.
Replace this line:
wndClassStruct.hbrBackground = (HBRUSH)COLOR_WINDOW;
With this:
wndClassStruct.hbrBackground = GetStockObject(NULL_BRUSH);
Alternatively, you can modify mainWndProc to swallow the WM_ERASEBKGND messages. It achieves the same effect by not allowing the window to erase itself before issuing a WM_PAINT.
LRESULT CALLBACK mainWinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (WM_PAINT == uMsg)
return onPaintMainWindow();
if (uMsg == WM_ERASEBKGND)
{
// ignore requests to erase the background since the wm_paint
// handler is going to redraw the entire window.
return 0;
}
if (WM_DESTROY == uMsg) {
PostQuitMessage(0); return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

How to implement "teamviewer quickconnect"-like button into foreign window?

I'd like to put a button in a foreign windows' title bar, much like Teamviewer does with the Quickconnect feature, or like Chrome has one in the top-right for switching users.
I know this is a repeat of How is Teamviewers Quickconnect button accomplished?
I'm just wondering if it would be possible to get a working example or a link to an open-source program that implements this. The answers given there are rather advanced for me. As in, how am I supposed to "hook" and "intercept" WM_NCPAINT message and so on.
This is the most simple example i can develop:
You need Visual Studio, add 2 project to the solution:
first project (HookDLL) is a dll project, second (Running app) is a win32 console project
in main.cpp (at project Running app) add this:
__declspec(dllimport) void RunHook();
int _tmain(int argc, _TCHAR* argv[])
{
RunHook();
return 0;
}
in dllmain button.cpp (at HookDLL project) add this code:
#include <Windows.h>
#include <stdio.h>
HINSTANCE hinstDLL;
HHOOK hhook_wndproc;
HWND b = NULL;
HBRUSH blue_brush = NULL, yellow_brush, red_brush;
int button_status = 0;
LRESULT CALLBACK DefaultWindowProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_CREATE:
if(!blue_brush)
{
blue_brush = CreateSolidBrush(RGB(0, 0, 255));
yellow_brush = CreateSolidBrush(RGB(255, 255, 0));
red_brush = CreateSolidBrush(RGB(255, 0, 0));
}
break;
case WM_PAINT:
{
HBRUSH b;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
switch(button_status)
{
case 0:
b = blue_brush;
break;
case 1:
b = yellow_brush;
break;
default:
b = red_brush;
}
FillRect(hdc, &ps.rcPaint, b);
EndPaint(hwnd, &ps);
}
return 0;
case WM_MOUSEMOVE:
if(button_status == 0)
{
SetTimer(hwnd, 1, 100, NULL);
button_status = 1;
InvalidateRect(hwnd, NULL, false);
}
return 0;
case WM_TIMER:
{
POINT pt;
GetCursorPos(&pt);
if(button_status == 1 && WindowFromPoint(pt) != hwnd)
{
KillTimer(hwnd, 1);
button_status = 0;
InvalidateRect(hwnd, NULL, false);
}
}
return 0;
case WM_MOUSELEAVE:
button_status = 0;
InvalidateRect(hwnd, NULL, false);
return 0;
case WM_LBUTTONDOWN:
button_status = 2;
InvalidateRect(hwnd, NULL, false);
return 0;
case WM_LBUTTONUP:
if(button_status == 2) MessageBox(GetParent(hwnd), "teamviewer like button clicked", "Message", MB_OK);
button_status = 1;
InvalidateRect(hwnd, NULL, false);
return 0;
}
return DefWindowProc(hwnd, Msg, wParam, lParam);
}
void InitButton(HWND parent, int xPos, int yPos)
{
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = DefaultWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hinstDLL;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = "DEFAULT_CLASS";
RegisterClass(&wc);
b = CreateWindowEx(WS_EX_TOOLWINDOW, "DEFAULT_CLASS", NULL, WS_BORDER | WS_POPUP | WS_VISIBLE, xPos, yPos, 20, 20, parent, NULL, hinstDLL, NULL);
}
LRESULT WINAPI HookCallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(nCode >= 0 && lParam != 0)
{
CWPRETSTRUCT *msg = (CWPRETSTRUCT*)lParam;
if(!IsWindow(msg->hwnd) || (GetWindowLong(msg->hwnd, GWL_STYLE) & WS_CHILD) != 0) return CallNextHookEx(hhook_wndproc, nCode, wParam, lParam);
switch(msg->message)
{
case WM_SHOWWINDOW:
if(!b && msg->wParam != 0)
{
b = (HWND)1;// see NOTES 5
RECT a;
GetWindowRect(msg->hwnd, &a);
InitButton(msg->hwnd, a.right - 150, a.top);
}
break;
case WM_SIZE:
if(GetParent(b) == msg->hwnd)
{
RECT a;
GetWindowRect(msg->hwnd, &a);
SetWindowPos(b, 0, a.right - 150, a.top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
break;
case WM_SIZING:
case WM_MOVING:
if(GetParent(b) == msg->hwnd)
{
RECT* lprc = (LPRECT) msg->lParam;
SetWindowPos(b, 0, lprc->right - 150, lprc->top, 0, 0, SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
}
}
return CallNextHookEx(hhook_wndproc, nCode, wParam, lParam);
}
__declspec(dllexport) void RunHook()
{
hhook_wndproc = SetWindowsHookEx(WH_CALLWNDPROCRET, HookCallWndProc, hinstDLL, 0);
char aux[10];
gets_s(aux);
UnhookWindowsHookEx(hhook_wndproc);
}
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hinstDLL = hModule;
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Now, make dll project depedent of running app project in project->project dependencies:
NOTES:
1) i dont use NC paint code, because not always works, if windows buffer non client region, erases customized NC paint buttons
2) in 64 bits enviroment, you need to run a 32 bits hook for 32 bits apps, and other hook for 64 bits apps
3) YOU CAN NOT DEBUG YOUR HOOK WHEN IS CONNECTED TO ANOTHER PROCCESS, i suggest you debug it with a windows in your app and thread, and test it late in another proccess when is working
4) i use a button like approach for simplicity
5) this line
b = (HWND)1;
I use it for "solve" a multi thread problem, i suggest you make better code (syncronization) this case
HOW THIS WORKS:
run the app
when it start install a hook
open any other app (same 32/64 bits, see NOTE 2)
you must see a blue button at left side of title bar
click it and see a message box
for finish hook: just press ENTER at console window
CODE FLOW:
Running app just calls RunHook() procedure in dll, and dll do the work
RunHook() in dll starts a hook HookCallWndProc (global)
HookCallWndProc captures required message and creates the window button using InitButton() procedure
DefaultWindowProc handles button message

How to handle click events on a Windows treeview items

How can I handle individual items being clicked in a MS Windows treeview ?
My windows proc has :
LRESULT CALLBACK WndProcTreeView(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT paintStruct;
HDC hDC;
switch (message)
{
case WM_PAINT:
{
hDC = BeginPaint(hwnd, &paintStruct);
EndPaint(hwnd, &paintStruct);
break;
}
case WM_NOTIFY:
{
switch (reinterpret_cast<LPNMHDR>(lParam)->code) {
case NM_CLICK:
MessageBox(nullptr, "click", "click", MB_OK);
}
}
default:
{
return DefWindowProc(hwnd, message, wParam, lParam);
break;
}
}
Which outputs a message box when I click on the treeview control. How can I handle individual elements ?
Example of a treeview item being added to the list :
std::string vTxt = std::string("Vertex count : ") + std::to_string(mesh.v.size());
tvinsert.hInsertAfter = mesh_items[mesh_items.size() - 1];
tvinsert.hParent = mesh_items[mesh_items.size() - 1];
tvinsert.item.mask = TVIF_TEXT;
tvinsert.item.pszText = (LPSTR)vTxt.c_str();
mesh_items_sub.push_back((HTREEITEM)SendMessage(hwnd, TVM_INSERTITEM, 0, (LPARAM)&tvinsert));
I have seen using SendDlgItemMessage instead (which gives an ID as LOWORD(wParam) inside the windows proc) but it requires IDs set in a resource file - which I don't know how to create.
Two things I needed for my code to work : first give each item a lparam value and changing TVIF_TEXT as item's mask to TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM (TVIF_PARAM allowing for an lparam to be passed to the window proc thus identifying the controller).
Working code excerpt :
TV_INSERTSTRUCT tvinsert;
// ...
tvinsert.hInsertAfter = Root;
tvinsert.hParent = Root;
tvinsert.item.pszText = std::string("some text...").c_str();
tvinsert.item.lParam = ID_SOME_ID; // << #defined constant or plain int
tvinsert.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
root_sub.push_back((HTREEITEM)SendMessage(hwnd, TVM_INSERTITEM, 0, (LPARAM)&tvinsert));
// window proc code below
LRESULT CALLBACK WndProcTreeView(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT paintStruct;
HDC hDC;
switch (message)
{
case WM_PAINT:
{
hDC = BeginPaint(hwnd, &paintStruct);
EndPaint(hwnd, &paintStruct);
break;
}
case WM_NOTIFY:
{
LPNM_TREEVIEW pntv = (LPNM_TREEVIEW)lParam;
if (pntv->hdr.code == TVN_SELCHANGED) {
switch (pntv->itemNew.lParam) {
case ID_SOME_ID:
std::cout << "ID_SOME_ID selected caught here..." << std::endl;
break;
}
}
}
default:
{
return DefWindowProc(hwnd, message, wParam, lParam);
break;
}
}
return 0;
}
Good explanation/example here (in french) http://chgi.developpez.com/windows/treeview/

Add Controls To Specific Tab Page in TabControl in C++ Win32

I want to add some controls to the tab page in tabcontrol but it seems that it will be added to all pages and there is not tab page in tabcontrol by default.
I have read these links below but they did not help me and in some parts of them, confused me.
How to add controls to a Tab control
http://www.cplusplus.com/forum/windows/37161/
https://msdn.microsoft.com/en-us/library/bb760551.aspx
https://msdn.microsoft.com/en-us/library/hh298366.aspx
https://msdn.microsoft.com/en-us/library/ms645398.aspx
Here is my code :
[Code]:
#define ID_LBL 500
#define ID_BTN 501
#define ID_TBC 502
HWND hWnd;
void InserTabItem(HWND handle, LPWSTR text, int id)
{
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = wcslen(text);
SendMessage(handle, TCM_INSERTITEM, id, LPARAM(&tci));
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CREATE:
{
HWND button_handle = 0;
HWND label_handle = 0;
HWND tab_handle = 0;
tab_handle = CreateWindowEx(WS_EX_CONTROLPARENT, WC_TABCONTROL, 0, WS_VISIBLE | WS_CHILD, 10, 10, 200, 150, hWnd, HMENU(ID_TBC), 0, 0);
InserTabItem(tab_handle, L"page1", 0);
InserTabItem(tab_handle, L"page2", 1);
button_handle = CreateWindowEx(0, WC_BUTTON, L"test-button-page2", WS_VISIBLE | WS_CHILD, 10, 50, 150, 30, tab_handle, HMENU(ID_BTN), 0, 0);
label_handle = CreateWindowEx(0, WC_STATIC, L"test-label-page1", WS_VISIBLE | WS_CHILD, 10, 100, 150, 30, tab_handle, HMENU(ID_LBL), 0, 0);
}
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
break;
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreviewInstance, LPSTR lpcmdline, int ncmdshow)
{
WNDCLASSEX wndexcls;
wndexcls.lpszClassName = L"win";
wndexcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndexcls.hbrBackground = (HBRUSH)(COLOR_3DSHADOW + 1);
wndexcls.lpszMenuName = NULL;
wndexcls.style = NULL;
wndexcls.hInstance = hInstance;
wndexcls.cbSize = sizeof(WNDCLASSEX);
wndexcls.cbClsExtra = 0;
wndexcls.cbWndExtra = 0;
wndexcls.lpfnWndProc = WndProc;
RegisterClassEx(&wndexcls);
hWnd = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_CONTROLPARENT, L"win", L"TestApp", WS_OVERLAPPEDWINDOW, 100, 100, 640, 380, 0, 0, hInstance, 0);
ShowWindow(hWnd, ncmdshow);
UpdateWindow(hWnd);
MSG wnd_msg;
while (GetMessage(&wnd_msg, NULL, 0, 0)>0)
{
TranslateMessage(&wnd_msg);
DispatchMessage(&wnd_msg);
}
return (int)wnd_msg.wParam;
}
I am looking for a safe and proper implementation.
Thanks for any help
========================================================
[Update]:
Thanks for comments but no answer in detail :(
Although that is not the implementation I am looking for(NotDialogBased), But from the forth link I mentioned :
How to Create a Tabbed Dialog Box :
https://msdn.microsoft.com/en-us/library/hh298366.aspx
Here is my code of that page :
[resource.h]:
#define IDD_Page1 101
#define IDD_Page2 102
#define IDD_Page3 103
#define IDD_Main_Dialog 104
#define IDC_BTN_Page1 1001
#define IDC_BTN2_Page1 1002
#define IDC_BTN_Page2 1013
#define IDC_BTN_Page3 1014
[Resource.rc]:
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//////////////////////////////////////////////////
//
// Dialog
//
IDD_Page1 DIALOGEX 0, 0, 313, 178
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
PUSHBUTTON "Button2-Page1",IDC_BTN2_Page1,129,107,67,22
PUSHBUTTON "Button-Page1",IDC_BTN_Page1,127,77,67,22
END
IDD_Page2 DIALOGEX 0, 0, 309, 177
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
PUSHBUTTON "Button-Page2",IDC_BTN_Page2,120,77,60,18
END
IDD_Page3 DIALOGEX 0, 0, 309, 177
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
PUSHBUTTON "Button-Page3",IDC_BTN_Page3,120,73,64,25
END
IDD_Main_Dialog DIALOGEX 0, 0, 309, 177
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_CHILD
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
END
////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
IDD_Page1, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 306
TOPMARGIN, 7
BOTTOMMARGIN, 171
END
IDD_Page2, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 302
TOPMARGIN, 7
BOTTOMMARGIN, 170
END
IDD_Page3, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 302
TOPMARGIN, 7
BOTTOMMARGIN, 170
END
IDD_Main_Dialog, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 302
TOPMARGIN, 7
BOTTOMMARGIN, 170
END
END
#endif // APSTUDIO_INVOKED
#endif
[Main.cpp]:
#include <windows.h>
#include <CommCtrl.h>
#include "resource.h"
#pragma comment(lib, "ComCtl32.lib")
#define C_PAGES 3
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
HWND Win_Handle;
HWND Dailog_Handle;
HINSTANCE hInstance_Win_Global;
typedef struct {
WORD dlgVer;
WORD signature;
DWORD helpID;
DWORD exStyle;
DWORD style;
WORD cDlgItems;
short x;
short y;
short cx;
short cy;
WORD pointsize;
WORD weight;
BYTE italic;
BYTE charset;
} DLGTEMPLATEEX;
typedef struct tag_dlghdr {
HWND hwndTab; // tab control
HWND hwndDisplay; // current child dialog box
RECT rcDisplay; // display rectangle for the tab control
DLGTEMPLATEEX *apRes[C_PAGES];
} DLGHDR;
void InserTabItem(HWND handle, LPWSTR text, int id)
{
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = wcslen(text);
SendMessage(handle, TCM_INSERTITEM, id, LPARAM(&tci));
}
DLGTEMPLATEEX* DoLockDlgRes(LPCTSTR lpszResName)
{
HRSRC hrsrc = FindResource(NULL, lpszResName, RT_DIALOG);
HGLOBAL hglb = LoadResource(hInstance_Win_Global, hrsrc);
return (DLGTEMPLATEEX *)LockResource(hglb);
}
VOID WINAPI OnChildDialogInit(HWND hwndDlg)
{
HWND hwndParent = GetParent(hwndDlg);
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(
hwndParent, GWL_USERDATA);
SetWindowPos(hwndDlg, NULL, pHdr->rcDisplay.left,
pHdr->rcDisplay.top,//-2,
(pHdr->rcDisplay.right - pHdr->rcDisplay.left),
(pHdr->rcDisplay.bottom - pHdr->rcDisplay.top),
SWP_SHOWWINDOW);
return;
}
VOID OnSelChanged(HWND hwndDlg)
{
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(hwndDlg, GWL_USERDATA);
int iSel = TabCtrl_GetCurSel(pHdr->hwndTab);
if (pHdr->hwndDisplay != NULL)
DestroyWindow(pHdr->hwndDisplay);
pHdr->hwndDisplay = CreateDialogIndirect(hInstance_Win_Global,
(DLGTEMPLATE *)pHdr->apRes[iSel], hwndDlg,DialogProc);
}
HRESULT OnTabbedDialogInit(HWND hwndDlg)
{
INITCOMMONCONTROLSEX iccex;
DWORD dwDlgBase = GetDialogBaseUnits();
int cxMargin = LOWORD(dwDlgBase) / 4;
int cyMargin = HIWORD(dwDlgBase) / 8;
TCITEM tie;
RECT rcTab;
HWND hwndButton;
RECT rcButton;
int i;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&iccex);
DLGHDR *pHdr = (DLGHDR *)LocalAlloc(LPTR, sizeof(DLGHDR));
SetWindowLong(hwndDlg, GWL_USERDATA, (LONG)pHdr);
pHdr->hwndTab = CreateWindow(
WC_TABCONTROL, L"",
WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
0, 0, 300, 200,
hwndDlg, NULL, hInstance_Win_Global, NULL
);
if (pHdr->hwndTab == NULL)
{
return HRESULT_FROM_WIN32(GetLastError());
}
tie.mask = TCIF_TEXT | TCIF_IMAGE;
tie.iImage = -1;
tie.pszText = L"First";
TabCtrl_InsertItem(pHdr->hwndTab, 0, &tie);
tie.pszText = L"Second";
TabCtrl_InsertItem(pHdr->hwndTab, 1, &tie);
tie.pszText = L"Third";
TabCtrl_InsertItem(pHdr->hwndTab, 2, &tie);
pHdr->apRes[0] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page1));
pHdr->apRes[1] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page2));
pHdr->apRes[2] = DoLockDlgRes(MAKEINTRESOURCE(IDD_Page3));
SetRectEmpty(&rcTab);
for (i = 0; i < C_PAGES; i++)
{
if (pHdr->apRes[i]->cx > rcTab.right)
rcTab.right = pHdr->apRes[i]->cx;
if (pHdr->apRes[i]->cy > rcTab.bottom)
rcTab.bottom = pHdr->apRes[i]->cy;
}
MapDialogRect(hwndDlg, &rcTab);
TabCtrl_AdjustRect(pHdr->hwndTab, TRUE, &rcTab);
OffsetRect(&rcTab, cxMargin - rcTab.left, cyMargin - rcTab.top);
CopyRect(&pHdr->rcDisplay, &rcTab);
TabCtrl_AdjustRect(pHdr->hwndTab, FALSE, &pHdr->rcDisplay);
SetWindowPos(pHdr->hwndTab, NULL, rcTab.left, rcTab.top,
rcTab.right - rcTab.left, rcTab.bottom - rcTab.top,
SWP_NOZORDER);
hwndButton = GetDlgItem(hwndDlg, IDC_BTN_Page1);
SetWindowPos(hwndButton, NULL,
rcTab.left, rcTab.bottom + cyMargin, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
GetWindowRect(hwndButton, &rcButton);
rcButton.right -= rcButton.left;
rcButton.bottom -= rcButton.top;
hwndButton = GetDlgItem(hwndDlg, IDC_BTN2_Page1);
SetWindowPos(hwndButton, NULL,
rcTab.left + rcButton.right + cxMargin,
rcTab.bottom + cyMargin, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
SetWindowPos(hwndDlg, NULL, 0, 0,
rcTab.right + cyMargin + (2 * GetSystemMetrics(SM_CXDLGFRAME)),
rcTab.bottom + rcButton.bottom + (2 * cyMargin)
+ (2 * GetSystemMetrics(SM_CYDLGFRAME))
+ GetSystemMetrics(SM_CYCAPTION),
SWP_NOMOVE | SWP_NOZORDER);
OnSelChanged(hwndDlg);
return S_OK;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_CREATE:
{
Dailog_Handle = CreateDialogParam(hInstance_Win_Global, MAKEINTRESOURCE(IDD_Main_Dialog), hWnd, DialogProc, 0);
ShowWindow(Dailog_Handle, SW_SHOWDEFAULT);
UpdateWindow(Dailog_Handle);
SetWindowPos(Dailog_Handle, 0, 10, 10, 500, 300, SWP_NOZORDER);
}
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
break;
}
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_INITDIALOG:
{
OnTabbedDialogInit(hWnd);
OnChildDialogInit(hWnd);
return (INT_PTR)TRUE;
}
break;
case WM_NOTIFY:
{
switch (((LPNMHDR)lParam)->code)
{
case TCN_SELCHANGE:
{
OnSelChanged(hWnd);
}
break;
default:
break;
}
}
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return (INT_PTR)FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreviewInstance, LPSTR lpcmdline, int ncmdshow)
{
WNDCLASSEX wndexcls;
wndexcls.lpszClassName = L"win";
wndexcls.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndexcls.hCursor = LoadCursor(NULL, IDC_ARROW);
wndexcls.hbrBackground = (HBRUSH)(COLOR_3DSHADOW + 1);
wndexcls.lpszMenuName = NULL;
wndexcls.style = NULL;
wndexcls.hInstance = hInstance;
wndexcls.cbSize = sizeof(WNDCLASSEX);
wndexcls.cbClsExtra = 0;
wndexcls.cbWndExtra = 0;
wndexcls.lpfnWndProc = WndProc;
RegisterClassEx(&wndexcls);
Win_Handle = CreateWindowEx(WS_EX_CLIENTEDGE | WS_EX_CONTROLPARENT, L"win", L"TestApp", WS_OVERLAPPEDWINDOW, 100, 100, 640, 380, 0, 0, hInstance, 0);
hInstance_Win_Global = hInstance;
ShowWindow(Win_Handle, SW_SHOWDEFAULT);
UpdateWindow(Win_Handle);
MSG wnd_msg;
while (GetMessage(&wnd_msg, NULL, 0, 0)>0)
{
TranslateMessage(&wnd_msg);
DispatchMessage(&wnd_msg);
}
return (int)wnd_msg.wParam;
}
The Problem is that , after debugging , the application exites and does not show anything. if I comment the OnSelChanged(hWnd) and OnChildDialogInit(hWnd)
the application starts normally and shows the tabcontrol but not the controls on the pages. it seems that the problem is here.
the output log :
First-chance exception at 0x00BE1886 in testcppapp.exe: 0xC0000005: Access violation reading location 0x00000014.
The program '[16220] testcppapp.exe' has exited with code 0 (0x0).
I have Read the link below about Access violation reading location:
http://www.cplusplus.com/forum/general/17094/
But I can not fix the problem.
Please Post your Answer and Explain about it , not just brief in comments !
Thanks for any help.
One problem is here:
pHdr->hwndDisplay = CreateDialogIndirect(hInstance_Win_Global,
(DLGTEMPLATE*)pHdr->apRes[iSel], hwndDlg, DialogProc);
You are reusing the same dialog procedure for both main dialog and child dialogs. Main dialog creates child dialogs, child dialogs use the same procedure to create child dialogs... Also there are no error checks.
Beyond that, this code is too complicated. Just use a dialog box for main window. Create a new dialog IDD_DIALOG1 and drag/drop a tab control in it. Assign IDC_TAB1 for tab control ID. Try starting with this code instead:
#include <Windows.h>
#include <CommCtrl.h>
#include "Resource.h"
#pragma comment(lib,"comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
HINSTANCE g_hinst;
struct TData {
HWND page1, page2, page3;
HWND tab;
} data;
BOOL CALLBACK DialogPage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch(msg) {
case WM_COMMAND:
switch (wp) {
//...
}
}
return FALSE;
}
void OnSelChange() {
int sel = TabCtrl_GetCurSel(data.tab);
ShowWindow(data.page1, (sel == 0) ? SW_SHOW : SW_HIDE);
ShowWindow(data.page2, (sel == 1) ? SW_SHOW : SW_HIDE);
}
BOOL CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_INITDIALOG: {
data.page1 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_Page1), hwnd, DialogPage);
data.page2 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_Page2), hwnd, DialogPage);
data.tab = GetDlgItem(hwnd, IDC_TAB1);
if (data.tab)
{
TCITEM tci = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = L"Page1";
TabCtrl_InsertItem(data.tab, 0, &tci);
tci.pszText = L"Page2";
TabCtrl_InsertItem(data.tab, 1, &tci);
RECT rc;//find tab control's rectangle
GetWindowRect(data.tab, &rc);
POINT offset = { 0 };
ScreenToClient(hwnd, &offset);
OffsetRect(&rc, offset.x, offset.y); //convert to client coordinates
rc.top += 50;
SetWindowPos(data.page1, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);
SetWindowPos(data.page2, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);
OnSelChange();
}
break;
}
case WM_NOTIFY: {
switch (((LPNMHDR)lp)->code) {
case TCN_SELCHANGE:
OnSelChange();
break;
}
}
break;
case WM_COMMAND:
switch (wp) {
case IDOK: EndDialog(hwnd, wp); break;
case IDCANCEL: EndDialog(hwnd, wp); break;
}
}
return FALSE;
}
int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE, LPWSTR, int)
{
g_hinst = hinst;
DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc);
return 0;
}
An approach that I am testing and looks promising is to use a dialog box template for each pane of the tab control within the modal dialog box.
For testing, I generated a starting Win32 GUI application and then hijacked the About box display triggered by IDM_ABOUT to display my own dialog box with a tab control rather than a standard About dialog.
case IDM_ABOUT:
// DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
{
CDialogTabTest x;
x.CreateModal (hWnd, hInst);
}
break;
After some research and refactoring and development I have the following approach.
I have created three classes for implementation: CDialogTest (handles the dialog box functionality), CDialogTabTest (handles the tab control within the dialog box), and CDialogTabPane (handles the individual tab panes that are displayed in the tab control).
The CDialogTest class provides the functionality for basic dialog box behavior. It allows for two kinds of dialog boxes, modal used to create the actual modal dialog box and non-modal used to create the tab control panes.
The CDialogTest class is designed to be used as a subclass and be extended for a particular dialog box or tab control pane.
The CDialogTabTest class extends CDialogTest to implement a modal dialog box. The CDialogTabPane class extends CDialogTest to implement a non-modal dialog box that is used as a tab control tab pane.
For MFC programmers some of the names may sound vaguely familiar as while working this out, I now understand some of the mechanics of the MFC classes for dialog boxes.
I put added some additional include files in the the stdafx.h file as that was a central place to put them for what I needed.
// TODO: reference additional headers your program requires here
#include "commctrl.h"
#include "windowsx.h"
#include <vector>
DialogTest.h
#pragma once
class CDialogTest
{
private:
static INT_PTR CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
public:
HINSTANCE m_hInst;
HWND m_hParent;
HWND m_hDlg;
CDialogTest();
CDialogTest(HINSTANCE hInst, HWND hParent = NULL);
virtual ~CDialogTest(void);
HWND CreateThing (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst);
INT_PTR WINAPI CreateModal (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst);
virtual int DoDataExchange (int iDir) { return 0; }
virtual int OnWmCommandInit () { return 0; }
virtual int OnWmNotify (LPNMHDR pNmhdr) { return 0; }
virtual int OnWmCommand (WPARAM wParam, LPARAM lParam) { return 0; }
};
and DialogTest.cpp
#include "StdAfx.h"
#include "DialogTest.h"
CDialogTest::CDialogTest(void)
{
}
CDialogTest::CDialogTest(HINSTANCE hInst, HWND hParent /* = NULL */) :
m_hInst (hInst), m_hParent(hParent)
{
}
CDialogTest::~CDialogTest(void)
{
}
INT_PTR CALLBACK CDialogTest::DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
{
// Set the provided object pointer into the dialog box user area
// so that we can use it to provide the necessary notifications
// to what ever object which has derived from this class.
::SetWindowLongPtr (hDlg, DWLP_USER, lParam);
// ensure that the object knows its dialog handle.
CDialogTest *pObj = (CDialogTest *)lParam;
pObj->m_hDlg = hDlg;
// call the objects handlers to initialize the dialog further
// and to then populate the data displayed in the dialog.
pObj->OnWmCommandInit ();
pObj->DoDataExchange (1);
}
return (INT_PTR)TRUE;
case WM_COMMAND:
// if this command is an Ok or Cancel button press then we are
// done. other command messages are routed to the derived object's
// how command message handler for further processing.
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
if (LOWORD(wParam) == IDOK) {
LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
CDialogTest *pObj = (CDialogTest *)lP;
pObj->DoDataExchange (0);
}
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
} else {
LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
CDialogTest *pObj = (CDialogTest *)lP;
pObj->OnWmCommand (wParam, lParam);
}
break;
case WM_NOTIFY:
{
LPARAM lP = ::GetWindowLongPtr (hDlg, DWLP_USER);
CDialogTest *pObj = (CDialogTest *)lP;
LPNMHDR pNmhdr = ((LPNMHDR)lParam);
pObj->OnWmNotify (pNmhdr);
}
break;
}
return (INT_PTR)FALSE;
}
// We need two different kinds of dialog create functions as we support
// both a modal dialog and a modeless dialog. The modal dialog is used to
// display an actual dialog box. The modeless dialog is used to display
// a tab control pane using a dialog template.
INT_PTR WINAPI CDialogTest::CreateModal (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst)
{
m_hInst = hInst;
m_hParent = hWnd;
// display the modal dialog box and return the identifier for the button clicked.
return DialogBoxParam(hInst, lpTemplateName, hWnd, &CDialogTest::DlgProc, (LPARAM)this);
}
HWND CDialogTest::CreateThing (HWND hWnd, LPCWSTR lpTemplateName, HINSTANCE hInst)
{
m_hInst = hInst;
m_hParent = hWnd;
// create a modeless dialog box to be used as a tab control pane or similar
// non-modal dialog box use.
m_hDlg = ::CreateDialogParam(hInst, lpTemplateName, hWnd, &CDialogTest::DlgProc, (LPARAM)this);
return m_hDlg;
}
The class CDialogTabTest creates the modal dialog box that is being displayed as a result of the IDM_ABOUT menu selection message in the main application message handler.
DialogTabTest.h
#pragma once
#include "stdafx.h"
#include "DialogTest.h"
#include "DialogTabPane.h"
#include "resource.h"
struct CCheckBoxData
{
UINT iCntrlId;
int iCheck;
std::wstring prompt;
};
typedef std::vector<CCheckBoxData> CCheckBoxVector;
class CDialogTabTest : public CDialogTest
{
protected:
CDialogTabPane m_hTabPane1;
CDialogTabPane m_hTabPane2;
CDialogTabPane m_hTabPane3;
enum {IDD_DIALOG = IDD_DIALOG_TAB00};
public:
CDialogTabTest(void);
virtual ~CDialogTabTest(void);
INT_PTR WINAPI CreateModal (HWND hWnd, HINSTANCE hInst);
static int SetValues (CCheckBoxData &x, HWND hDlg);
static int GetValues (CCheckBoxData &x, HWND hDlg);
virtual int DoDataExchange (int iDir);
virtual int OnWmCommandInit ();
virtual int OnWmNotify (LPNMHDR pNmhdr);
};
and DialogTabTest.cpp which creates the modal dialog box and initializes it.
#include "StdAfx.h"
#include "DialogTabTest.h"
CDialogTabTest::CDialogTabTest(void)
{
}
CDialogTabTest::~CDialogTabTest(void)
{
}
int CDialogTabTest::SetValues (CCheckBoxData &x, HWND hDlg)
{
HWND hWnd = GetDlgItem (hDlg, x.iCntrlId);
::SetWindowText (hWnd, x.prompt.c_str());
Button_SetCheck (hWnd, x.iCheck);
return 0;
}
int CDialogTabTest::GetValues (CCheckBoxData &x, HWND hDlg)
{
HWND hWnd = GetDlgItem (hDlg, x.iCntrlId);
x.iCheck = Button_GetCheck (hWnd);
return 0;
}
int CDialogTabTest::OnWmCommandInit ()
{
// get the window handle for the tab control. This is going to
// be the parent window for all of the tab panes that are inserted
// into the tab control. By making the tab control the parent,
// the tab panes will be contained within the tab control and
// will flow with it should it be moved about.
HWND hTabCntrl = GetDlgItem (m_hDlg, IDC_TAB1);
TCITEM tie = {0};
tie.mask = TCIF_TEXT | TCIF_IMAGE;
tie.iImage = -1;
tie.pszText = L"First";
m_hTabPane1.CreateThing (&tie, 0, MAKEINTRESOURCE(IDD_DIALOG_TAB01), m_hInst, hTabCntrl);
tie.pszText = L"Second";
m_hTabPane2.CreateThing (&tie, 1, MAKEINTRESOURCE(IDD_DIALOG_TAB02), m_hInst, hTabCntrl);
tie.pszText = L"Third";
m_hTabPane3.CreateThing (&tie, 2, MAKEINTRESOURCE(IDD_DIALOG_TAB03), m_hInst, hTabCntrl);
// set the first tab to be displayed.
NMHDR Nmhdr = {hTabCntrl, 0, TCN_SELCHANGE};
m_hTabPane1.OnWmNotify (&Nmhdr);
return 0;
}
int CDialogTabTest::OnWmNotify (LPNMHDR pNmhdr)
{
// propagate the notification to the various panes in our tab control
// so that they can do what they need to do.
m_hTabPane1.OnWmNotify (pNmhdr);
m_hTabPane2.OnWmNotify (pNmhdr);
m_hTabPane3.OnWmNotify (pNmhdr);
return 0;
}
int CDialogTabTest::DoDataExchange (int iDir)
{
if (iDir) {
CCheckBoxData iVal = {IDC_CHECK1, BST_UNCHECKED, L"DoData: Check Box 1 Set and Not Checked"};
SetValues (iVal, m_hTabPane1.m_hDlg);
iVal.iCntrlId = IDC_CHECK2;
iVal.iCheck = BST_CHECKED;
iVal.prompt = L"DoData: Check Box 2 is Set and Checked.";
SetValues (iVal, m_hTabPane1.m_hDlg);
iVal.iCntrlId = IDC_CHECK4;
iVal.iCheck = BST_CHECKED;
iVal.prompt = L"DoData: Check Box 4 is Set, Checked Tab 3.";
SetValues (iVal, m_hTabPane3.m_hDlg);
} else {
CCheckBoxData iVal = {IDC_CHECK1, BST_UNCHECKED, L""};
GetValues (iVal, m_hTabPane1.m_hDlg);
}
return 0;
}
INT_PTR WINAPI CDialogTabTest::CreateModal (HWND hWnd, HINSTANCE hInst)
{
// this is a modal dialog box so we use the CreateModal() method
// in order to create the dialog box and display it.
return CDialogTest::CreateModal(hWnd, MAKEINTRESOURCE(IDD_DIALOG), hInst);
}
Finally there is the CDialogTabPane class which implements the functionality for individual tab control tab panes.
DialogTabPane.h
#pragma once
#include "stdafx.h"
#include "DialogTest.h"
class CDialogTabPane : public CDialogTest
{
protected:
int m_iTab;
public:
DWORD m_dwLastError;
CDialogTabPane(void);
virtual ~CDialogTabPane(void);
HWND CreateThing (LPTCITEM pTie, int iTab, LPCWSTR lpTemplateName, HINSTANCE hInstance, HWND hWndParent);
// virtual int DoDataExchange (int iDir);
// virtual int OnWmCommandInit ();
virtual int OnWmNotify (LPNMHDR pNmhdr);
};
and DialogTabPane.cpp
#include "StdAfx.h"
#include "DialogTabPane.h"
CDialogTabPane::CDialogTabPane(void)
{
}
CDialogTabPane::~CDialogTabPane(void)
{
}
HWND CDialogTabPane::CreateThing (LPTCITEM pTie, int iTab, LPCWSTR lpTemplateName, HINSTANCE hInstance, HWND hWndParent)
{
// insert the new tab into the tab control with the specified parameters.
TabCtrl_InsertItem(hWndParent, iTab, pTie);
m_iTab = iTab;
// We need to figure out the adjustment of the dialog window position
// within the tab control display area so that when the dialog and its
// controls are displayed for a given tab the tab selection list can
// still be seen above the top of the displayed dialog.
// The tab selection list must be visible so that the user can select
// any of the specified tabs.
RECT tabDisplay = {0};
GetWindowRect (hWndParent, &tabDisplay);
RECT dialogDisplay = tabDisplay;
// NOTE: TabCtrl_AdjustRect() - This message applies only to tab controls that
// are at the top. It does not apply to tab controls that are on the
// sides or bottom.
// But then tab controls on the side or bottom are an abomination.
TabCtrl_AdjustRect (hWndParent, FALSE, &dialogDisplay);
dialogDisplay.left -= tabDisplay.left; dialogDisplay.top -= tabDisplay.top;
m_dwLastError = 0;
// create our dialog and then position it within the tab control properly.
CDialogTest::CreateThing(hWndParent, lpTemplateName, hInstance);
if (!::SetWindowPos (m_hDlg, HWND_TOP, dialogDisplay.left, dialogDisplay.top, 0, 0, SWP_NOSIZE)) {
m_dwLastError = GetLastError ();
}
return m_hDlg;
}
int CDialogTabPane::OnWmNotify (LPNMHDR pNmhdr)
{
int currentSel = TabCtrl_GetCurSel(pNmhdr->hwndFrom);
switch (pNmhdr->code)
{
case TCN_SELCHANGING:
if (currentSel == m_iTab)
::ShowWindow (m_hDlg, SW_HIDE);
break;
case TCN_SELCHANGE:
if (currentSel == m_iTab)
::ShowWindow (m_hDlg, SW_SHOW);
break;
}
return 0;
}
The dialog box resources used for the individual tab control tab panes are similar and look like:
IDD_DIALOG_TAB01 DIALOGEX 0, 0, 186, 115
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
LTEXT "First tab",IDC_STATIC,68,7,96,14
CONTROL "Check1",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,21,150,13
CONTROL "Check2",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,36,150,13
CONTROL "Check3",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,53,150,13
CONTROL "Check4",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,69,150,13
CONTROL "Check5",IDC_CHECK5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,84,150,13
END
And the resource for the actual dialog containing the tab control looks like:
IDD_DIALOG_TAB00 DIALOGEX 0, 0, 336, 179
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Dialog Tab Test"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,279,7,50,14
PUSHBUTTON "Cancel",IDCANCEL,279,24,50,14
CONTROL "",IDC_TAB1,"SysTabControl32",0x0,47,35,226,137
END
Displaying dialog box with tab 1 selected and then tab 3 selected.

Resources