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

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.

Related

Disable cursor changing to SizeWE for some items in Win32 header

I need to prevent resizing of some items in a Win32 header control. No problem to process the HDN_BEGINTRACK notification message and cancel it - the problem is in the cursor indicating that the item can be resized. For instance, if the first item can't be resized, I see this:
, but I'd prefer to see this:
I can ignore the cursor change by suppressing the WM_SETCURSOR message, but the problem is how to know the header item WM_SETCURSOR is generated for. I can detect the item under the mouse pointer in WM_MOUSEMOVE using the HDM_HITTEST message, but WM_MOUSEMOVE is sent to window procedure only after WM_SETCURSOR. I analyzed all notification messages for the Win32 header control, and it seems, it does not have an equivalent of the MouseEnter event that is sent to the window procedure before WM_SETCURSOR.
Any ideas how to solve this problem?
You need to sub-class the header control if you haven't already.
In the sub-class, intercept the WM_SETCURSOR message, and use GetMessagePos() to get the coordinates of the mouse. These are in screen coordinates, so you need to convert them to client coordinates for the header control hit test.
// in the window sub-class
if (uMsg == WM_SETCURSOR)
{
DWORD dwPos = GetMessagePos();
HDHITTESTINFO hti;
hti.pt.x = GET_X_LPARAM(dwPos);
hti.pt.y = GET_Y_LPARAM(dwPos);
ScreenToClient(hWnd, &hti.pt);
SendMessage(hWnd, HDM_HITTEST, 0, reinterpret_cast<LPARAM>(&hti));
if (...) // test for items we want to block
{
SetCursor(LoadCursor(0, IDC_ARROW));
return TRUE;
}
// pass through to regular WndProc
}

How to compute the actual height of the text in a static control

My simple Win32 DialogBox contains two static text controls (IDC_STATIC_TITLE and IDC_STATIC_SECONDARY), here's what it looks like in the resource editor:
At run time, the text first string is updated dynamically. Also, the font of the that text string is replaced such that it's bigger than the IDC_STATIC_SECONDARY string below it. The resulting text string might span a single line, two lines, or more.
I want the other static control holding the secondary text to be placed directly underneath the title string at run time. However, my resulting attempt to re-position this control in the WM_INITDIALOG callback isn't working very well. The second string is overlapping the first. I thought I could use DrawText with DT_CALCRECT to compute the height of the primary text string and then move the secondary text string based on the result. My code is coming up a bit short as seen here:
DrawText returns a RECT with coordinates {top=42 bottom=74 left=19 right=461} Subtracting bottom from top is "32". That seems a little short. I suspect I'm not invoking the API correctly and/or an issue with the different mappings between logical and pixel units.
Here's the relevant ATL code. The CMainWindow class just inherits from ATL's CDialogImpl class.
CMainWindow::CMainWindow():
_titleFont(NULL),
_secondaryFont(NULL)
{
LOGFONT logfont = {};
logfont.lfHeight = 30;
_titleFont = CreateFontIndirect(&logfont);
}
LRESULT CMainWindow::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
CString strTitle;
RECT rectDrawText = {}, rectTitle={}, rectSecondary={};
CWindow wndTitle = GetDlgItem(IDC_STATIC_TITLE);
CWindow wndSecondary = GetDlgItem(IDC_STATIC_SECONDARY);
this->GetDlgItemText(IDC_STATIC_TITLE, strTitle);
wndTitle.SetFont(_titleFont); // font created with Create
wndTitle.GetWindowRect(&rectTitle);
wndSecondary.GetWindowRect(&rectSecondary);
ScreenToClient(&rectTitle);
ScreenToClient(&rectSecondary);
rectDrawText = rectTitle;
DrawText(wndTitle.GetDC(), strTitle, strTitle.GetLength(), &rectDrawText, DT_CALCRECT|DT_WORDBREAK); // compute the actual size of the text
UINT height = rectSecondary.bottom - rectSecondary.top; // get the original height of the secondary text control
rectSecondary.top = rectDrawText.bottom; // position it to be directly below the bottom of the title control
rectSecondary.bottom = rectSecondary.top + height; // add the height back
wndSecondary.MoveWindow(&rectSecondary);
return 0;
}
What am I doing wrong?
Despite what its name may make it sound like, wndTitle.GetDC() doesn't return some pointer/reference that's part of the CWindow and that's the same every call. Instead, it retrieves a brand new device context for the window each time. (It's basically a thin wrapper for the GetDC() Windows API call, right down to returning an HDC instead of the MFC equivalent.)
This device context, despite being associated with the window, is loaded with default parameters, including the default font (which IIRC is that old "System" font from the 16-bit days (most of this screenshot)).
So what you need to do is:
Call wndTitle.GetDC() to get the HDC.
Call SelectObject() to select the correct window font in (you can use WM_GETFONT to get this; not sure if MFC has a wrapper function for it), saving the return value, the previous font, for step 4
Call DrawText()
Call SelectObject() to select the previous font back in
Call wndTitle.ReleaseDC() to state that you are finished using the HDC
More details are on the MSDN page for CWindow::GetDC().

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.

