Intercept CDialog creation - windows

I have a rather large app that displays many different MFC CDialog-derived dialog windows. All of the dialogs are displayed from a central function that is similar to this:
void ShowDialog(CDialog& dlg)
{
dlg.DoModal();
}
Now I need to essentially call a function in every dialog's OnInitDialog method. It doesn't technically need to be within OnInitDialog, but preferably before the dialog is visible.
The brute force method would be to go through the code and find every last dialog and add the function call to the OnInitDialog method (if it has one, and if it doesn't, add one). But it seems like there must be a more elegant way...
Note that dlg is not actually a CDialog, but something that derives from it.
Any thoughts, tricks or hacks? I'm not above patching the message map, but hope to find something cleaner/safer.

If you've got a common ancestor for all your dialogs, which you seem to imply you have, then you can simply put the code in that common ancestor in a suitable location of your choice. For example OnInitDialog() is virtual.

Turns out it is quite easy to do:
HHOOK gPrevHook = SetWindowsHookEx(WH_CALLWNDPROCRET, HookProc, NULL, myGUIThreadID);
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(NULL != wParam)
{
CWPRETSTRUCT* pS = (CWPRETSTRUCT*)lParam;
if(WM_INITDIALOG == pS->message)
CallFuncOnWindow(pS->hwnd);
}
return CallNextHookEx(gPrevHook, nCode, wParam, lParam);
}
Probably not the thing to do for a high performance app, but for something that is a simple GUI it works perfectly. No other code changes required.

Related

Are there any Win32 functions I can use to get the count / itemdata from a CComboBoxEx control?

My parent dialog has a CComboBoxEx control (which is mapped to a derived class called CDatesComboBoxEx).
In one part of the application this dialog displays a popup modal dialog. And, inside the modal dialog, it needs access to information from the dates combo.
What I decided to do (which works fine) is pass the address of my combo in the constructor of the popup dialog. So I can now do things like:
m_pComboDates->GetCount()
m_pComboDates->GetItemDataPtr(i)
I was wondering if there was any way to use native Win32 code here instead?
We can get access to the parents handle (GetParent()->GetSafeHWnd()).
We know the ID of the control on the parent dialog (IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING).
So is it possible to somehow directly get the count and item data?
I know that there are these macros:
ComboBox_GetCount
ComboBox_GetItemData
But:
Can these be macros be used with CComboBoxEx control? And ...
How do we get the HWND on the combo given the context I previously described?
Actually, I think I missunderstood the purpose of those "macros". I can get the combo handle like this:
HWND hDatesCombo = ::GetDlgItem(
GetParent()->GetSafeHwnd(), IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING);
But, ComboBox_GetCount does not return a value. Nor the others. So I am somewhat confused.
Based on the answer, this bit is now fine:
HWND hDatesCombo = ::GetDlgItem(GetParent()->GetSafeHwnd(), IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING);
int iNumDates = static_cast<int>(::SendMessage(hDatesCombo, CB_GETCOUNT, 0, 0));
And inside my for loop I am doing this:
LRESULT itemData = ::SendMessage(hDatesCombo, CB_GETITEMDATA, static_cast<WPARAM>(i), 0);
auto* pEntry = static_cast<CChristianLifeMinistryEntry*>((LPVOID)itemData);
That is the only way I can find to cast it. If I try static_cast<LPVOID> it won't work either.
I was wondering if there was any way to use native Win32 code here instead?
Yes, there is. The SendMessage function (and its returned value) is what you need …
Once you have the HWND of your combo-box, you can send it the CB_GETCOUNT message to ask it how many items it contains:
HWND hDatesCombo = ::GetDlgItem(GetParent()->GetSafeHwnd(), IDC_COMBOBOXEX_OCLM_WEEK_OF_MEETING);
LRESULT nItems = ::SendMessage(hDatesCombo, CB_GETCOUNT, 0, 0);
And, to get the item data associated with a particular entry, send the CB_GETITEMDATA message, with the (zero-based) index of the item in question as the wParam argument:
//...
LRESULT *ItemData = new LRESULT[static_cast<size_t>(nItems)];
for (int i = 0; i < nItems; ++i) {
ItemData[i] = ::SendMessage(hDatesCombo, CB_GETITEMDATA, static_cast<WPARAM>(i), 0);
}
//...
delete[] ItemData; // When you're done with the data list
Of course, if your item data are pointers (such as if you have an owner-drawn combo with1 the CBS_HASSTRINGS style), you would need to modify the second code snippet accordingly, adding relevant the reinterpret_cast operations where necessary. (Note that both the LRESULT and WPARAM types are defined as being suitable for storing pointers.)
1 The linked M/S documentation page is a bit fuzzy on whether this applies to owner-drawn combos with or without the CBS_HASSTRINGS style.

