How to make checkboxes exclusive? - c++

I'm a beginner with the WinAPI and trying to modify code from someone else, making a pair of checkboxes exclusive which were non-exclusive previously.
My initial try was to uncheck the other immediately if one was checked, but this obviously works in one direction only. If IDC_2 is checked and I try to check IDC_1 it triggers the first 'if' statement again and it fails.
This is the code I have:
static WDL_DLGRET dlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (IsDlgButtonChecked(hwndDlg, IDC_1)) {
// do something
// try to make exclusive
CheckDlgButton(hwndDlg, IDC_2, BST_UNCHECKED);
}
if (IsDlgButtonChecked(hwndDlg, IDC_2)) {
// do something
// try to make exclusive
CheckDlgButton(hwndDlg, IDC_1, BST_UNCHECKED);
}
}
I know about radio buttons, but in this case the choices can also be non mutually exclusive. It's possible to have non of the two boxes checked (but not both at once) so I think radio buttons wouldn't be the right choice here, or ?

You have two solutions that immediately present themselves
Radio buttons but add a third "none" option
react to check-box activation (being ticked) and clear out your other check-boxes.
Take option 1. Principle of least suprise. It is a bad idea to subvert common (well known and functionally accepted) user interfaces.
You could keep the checkboxes if this is a setup that later validates the input, e.g. in a dialog or form that you then submit, but you will need to add a description along the lines of:
select up to one option below
and then perform validation, refusing to accept until a maximum of one are selected.
Just take option 1.

Related

CComboBox not selecting CurSel when dropped down

I have an alphabetically sorted combobox in a dialog. This combo contains multiple strings, but some are duplicated with different cases. i.e. we have an 'On' and an 'ON', an 'Off' and an 'OFF'. This may seem redundant but there is a reason, although this is not important right now.
The duplicates obviously appear one after the other in the list, with the capitalized strings first. i.e.:
OFF
Off
ON
On
When the user selects the 'On' (lower case), the correct index is set as CurSel and the correct string is displayed. However, when I click on the arrow of the combobox to drop down the list, it does not highlight the CurSel, but the one previous to it, the capitalized string. See images below.
This is was is selected in the dropdown:
This is what is selected in the combobox when expanding the dropdown.
I have captured the ON_CBN_DROPDOWN message, and checked the cursel value and it is as I expected.
I have also already subclassed this combobox so that I can search for strings in this list in a case-sensitive way, as I know its not implemented normally, so it may be what is causing my issue.
But I don't understand why the string would be overriding the cursel value at this stage? Should the CurSel value not be the one used to select the relevant item?
Any ideas on how I can fix this would be greatly appreciated.
EDIT:
I have tried to capture the CBN_DROPDOWN message by overwriting the OnWndMsg. When this message occurs, I get the currently selected item (which is the correct item) before dropping down the menu. I then drop the menu, and call SetCurSel to what I retrieved before.
BOOL CMyComboBox::OnWndMsg(UINT message, WPARAM wParam, LPARAM lParam, LRESULT *pResult)
{
if(message == CBN_DROPDOWN)
{
int nCurSel = GetCurSel();
if(nCurSel != CB_ERR)
{
ShowDropDown();
SetCurSel(nCurSel);
return TRUE;
}
}
return CComboBox::OnWndMsg(message, wParam, lParam, pResult);
}
This kind of works but when I kill focus, or click on the dropdown arrow again to hide the dropdown, the wrong item is displayed in the text box. Is this a valid method, or am I completely off base here?
What message is sent when the drop down is collapsed?
EDIT 2:
I have implemented the case-sensitive combobox from code project and it works great.
Further to my comment. I think you will find that the internal mechanics is using SelectString to set the index when it is a dropdown style.
The side effect is that it may not pick the right entry for you from the list. Therefore, given the nature of the content in your combo, please try this:
int iIndex = m_cbData.FindStringExact(-1, "On");
m_cbData.SetCurSel(iIndex);
Or
int iIndex = m_cbData.FindStringExact(-1, "OFF");
m_cbData.SetCurSel(iIndex);
However, be warned, the document for FindStringExact says the search is not case sensitive. But SelectString (default behaviour) is even worse.
An alternative, which may resolve all of this, is to use SetWindowText and do it that way. This way, it does not matter what is in the listbox component. Eg:
m_cbData.SetWindowText("On");
m_cbData.SetWindowText("ON");
And get the value for the variable by either mapping to a string, or directly using GetWindowText.
UPDATE: Someone has done the work already! Here is a Case Sensitive ComboBox class:
http://www.codeproject.com/Articles/1363/Case-sensitive-ComboBox

