EN_UPDATE does not send to the subclassed edit box procedure - windows

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.

Related

Any suggestions to effectively update the status bar of an application?

The status bar window of this program needs to be updated every time the user press a key that is likely to move the caret of the EDIT control, and the code below works like a charm! In a nutshell, pressing a key on the keyboard will update some values and send a message "ECM_GETLINEINFOS" that is next processed in the main window procedure (code below)
However, there is flickering that is not disturbing, of course, but I wonder if it's related to how I set the text on the status bar (maybe too many updates ?) or just a problem with the drawing part.
PS: The flickering occurs on the text, not the status bar in itself, so that is why I'm questioning how I should manage the update of my window.
constexpr int failed_val = -1;
LRESULT MainWindow::HandleMessage(UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
// Custom message sent by an EDIT control, I
// use this message to tell the status bar it must update its text.
case CEM_GETLINEINFO:
{
const size_t buffSz = 24;
std::wstring buffer(buffSz, L'\0');
int line = LOWORD(wParam);
int column = HIWORD(wParam);
int count = _snwprintf_s(buffer.data(), buffer.size(),
_TRUNCATE, L"Ln %d, Col %d", line, column);
if (count != failed_val) {
// Param 1 : The text to be displayed
// Param 2 : Which status bar part
m_statusBar->SetText(buffer, 0);
}
}
return 0;
}
}
Just as Flicker-Free Displays Using an Off-Screen DC directed by the answer said,
What makes this window flicker when we update it frequently? The
answer is that Windows asks the window procedure to repaint the window
as a two-step process. First, it sends a WM_ERASEBKGND message and
then a WM_PAINT message. The default handling for the WM_ERASEBKGND
message is to fill the area with the current window background color.
So the sequence of events is first to fill the area with solid color
and then to draw the text on top. The net result of doing this
frequently is that the window state alternates between its erased
state and its drawn state—it flickers.
And
To prevent the control from flickering when we update it frequently,
we need to make two changes to how the control handles messages.
First, we need to prevent Windows from providing the default handling
of WM_ERASEBKGND messages. Secondly, we need to handle WM_PAINT
messages so that the background is painted with the window background
color and so that the changes to the control's client area happen at
once.
A status bar flicker free solution in .NET: Searching Visual Studio .NET style status bar. Or Simple Mode Status Bars could be enough.

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.

Calling ShowWindow(SW_SHOW) on ATL modeless dialog not bringing window to front

I have a modeless dialog which I am showing and hiding programmatically from a worker thread. The problem is that the CWindow::ShowWindow(SW_SHOW) function is sometimes bringing the dialog to the front, and sometimes not.
Task: Show/Hide a simple logging & control window for an ATL COM object (dll) that runs in a separate thread. The COM object isn't a control: it is being used to wrap processing code for use in Excel VBA.
I've created a CMyDialog class derived from the CAxDialogImpl template. Then I'm using the CWorkerThread template for the worker thread. This is the Execute block (ie the function that runs in the worker thread).
HRESULT CLogProcessor::Execute(DWORD_PTR dwParam, HANDLE hObject)
{
m_bRunning = true; //Winging it as this isn't thread-safe, but I don't think that's the issue.
if (m_pDlg == 0)
{
m_pDlg = new CMyDialog(); //My dialog class
m_pDlg->SetStopEvent(m_hStopEvent); //Save the handle of the stop event in the Dialog class
m_pDlg->Create(NULL); //Create with no parent
m_pDlg->ShowWindow(SW_SHOW); //Show the window
}
//Loop here until told to stop by the Stop Event being signalled in the calling thread
while (WaitForSingleObject(m_hStopEvent, 0) == WAIT_TIMEOUT) //Polling Stop Event
{
//Have to keep the message loop going!
MSG uMsg;
while (PeekMessage(&uMsg, m_pDlg->m_hWnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&uMsg);
DispatchMessage(&uMsg);
}
//The thread uses a protected "stack" of string messages to write to my logging window
string strMsg;
while (m_MsgStack.PopFront(strMsg))
{
std::wstring ws = std::wstring(strMsg.begin(), strMsg.end());
CWindow eb(m_pDlg->GetDlgItem(IDC_EDIT1));
eb.SendMessage(EM_REPLACESEL, 0, (LPARAM)ws.c_str());
}
}
//Stop event has been signalled so clear up the dialog
m_pDlg->DestroyWindow();
delete m_pDlg;
m_pDlg = 0;
m_bRunning = false;
return S_OK;
}
My Log object has two functions, StartLog() and StopLog() which are called by the COM object which is doing the main work of data processing.
void CLogProcessor::StartLog()
{
if (!m_bRunning)
{
::SetEvent(m_hStartEvent);
}
}
void CLogProcessor::StopLog()
{
if (m_bRunning)
{
::SetEvent(m_hStopEvent);
}
}
This works fine. I run VBA code in an Excel spreadsheet with buttons that call (via the COM object) StartLog() and StopLog(). The first time the Log Dialog appears all is good: it is activated and comes to the front. Firing StopLog() closes the dialog and it disappears. But the second time I call StartLog() the dialog window appears but it is not at the front, but behind other windows. I have tried a CWindow::BringWindowToTop() and/or SetFocus() call straight after the ShowWindow() but it makes no difference.
Another interesting feature is that my dialog has a system menu and so an 'X' to close it in the top-right hand corner. In the Dialog's message map, I am handling the WM_CLOSE message:
BEGIN_MSG_MAP(CMyDialog)
... Other entries
MESSAGE_HANDLER(WM_CLOSE, OnClose)
...
CHAIN_MSG_MAP(CAxDialogImpl<CMyDialog>)
END_MSG_MAP()
With OnClose() setting the Stop Event handle (previously passed to the Dialog object). Ie doing pretty much the same as the StopLog() function.
LRESULT CMyDialog::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
::SetEvent(m_hStopEvent);
return 0;
}
If I close the log dialog with the mouse click, it goes away as before. BUT the next time I call StartLog() the dialog does what it is supposed to do and comes to the front, which is the action I want. So I replaced the StopLog() code with a SendMessage(WM_CLOSE) to my dialog: it closed the window, but again it did not reappear on top. Another difference is that using the Close X on the dialog means that the Dialog has the focus when it is closed. Closing it from the Excel button means that the Excel window has the focus.
I realize this is rather a long question, and thanks very much if you have soldiered down to the end! I'd been keen for any solution / insight.
Update: I didn't solve the problem as stated, just re-worked the code to use a modal Dialog box in the worker thread. When I am done with the dialog, I PostMessage(WM_CLOSE) from the main thread: the dialog ends and the worker thread finishes (having set an event to say it is done, so that the thread is not destroyed before the Dialog is cleaned up).
The dialog polls the stack of strings to display in response to a WM_USER+xxx message, posted from the main thread using PostMessage();
I still have the same issue that the dialog doesnt appear on top the second time I run the worker thread, but this time adding a ShowWindow(SW_SHOW) in the OnInitDialog() handler seems to bring the dialog to the top when it appears.
It also means I don't need my own message loop.

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.