Double click in empty area of CListBox does not call my double click function

I am using Visual Studio MFC for GUI programming.
I currently have a CListBox, and I want it to call a function when I double click on an empty part of it. (when no item is selected) Currently, I am only able to add items to it by pressing a separate button.
I made the following test code to test whether the CListBox is responding to a double click at an empty spot.
BEGIN_MESSAGE_MAP(CScnBuildDlg, CDialog)
ON_LBN_DBLCLK(IDC_EVENT_LIST, OnDblclkEventList)
END_MESSAGE_MAP()
void CScnBuildDlg::OnDblclkEventList()
{
exit(-1); //Currently, it only exits when double clicking on a specific item, not on an empty space
}
Any ideas on how to fix this?
Thanks.
An additional way of trapping this event is possible by using CWnd::Oncommand. If you add this event handler to your dialog code as follows, you will be able to trap the double-click.
BOOL CScnBuildDlg::OnCommand(
WPARAM wParam,
LPARAM lParam
)
{
if (LOWORD(wParam) == IDC_EVENT_LIST && HIWORD(wParam) == LBN_DBLCLK)
DoSomething ();
return CDialog::OnCommand(wParam, lParam);
}
However, you'll need to be careful because this event will trap the double-click on an existing list box item as well. You'll also need to make sure that you allow the base class a chance to handle the WM_COMMAND message. If not, you may experience some strange bugs.

EN_UPDATE does not send to the subclassed edit box procedure

I have subclassed Edit control window procedure and then found out it no longer
sent the EN_UPDATE.
Am I missing something , and could somebody suggest me a workaround on this one?
LRESULT CALLBACK EditBoxProc_textbox3( HWND hwnd, UINT message , WPARAM wParam, LPARAM lParam )
{
int previous_position = 0 ;
WCHAR previous_text [1024];
WCHAR current_text [1024];
switch (message)
{
case WM_CREATE:
previous_text[0] = L'\0';
current_text[0] = L'\0';
break;
case EN_SETFOCUS:
// :TODO: read the current text of the textbox3 and update textbox2
// text according to it. //
Edit_Enable(hwndTextBox2,FALSE);
break;
case EN_UPDATE:
MessageBox(NULL,L"EN_UPDATE", lpszAppName,MB_OK);
GetWindowText( hwndTextBox3, current_text ,1024);
if( is_valid_textbox3(current_text))
{
wcscpy(previous_text,current_text);
previous_position = LOWORD(Edit_GetSel(hwndTextBox3));
update_textbox2(NULL);
}else
{
SetWindowText(hwndTextBox3, previous_text );
Edit_SetSel(hwndTextBox3,previous_position, previous_position);
}
break;
case EN_KILLFOCUS:
Edit_Enable(hwndTextBox2,TRUE);
break;
default:
break;
}
return CallWindowProc(edit_old_wndproc_textbox3,hwnd,message,\
wParam,lParam);
}
and then found out it no longer sent the EN_UPDATE
It never sent EN_UPDATE in the first place. It is a notification message that's actually sent as a WM_COMMAND message. And it is sent to the parent of the Edit control, not the control itself. Same goes for EN_SET/KILLFOCUS.
The design philosophy here is that an Edit control can simply be put on, say, a dialog. And the custom code that makes the dialog behave in a certain way is written in the parent window's procedure with no requirement to subclass the control. Which is fine but it makes it difficult to create a customized edit control that can have its own behavior. Or in other words, it makes it hard to componentize an edit control. The EN_SET/KILLFOCUS notifications are not a problem, you can simple detect their corresponding WM_SET/KILLFOCUS messages. But you'll hit the wall on EN_UPDATE, the control doesn't send any message like that to itself. Only the parent window can detect it.
Componentizing an edit control is pretty desirable and actively pursued by object-oriented class libraries like Winforms and Qt. They have a class wrapper for the control (TextBox, QLineEdit) that has a virtual method that can be overridden (OnTextChanged, changeEvent) so that the control can be customized into a derived class with its own behavior. And generate an event (aka signal) that anybody can subscribe to, not just the parent (TextChanged, textChanged). To make that work, the parent window needs to participate. When it gets the WM_COMMAND message, it must echo the notification back to the child control. Either by sending a special message back or by calling a virtual method on the child class.
You can of course implement this yourself as well, albeit that you are liable of reinventing such a class library. Consider using an existing one instead.

