When my application have multiple Window.
and GetTopLevelParent only returned one result.
Did the following code are act on all window?
HWND tParent = CWnd::GetTopLevelParent(); // Get a valid handle now.
if (tParent != NULL)
{
cwnd->SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE);
}
Related
I'm scratching my head in receiving the parent window handle in a namespace extension project i'm working on.
The use case is as follows:
User browses to a virtual folder through windows explorer
User performs search (search box above)
I need to retrieve the search box's text before the search starts.
I've managed to do that on a test console app with ISearchBoxInfo interface (https://msdn.microsoft.com/en-us/library/windows/desktop/dd562062%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396)
There are 2 ways i can receive a pointer to this interface:
Using IObjectWithSite::SetSite call - which is not relevant as the search is conducted in a different thread, and i cannot share the COM object between those threads
Identifying the window handle and retrieve the ISearchBox through IWebBrowser2 interface.
Both methods don't work as when i perform the search, the EnumObjects is called via a different thread, and i cannot find a way to identify who is te parent explorer window.
When doing search, the hwnd that comes is always null as follows:
This is the EnumObjects code:
// Allows a client to determine the contents of a folder by
// creating an item identifier enumeration object and returning
// its IEnumIDList interface. The methods supported by that
// interface can then be used to enumerate the folder's contents.
HRESULT CFolderViewImplFolder::EnumObjects(HWND hwnd, DWORD grfFlags, IEnumIDList **ppenumIDList)
{
HRESULT hr;
_fd = hwnd;
if (hwnd != NULL) // NULL when performing a search
{
const int n = GetWindowTextLength(hwnd);
wstring text(n + 1, L'#');
if (n > 0)
{
GetWindowText(hwnd, &text[0], text.length());
}
}
if (m_nLevel >= g_nMaxLevel)
{
*ppenumIDList = NULL;
hr = S_FALSE; // S_FALSE is allowed with NULL out param to indicate no contents.
}
else
{
CFolderViewImplEnumIDList *penum = new (std::nothrow) CFolderViewImplEnumIDList(grfFlags, m_nLevel + 1, this);
hr = penum ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = penum->Initialize();
if (SUCCEEDED(hr))
{
hr = penum->QueryInterface(IID_PPV_ARGS(ppenumIDList));
}
penum->Release();
}
}
return hr;
}
In addition to my tests, as i have also implementation of IShellFolderViewCB.MessageSFVCB, which runs on the correct thread where i can retrieve the IShellBrowser and thus the handle - when i conduct the search i encounter the following messages:
103, 103, 67, UnmergeMenu, WindowClosing, 106, ViewRelease, ViewRelease
Afterward, no more messages are posted (no matter if i re-search) - the first breakpoint is always at EnumObjects method which i have no context about the parent window.
Any light shedding would be nice.
== EDIT ==
I've came up with some workaround - this is not perfect for all cases but works for most of them - other options will still be nice.
Whenever EnumObjects is called with hwnd = NULL, i'm doing the following: (it's in C# - but it can be easily in C++ also)
static public string PrepareSearch(string currentFolderName, IntPtr hwnd)
{
SHDocVw.ShellWindows shellWindows = new ShellWindows();
SHDocVw.IWebBrowser2 foundBrowser = null;
bool wasFound = false;
string foundTxt = null;
foreach (SHDocVw.IWebBrowser2 eie in shellWindows)
{
// as the search is conducted in another thread, while the main window is "free" and in a search mode, it should be first busy.
string locName = eie.LocationName;
string exeName = eie.FullName;
if (!string.IsNullOrEmpty(exeName) && exeName.IndexOf("explorer.exe", StringComparison.OrdinalIgnoreCase) >= 0 &&
!string.IsNullOrEmpty(locName) &&
eie.Busy && eie.ReadyState == tagREADYSTATE.READYSTATE_LOADING)
{
// in here we're ok, we would also want to get the window title to make sure we're searching correctly.
string title = NSEFolder.WindowText((IntPtr)eie.HWND);
if (!string.IsNullOrEmpty(title) &&
title.IndexOf(currentFolderName) >= 0)
{
// one or more windows were found, ignore the quick search.
if (wasFound)
{
return null;
}
wasFound = true;
foundTxt = locName;
}
}
}
if (wasFound && !string.IsNullOrEmpty(foundTxt))
{
return foundTxt;
}
return null;
}
Basically i'm going over all explorer windows, trying to find one that is indeed "explorer.exe" + not empty search string (LocationName) + busy... + title contains name of the current folder name.
it will fail when 2 windows are busy and have the same folder name in the title - but this might be good enough... Not sure here.
So, after a talk with microsoft - it's not possible to do so.
the workaround i've added is a valid one (thoguh not perfect).
Does any one in 2017 knows how to implement calling HtmlHelp function that will open .chm file with "Search" pane and query string in it's listbox and will be able to execute this query?
I try following:
HH_FTS_QUERY query;
query.cbStruct = sizeof(HH_FTS_QUERY);
query.fUniCodeStrings = TRUE;
query.pszSearchQuery = szSearchStr;
query.iProximity = HH_FTS_DEFAULT_PROXIMITY;
query.fStemmedSearch = TRUE;
query.fTitleOnly = TRUE;
query.fExecute = TRUE;
query.pszWindow = NULL;
HWND hHelpWnd = ::HtmlHelp(m_hWnd, szFile, HH_DISPLAY_SEARCH, (DWORD_PTR)&query);
but the query in query.pszSearchQuery won't be executed. I have opened .chm file with query.pszSearchQuery in "Search" pane's listbox on my screen, but I have to click "List Topics" myself to show results of search.
With the help of HTMLHelp API - VBA, VB6 and VB2003, i will try to reply.
I believe there no API function to list topics in vc++. You have send button click event to launched help window.
LRESULT OnSearch(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
TCHAR szBuf[128];
GetDlgItem(IDC_EDIT1).GetWindowText(szBuf, sizeof(szBuf));
if (!_tcslen(szBuf))
return 0;
//
// First, invoke HtmlHelp with HH_DISPLAY_SEARCH.
// It doesn't execute search, but it's need for showing 'search' tab.
//
HH_FTS_QUERY fq;
ZeroMemory(&fq, sizeof(fq));
fq.cbStruct = sizeof(fq);
fq.fUniCodeStrings = FALSE;
fq.pszSearchQuery = (LPCTSTR)szBuf;
fq.iProximity = HH_FTS_DEFAULT_PROXIMITY;
fq.fStemmedSearch = FALSE;
fq.fTitleOnly = FALSE;
fq.fExecute = FALSE;
fq.pszWindow = NULL;
HWND hwndHelp = ::HtmlHelp(NULL, _T("realplay.chm"), HH_DISPLAY_SEARCH, (DWORD)&fq);
//
// Find query combobox and set query text.
//
HWND hPaneWnd, hPaneLast, hTabWnd, hDlgWnd, hCtlWnd;
hPaneWnd = FindWindowEx(hwndHelp, NULL, _T("HH Child"), NULL);
for (;;) // last HH Child
{
hPaneLast = FindWindowEx(hwndHelp, hPaneWnd, _T("HH Child"), NULL); // last HH Child
if (!hPaneLast)
break;
hPaneWnd = hPaneLast;
}
ATLTRACE("hPaneWnd == %x", hPaneWnd);
hCtlWnd = FindWindowEx(hPaneWnd, NULL, _T("Button"), NULL); // skip Tab Control
//
// There are two types of interfaces:
//
// 1.
// Window hierarchy:
// + Main window
// + HH Child
// + Browser ...
// + HH Child <- second "HH Child" window
// - Edit <- we have to fill this edit
// - Buttons <- and press this buttons
// ...
if (hCtlWnd)
{
hCtlWnd = FindWindowEx(hPaneWnd, NULL, _T("Edit"), NULL); // skip Tab Control
// Set window text
ATLASSERT(hCtlWnd != NULL);
::SendMessage(hCtlWnd, WM_SETTEXT, 0, (LPARAM)szBuf); // fill it by our query
::SendMessage(hwndHelp, WM_COMMAND, 0xbc7, 0); // 0x3ee -- 'List Topics' button, it runs search
if (::SendMessage(GetDlgItem(IDC_CHECK1), BM_GETCHECK, 0, 0) == BST_CHECKED)
::SendMessage(hwndHelp, WM_COMMAND, 0xbbe, 0); // 0x3f1 -- 'Display' button, it shows first item
}
//2.
// Window hierarchy:
// + Main window
// + HH Child
// + Browser ...
// + HH Child <- second "HH Child" window
// + Tab Control
// + Dialog
// - Combobox <- we have to fill this combo
// - Buttons <- and press this buttons
// ...
else
{
hTabWnd = FindWindowEx(hPaneWnd, NULL, _T("SysTabControl32"), NULL); // skip Tab Control
hDlgWnd = FindWindowEx(hTabWnd, NULL, NULL, NULL); // skip dialog
TCHAR szClass[64];
GetClassName(hDlgWnd, szClass, sizeof(szClass));
ATLTRACE("hDlgWnd(1) == %x", hDlgWnd);
if (!_tcsstr(szClass, "#")) // Is it dialog?
hDlgWnd = FindWindowEx(hTabWnd, hDlgWnd, NULL, NULL); // skip dialog
hCtlWnd = FindWindowEx(hDlgWnd, NULL, _T("ComboBox"), NULL); // well, it's combobox
// Set window text
ATLASSERT(hCtlWnd != NULL);
::SendMessage(hCtlWnd, WM_SETTEXT, 0, (LPARAM)szBuf); // fill it by our query
//
// Run search and show first finded page
//
::SendMessage(hwndHelp, WM_COMMAND, 0x3ee, 0); // 0x3ee -- 'List Topics' button, it runs search
if (::SendMessage(GetDlgItem(IDC_CHECK1), BM_GETCHECK, 0, 0) == BST_CHECKED)
::SendMessage(hwndHelp, WM_COMMAND, 0x3f1, 0); // 0x3f1 -- 'Display' button, it shows first item
}
return 0;
}
EDIT:
You can get control ID of List Topic by spy++
Question
Why is pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd); occasionally returning NULL in the CWnd::DestroyWindow() function causing an ASSERT statement to fail while trying to destroy a modal dialog?
Furthermore, between causing the issue and attempting to destroy the window, why does the PreTranslateMessage() function stop getting called?
Initial Information
I have an ActiveX control that implements an on screen keyboard.
This keyboard accepts both touch/click events and keystroke events from an attached hardware keyboard.
Input to the control is validated using an algorithm defined by the module that created the keyboard.
Touching the buttons on screen generates input that follows
OnButtonUP()->AddCharacter()->ValidateText()
Typing on the physical keyboard generates input that follows
PreTranslateMessage()->AddCharacter()->ValidateText()
The ValidateText() function fires an event that is handled by the module that created the keyboard.
I have added a try-catch block around the event firing to handle any errors that occur while the event is being processed.
Confusing Behavior
What I have found is that if an error is caused by using the USB Keyboard, when the user attempts to close the Keyboard a debug assert statement in CWnd::DestroyWindow() fails.
If the same error is caused by using the touch screen interface, the assert statement succeeds.
I have also discovered that if the error is caused using the USB keyboard, that the PreTranslateMessage() function for the ActiveX control stops being called. I confirmed this by setting a breakpoint on the first line of the function and spying on the messages being sent to the window. After causing the error there were still a large number of messages (mainly wm_mousemoves) but the breakpoint no longer caused the program to break.
Source Code Snippets
The code for firing the event is as follows:
try
{
m_control->OnValidate(&text, &isValid);
}
catch (...)
{
LOG_CRITICAL_ERROR(/*Format(*/L"Keyboard::ValidateText: Error occured while validating"/*, badText.c_str()).c_str()*/);
}
Where the OnValidate() function is defined as:
void OnValidate(BSTR* text, LONG* isValid)
{
FireEvent(eventidOnValidate, EVENT_PARAM(VTS_PBSTR VTS_PI4), text, isValid);
}
The CWnd::DestroyWindow() function is (With some bits preprocessor bits removed) :
BOOL CWnd::DestroyWindow()
{
CWnd* pWnd;
CHandleMap* pMap;
HWND hWndOrig;
BOOL bResult;
if ((m_hWnd == NULL) && (m_pCtrlSite == NULL))
return FALSE;
bResult = FALSE;
pMap = NULL;
pWnd = NULL;
hWndOrig = NULL;
if (m_hWnd != NULL)
{
pMap = afxMapHWND();
ENSURE(pMap != NULL);
pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd); //<- This returns NULL before the Assert fails
hWndOrig = m_hWnd;
}
if ((m_hWnd != NULL) || (m_pCtrlSite != NULL))
{
if (m_pCtrlSite == NULL)
bResult = ::DestroyWindow(m_hWnd);
else
bResult = m_pCtrlSite->DestroyControl();
}
if (hWndOrig != NULL)
{
// Note that 'this' may have been deleted at this point,
// (but only if pWnd != NULL)
if (pWnd != NULL)
{
// Should have been detached by OnNcDestroy
ASSERT(pMap->LookupPermanent(hWndOrig) == NULL);
}
else
{
ASSERT(m_hWnd == hWndOrig); //<-This is the ASSERT that fails!
// Detach after DestroyWindow called just in case
Detach();
}
}
return bResult;
}
From what I have been able to obtain via tracepoints, when the assert fails, pWnd = (CWnd*)pMap->LookupPermanent(m_hWnd); returns NULL.
Using the information from the tracepoints, the DestroyWindow() call for the Keyboard control seems to follow:
When It Works (Error caused by touch input handled)
Start Destroying Keyboard: m_hWnd = NON-NULL, hWndOrig = NON-Null, pWnd = NON-NULL
Recurse into destroying keyboard buttons: m_hWnd = NON-NULL, hWndOrig = NON-Null, pWnd = NON-NULL
Return from destroying all buttons: m_hWnd = NULL, hWndOrig = NON-Null, pWnd = NON-NULL
ASSERT(pMap->LookupPermanent(hWndOrig) == NULL); is called and succeeds.
When It Breaks (Error caused by hardware input handled)
Start Destroying Keyboard: m_hWnd = NON-NULL, hWndOrig = NON-Null, pWnd = NULL
Recurse into destroying keyboard buttons: m_hWnd = NON-NULL, hWndOrig = NON-Null, pWnd = NON-NULL
Return from destroying all buttons: m_hWnd = NULL, hWndOrig = NON-Null, pWnd = NULL
ASSERT(m_hWnd == hWndOrig); is called and fails.
I am trying to create a Worker thread but I have not done Visual C++ since 2004 and threading syntax has changed (Please don't worry about timing issues, I will have that covered). the issue is that in an MFC dialog app, every way I know of trying to create a thread won't get past the compiler. I also have tried the "&" trick. It can't seem to find this function at all. Can anyone help please? NOTE: I tried three methods and I left the first uncommented.
if (i_found >= 0) { //this is just a combobox snippet to show the calls
MessageBox(wch.GetBuffer(0), L"Port Select", MB_OK);
_serialPort->Close();
ConversationRight = gcnew Conversation(systrName);
_beginthread(&CDLP_Printer_ControlDlg::Mine_QL, 0, 0); //NONE WORKING!!
//unsigned long lpRecvThread = _beginthreadex(NULL, 0, Mine_QL, (void*)this, NULL, NULL);
//unsigned long lpThreadIdRequest;
//::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Mine_QL, (void*)NULL, 0, &lpThreadIdRequest);
}
else
MessageBox(L"Not the left board", L"Port Select", MB_OK);
_serialPort->Close();
}
void CDLP_Printer_ControlDlg::OnBnClickedBtnStop()
{
ConversationRight->WriteLn("5");
}
bool CDLP_Printer_ControlDlg::UpdateCommsWindow_left(String^ strCommsLine)
{
return false;
}
void CDLP_Printer_ControlDlg::Mine_QL()
{
//_endthread();
}
I'm creating dialog with do modal:
MainHamsterDlg MainHamsterDlg;
if (MainHamsterDlg.DoModal() == IDCANCEL)
break;
Then in that dialog create worker thread:
BOOL MainHamsterDlg::OnInitDialog()
{
AfxBeginThread(WorkerThreadProc, m_hWnd, THREAD_PRIORITY_NORMAL, 0, 0, NULL);
CDialogEx::OnInitDialog();
return TRUE;
}
the thread must exit dialog by sending return value IDCANCEL.
UINT WorkerThreadProc(LPVOID Param) //Sample function for using in AfxBeginThread
{
Sleep(1000); // process simulation
MainHamsterDlg * self = (MainHamsterDlg *)Param;
self->EndDialog(IDCANCEL);
return FALSE;
}
When compiling I do not get any error. when processing then getting on the point:
self->EndDialog(IDCANCEL);
error message:
Unhandled exception at 0x01503AD4 in L2Hamster.exe: 0xC0000005:
Access violation reading location 0x00000020.
that pointing my to dlgcore.cpp file:
void CDialog::EndDialog(int nResult)
{
ASSERT(::IsWindow(m_hWnd)); <<<<<===== to that line
m_bClosedByEndDialog = TRUE;
if (m_nFlags & (WF_MODALLOOP|WF_CONTINUEMODAL))
EndModalLoop(nResult);
::EndDialog(m_hWnd, nResult);
}
I don't know what I'm doing wrong. any solution?
I assume you intended to pass a pointer to the dialog to the thread proc. But you passed null instead:
AfxBeginThread(WorkerThreadProc, NULL, THREAD_PRIORITY_NORMAL, 0, 0, NULL);
Should be:
AfxBeginThread(WorkerThreadProc, this, THREAD_PRIORITY_NORMAL, 0, 0, NULL);
You will also need to coordinate the end of the thread with the end of the dialog to ensure the dialog isn't dismissed before the thread ends. (I assume it is some sort of progress dialog).
This looks like it is the problem to me:
AfxBeginThread(WorkerThreadProc, NULL, THREAD_PRIORITY_NORMAL, 0, 0, NULL);
followed by
UINT WorkerThreadProc(LPVOID Param) //Sample function for using in AfxBeginThread
{
MainHamsterDlg * self = (MainHamsterDlg *)Param;
self->EndDialog(IDCANCEL);
...
}
Here, Param is NULL, so self is NULL. You then dereference a null pointer.