Low level keyboard hook: differentiate between key codes

I'm trying to limit the access of the users of my program to the keyboard. To do that I've defined a low level keyboard hook:
LRESULT CALLBACK lowLevelKeyboardProc(int key, WPARAM wParam, LPARAM lParam)
{
KBDLLHOOKSTRUCT* pkbhs = (KBDLLHOOKSTRUCT*)lParam;
switch (key)
{
case HC_ACTION:
....
and hooked it:
m_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)lowLevelKeyboardProc, 0, 0);
I need the users to be able to use only the alphanumeric chars, ~, #, # ... only the chars that can be in a password (printable chars).
What would be the easiest way to differentiate between those chars and all the others using the low level keyboard hook parameters: int key, WPARAM wParam, LPARAM lParam ?
The program is written in c++ and compiled in VC2010.
All help is appreciated!
Before actually answering your question, I have a couple of questions for you:
Why would you want to do this globally, using a global low-level keyboard hook?
Although sometimes they are the only way to solve a problem, using a global hook like this is generally strongly discouraged for many reasons. There are many better ways of preventing a user from entering invalid or unacceptable data. For example, I would just disable the keys you don't want for a particular control or set of controls (e.g. all textboxes). That way, the user can still use keyboard shortcuts and other non-alphanumeric keys to interact with your application, which is critical for accessibility reasons. Remember that a global hook will affect all of the other threads running on the machine, not just your app—that is probably not what you want.
Even if you do decide to use a global hook, are you sure that you really need to disable all of the non-alphanumeric keys? What about Backspace and Delete? Shouldn't the user be able to delete things? And what about Tab? Enter? And what about modifier keys, like Shift, Ctrl, and Alt: are those allowed?
Please seriously reconsider whether you really need a low-level hook before continuing forward with this design. If you need help on devising another solution, please ask a new question, describing what you want to accomplish (e.g., I want to prevent users from entering any non-alphanumeric characters in a textbox control; here is the code I use to create my textbox…).
But if you insist on ignoring my advice, the solution is rather simple: investigate the members of the KBDLLHOOKSTRUCT structure that is passed to the hook procedure. The vkCode member gives you the virtual key code of the key that was pressed. Chances are, that is all the information you need. But just in case it's not, the hardware scan code of the key is also provided in the scanCode member.
Unfortunately, the code that you currently have is wrong. The first parameter to the hook callback procedure is indeed an int, but it is not a key code. Rather, it is a code that signals how the hook procedure should process the message. Use it like this:
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// If nCode is greater than or equal to HC_ACTION, process the message.
if (nCode >= HC_ACTION)
{
KBDLLHOOKSTRUCT* pkbhs = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
// Handle the keys as you wish here.
//
// Remember that pkbhs->vkCode gives you the virtual key code
// of the key that was pressed.
//
// To prevent a particular key from being processed, you should
// return a non-zero value (e.g. 1) immediately.
}
// Pass the message on.
return CallNextHookEx(m_hHook, nCode, wParam, lParam);
}
And when you install the hook, there is absolutely no need to cast the function pointer. Pointless casts like this just hide potential compile-time errors, leading to a crash at runtime. Write it simply:
m_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, 0, 0);

How to create additional controls in WinAPI? Only the first control works?