VB6: Get hWnd of a child control inside a 3rd Party control

I have a vb6 third party UpDown Control (let it be ControlX), with UISpy i could see that ControlX has 2 controls inside, one is a "ThunderRT6TextBox" the other is a "UpDown20WndClass".
I am drawing a border around ControlX. I am using the ControlX hWnd, and i draw the border like this:
hdc = BeginPaint(hwnd, tPS)
GetClientRect hwnd, controlXRect
DrawEdge hdc, controlXRect, BDR_SUNKENOUTER, BF_RECT
The problem is that the border is drawn around the ThunderRT6TextBox but not the UpDown20WndClass (maybe the ControlX hWnd returns it's inner ThunderRT6TextBox control hwnd).
I would like to get the ControlX's inner UpDown20WndClass control hWnd, to draw a border around it.
How can i do this?
Thanks in advance.
If the updown control has been made a child of the textbox, you should be able to use EnumChildWindows to find it. You might use WinSpy to see if there's an overall parent hwnd that contains both the textbox and the updown, then enum for it.
I when get ControlX hwnd it returns the hwnd of the "ThunderRT6TextBox". To get the "UpDown20WndClass" hwnd, i need its parent hwnd (ControlX hwnd). I used GetParent with "ThunderRT6TextBox" hwnd, and got the common parent hwnd, and then with FindWindowEx i got the "UpDown20WndClass".

Programmatically change keyboard-layout [duplicate]

I found this keyboard hook code, which I'm trying to slightly modify for my purposes: http://blogs.msdn.com/toub/archive/2006/05/03/589423.aspx
As an overview, I want to have the user press a key, say 'E', and have the keyboard return a different character, 'Z', to whatever app is in focus.
The relevant method I changed now looks like:
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
//The truely typed character:
int vkCode = Marshal.ReadInt32(lParam);
Console.WriteLine((Keys)vkCode);
KBDLLHOOKSTRUCT replacementKey = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
replacementKey.vkCode = 90; // char 'Z'
Marshal.StructureToPtr(replacementKey, lParam, false);
//Now changed to my set character
vkCode = Marshal.ReadInt32(lParam);
Console.WriteLine((Keys)vkCode);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
The console correctly outputs this as:
E
Z
T
Z
G
Z
etc.
HOWEVER, the in focus app still types 'E' instead of 'Z'. Why? I changed the hooked keyboard input to contain 'Z' instead of 'E', and the console lines show that it was changed correctly!
As I understand it, calling the return CallNextHookEx(_hookID, nCode, wParam, lParam); is what sends the "print this now" command to the open app. Is that not how it works? Is there something that's preventing me from typing the character I want? I know apps like AutoHotkey take an input key, check it, and return a different character. How do I do the same here?
Thanks!
I've done this before but a little different.
Instead of trying to change the parameters sent to CallNextHookEx, I 'swallowed' the key press (you can do this by returning a nonzero value from the hook procedure to prevent subsequent procedures from being called).
Then I used SendInput to send the new key that I wanted to 'inject'.
So basically it works like this:
Hook procedure identifies a target key is pressed
Call to SendInput, with the new key
Return 1 from the hook procedure to ignore the original key
Be careful of cyclic redirects, i.e. 'a' redirected to 'b' redirected to 'a', it can easily blow up ;)
You have, most likely, installed the hook "thread wide" and not "system wide", which means that the key translation will occur only for the thread installing the hook.
In order to install it "system wide" you will need two pieces: one dll having the "hook provider" and an exe managing it.
Here is a good tutorial
http://www.codeproject.com/KB/system/hooksys.aspx
and here an example:
http://www.codeguru.com/cpp/com-tech/shell/article.php/c4509/
But:
1. Installing system wide hooks can seriously screw up you system (make sure that you forward the keys that you don't translate).
2. Please... don't create another keylogger

Resources