Related
Below is part of a working normal win32 application created from scratch, not dialog based. it will show the text on the buttom, not top:
hButtonApply = CreateWindow(
"BUTTON",
"Reset",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_BOTTOM,
95, 130, 120, 40,
hWnd, (HMENU)IDC_BUTTON_RESET, hInstance, NULL);
SendMessage(hButtonApply, BM_SETSTYLE, BS_TOP, TRUE);
this however makes it a radio button:
SendMessage(hButtonApply, BM_SETSTYLE, BS_RADIOBUTTON, TRUE);
same for a resource-defined dialog button control.
Subclassing is acceptable. Owner-Drawn Buttons are not acceptable. Thanks, Haotian Yang
Some window styles can only be set during CreateWindow, I guess this might be one of them.
Did you try SetWindowLong ?
Edit:
This seems to work:
LONG style = GetWindowLong(hBtn,GWL_STYLE);
style = (style & ~BS_BOTTOM) | BS_TOP;
SetWindowLong(hBtn,GWL_STYLE,style);
We have a series of dialogs in our application for which the dialog template defines 4 buttons along the bottom of the screen. However (depending which version of our hardware the application is running on) we sometimes create 2 additional buttons and then line up the 6 buttons (4 from the template, 2 created by calling CButton::Create()) along the bottom.
The problem I have is that usually the user can move the focus between these buttons using the left / right arrow keys (there is no mouse or touchscreen, just a restricted keyboard). This follows the control TAB-order as you would expect for the 4 buttons from the template. However the 2 dynamically create controls appear to be inserted at the beginning of the TAB-order, and that means (because they are put at the right-hand side of the screen) that they are in the "wrong" order as far as the cursor keys goes. In other words, when the focus gets to the left-hand button (TAB order 1) pressing the left arrow jumps the focus to the button on the right-hand side, which is just plain confusing...
There appears to be some link between Z-order (which I can affect with SetWindowPos()) and TAB-order but it does not seem to be a simple 1-to-1: by changing the Z-order I can move the sequence around so that the buttons are completely in the wrong order, so I can change the Z-order, but I can't work out how to get them in the right order.
Can anyone give a concise explanation of how TAB-order works, and how to control the sequencing of controls at runtime?
Edit:
kol suggested below using SetWindowPos() to set the Z-order. This I had tried before but it does not let the cursor keys control the focus as expected.
However, by rigging this up so I can use TAB (as a test -- this isn't practical for the end-user solution) I find that kol's solution does resolve the TAB-order. The problem I have is that this is not the same as the order used by the cursor keys!
So, revised question: how can I specify the order in which left / right cursor keys move focus between controls?
Solution:
With assistance from kol and MarkRansom's input, I have got this working now.
I used SetWindowPos() as suggested by kol to put my new buttons after the existing buttons in the TAB order, and then (as Mark suggested) made the first button WS_GROUP | WS_TABSTOP but cleared those flags from the other buttons.
However this was not enough to solve the problem, with the 2 new buttons still appearing to come before the first rather than after the second when using arrow keys (not TAB) to move around.
I looked at my dialog template, and it was like this:
IDD_QUERY DIALOG 0, 0, 156, 34
STYLE DS_SETFONT | WS_POPUP | WS_CAPTION
FONT 8, "MS Sans Serif"
BEGIN
PUSHBUTTON "+++Skey1+++",IDC_SKEY_1,1,21,36,12
PUSHBUTTON "+++Skey2+++",IDC_SKEY_2,40,21,37,12
PUSHBUTTON "+++Skey3+++",IDC_SKEY_3,79,21,36,12
PUSHBUTTON "+++Skey4+++",IDC_SKEY_4,118,21,36,12
LTEXT "Static",IDC_QUERY_MSG,2,1,153,15
END
So the static IDC_QUERY_MSG, which is used to display information to the user, was coming after the 4th button in the template. To resolve the problem, I moved IDC_QUERY_MSG before the first button (IDC_SKEY_1): this means the 6 buttons are not split up by a static inbetween, and has solved the problem.
Thanks to everyone for their assistance!
Use the SetWindowPos member of your buttons. Calling it on button A and setting its first parameter to button B, puts button A after button B in the TAB order. If you want to set the order of two controls, you have to know the controls before and after them in the TAB order - this example shows how to do this (its not MFC but pure WinAPI, but it's easy to understand).
== UPDATE ==
I created a dialog template with four buttons at the bottom and an editbox at the top, with TAB order button1 -> button2 -> button3 -> button4 -> editbox -> button1 -> ... In OnInitDialog, I added two additional buttons at runtime, and inserted them into the existing TAB order between button4 and editbox using SetWindowPos and GetNextWindow. Pressing TAB repeatedly shows that the TAB order is correct: button1 -> button2 -> button3 -> button4 -> button5 -> button6 -> editbox -> button1 -> ...
class CTestDlg : public CDialogEx
{
public:
CTestDlg() : CDialogEx(CTestDlg::IDD) {}
enum { IDD = IDD_TESTDIALOG };
protected:
CButton button5;
CButton button6;
virtual BOOL OnInitDialog();
};
BOOL CTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
CButton* button4 = (CButton*)GetDlgItem(IDBUTTON4);
CWnd* next = button4->GetNextWindow(GW_HWNDNEXT);
button5.Create("Button5", WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_PUSHBUTTON, CRect(340, 172, 415, 195), this, 1005);
button5.SetWindowPos(button4, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
button6.Create("Button6", WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_PUSHBUTTON, CRect(422, 172, 497, 195), this, 1006);
button6.SetWindowPos(&button5, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
if (next != NULL)
next->SetWindowPos(&button6, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
return TRUE;
}
void CDynamicMfcButtonTestApp::OnTestRun()
{
CTestDlg testDlg;
testDlg.DoModal();
}
== UPDATE2 ==
The order of controls can be set using SetWindowPos and GetNextWindow, as above. The "tab order" and the "arrow order" can be set by setting the WS_TABSTOP and WS_GROUP styles of the controls:
The WS_TABSTOP style specifies the controls to which the user can
move by pressing the TAB key or SHIFT+TAB keys.
The WS_GROUP style
marks the beginning of a group of controls. If a control in the group
has the input focus when the user begins pressing direction keys, the
focus remains in the group. In general, the first control in a group
must have the WS_GROUP style and all other controls in the group must
not have this style. All controls in the group must be
contiguous—that is, they must have been created consecutively with no
intervening controls.
More details can be found on MSDN, here.
Try this:
Create a vector of dlg ids and populate it in the order you want the tab order to be:
typedef std::vector<int> TABLIST;
TABLIST lst;
lst.push_back( IDC_CONTROL1 );
lst.push_back( IDC_CONTROL2 );
lst.push_back( IDC_CONTROL3 );
Then call the setTabOrder method with the list:
void setTabOrder( TABLIST * plstTabs)
{
// Iterate through the list and put each item at the top of the tab order.
// Remember to do this backwards so the last item is actually the first item
// in the tab order
HDWP hDefer = BeginDeferWindowPos( (int)plstTabs->size() );
for ( TABLIST::reverse_iterator itTab = plstTabs->rbegin(); itTab != plstTabs->rend(); ++itTab )
{
DeferWindowPos( hDefer, GetDlgItem( *itTab )->m_hWnd, HWND_TOP, 0,0,0,0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOREPOSITION );
}
EndDeferWindowPos( hDefer );
}
Modified version of #snowduded's code that makes sure the WS_TABSTOP style is in also.
// Build your order in a vector.
std::vector<WORD> vCtrlIds;
vCtrlIds.push_back(IDC_CONTROL1);
vCtrlIds.push_back(IDC_CONTROL2);
vCtrlIds.push_back(IDC_CONTROL3);
// ... keep doing it!
// Iterate through the list and put each item at the top of the tab order.
// Remember to do this backwards so the last item is actually the first item
// in the tab order.
HDWP vDefer = BeginDeferWindowPos(vCtrlIds.size());
for(auto vCtrlId(vCtrlIds.rbegin()); vCtrlId != vCtrlIds.rend(); ++vCtrlId){
HWND vCtrl(GetDlgItem(*vCtrlId)); // Grab the handle.
SetWindowLongPtr(vCtrl, GWL_STYLE, // Make sure we have WS_TABSTOP.
GetWindowLongPtr(vCtrl, GWL_STYLE) | WS_TABSTOP);
DeferWindowPos(vDefer, vCtrl, HWND_TOP, 0, 0, 0, 0, // Reorder.
SWP_NOSIZE | SWP_NOMOVE | SWP_NOREPOSITION );
}
EndDeferWindowPos(vDefer);
Props to #snowdude for an excellent reusable solution.
If you are talking about a tab order of different controls over a dialog:
Try this
Open the dlg in resource and press Ctrl+D
Now you can set order by selecting controls one after another.
You can't change the tab order in runtime. What you can do is to put these two buttons in your dialog resource (with the right tab order), and make them invisible. Then you show/arrange the buttons as soon as you need them.
I've created two instances of Fred Acker's CHoverButtonEx class with a slight modification to include a disabled state.
These buttons exist on modeless dialog which contains the following properties:
IDD_MY_DIALOG DIALOGEX 0, 0, 162, 27
STYLE DS_SETFONT | WS_POPUP
EXSTYLE WS_EX_TOPMOST | WS_EX_TOOLWINDOW
FONT 9, "Arial", 400, 0, 0x0
BEGIN
CONTROL 146,IDC_STATIC_BKGND,"Static",SS_BITMAP,0,0,162,27
LTEXT "",IDC_STATIC_1,6,4,101,9,SS_WORDELLIPSIS
LTEXT "",IDC_STATIC_2,6,15,101,9
CONTROL "",IDC_BUTTON_1,"Button",BS_OWNERDRAW | WS_TABSTOP,108,4,24,19
CONTROL "",IDC_BUTTON_2,"Button",BS_OWNERDRAW | WS_TABSTOP,134,4,24,19
END
Everything is working well with the buttons except that now I need to implement a focus state but the behavior is strange and unexpected.
In my DrawItem message handler, I have the following code which functions pretty much exactly the same as the original minus some stuff I cleaned out which wasn't needed:
void CHoverButtonEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// Do other stuff above and now find the state and draw the bitmap
if(lpDrawItemStruct->itemState & ODS_SELECTED)
{
//mydc->BitBlt(0,0,m_ButtonSize.cx,m_ButtonSize.cy,pMemDC,m_ButtonSize.cx,0,SRCCOPY);
mydc->StretchBlt(0,0, lpDrawItemStruct->rcItem.right-lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom-lpDrawItemStruct->rcItem.top,
pMemDC,m_ButtonSize.cx,0, m_ButtonSize.cx,m_ButtonSize.cy, SRCCOPY );
}
else
{
if(m_bHover)
{
//mydc->BitBlt(0,0,m_ButtonSize.cx,m_ButtonSize.cy,pMemDC,m_ButtonSize.cx*2,0,SRCCOPY);
mydc->StretchBlt(0,0, lpDrawItemStruct->rcItem.right-lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom-lpDrawItemStruct->rcItem.top,
pMemDC,m_ButtonSize.cx*2,0, m_ButtonSize.cx,m_ButtonSize.cy, SRCCOPY );
}
else
{
if (IsWindowEnabled())
{
//mydc->BitBlt(0,0,m_ButtonSize.cx,m_ButtonSize.cy,pMemDC,0,0,SRCCOPY);
mydc->StretchBlt(0,0, lpDrawItemStruct->rcItem.right-lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom-lpDrawItemStruct->rcItem.top,
pMemDC,0,0, m_ButtonSize.cx,m_ButtonSize.cy, SRCCOPY );
}
else
{
//mydc->BitBlt(0,0,m_ButtonSize.cx,m_ButtonSize.cy,pMemDC,m_ButtonSize.cx*3,0,SRCCOPY);
mydc->StretchBlt(0,0, lpDrawItemStruct->rcItem.right-lpDrawItemStruct->rcItem.left,
lpDrawItemStruct->rcItem.bottom-lpDrawItemStruct->rcItem.top,
pMemDC,m_ButtonSize.cx*3,0, m_ButtonSize.cx,m_ButtonSize.cy, SRCCOPY );
}
}
}
if (lpDrawItemStruct->itemAction & ODA_FOCUS)
{
RECT rcFocus;
int iChange = 3;
rcFocus.top = lpDrawItemStruct->rcItem.top + iChange;
rcFocus.left = lpDrawItemStruct->rcItem.left + iChange;
rcFocus.right = lpDrawItemStruct->rcItem.right - iChange;
rcFocus.bottom = lpDrawItemStruct->rcItem.bottom - iChange;
pMemDC->DrawFocusRect(&rcFocus);
}
// clean up
pMemDC -> SelectObject(pOldBitmap);
delete pMemDC;
}
What is occurring is that when the dialog is the active window and I press tab once, the focus box jumps to the second button, even though I can confirm through the button's click handler that the first button has the real focus. Then when I press tab again the focus box jumps to include both buttons. Then another tab press moves the focus box to the other button and finally another tab press removes the focus box entirely. This sequence keeps occurring. Even holding Shift-Tab won't affect it.
I sniffed out the windows messages using spy++ and it looks pretty normal. I get a WM_DRAWITEM message for both button controls and they are successfully handled.
I'll mention one last thing; in my dialog code, when I'm initializing the buttons, I'm forced to place the buttons in the bottom of the z-order or else the IDC_STATIC_BKGND would draw over the buttons. This didn't seem normal to me because they should already be at the bottom of the z-order. (Just adding it in case it's a part of the cause to my problem).
m_button1.SetHorizontal(true);
m_button1.SetMoveable(FALSE);
m_button1.LoadBitmap(IDB_BUTTON_1);
m_button1.SetToolTipText(_T("Some Text1"));
// Draws the button after the background is drawn
m_button1.SetWindowPos(&CWnd::wndBottom, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
m_button2.SetHorizontal(true);
m_button2.SetMoveable(FALSE);
m_button2.LoadBitmap(IDB_BUTTON_2);
m_button2.SetToolTipText(_T("Some Text2"));
// Draws the button after the background is drawn
m_button2.SetWindowPos(&CWnd::wndBottom, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
Does anyone know how to correctly add a focus box for my situation?
Thanks.
Update:
After trying BrendanMcK's suggestion and not resolving the issue, I dug some more in the captured messages in spy++ and noticed something that seems like odd behavior. The following messages represent a single tab press in the dialog.
<00283> 00870794 R WM_CTLCOLORBTN hBrush:01900015
<00284> 00870794 S WM_DRAWITEM idCtl:112 lpdis:002AEF2C
<00285> 00870794 R WM_DRAWITEM fProcessed:True
<00286> 00870794 S DM_GETDEFID
<00287> 00870794 R DM_GETDEFID wHasDef:DC_HASDEFID wDefID:0001
<00288> 00870794 S WM_CTLCOLORBTN hdcButton:110114A7 hwndButton:01090502
<00289> 00870794 R WM_CTLCOLORBTN hBrush:01900015
<00290> 00870794 S WM_DRAWITEM idCtl:113 lpdis:002AF2A0
<00291> 00870794 R WM_DRAWITEM fProcessed:True
As evident there are two separate WM_DRAWITEM messages delivered. The details for each message is:
Message #284: action= ODA_FOCUS, state= 0x0110 (ODS_FOCUS = 0x0010)
Message #290: action= ODA_DRAWENTIRE, state= ODS_NOACCEL
I would have expected that in message #290, the action would again be ODA_FOCUS to allow the other button to "un-draw" the focus box.
I'm not sure why I'm even receiving an ODS_NOACCEL state as I'm using Win7. Is there something I've forgot to disable?
From MSDN on DRAWITEMSTATE:
ODA_FOCUS
The control has lost or gained the keyboard focus. The itemState member should be checked to determine whether the control has the focus.
Because you're redrawing the control, you should only be drawing the focus rect if the itemState indicates that the control has focus. Instead, you're drawing it in all cases, regardles of whether the control gains or loses focus. Add a check to see if itemState & ODS_FOCUS and you should be good.
I'm trying to write a wrapper for Winamp input plugins and have hit a bit of a snag. I'd like my wrapper to be able to display a plugin's configuration dialog, which is (or should be) achieved by calling the plugin's Config(HWND hwndParent) function.
For most plugins, this works fine and my program is able to display the plugin's configuration dialog. However, 64th Note (a plugin for playing USF files) is giving me problems. Winamp can display its configuration dialog just fine, but whenever I try to display it from my wrapper, the dialog gets destroyed before it ever shows itself. Thankfully, 64th Note is open source, so I took a look at its innards to try and get an idea of what's going wrong. I've trimmed off the irrelevant bits and am left with this:
Config function in the plugin (should show configuration dialog):
void Config(HWND hwndParent) {
DialogBox(slave, (const char *) IDD_CONFIG_WINDOW, NULL, configDlgProc);
}
(Slave is the plugin DLL's HINSTANCE handle.) The proc for the dialog is as follows (I have stripped out all the functionality, since it doesn't appear to have an influence on this problem):
BOOL CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
return 0;
}
The template for IDD_CONFIG_WINDOW is as follows:
IDD_CONFIG_WINDOW DIALOGEX 0, 0, 269, 149
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "64th Note configuration"
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
DEFPUSHBUTTON "OK",IDOK,212,38,50,14
CONTROL "Play Forever",IDC_NOLENGTH,"Button",BS_AUTORADIOBUTTON,7,7,55,8
CONTROL "Always Use Default Length",IDC_SETLEN,"Button",BS_AUTORADIOBUTTON,7,17,101,8
CONTROL "Default Length",IDC_DEFLEN,"Button",BS_AUTORADIOBUTTON,7,29,63,8
EDITTEXT IDC_DEFLENVAL,71,28,38,12,ES_AUTOHSCROLL
EDITTEXT IDC_DEFFADEVAL,71,42,38,12,ES_AUTOHSCROLL
CONTROL "Detect Silence",IDC_DETSIL,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,56,63,8
EDITTEXT IDC_DETSILVAL,71,56,38,12,ES_AUTOHSCROLL
CONTROL "Slider2",IDC_PRISLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,74,90,108,11
EDITTEXT IDC_TITLEFMT,7,127,255,15,ES_AUTOHSCROLL
CONTROL "Default to file name on missing field",IDC_FNONMISSINGTAG,
"Button",BS_AUTOCHECKBOX | WS_TABSTOP,50,114,124,8
CONTROL "Use Recompiler CPU",IDC_RECOMPILER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,7,83,8
CONTROL "Round Frequency",IDC_ROUNDFREQ,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,16,73,8
CONTROL "Seek Backwards",IDC_BACKWARDS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,26,70,8
CONTROL "Fast Seek",IDC_FASTSEEK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,35,48,8
CONTROL "RSP Sections",IDC_SECTIONS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,45,60,8
CONTROL "Soft Amplify",IDC_SOFTAMP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,54,53,8
CONTROL "Audio HLE",IDC_AUDIOHLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,63,50,8
CONTROL "Auto Audio HLE",IDC_AUTOAUDIOHLE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,72,64,8
CONTROL "Display Errors",IDC_DISPERROR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,145,81,58,8
EDITTEXT IDC_RELVOL,211,104,28,12,ES_AUTOHSCROLL
PUSHBUTTON "Cancel",IDCANCEL,212,54,50,14
PUSHBUTTON "Help",IDHELPBUTTON,212,71,50,14
LTEXT "Title format:",IDC_STATIC,7,113,38,8
LTEXT "seconds",IDC_STATIC,112,29,28,8
LTEXT "Default Fade",IDC_STATIC,19,43,42,8
LTEXT "seconds",IDC_STATIC,112,43,28,8
LTEXT "seconds",IDC_STATIC,112,57,28,8
CTEXT "CPU Thread Priority",IDC_STATIC,7,91,63,8
CTEXT "Look ma, I'm data!",IDC_CPUPRI,75,104,108,8
LTEXT "Relative Volume",IDC_STATIC,199,94,52,8
LTEXT "Fade Type",IDC_STATIC,7,75,35,8
COMBOBOX IDC_FADETYPE,45,72,87,74,CBS_DROPDOWNLIST | WS_TABSTOP
END
Naturally, without any substance in the proc function, the dialog doesn't have any functionality, but it still displays in Winamp when the Config function is invoked. However, it does not appear when I invoke it from my wrapper program. When I monitored the messages sent to the dialog in its proc function, I saw that WM_DESTROY and WM_NCDESTROY were sent within the first few messages, though I have no clue as to why.
If I change the Config function so that it displays the plugin's About dialog instead of its configuration dialog, both Winamp and my wrapper will display the About dialog, which suggests that there is something unique to the configuration dialog template that's causing the problem. The modified Config function reads like so:
void Config(HWND hwndParent) {
DialogBox(slave, (const char *) IDD_ABOUTBOX, NULL, configDlgProc);
}
The template for the About dialog is as follows:
IDD_ABOUTBOX DIALOGEX 0, 0, 152, 151
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "About 64th Note"
FONT 8, "MS Sans Serif", 0, 0, 0x1
BEGIN
LTEXT "64th Note v1.2 beta 3\nBased on Project 64 1.6 by Zilmar and Jabo\nAudio HLE by Azimer\nPSF concept and tagging by Neill Corlett\nPlayer by hcs, Josh W, dr0\nhttp://hcs64.com/usf",IDC_STATIC,7,94,138,50
CONTROL 110,IDC_STATIC,"Static",SS_BITMAP,26,7,95,86,WS_EX_DLGMODALFRAME
END
Like I said, my wrapper displays the About dialog just fine, as does Winamp. Why can Winamp display the Config dialog, while my wrapper cannot?
Perhaps you haven't registered the Trackbar control's WNDCLASS? You have to load the common control DLL before you can instantiate it.
I'm specifically looking at this line:
CONTROL "Slider2",IDC_PRISLIDER,"msctls_trackbar32",TBS_AUTOTICKS | WS_TABSTOP,74,90,108,11
EDIT: You'll need to link with comctl32.lib and call InitCommonControlsEx during your program startup.
im starting with Win32 api, im adding a button control to my main window with the flowing code:
HWND boton = CreateWindow(
"BUTTON", //
"Caption", //
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles.
250, // x position.
10, // y position.
100, // Button width.
40, // Button height.
hwnd, // Parent window.
NULL, // No menu.
(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
NULL); // Pointer not needed.
how can i assign it an id, so i can get the message on the loop, in the message loop im trying to catch the message as WM_COMMAND but i don't get any result i've tried with WM_NOTIFY too.
To assign it an ID, you have to use the hMenu parameter. If you have specified that the window will be a child (i.e. with WS_CHILD), the hMenu parameter will be interpreted as an integer ID for the window. Also, provide the BS_NOTIFY style.
HWND boton = CreateWindow (
"BUTTON",
WS_TAPSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON | BS_NOTIFY,
250, 10, 100, 40,
hwnd,
(HMENU)101, // This becomes the Control ID
(HINSTNACE)GetWindowLong(hwnd,GWL_HINSTANCE),
NULL);
EDIT: Special shout goes out to Heath Hunnicutt for the info on BS_NOTIFY.
In fact, you do not need to specify an ID for the button. The problem is your code is missing a style bit to CreateWindow().
You must specify the style BS_NOTIFY for the parent window to receive notifications from Button controls.
You will then receive window message WM_COMMAND with (HIWORD(w_param) == BN_CLICKED) every time your button is clicked. See the BN_CLICKED documentation for more.
Using a control ID is unnecessary because the BN_CLICKED message will provide you with the control's window handle. Because you are already required to keep track of the window handle in order to properly call DestroyWindow when you receive WM_DESTROY comparing the button's window handle is just as easy as using a control ID.
To set the window id, pass it in as if it were an HMENU:
(HMENU) nChildID