I've recently started to study WinAPI. After going through about ten top search results for tutorials on it, I'm still confused where you add code for more than one control per window? For example, if I want to add a second button, what kind of code and where do you place the code for it?
When I #define IDC_BUTTON WM_USER + 1, add HINSTANCE hIns; and HWND hButton; to the Callback, and then call this:
case WM_CREATE:
hIns=((LPCREATESTRUCT)lParam)->hInstance;
hButton=CreateWindow("Button","Click Me",WS_CHILD|WS_VISIBLE,70,60,150,30,hwnd,(HMENU)IDC_BUTTON,hIns,0);
break;
It works and the button appears in the window. But when I try to add a second one, nothing appears, even with changed names:
#define IDC_BUTTON2 WM_USER + 2
+
HINSTANCE hIns;
HWND hButton2;
case WM_CREATE:
hIns=((LPCREATESTRUCT)lParam)->hInstance;
hButton=CreateWindow("Button","Click Me",WS_CHILD|WS_VISIBLE,70,60,150,30,hwnd,(HMENU)IDC_BUTTON,hIns,0);
hButton2=CreateWindow("Second","Press here",WS_CHILD|WS_VISIBLE,170,160,250,130,hwnd,(HMENU)IDC_BUTTON2,hIns,0);
break;
The first button appears OK, but the second does not (different coordinates also). What am I doing wrong here? Thanks!
Edit:
I replaced the hIns2 with hIns in the code.
hButton2=CreateWindow("Second","Press here",WS_CHILD|WS_VISIBLE,...);
Let's tackle this at the core. The fundamental thing you are doing wrong is completely ignoring the need to check for errors. CreateWindow() returns NULL when it failed. GetLastError() then returns an error code that indicates the problem. That same error code is also visible in a debugger with the "#err" pseudo variable.
You would then quickly have discovered that you got error 1407, described like this in the WinError.h SDK header:
//
// MessageId: ERROR_CANNOT_FIND_WND_CLASS
//
// MessageText:
//
// Cannot find window class.
//
#define ERROR_CANNOT_FIND_WND_CLASS 1407L
Which makes it crystal clear that you flubbed the class name in the CreateWindow() call.
Never skip error checks, especially when you are just starting out with winapi programming. It isn't just useful to let programs graciously fail when things go wrong on the user's machine, it is clearly also extremely useful to diagnose bugs in your code.
First of all there is no class name called "SECOND unless you defined one already so if you want to make say 10 buttons the first parameter in the CreateWindow function is going to be "BUTTON" in all of them, you can also change it to "EDIT" if you want an editable text box or "STATIC" if you ant to put a static box.
Secondly you can make a process of adding buttons alot more easier if you followed this note:
Instead of defining your button by making
#define IDB_BUTTON WM_USER + 2
and then inserting the IDB_BUTTON into the CreateWindow function as (HMENU)IDB_BUTTON
you can simply just write (HMENU)<any number of your choice> in to the CreateWindow function just like (HMENU) 2 without needing to predefine that number using #define and then when you want to make an action when the button is pressed you can just pass that number in a case of a switch statement that has (LOWORD(wParam)) as its switch .
It seems confusing but see the code to understand what i mean
full code in your procedure:
LRESULT CALLBACK WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch(Msg)
{
case WM_CREATE:
hButton=CreateWindow("Button","Click Me",WS_CHILD|WS_VISIBLE,70,60,150,30,hWnd,(HMENU) 1,NULL,NULL);
hButton2=CreateWindow("BUTTON","Press here",WS_CHILD|WS_VISIBLE,170,160,250,130,hwnd,(HMENU) 2,NULL,NULL);
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case 1:
//What will the first button do?write it here
break;
case 2:
//What will the second button do?write it here
break;
}
break;
}
return 0;
}
Good luck on your path studying win32 c++ i remember when i started also it was confusing and it is still for me but by hard work you will master it
;)

Window Maximum Maximise

