I got a dialog-based MFC-Tool that is supposed to show the title of a window of another application in a messagebox when i click on it.
My Problem is that the WM_KILLFOCUS doesn't work here. Maybe I'm doing it wrong.
I do the following:
BEGIN_MESSAGE_MAP(CMyDlg, CDialog)
ON_WM_KILLFOCUS()
END_MESSAGE_MAP()
...
...
void CMyDlg::OnKillFocus( CWnd* pNewWnd )
{
CDialog::OnKillFocus(pNewWnd);
if(m_bSelectorModeActive)
{
HWND hwnd(GetForegroundWindow());
TCHAR buf[512];
::GetWindowText(hwnd, buf, 512);
MessageBox(buf);
}
}
Any idea what's wrong?
This is my guess
Replace HWND hwnd(GetForegroundWindow()); with GetActiveWindow(void) .
I solved it, thanks for your efforts.
Yes, i do use CStrings, this was just a little example of a bit more complex thing i do. My problem was not the function itself but the event WM_KILLFOCUS that didn't seem to work.
Maybe I was not clear enough here, sorry.
WM_ACTIVATE does what i need. It notifies my dialog when the focus is set and/or lost.
The code you've shown shouldn't even compile. The GetForegroundWindow function provided by MFC doesn't return an HWND, so you can't initialize the hwnd variable using its return value.
If you want to get an HWND, you need to call GetForegroundWindow from the Windows API by escaping the call with ::, just like you did for GetWindowText. So simply rewrite your code as follows:
void CMyDlg::OnKillFocus( CWnd* pNewWnd )
{
CDialog::OnKillFocus(pNewWnd);
if(m_bSelectorModeActive)
{
HWND hwnd(::GetForegroundWindow());
TCHAR buf[512];
::GetWindowText(hwnd, buf, 512);
MessageBox(buf);
}
}
Beyond that, in looking at your code, one wonders that you seem to be ignoring the object-orientation MFC so humbly attempts to bring to the Windows API. You shouldn't need to work directly with window handles. And one could argue that the most compelling reason to use MFC is its CString class. There's no reason you should have to deal with an array of TCHARs anymore. I might write this instead:
void CMyDlg::OnKillFocus( CWnd* pNewWnd )
{
CDialog::OnKillFocus(pNewWnd);
if(m_bSelectorModeActive)
{
CWnd* pForeWnd = GetForegroundWindow();
CString windowText;
pForeWnd->GetWindowText(windowText);
MessageBox(windowText);
}
}
Related
I'm trying to figure out if it is possible to use OR re-create the CFolderPickerDialog dialog without using MFC, or if there has been an attempt. So far I did not find a lot of hints. This old question does not seem to help me either.
I currently open the normal folder dialog with SHBrowseForFolder. But I need an Explorer-style dialog.
Here is the Explorer-style dialog (MFC) from another application:
#include <afxdlgs.h> requires MFC. I cannot use MFC in this specific project.
Is there a way to do this without using MFC ?
Honestly, I didn't even know that MFC had wrapped this. My class library has its own implementation. And, as Barmak points out, the MFC implementation may even be buggy, and certainly has usage caveats that would not be obvious had you failed to read the documentation carefully.
That said, in general, it is good advice to use functionality that is already wrapped up in a library because this makes your life easier. If you don't want to use the whole library, but still want to see how it implements a particular feature, you can check the library's source code. MFC is provided with reference source so that you can do this easily (also so you can debug it). Although it would probably be a violation of the license to copy and paste code directly out of MFC (it would also be nigh-impossible, since it uses so many MFC-specific idioms), you can look at the code to see what they're doing, then go back to the Windows SDK documentation to figure out how to write the code yourself.
In this case, the relevant SDK documentation is here. Modern versions of Windows (since Vista) use the Common Item Dialog API to display open/save file/folder dialogs. The API consists of a base IFileDialog interface, with two sub-interfaces, IFileOpenDialog and IFileSaveDialog. There is a lot of flexibility here; the details are in the documentation, along with sample code.
Note that the Common Item Dialog is only available on Windows Vista and later. If you need to support older operating systems (I still support Windows XP), you need a fallback. The SHBrowseForFolder dialog is that fallback. It certainly has its design flaws, but it is better than nothing.
If all you want is a simple folder-picker dialog, here is an approximation of the code that I use. It uses a couple of ATL/MFC types, like the CString and CComPtr wrapper classes, but you can translate that to alternate classes of your own choosing (such as std::wstring and _com_ptr_t). It displays a simple browse-for-folder dialog, appropriate for the current operating system, with a caller-specified title and starting path. If it succeeds, it returns a string containing the path to the folder selected by the user; otherwise, it returns an empty string.
namespace
{
HRESULT Downlevel_SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx* pbc, REFIID riid, void** ppv)
{
_ASSERTE(IsWinVistaOrLater());
HRESULT hResult = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
const HINSTANCE hinstLib = GetModuleHandle(TEXT("shell32"));
if (hinstLib)
{
typedef HRESULT (WINAPI * pfSHCreateItemFromParsingName)(PCWSTR, IBindCtx*, REFIID, void**);
const pfSHCreateItemFromParsingName pf = reinterpret_cast<pfSHCreateItemFromParsingName>(GetProcAddress(hinstLib, _CRT_STRINGIZE(SHCreateItemFromParsingName)));
if (pf)
{
hResult = pf(pszPath, pbc, riid, ppv);
}
}
return hResult;
}
int CALLBACK BrowseForFolderCallbackProc(HWND hWnd, UINT uMsg, LPARAM /* lParam */, LPARAM lData)
{
if (uMsg == BFFM_INITIALIZED)
{
// Start with BFFM_SETSELECTION, which is always available.
SendMessage(hWnd, BFFM_SETSELECTION, TRUE, lData);
#ifdef UNICODE
// If possible, also try to use BFFM_SETEXPANDED, which was introduced with
// version 6.0 of the shell (Windows XP).
SendMessage(hWnd, BFFM_SETEXPANDED, TRUE, lData);
// You can also set the caption for the dialog's "OK" button here, if you like
// (e.g., by loading a string from a resource).
//SendMessage(hWnd,
// BFFM_SETOKTEXT,
// 0,
// reinterpret_cast<LPARAM>(pszOKBtnCaption));
#endif // UNICODE
}
return 0;
}
}
CString ShowFolderBrowserDialog(HWND hwndOwner, const CString& strDlgTitle, const CString& strStartPath)
{
if (IsWinVistaOrLater())
{
CComPtr<IFileOpenDialog> pFileOpenDlg;
if (SUCCEEDED(pFileOpenDlg.CoCreateInstance(__uuidof(FileOpenDialog))))
{
if (SUCCEEDED(pFileOpenDlg->SetTitle(strDlgTitle)))
{
FILEOPENDIALOGOPTIONS options;
if (SUCCEEDED(pFileOpenDlg->GetOptions(&options)))
{
if (SUCCEEDED(pFileOpenDlg->SetOptions(options | FOS_PATHMUSTEXIST | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM)))
{
CComPtr<IShellItem> psiStartPath;
if (SUCCEEDED(Downlevel_SHCreateItemFromParsingName(static_cast<const TCHAR*>(strStartPath),
NULL,
IID_PPV_ARGS(&psiStartPath))))
{
if (SUCCEEDED(pFileOpenDlg->SetFolder(psiStartPath)))
{
if (SUCCEEDED(pFileOpenDlg->Show(hwndOwner)))
{
CComPtr<IShellItem> pShellItemResult;
pFileOpenDlg->GetResult(&pShellItemResult);
CComHeapPtr<TCHAR> pszSelectedItem;
if (SUCCEEDED(pShellItemResult->GetDisplayName(SIGDN_FILESYSPATH, &pszSelectedItem)))
{
return pszSelectedItem;
}
}
}
}
}
}
}
}
}
else
{
TCHAR szBuffer[MAX_PATH + 1];
szBuffer[0] = TEXT('\0');
BROWSEINFO bi;
bi.hwndOwner = hwndOwner;
bi.pidlRoot = nullptr;
bi.pszDisplayName = szBuffer;
bi.lpszTitle = strDlgTitle;
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE | BIF_SHAREABLE | BIF_NONEWFOLDERBUTTON;
bi.lpfn = BrowseForFolderCallbackProc;
bi.lParam = reinterpret_cast<LPARAM>(static_cast<const TCHAR*>(strStartPath));
CComHeapPtr<ITEMIDLIST> pidl(SHBrowseForFolder(&bi));
if (pidl && SHGetPathFromIDList(pidl, szBuffer))
{
return pszSelectedItem;
}
}
return TEXT("");
}
The dialog only shows actual folders in the filesystem. Although the Common Item Dialog API supports other types of special folders and namespaces, I don't need that in my app, so my code doesn't deal with the complexity. Use this as a starting point, along with the documentation, if you need more features. The most notable aspect is probably the use of SHCreateItemFromParsingName (which I have wrapped up in a dynamic call so that the code continues to run on older operating systems) to translate the caller-specified starting path (which is a string) to a Shell item object (as required by the Common Item Dialog API).
How do I disable MFC dialog OK button?
This code:
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(FALSE);
causes exception "Access violation reading location..."
in line ASSERT(::IsWindow(m_hWnd) || (m_pCtrlSite != NULL)); of function CWnd::EnableWindow(BOOL bEnable) in winnocc.cpp from mfc90d.dll
In this time focus is on another control.
What's can be wrong?
Thanks for help.
[EDITED]
bool CSCalcNormCell::OnSelectionChanged( CWnd* pDlg, int type, int page, UINT ctrl_id )
{
DDX_DataBox(pDX.get(), IDC_WORKSHOP_COMBO, ws_code);
if (!CInfactoryPriceAdapter::CanEditPricesForWorkshop( ws_code ))
{
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(FALSE);
}
else
{
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
fieldOK->EnableWindow(TRUE);
}
}
I'm not sure why would wouldn't be able to do it. If I take a regular CDialog and I do an init like this:
BOOL CMyDialog::OnInitDialog() {
CDialog::OnInitDialog();
CWnd *okbtn = GetDlgItem( IDOK );
if ( okbtn ) {
okbtn->EnableWindow( FALSE );
}
return TRUE;
}
it disables the button just fine. Perhaps something else is wrong?
Try this: http://support.microsoft.com/kb/122489
How to Disable Default Pushbutton Handling for MFC Dialog
Although default button (pushbutton) support is recommended, you might
want to disable or modify the standard implementation in certain
situations. You can do this in an MFC application by following these
steps:
Load the dialog into App Studio and change the OK button identifier
from IDOK to something else such as IDC_MYOK. Also, clear the check
from Default Button property.
Use ClassWizard to create a message
handling function for this button named OnClickedMyOK. This function
will be executed when a BN_CLICKED message is received from this
button.
In the code for OnClickedMyOK, call the base class version of
the OnOK function. Here is an example:
void CMyDialog::OnClickedMyOK()
{
CDialog::OnOK();
}
Override OnOK for your dialog, and do nothing inside the function. Here is an example:
void CMyDialog::OnOK()
{
}
Run the program and bring up the dialog. Give focus to a control other
than the OK button. Press the RETURN key. Notice that CDialog::OnOK()
is never executed.
I suspect the problem comes from pDlg pointer. When you call pDlg->GetDlgItem(IDOK), is the dialog already created already?
Make a breakpoint at the line CWnd* fieldOK = pDlg->GetDlgItem(IDOK); and debug into it to see if fieldOK pointer is null or a valid pointer.
That is why I think mark's answer is very close. You can disable it onOnInitDialog` or other members of you dialog class after it showed up.
The problem you have is that the button control has not been created on the interface yet. We do not get the full vision of your problem.
Anyway, you should protect your code from crashing. It is better that your code does nothing than to crash the application. Restructuring it like this avoids the access violation problem due to the NULL pointer:
bool CSCalcNormCell::OnSelectionChanged( CWnd* pDlg, int type, int page, UINT ctrl_id )
{
DDX_DataBox(pDX.get(), IDC_WORKSHOP_COMBO, ws_code);
CWnd* fieldOK = pDlg->GetDlgItem(IDOK);
if (fieldOK)
{
if (!CInfactoryPriceAdapter::CanEditPricesForWorkshop( ws_code ))
fieldOK->EnableWindow(FALSE);
else
fieldOK->EnableWindow(TRUE);
}
}
You need to load a bitmap for the disable mode of the OK button in LoadBitmaps() function.
Using pseudo funcs for subclassing:
CreateSpecialHandle(TWinControl *Control, const TCreateParams &Params, const AnsiString SubClass)
{
......;
set Control DefWndProc to SubClass.lpfnWndProc
set Control WindowHandle from CreateWindowEx
......;
subclass(TWinControl *Control);
}
subclass(TWinControl *Control)
{
......;
oldWProc = (void*)GetWindowLong(Control->Handle, GWL_WNDPROC);
oldDefWProc = (void*)(Control->DefWndProc);
oldWindowProc = Control->WindowProc;
MakeObjectInstance(newWProc) for SetWindowLong
MakeObjectInstance(newDefWProc) for Control->DefWndProc
Control->WindowProc = newWindowProc;
......;
}
Now, we have unexpected behavior of subclassed control.
WM_NCHITTEST result 0, etc...
For example when newWProc intercepts WM_NCHITTEST and sets Result to HTCLIENT
we have mouse response, but, is that not responding without setting msg.result to 1 for msg.msg WM_NCHITTEST consequence of my mistake and wrong subclassing, what else we need to handle manually?
newWProc make callback of oldWProc
newDefWProc make callback of oldDefWProc
newWindowProc calls oldWindowProc
Do we have to subclass parent control of subclassed control as well?
Also, sending WM_GETTEXT results with empty buffer.
Obviously, we are doing something wrong here. We need explanation,
Thank You all in advance
Update:
in TDCEdit:public TCustomEdit overriding CreateWindowHandle
void __fastcal CreateWindowHandle(const TCreateParams &Params)
{
CreateSpecialHandle(this,Params,TEXT("EDIT"));
}
void CreateSpecialHandle(TWinControl *Control,const TCreateParams &Params, AnsiString SubClass)
{
...
Control->WindowHandle = CreateWindowEx(...,"EDIT",....);
....
subclass(Control);
}
subclass(TWinControl* Control)
{
......;
oldWProc = (void*)GetWindowLong(Control->Handle, GWL_WNDPROC);
oldDefWProc = (void*)(Control->DefWndProc);
oldWindowProc = Control->WindowProc;
MakeObjectInstance(newWProc) for SetWindowLong
MakeObjectInstance(newDefWProc) for Control->DefWndProc
Control->WindowProc = newWindowProc;
......;
}
Now, when I use TDCEdit and intercept Message.Msg == WM_NCHITTEST
inside newWProc Message.Result is 0 and stay 0 through all message process chain.
Note that subclassing TCustomEdit is one among other controls we need to subclass
in project and we try to use same subclass(TWinControl*) function for all.
Here is part of newWProc with few more lines to focus on problem
void __fastcall TControlWrapper::newWProc(Messages::TMessage &Message)
{
if(Message.Msg == WM_NCHITTEST ) // TEST
if(Message.Result == 0)
Message.Result=1;//<- WHY I NEED TO DO THIS
if( Message.Msg == WM_DESTROY) {
HandleWMDestroy(Message);
return;
}
CallWindowProcW( (int(__stdcall*)())oldWProc,
Handle, Message.Msg, Message.WParam,
Message.LParam);
if(Message.Msg == WM_NCHITTEST )
if(Message.Result == 0)Message.Result=1;//<- OR THIS
}
This is a confusing question - it doesn't help that your code samples are not C++.
set Control DefWndProc to SubClass.lpfnWndProc
is not a line in a C++ function, for example. Can you show your actual code please?
I can make a guess at what you're trying to do: are you trying to subclass a window (perhaps a form?) so that it moves when the mouse is clicked on it? If so, you don't need to do any raw Windows API-style subclassing, the way you appear to be doing with GetWindowLong. In C++ Builder, the VCL is an object-oriented wrapper around the Windows API, and you can do this in one of two much cleaner ways:
Create a new WindowProc and set it; this is a property pointing to a new window procedure, and you simply call the old one too;
Create a descendant class of your TWinControl (if you're using a form, you already have one) and implement the virtual method WndProc.
An example of #1, in Delphi (but you should be easily able to convert it to C++) is in the Embarcadero documentation on subclassing WndProc.
An example of #2, the cleanest OO version, is here, and this actually shows how to do what you're trying to do, too: C++Builder: Create a TForm with BorderStyle bsNone that is nevertheless movable and resizable
Given what you appear to want to do, I would suggest going with #2.
I want to add visible window titles to a combobox. Here is my source:
BOOL CALLBACK EnumWindowsProc(HWND hWnd, long lParam)
{
TCHAR buff[255];
CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_PROCESS);
if (IsWindowVisible(hWnd))
{
GetWindowText(hWnd, buff, 254);
pComboBox->AddString(buff);
}
return TRUE;
}
void CFindProcess::OnDropdownComboProcess()
{
EnumWindows(EnumWindowsProc, 0);
}
but I get error:
error C2660: 'GetDlgItem' : function does not take 1 arguments 60
How I can correctly add titles to combo?
MFC objects are thread-sensitive, GetDlgItem works well in the thread that created the object, probably the main UI thread. Function EnumWindows probably creates a worker thread to access the callback function, and that is why GetDlgItem failed to get a valid handle of the combobox.
To access the combobox properly in another thread, you have to use the static function: CWnd::FromHandle with the raw handle of the combobox object as follows:
BOOL CALLBACK EnumWindowsProc(HWND hWnd, long lParam)
{
if (IsWindowVisible(hWnd))
{ TCHAR szBuffer[255];
INT nLength = GetWindowText(hWnd, szBuffer, 254);
if (nLength>0)
{ // only add windows that has a caption
CComboBox *pComboBox = (CComboBox*)CWnd::FromHandle((HWND)lParam);
pComboBox->AddString(szBuffer);
}
}
return TRUE;
}
// call EnumWindows --------------------
CComboBox *pComboBox = (CComboBox *)GetDlgItem(IDC_COMBO1);
// passing the raw handle of the combobox as parameter
EnumWindows(EnumWindowsProc, (LPARAM)pComboBox->m_hWnd);
Firstly, your GetDlgItem has two parameters, and the first is a handle to the dialog box that contains the control.
So it expects a HWND parameter of the dialog that contains this control, I would presume that will be the HWND you pass as a parameter to your function.
CComboBox* pComboBox = (CComboBox*)GetDlgItem(hWnd,IDC_COMBO_PROCESS);
^^^^ added parameter
If you look at EnumWindows in MSDN, you'll see you have to pass a callback and it has a HWND parameter, if you look at what this parameter is for it says:
A handle to a top-level window.
This is exactly what you have to pass to GetDlgItem.
Also, you should check the return value of GetWindowText as this returns the number of characters written to the buff you passed it.
int ret = GetWindowText(hWnd, buff, 254);
if (ret > 0) pComboBox->AddString(buff); // only add non-empty strings.
In addition to what user #mfc has provided, I would not do UI update from a different thread. I believe EnumWindows does not create thread for enumeration. It would call the callbacks within the call-stack of current thread.
This, in turn, means that UI may freeze for a while. Thus, it is recommended to create a thread for enumeration. More over, I would not directly update UI from different thread. May be a vector of string, or a PostMessage (on each iteration) I would have used.
It is true that EnumWindows may perform quite fast. But when you move to enumerate other (kernel) objects like file, printers, users etc - the UI is definitely going to freeze. So, better practice writing multithreaded code. Initially writing MT-code would be a pain, but later you'd love it, appreciate it, and cannot live without it.
There is an externally running program that i need the capability to resize. The kicker for me is that part of the title is the version and other specific information related to that instance. I know the substring that should be consistent across versions.
I have attempted the Findwindow() function, which works well if you have the exact wording of the title, but not when you only have a portion. I have also tried EnumWindows, but i believe that has the same limitations (i didn't have much luck with it).
I feel the simplest thing i could do (if possible) would be to get the window handle from the image name in order to do my resizing.
Ideas?
Here's a working piece of code I just tested on MSVS 2010 that works perfectly:
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include <windows.h>
BOOL CALLBACK FindWindowBySubstr(HWND hwnd, LPARAM substring)
{
const DWORD TITLE_SIZE = 1024;
TCHAR windowTitle[TITLE_SIZE];
if (GetWindowText(hwnd, windowTitle, TITLE_SIZE))
{
//_tprintf(TEXT("%s\n"), windowTitle);
// Uncomment to print all windows being enumerated
if (_tcsstr(windowTitle, LPCTSTR(substring)) != NULL)
{
// We found the window! Stop enumerating.
return false;
}
}
return true; // Need to continue enumerating windows
}
int main()
{
const TCHAR substring[] = TEXT("Substring");
EnumWindows(FindWindowBySubstr, (LPARAM)substring);
}
EnumWindows was meant specifically for this. You create your own callback function to pass to EnumWindows, and it will call your callback function for each window it enumerates and pass it the hwnd of the window. You can call GetWindowText inside of your callback function to get the window title and search that text like any other. What problem did you have with that code? Why don't you post it?