Windows message Loop confusion

I have a GUI window in WTL that runs inside a thread inside a CMessageLoop instance which has been added to the application instance and runs. Now, inside a button handler within the main GUI I create a new window. Once I click that button and create the window and try to post the quit message to the main GUI loop. The code:
Main window, has its own thread:
CMessageLoop theLoop;
_MyppModule.AddMessageLoop(&theLoop);
if(m_pMyDlg == NULL) {
m_pMyDlg = new CMyDlg();
if(!IsWindow(*m_pMyDlg))
{
m_pMyDlg->Create(NULL);
m_pMyDlg->ShowWindow(SW_SHOW);
nRet = theLoop.Run();
_MyppModule.RemoveMessageLoop();
}
}
Button handler & child window creation:
LRESULT CMyDlg::OnButtonClicked(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
ChildWindowDlg childDlg;
childDlg.Create(m_hWnd);
childDlg.ShowWindow(SW_SHOW);
CMessageLoop _loop;
);
_loop.Run();
::DestroyWindow(childDlg);
return S_OK;
}
Now, if I click the Close button in my MyDlg window the button's handler will get called, inside it I do ::PostQuitMessage but that never reaches the theLoop messageloop from the first code snippet.
This happens after I exit the second loop, so _loop gets destroyed and the child dialog is destroyed.
What is happening here?
You have two message loops here, the child's one is nested. On the other hand, the message queue is one per thread, and is pumped by the most inner message loop (with GetMessage). So, WM_QUIT message gets retrieved by the inner message loop inside CMyDlg::OnButtonClicked.
The second code snippet is totally unnatural. If your goal is to have a window popped up, then closed and and then you want to complete execution of your button click handler, then modal dialog is what you need:
LRESULT CMyDlg::OnButtonClicked(WORD wNotifyCode, WORD wID, HWND hWndCtl,
BOOL& bHandled)
{
ChildWindowDlg childDlg; // Add constructor parameters if needed
// Additional initlaization calls might go here
const INT_PTR nResult = childDlg.DoModal(m_hWnd); // DoModal handles it all
if(nResult == IDOK) { ... } // Hey's we even have result coming from `EndDialog`
return 0; // No S_OK here
}
No message loops, no PostQuitMessage, no separate window creation/destruction calls needed. This is what modal dialogs are for.
If you don't want to block "calling" window, and the idea is to have both master and slave windows running side by side (or, one is a part of another, anyway both to be responsive at the same time), then you don't want to block your message handler. The handler will create window and set it up (.Create, .ShowWindow) and then exit from OnButtonClicked function.
Both windows are created, both are alive and have their message sent to them by top level message loop. This is the correct approach, you don't normally need more than one message loop per thread. Sometimes it might make sense for specific operations, but this is really rare. Windows are passive instances. They respond to messages with their message handlers. A thread message loop serves all thread windows, because what it does is DispatchMessage API call, which in turn looks for target window, takes its WndProc and calls it handing message details in.

Resources