I am looking to create a program where I can set the maximum maximize size (as in the size the window maximises to when you hit the maximise button) and maximize position (X/Y coordinated for the maximised window) for all of the windows that are running. This is so that I can have my Rainmeter visible at all times on my secondary monitor without having to manually resize every window fit inside of it.
I have managed to do this for a simple program I wrote using MINMAXSIZE from the WinAPI. This method seems to work perfectly for my little program, but there is very little documentation on it beside 1 or 2 articles on the internet. I was wondering if this would be the best way to set the maximum maximise size, or if there is another way to do this.
They way I planned to implement this into all of the applications was going to be either DLL Injection or Hooks (neither of which I have any experience with), so I was also wondering your guys' thoughts on these methods.
I know there are a few applications out there that already do this, but I thought this could be a learning experience, and as well, all of the applications I tried do not work very well (although this could be the case with all of them due to the way Windows functions).
If any of you are still unsure about what I am talking about, MaxMax does exactly what I want (although it doesn't work so well, as I stated in my previous paragraph).
Thank you all in advance.
What you're probably looking for is the work area setting, that you can set/retrieve with the SystemParametersInfo function, called with the flags SPI_SETWORKAREA/SPI_GETWORKAREA.
What you want to do is use a global windows hook to handle WM_GETMINMAXINFO. As you may be aware, this is the message that is:
Sent to a window when the size or position of the window is about to
change. An application can use this message to override the window's
default maximized size and position, or its default minimum or maximum
tracking size.
The best way to use this to override the default maximum is to fill in the MINMAXINFO structure like so:
case WM_GETMINMAXINFO: {
DefWindowProc(hWnd, message, wParam, lParam);
MINMAXINFO* mmi = (MINMAXINFO*)lParam;
mmi->ptMaxTrackSize.x = 100;
mmi->ptMaxTrackSize.y = 100;
return 0;
}
This will allow the default values to be assigned to the sizes you don't care about (min x/y) in this case, leaving you to fiddle with the max values as you please. Your windows hook should be done with SetWindowsHookEx() and should look something like this:
SetWindowsHookEx(WH_CALLWNDPROC, hook_procedure, instance_handle, 0);
hMod (instance_handle) should only be set depending on the circumstances (check the docs for this). The dwThreadId mandates a global hook. Your CallWndProc might looks something like this:
__declspec(dllexport) LRESULT WINAPI CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
CWPSTRUCT* cwp = (CWPSTRUCT*)lParam;
if(WM_GETMINMAXINFO == cwp->message) {
DefWindowProc(hWnd, message, wParam, lParam);
MINMAXINFO* mmi = (MINMAXINFO*)lParam;
mmi->ptMaxTrackSize.x = 100;
mmi->ptMaxTrackSize.y = 100;
return 0;
}
return CallNextHookEx(next_hook, nCode, wParam, lParam);
}
Unfortunately something you are going to have to deal with is that the only windows that will be hooked are the ones that had were existing when you made your call to SetWindowsHookEx(). I'm not aware of a clean way of getting past this, short of looping a call to SetWindowsHookEx() (ergh!).
You could potentially do this with DLL injection and effectively subclass every window with EnumWindows, EnumChildWindow and SetWindowLongPtr/SetWindowSubclass. But why go to all that trouble when you could just use SetWindowsHookEx? :)
To alter the x/y, you might have to add an override for WM_SYSCOMMAND and check for SC_MAXIMIZE then use SetWindowPos/MoveWindow to position it properly (if you don't want it on the default 0, 0).

C++/MFC: Handling multiple CListCtrl's headers HDN_ITEMCLICK events

I'm coding an MFC application in which i have a dialog box with multiple CListCtrls in report view. I want one of them to be sortable.
So i handled the HDM_ITEMCLICK event, and everything works just fine .. Except that if i click on the headers of another CListCtrl, it does sort the OTHER CListCtrl, which does look kind of dumb.
This is apparently due to the fact that headers have an ID of 0, which make the entry in the message map look like this :
ON_NOTIFY(HDN_ITEMCLICK, 0, &Ccreationprogramme::OnHdnItemclickList5)
But since all the headers have an id of zero, apparently every header of my dialog sends the message.
Is there an easy way around this problem ?
EDIT: Maybe i wasn't clear, but i did check the values inside the NMHDR structure. The HwndFrom pointer is different depending on which header is clicked, which doesn't help me a lot since it's value is obviously different at each runtime. The idFrom value is 0, for the very reasons i explained above, because that's the id of every header. Thanks
EDIT2: The hwnd pointer values do also not correspond to the CListCtrl, probably because it's coming from a different object entirely.
Check the values of the NMHDR structure.
http://msdn.microsoft.com/en-us/library/bb775514%28VS.85%29.aspx
Ok i found a solution, though i find it a bit dirty but it works, so i'll post it for future reference.
You can get the Header through the GetHeaderCtrl member function of CListCtrl. You can then get it's handler thru m_hWnd. So all you got to do is to test if that handler is the same as the one in the NMHDR structure, so the code looks like this :
void Ccreationprogramme::OnHdnItemclickList5(NMHDR *pNMHDR, LRESULT *pResult)
{
if (pNMHDR->hwndFrom == LC_gen_schedules.GetHeaderCtrl()->mhWnd)
{
// Code goes here
}
*pResult = 0;
}
Thanks all for the help
The LPARAM passed to your message handler is actually a pointer to an NMHEADER structure, which contains an NMHDR structure, which in turn contains the HWND and control ID of the control which sent the message. You may be able to compare that to your list controls' HWNDs to determine which window's header control was clicked.
Alternatively, you could derive a class from CListCtrl and reflect the HDN_ITEMCLICK messages back to the list control. That way, each list control object handles its own header's notifications.