WinApi, hide cursor inside window client area

I want hide cursor inside window client area without borders and title bar (it is simple opengl application). So, function
ShowCursor(FALSE);
is not suitable. After some searching the winapi i find this solution:
//when create window class for application window
WNDCLASSEX WndClass;
//...
BYTE CursorMaskAND[] = { 0xFF };
BYTE CursorMaskXOR[] = { 0x00 };
WndClass.hCursor = CreateCursor(NULL, 0,0,1,1, CursorMaskAND, CursorMaskXOR);
Is this a good way to solve this typical task? What way is the best?
MSDN says that you can set the WNDCLASSEX hCursor field to NULL, in which case you must explicitly set the cursor in your window procedure (which means handling the WM_SETCURSOR message). For example:
if (Msg == WM_SETCURSOR && LOWORD(lParam) == HTCLIENT)
{
SetCursor(NULL);
return TRUE;
}
// Remainder of window procedure code
Checking for HTCLIENT ensures that the cursor is only hidden in the client area, and that the window frame and caption will use the correct cursors.
The SetCursor() call you're using doesn't take a BOOL - it takes an HCURSOR. So you're calling SetCursor( NULL ) which means "hide that cursor". What I found in the old days on Windows is that this is video driver dependent and many drivers don't respect it. The most consistent way to handle this is to make a transparent cursor resource in your app, and return a handle to that cursor in the WM_SETCURSOR message from your main window.
I found that first setting hCursor to NULL:
wc.hCursor = NULL;
and then setting the cursor to NULL:
SetCursor(NULL);
will make it disappear.
From MSDN, I read that the application will set its own cursor by default if one is not defined in hCursor. That's what the first line of code is doing.
Then, after the application sets its own cursor, I mess with it with the second line of code. Or at least, I think that's what happens.

How can I notify an application of a programmatically set scrollbar value?

My code involves standard Scroll Bar control and it happens that I need to change its value programmatically in some cases. I do this using SetScrollInfo function, as in this example:
void setScrollBarValue( HWND scrollBar, int value )
{
SCROLLINFO si = { sizeof( SCROLLINFO ); }
si.fMask = SIF_POS;
si.nPos = value;
::SetScrollInfo( scrollBar, SB_CTL, &si, true /* redraw */ );
}
This appears to work fine (the thumb of the scrollbar moves around) but it fails to notify the rest of the application of the new scrollbar value. For instance, an edit control which uses the scroll bar (much like in the Windows notepad application) fails to scroll around because it doesn't get notified about the new scrollbar value.
In case it matters: the scrollbar I'm modifying is not in the same process as the above
setScrollBarValue function.
Does anybody know how to achieve this?
Edit: I found out how to do this with default window scrollbars (those of type SB_VERT or SB_HORZ). I can send the WM_HSCROLL and WM_VSCROLL to the window like this:
::SendMessage( windowContainingScrollBar,
WM_HSCROLL,
MAKEWPARAM( SB_THUMBPOSITION, si.nPos ), NULL );
However, in my case the scroll bar has a window handle of its own (it has the type SB_CTL). This means that I don't know the orientation of the scroll bar (so I cannot tell whether to send WM_HSCROLL or WM_VSCROLL) and I don't know what window to send the message to.
Try sending the WM_VSCROLL message after calling SetScrollInfo().

Resources