Win32 API: How to scroll down automatically a text inside EDIT control? - winapi

I have an EDIT control created like this:
hwndEDIT_5 = CreateWindowEx (
0, "EDIT", NULL,
WS_VSCROLL | WS_BORDER | WS_VISIBLE | WS_CHILD | ES_MULTILINE | ES_READONLY,
135, 450, 555, 200,
h2, ( HMENU ) ID_EDIT_CONSOLE,
h1, NULL
);
As you can see it is a read-only EDIT area where multi lines text can be displayed. It is supposed to be a console where I can display some information for users when they use the program. I would like the text area to automatically scroll to the bottom-most entry (the newest one) whenever a new line (or message for an user) is added. I've implemented this:
SetDlgItemText ( h2, ID_EDIT_CONSOLE, ch_s );
SCROLLINFO scr;
SCROLLINFO * scr_p = &scr;
scr.cbSize = sizeof ( SCROLLINFO );
scr.fMask = SIF_RANGE;
GetScrollInfo ( GetDlgItem ( h2, ID_EDIT_CONSOLE), SB_VERT, scr_p );
int mmax = scr.nMax;
scr.fMask = SIF_POS;
scr.nPos = mmax;
SetScrollInfo ( GetDlgItem ( h2, ID_EDIT_CONSOLE), SB_VERT, scr_p, TRUE );
That code is scrolling vertical scrollbar to the end of an EDIT control after adding new msg and it works great, the scrollbar gets scrolled but the text still remains visible from the beginning - it rewinds to the beginning after addition while scrollbar rewinds to the bottom. How to make it properly?
Last but not least - this is might be important - in order to display a message firstly I capture the text that is already displayed by using:
GetDlgItemText ( h2, ID_EDIT_CONSOLE, buf, len + 1 );
then I convert buf into string and add to that string a new message that I want to display. Then I convert it back to char array and set it up with SetDlgItemText. I seperate lines by using \r\n. I've coded it that way because I didn't know how to add a line to an EDIT control in different way than using SetDlgItemText. And it adds only one entry AFAIK - if used twice I will not come up with two entries added to an EDIT control, but the first one will get replaced by second function call.

Don't use SetScrollInfo. Use SendMessage() with the EM_LINESCROLL message, sending the message to the edit control's window handle.
SendMessage(MemoHwnd, EM_LINESCROLL, 0, NumLinesToScroll);
The documentation says:
The control does not scroll vertically past the last line of text in the edit control. If the current line plus the number of lines specified by the lParam parameter exceeds the total number of lines in the edit control, the value is adjusted so that the last line of the edit control is scrolled to the top of the edit-control window.

I had the same problem and solved it with Jerry Coffin's answer and some research.
This is the way I use now:
string text = "Append this text";
SendMessageA(hEdit, EM_SETSEL, 0, -1); //Select all
SendMessageA(hEdit, EM_SETSEL, -1, -1);//Unselect and stay at the end pos
SendMessageA(hEdit, EM_REPLACESEL, 0, (LPARAM)(text.c_str())); //append text to current pos and scroll down
If needed: To scroll at the end of Edit Control without appending text:
SendMessageA(hEdit, EM_SETSEL, 0, -1); //Select all.
SendMessageA(hEdit, EM_SETSEL, -1, -1);//Unselect and stay at the end pos
SendMessageA(hEdit, EM_SCROLLCARET, 0, 0); //Set scrollcaret to the current Pos

You can add text by setting the beginning and end of the selection to the end of the text in the control (EM_SETSEL), then replacing the (empty) selection with your new text (EM_REPLACESEL).
Scrolling to the bottom can be done with EM_SCROLLCARET after the caret (the selection) is at the end of the text. There are other ways, but if you're doing it immediately after adding text, this is probably the easiest.

in my case I had a multi line string and Ken White's idea worked very well:
HWND hEdit = this->GetDlgItem(IDC_EDIT_LOG)->m_hWnd;
if (hEdit)
{
int lineCount = m_strClientLog.Replace(_T("\n"), _T("\n"));
::SendMessage(hEdit, EM_LINESCROLL, 0, lineCount);
}

for MFC projects you can use:
mLoggingTextCtl.SendMessage(EM_SETSEL, 0, -1); //Select all.
mLoggingTextCtl.SendMessage(EM_SETSEL, -1, -1);//Unselect and stay at the end pos
mLoggingTextCtl.SendMessage(EM_SCROLLCARET, 0, 0); //Set scrollcaret to the current Pos

Related

How can I get the same look as the standard with an owner draw menu?

I consider using an owner draw menu in a Windows application that should have the same look as the standard menu. (Reason: the standard menu doesn't work well in some mixed DPI situations.)
Currently I have a problem providing the correct width during WM_MEASUREITEM.
This is a screenshot of the Edit menu of notepad where each item has a shortcut.
We can see that there is a constant gap between the item texts and shortcut texts as if they were columns. It seems as if the widths of the item texts and the widths of the shortcut texts are retrieved separately, as the longest item text "Time/Date" reserves a shortcut width suitable for Ctrl+A while it only needs one for F5.
I could not find any API functionality where I can give the width of the item text and the shortcut text separately, nor did I find any metric specifying the size of the gap.
So my question is: Is it possible to achieve the desired behavior within the usual WM_MEASUREITEM message and if yes, how? If not, is there any other means to get this right or is it just not possible at all?
This is how ReactOS does it:
To measure a menu item:
if ((p = wcschr( lpitem->Xlpstr, '\t' )) != NULL) {
RECT tmprc = rc;
LONG tmpheight;
int n = (int)( p - lpitem->Xlpstr);
/* Item contains a tab (only meaningful in popup menus) */
/* get text size before the tab */
txtheight = DrawTextW( hdc, lpitem->Xlpstr, n, &rc,
DT_SINGLELINE|DT_CALCRECT);
txtwidth = rc.right - rc.left;
p += 1; /* advance past the Tab */
/* get text size after the tab */
tmpheight = DrawTextW( hdc, p, -1, &tmprc,
DT_SINGLELINE|DT_CALCRECT);
lpitem->dxTab += txtwidth;
txtheight = max( txtheight, tmpheight);
txtwidth += MenuCharSize.cx + /* space for the tab */
tmprc.right - tmprc.left; /* space for the short cut */
}
Then to draw it:
Text = lpitem->Xlpstr;
if(Text)
{
for (i = 0; Text[i]; i++)
if (Text[i] == L'\t' || Text[i] == L'\b')
break;
}
if(lpitem->fState & MF_GRAYED)
DrawTextW( hdc, Text, i, &rect, uFormat);
/* paint the shortcut text */
if (!menuBar && L'\0' != Text[i]) /* There's a tab or flush-right char */
{
if (L'\t' == Text[i])
{
rect.left = lpitem->dxTab;
uFormat = DT_LEFT | DT_VCENTER | DT_SINGLELINE;
}
else
{
rect.right = lpitem->dxTab;
uFormat = DT_RIGHT | DT_VCENTER | DT_SINGLELINE;
}
DrawTextW( hdc, Text + i + 1, -1, &rect, uFormat );
}
So to insert a keyboard accelerator in a menu item, you simply separate it from the item text with the tab character. The measuring and drawing code then looks for this tab character and acts accordingly.
Note, however, that for the keyboard accelerator to be right-aligned in the menu like it is in your screenshot (which is achieved with DrawText with DT_RIGHT) the drawing code expects it to be separated from the item text with the '\b' character, not the tab character, and unless I'm missing something this is not accounted for in the measuring code, so you might want to compensate for that.
Replicating the standard menu with owner draw is a world of pain. You have to deal with Visual Styles on and off, mnemonics/Access keys, accessibility and all the undocumented metrics. It is better just to use the normal menu if you can.
Per-monitor DPI support seems to change in every Windows 10 release. 1607 added EnableNonClientDpiScaling which scales the menu and other non-client areas. 1703 added Per Monitor v2 and MSDN says this about PMv2:
Scaling of non-client area - All windows will automatically have their non-client area drawn in a DPI sensitive fashion. Calls to EnableNonClientDpiScaling are unnecessary.
Scaling of Win32 menus - All NTUSER menus created in Per Monitor v2 contexts will be scaling in a per-monitor fashion.
Notepad is PMv2 and its menu seems to work fine:
Windows 8.1 and < 10 Anniversary Update will require more work and I would suggest that you just don't declare yourself DPI aware on these systems and let Windows scale your window for you (with some blurriness) if the system has multiple monitors.

Mirrored text when changing a combobox RTL style

I'm trying to create a dynamic dialog, which can be made RTL depending on the language. But I have the following issue: whenever I change the RTL style of the combo box, the text comes up reversed. I tried using functions such as InvalidateRect, RedrawWindow, etc., but couldn't make it work correctly.
Relevant code (WinAPI with WTL):
CComboBox combo = hWndCtl;
if(combo.GetCurSel() == 0)
combo.ModifyStyleEx(WS_EX_LAYOUTRTL, 0);
else
combo.ModifyStyleEx(0, WS_EX_LAYOUTRTL);
A demo project: here.
A demonstration of the issue:
It seems you are responding to CBN_SELCHANGE notification. This is notification is sent after combobox sets the text in its editbox.
You should respond to CBN_SELENDOK instead. CBN_SELENDOK is sent before CBN_SELCHANGE, this gives you time to modify the style before combobox sets the text.
switch (HIWORD(wParam))
{
case CBN_SELENDOK:// CBN_SELCHANGE:
if (SendMessage(hComboBox, CB_GETCURSEL, 0, 0) == 0)
ModifyStyleEx(hComboBox, WS_EX_LAYOUTRTL, 0);
else
ModifyStyleEx(hComboBox, 0, WS_EX_LAYOUTRTL);
break;
default:break;
}
Edit: Windows 10 has fade in/out effect. If you change the combo selection with keyboard, while the color is fading, the text is still goes backward.
ComboBox has an edit control which might be causing this problem. It's better to use WS_EX_RIGHT | WS_EX_RTLREADING instead of WS_EX_LAYOUTRTL. This will also work with CBN_SELCHANGE.
case CBN_SELENDOK: //(or CBN_SELCHANGE)
if (SendMessage(hComboBox, CB_GETCURSEL, 0, 0) == 0)
ModifyStyleEx(hComboBox, WS_EX_RIGHT | WS_EX_RTLREADING, 0);
else
ModifyStyleEx(hComboBox, 0, WS_EX_RIGHT | WS_EX_RTLREADING);
break;

Can't change tooltip coordinates MFC

I need to make tooltip a little bit right and lower to mouse cursor, but i can't do it in any way, tried different coordintaes but nothing seems to work. Where is the problem? Thank you.
// Add the new tooltip (if available)
if (m_LastToolTipRow!=-1 && m_LastToolTipRow!=-1)
{
// Not using CToolTipCtrl::AddTool() because it redirects the messages to CListCtrl parent
TOOLINFO ti = {0};
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT; // Indicate that uId is handle to a control
ti.uId = (UINT_PTR)m_hWnd; // Handle to the control
ti.hwnd = m_hWnd; // Handle to window to receive the tooltip-messages
ti.hinst = AfxGetInstanceHandle();
ti.lpszText = LPSTR_TEXTCALLBACK;
m_OwnToolTipCtrl.SendMessage(TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
m_OwnToolTipCtrl.SendMessage(TTM_TRACKPOSITION, 0, (LPARAM)MAKELPARAM(pt.x + 100, pt.y + 100));
m_OwnToolTipCtrl.SendMessage(TTM_TRACKACTIVATE, true, (LPARAM)&ti);
m_OwnToolTipCtrl.Activate(TRUE);
//Multiline
m_OwnToolTipCtrl.SetMaxTipWidth(256);
//m_OwnToolTipCtrl.SetMaxTipWidth(SHRT_MAX);
}
TTF_IDISHWND
Indicates that the uId member is the window handle to the tool. If this flag is not set, uId is the tool's identifier.
According to this, the window with m_hWnd handle is the one that shows the tooltip and you can position the window itself. If you meant a tooltip separate to that window than there is a principal problem there.

Placeholder still visible when adding edit control to a CToolbar?

Years ago, I added an edit control to the toolbar in my application following directions similar to these:
http://www.codeproject.com/Articles/1106/Adding-a-Combo-Box-to-a-Docking-Toolbar
Similar directions can be found in many articles, so I think the procedure is fairly common. Until a few years ago, this worked fine, and the result was as shown in the article. However, I believe the move to XP changed the appearance of buttons in the toolbar, and instead I now see this in my application:
It seems as though the original instructions worked only because controls prior to the change occupied the entire height of the toolbar, so the edit control obstructed the separator behind it.
Ideally, I think the underlying separator should be made invisible. However, this doesn't seem to be handled explicitly in any of the articles I've found, and I'm not quite sure myself how to prevent the separator from being drawn.
Any help would be greatly appreciated. Thanks!
If you follow that article on codeproject exactly, you have probably modified the place holder into a separator from a button. This is why the separator line shown thru when the height of the button image is bigger than the height of the combo box.
If you keep the place holder as an empty button, you will not have such problem. A series of several place holder buttons may be needed in cascaded formation for a really useful length for the combobox.
This technique is demonstrated as follows:
// standard creation of the toolbar in CMainFrame::OnCreate
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
// status bar creation .....
// .....
// the place holders are a series of 5 empty toolbar buttons ie: ID_COMBO_1 to ID_COMBO_5
// get index of first combobox place holder
INT nIndex = m_wndToolBar.GetToolBarCtrl().CommandToIndex(ID_COMBO_1);
// get size of first place holder rectangle
CRect rcRect;
m_wndToolBar.GetToolBarCtrl().GetItemRect(nIndex, &rcRect);
INT nWidth = rcRect.Width();
// calculate width of combobox with sum of all place holder (5 in total)
nWidth = nWidth * 5;
rcRect.top = 5; // top of combo box
rcRect.bottom = rcRect.top + 250; // drop height
rcRect.right = rcRect.left + nWidth;
// create the combobox to sit above the place holders
if(!m_comboBox.Create(CBS_DROPDOWNLIST | CBS_SORT | WS_VISIBLE |
WS_TABSTOP | WS_VSCROLL, rcRect, &m_wndToolBar, ID_COMBO_1))
{
TRACE(_T("Failed to create combo-box\n"));
return FALSE;
}
m_comboBox.AddString("Toolbar Combobox item one");
m_comboBox.AddString("Toolbar Combobox item two");
m_comboBox.AddString("Toolbar Combobox item three");
I've used this technique from CodeGuru. It's old, but, I've been using it for many years and it still works.

Color change in Rich Edit Control

when you erase a coloured text. By default, the control sets the new entered text colour back to that was recently erased. how can you avoid that? do you need to check each character style before you type?
UPDATE:
I'm trying to set the text color like this.
SendMessage(hEdit, EM_SETSEL, start_pos, end_pos); //select text for coloring
CHARFORMAT cf;
memset( &cf, 0, sizeof cf );
cf.cbSize = sizeof cf;
cf.dwMask = CFM_COLOR;
cf.crTextColor = RGB(255,0,0);
SendMessage( hEdit , EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
SendMessage(hEdit, EM_SETSEL, -1, 0 ); //deselect text
cf.crTextColor = RGB(0,0,0); //reset colour
SendMessage( hEdit , EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf); //set colour
Your question is quite unclear. Wild stab at it: you lose all formatting when you assign the Text property. Be sure to use AppendText() instead. And to set the SelectionColor and SelectionBackColor properties back to what it was after colorizing any text so that newly entered text gets the preferred default colors.

Resources