I understand that for GetTextExtendPoint32 to work correctly, it needs to know the correct font. However, I'm confused as to why SelectObject needs to be called. Example I wanted to calc the length of the text for a check box.
Works:
Size sizeChkBox;
CString csChkBox;
m_ChxBox.GetWindowText(csChkBox);
CDC* dc = m_ChkBox.GetDC();
HFONT hfontChK = (HFONT)GetWindowFont(m_ChkBox.GetSafeHwnd());
SelectObject(*dc, hfontChK);
GetTextExtentPoint32(*dc, csChkBox, strlen(csChkBox), &sizeChkBox);
Doesn't Work:
Size sizeChkBox;
CString csChkBox;
m_ChxBox.GetWindowText(csChkBox);
CDC* dc = m_ChkBox.GetDC();
GetTextExtentPoint32(*dc, csChkBox, strlen(csChkBox), &sizeChkBox);
I guess my question really is why doesn't dc have the correct font already since its "made from the checkbox"?
GetDC(HWND) creates an HDC with all default settings, set up for drawing on a given window. It doesn't actually interrogate the window for its properties: in particular, it doesn't send WM_GETFONT to it.
Realize that WM_SETFONT and WM_GETFONT work only by convention. Nothing says that a window must handle these messages, or use the font provided in its WM_PAINT implementation. Standard controls tend to do this, as a common courtesy, but this is by no means a universal requirement.
Related
I am trying to set up a MFC C++ App in Visual Studio 2019 such that modifies the user's text as they are typing.
Current layout is 2 radio buttons,
ID= rdbOn (set to Group = True, with Value int variable m_isOn = 1)
ID= rdbOff, m_isOn value would be = 0
and 1 Edit Control,
ID= txtInputBox, with Value CString variable m_inputString
Currently, for testing I can see how it would work for a button on click, it would take something like the following and just SetDlgItemText of the result. But that would be after they have typed, not WHILE they are typing.
void Onsomebtnclick()
{
//convert CString to String of m_inputString
//do some string manipulation
//convert back to CString
//SetDlgItemText(txtInputBox, result)
}
Update:
got EN_CHANGE to work
I was able to get EN_CHANGE working with the flag suggestion from user #GoGoWorx. However, now I just have a slight problem that the cursor is back to the beginning of the edit control txtInput.
I'm reading about using a CEdit::SetSel but don't know how to use that directly in my code. I tried
CEdit control MFC, placing cursor to end of string after SetWindowText
someDlg::someFunction()
{
//some logic stuff to get a result string
SetDlgItemText(txtInputBox, result);
//need it to set the cursor to the end
//I tried these, but it didn't recognize (expression must have class type?)
//txtInputBox.SetSel(0, -1);
//txtInputBox.SetSel(-1);
}
It sounds like you need to use the ON_EN_CHANGE message-map notification (called after the control has been updated due to typing or pasting for example)
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_EN_CHANGE(IDC_EDIT_CONTROL, &CMyDialog::OnEnChangeEditControl)
END_MESSAGE_MAP()
void CMyDialog::OnEnChangeEditControl()
{
// Copy or call your Onsomebtnclick() here
}
I'm not sure what you're using for the numeric identifier for the edit control, since these are typically upper case defines - replace IDC_EDIT_CONTROL above with your define (possibly txtInputBox, but again, these are normally upper case, so I'm not sure).
Also change CMyDialog for the name of your dialog class too.
Note that we're using the ON_EN_CHANGE message-map handler here instead of the ON_EN_UPDATE, since the ON_EN_CHANGE message is sent after the control has been updated, whereas ON_EN_UPDATE is called just before it's updated.
The message-map handlers are described in the Remarks section of the CEdit control documentation: https://learn.microsoft.com/en-us/cpp/mfc/reference/cedit-class?view=msvc-160
Regarding your concern about modifying things as the user types - this should be fine, since every change (keystroke or paste from clipboard, etc.) should trigger this handler to be called, where you can change whatever you need. Just be sure that when you're updating the control, you don't trigger the ON_EN_CHANGE again and end up in a recursive 'change' loop.
You might be able to do this with some sort of flag to indicate you're the one updating the control, as opposed to the user, however it's probably better to subclass the CEdit control to do what you're wanting. There are a few examples out there of how to do this (it's not as difficult as it might sound), for example:
https://www.codeproject.com/Articles/27376/Avoiding-EN-CHANGE-notifications
I have this CTaskDialog that I am working on:
The code is as follows:
CTaskDialog dlg(_T("How would you like to download the data?"),
_T("Download Schedule Information"),
_T("Meeting Schedule Assistant"), TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON);
dlg.SetMainIcon(TD_INFORMATION_ICON);
dlg.SetFooterIcon(TD_INFORMATION_ICON);
dlg.SetFooterText(_T("All assignments for the selected weeks will be reset."));
dlg.AddRadioButton(44444, _T("Download data for all weeks"));
dlg.AddRadioButton(44445, _T("Download data for selected week"));
dlg.AddRadioButton(44446, _T("Download data for selected week and all additional weeks"));
// Set Width in dialog units (40% screen width)
int iPixelWidth = (::GetSystemMetrics(SM_CXSCREEN) / 100) * 40;
int iDialogUnitsWidth = MulDiv(iPixelWidth, 4, LOWORD(GetDialogBaseUnits()));
dlg.SetDialogWidth(iDialogUnitsWidth);
if(dlg.DoModal() == IDOK)
{
auto iSelection = dlg.GetSelectedRadioButtonID();
}
Is it possible to set the main icon as a question? I can only see these defines in the source:
#define TD_WARNING_ICON MAKEINTRESOURCEW(-1)
#define TD_ERROR_ICON MAKEINTRESOURCEW(-2)
#define TD_INFORMATION_ICON MAKEINTRESOURCEW(-3)
#define TD_SHIELD_ICON MAKEINTRESOURCEW(-4)
The SetMainIcon member function is what you're looking for. Like most functions that deal with Win32 resources, it has two overloads:
void SetMainIcon(
HICON hMainIcon
);
void SetMainIcon(
LPCWSTR lpszMainIcon
);
The first takes a handle to an icon resource (HICON), while the second takes a string identifying a resource from which an icon resource can be loaded.
If you want to set the task dialog to display your application's icon, then you can simply pass in the appropriate HICON. You can also use a custom icon loaded from your application's resources.
I'm not entirely sure, but I think what you're asking is how to use a question-mark icon. Note first that the use of such icons in message boxes has been deprecated since Windows 95, and Microsoft strongly discourages their use. It is recommended that you only use them to denote entry points to online help. Quoting from the Standard Icons section of the official Win32 style guide:
Question mark icons
Use the question mark icon only for Help entry points. For more information, see the Help entry point guidelines.
Don't use the question mark icon to ask questions. Again, use the question mark icon only for Help entry points. There is no need to ask questions using the question mark icon anyway it's sufficient to present a main instruction as a question.
Don't routinely replace question mark icons with warning icons. Replace a question mark icon with a warning icon only if the question has significant consequences. Otherwise, use no icon.
So, this is why there's no standard question mark icon defined. These TD_*_ICON defines are straight from the Win32 headers for the Task Dialog (they're the same ones you'd use with the TASKDIALOGCONFIG structure), not part of the MFC wrapper class.
If you absolutely must use this icon, the workaround is as follows:
const HICON hiconQuestion = AfxGetApp()->LoadStandardIcon(IDI_QUESTION);
dlg.SetMainIcon(hiconQuestion);
(Note that the same HICON could be passed to the CTaskDialog's SetFooterIcon member function.)
using MFC and Unicode-Build
i want to change the colum-header-text of a listctrl and to do so i have to convert an LPCTSTR to an LPWSTR. what i do now is
void CSPCListViewCtrl::SetHeaderText(long lCol, LPCTSTR lColText)
{
CListCtrl& ListCtrl = GetListCtrl();
LV_COLUMN lvc;
::ZeroMemory((void *)&lvc, sizeof(LVCOLUMN));
lvc.mask |= LVCF_TEXT;
ListCtrl.GetColumn(lCol, &lvc);
lvc.pszText = const_cast<LPWSTR>(lColText);
ListCtrl.SetColumn(lCol, &lvc);
}
it seems to work but the const_cast looks somewhat strange and wrong to me so i tried out something like
USES_CONVERSION;
lvc.pszText = CT2W(lColText);
this seems to work in release-build but produces garbage in debug-build so i wonder what is the correct way to do it?
TL;DR: Using const_cast<LPTSTR>(lColText) is safe when calling CListCtrl::SetColumn.
But why then is the pszText member of the LVCOLUMN structure declared non-const? The LVCOLUMN structure is used both to set and retrieve information. When retrieving a column's text, you need to pass in a modifiable buffer (and length argument). When setting a columns text, on the other hand, the system uses the pszText member and internally stores a copy. It does not attempt to write to it. This is documented as well, even if very subtly:
cchTextMax
Size in TCHARs of the buffer pointed to by the pszText member. If the structure is not receiving information about a column, this member is ignored.
This is a common pattern in the Windows API, where the same structure is used both as an input and output parameter. The only option to work around this would have been to introduce a const-ified version for those structures. When the Windows API was invented 30 years ago, this wasn't deemed necessary or useful. Besides, it would have made a common pattern (read-update-write) more tedious and error prone, as the data would have to be manually copied between unrelated types.
Now that you know, that using a const_cast is safe in the scenario you described, you may be wondering, if it is a good idea to use it. That depends on a number of factors, e.g.
Can you come up with a helpful comment to concisely explain, why this is ok? Something along the lines of // Totally safe, it's just that M$ sucks probably won't cut it.
Will all members on your team understand the implications, or will this present a red herring in heap corruption debug sessions?
Do you have coding guidelines that allow the use of const_casts?
Are you using static code analysis tools that won't produce false positives for const_casts?
If you find that you cannot satisfactorily answer all those questions, you may consider implementing (technically unnecessary) manual copying (and exchange a const_cast for exception handling):
void CSPCListViewCtrl::SetHeaderText(long lCol, LPCTSTR lColText) {
CListCtrl& ListCtrl = GetListCtrl();
LVCOLUMN lvc = {0};
lvc.mask |= LVCF_TEXT;
// Create modifiable copy of lColText
size_t colTextLength = _tcslen(lColText);
std::vector<TCHAR> buffer(colTextLength + 1);
std::copy(lColText, lColText + colTextLength + 1, buffer.data());
lvc.pszText = buffer.data();
ListCtrl.SetColumn(lCol, &lvc);
}
One more note on your use of character string encodings: You are mixing the use of Generic-Text Mappings with explicit Unicode (UTF-16LE) encoding. To add consistency, you should change the lColText argument to type LPCWSTR, and use an LVCOLUMNW structure (as well as a const_cast<LPWSTR> if you decide to go that route). Unfortunately, when using MFC, you will have to resort to using the generic-text mappings when calling any class members. But at least you will get a compiler error in case of mismatching character encodings.
You can use CString::GetBuffer()
void SetHeaderText(long lCol, LPCTSTR lColText)
{
LV_COLUMN lvc;
::ZeroMemory((void *)&lvc, sizeof(LVCOLUMN));
lvc.mask |= LVCF_TEXT;
list.GetColumn(lCol, &lvc);
CString str = lColText;
lvc.pszText = str.GetBuffer();
list.SetColumn(lCol, &lvc);
str.ReleaseBuffer();
//ReleaseBuffer is option in this case because
//"str" is local variable and is destroyed before being used again*
}
SetHeaderText(0, L"text");
In UNICODE, LPTSTR is just LPWSTR (or simply wchar_t*)
If for some reason you have ANSI text, then you can use CString for conversion
CStringA ansi = "Text";
CStringW wide = CStringW(ansi);
SetHeaderText(0, wide);
See also
CString::GetBuffer()
I'm drawing a text using GDI+. I recently noticed that this text is automatically scaled when the DPI is changed. Is there a way to make the GDI+ text drawing independent of the DPI? E.g. I want to draw a text up to 20 pixels, regardless of the DPI. Is it possible? How to do this?
Below is a sample code. I want to draw the first text with a constant size, regardless of the DPI, and the second text normally:
case WM_PAINT:
{
inherited::WndProc(message);
Canvas->Brush->Style = bsSolid;
Canvas->Brush->Color = clWhite;
Canvas->FillRect(ClientRect);
// get GDI+ graphics from canvas
Gdiplus::Graphics graphics(Canvas->Handle);
// set text rendering hint
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
std::auto_ptr<Gdiplus::Font> pFont(new Gdiplus::Font(Canvas->Handle, Font->Handle));
std::auto_ptr<Gdiplus::SolidBrush> pBrush(new Gdiplus::SolidBrush(Gdiplus::Color(255, 0, 0, 0)));
std::auto_ptr<Gdiplus::StringFormat> pFormat(new Gdiplus::StringFormat());
Gdiplus::FontFamily fontFamily;
pFont->GetFamily(&fontFamily);
std::auto_ptr<Gdiplus::Font> pFont2(new Gdiplus::Font(&fontFamily, pFont->GetSize(),
pFont->GetStyle(), Gdiplus::UnitPixel));
Gdiplus::Unit test = pFont->GetUnit();
Gdiplus::Unit test2 = pFont2->GetUnit();
pFormat->SetAlignment(Gdiplus::StringAlignmentNear);
pFormat->SetLineAlignment(Gdiplus::StringAlignmentNear);
Gdiplus::StringFormatFlags flags = Gdiplus::StringFormatFlagsBypassGDI;
//flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsDirectionRightToLeft);
//flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsDirectionVertical);
//flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsNoWrap);
//flags = (Gdiplus::StringFormatFlags)(flags | Gdiplus::StringFormatFlagsNoClip);
pFormat->SetFormatFlags(flags);
pFormat->SetTrimming(Gdiplus::StringTrimmingEllipsisCharacter);
pFormat->SetHotkeyPrefix(Gdiplus::HotkeyPrefixNone);
std::wstring text = L"This is a sample code";
Gdiplus::Unit prevPageUnit = graphics.GetPageUnit();
try
{
graphics.SetPageUnit(Gdiplus::UnitPixel);
// draw text
graphics.DrawString(text.c_str(), text.length(), pFont2.get(), Gdiplus::RectF(ClientRect.Left,
ClientRect.Top, ClientWidth, ClientHeight), pFormat.get(), pBrush.get());
}
__finally
{
graphics.SetPageUnit(prevPageUnit);
}
// draw text 2
graphics.DrawString(text.c_str(), text.length(), pFont.get(), Gdiplus::RectF(ClientRect.Left,
ClientRect.Top + 25, ClientWidth, ClientHeight), pFormat.get(), pBrush.get());
return;
}
Regards
I wanted to mention something, slightly unrelated to your question. You shouldn't be using Graphics.DrawString in GDI+ anymore. It was deprecated in .NET 2. Instead Microsoft created TextRenderer.DrawString.
There are two ways of drawing text in .NET:
GDI+ (graphics.MeasureString and graphics.DrawString)
GDI (TextRenderer.MeasureText and TextRenderer.DrawText)
In .NET 1.1 everything used GDI+ for text rendering. But there were some problems:
There are some performance issues caused by the somewhat stateless nature of GDI+, where device contexts would be set and then the original restored after each call.
The shaping engines for international text have been updated many times for Windows/Uniscribe and for Avalon (Windows Presentation Foundation), but have not been updated for GDI+, which causes international rendering support for new languages to not have the same level of quality.
So they knew they wanted to change the .NET framework to stop using GDI+'s text rendering system, and use GDI. At first they hoped they could simply change:
graphics.DrawString
to call the old DrawText API instead of GDI+. But they couldn't make the text-wrapping and spacing match exactly as what GDI+ did. So they were forced to keep graphics.DrawString to call GDI+ (compatiblity reasons; people who were calling graphics.DrawString would suddenly find that their text didn't wrap the way it used to).
A new static TextRenderer class was created to wrap GDI text rendering. It has two methods:
TextRenderer.MeasureText
TextRenderer.DrawText
Note:
- TextRenderer is a wrapper around GDI
- graphics.DrawString is still a wrapper around GDI+
Then there was the issue of what to do with all the existing .NET controls, e.g.:
Label
Button
TextBox
They wanted to switch them over to use TextRenderer (i.e. GDI), but they had to be careful. There might be people who depended on their controls drawing like they did in .NET 1.1. And so was born "compatible text rendering".
By default controls in application behave like they did in .NET 1.1 (they are "compatible").
You turn off compatibility mode by calling:
Application.SetCompatibleTextRenderingDefault(false);
This makes your application better, faster, with better international support. To sum up:
SetCompatibleTextRenderingDefault(true) SetCompatibleTextRenderingDefault(false)
======================================= ========================================
default opt-in
bad good
the one we don't want to use the one we want to use
uses GDI+ for text rendering uses GDI for text rendering
graphics.MeasureString TextRenderer.MeasureText
graphics.DrawString TextRenderer.DrawText
Behaves same as 1.1 Behaves *similar* to 1.1
Looks better
Localizes better
Faster
It's also useful to note the mapping between GDI+ TextRenderingHint and the corresponding LOGFONT Quality used for GDI font drawing:
TextRenderingHint mapped by TextRenderer to LOGFONT quality
======================== =========================================================
ClearTypeGridFit CLEARTYPE_QUALITY (5) (Windows XP: CLEARTYPE_NATURAL (6))
AntiAliasGridFit ANTIALIASED_QUALITY (4)
AntiAlias ANTIALIASED_QUALITY (4)
SingleBitPerPixelGridFit PROOF_QUALITY (2)
SingleBitPerPixel DRAFT_QUALITY (1)
else (e.g.SystemDefault) DEFAULT_QUALITY (0)
Samples
Here's some comparisons of GDI+ (graphics.DrawString) verses GDI (TextRenderer.DrawText) text rendering:
GDI+: TextRenderingHintClearTypeGridFit, GDI: CLEARTYPE_QUALITY:
GDI+: TextRenderingHintAntiAlias, GDI: ANTIALIASED_QUALITY:
GDI+: TextRenderingHintAntiAliasGridFit, GDI: not supported, uses ANTIALIASED_QUALITY:
GDI+: TextRenderingHintSingleBitPerPixelGridFit, GDI: PROOF_QUALITY:
GDI+: TextRenderingHintSingleBitPerPixel, GDI: DRAFT_QUALITY:
i find it odd that DRAFT_QUALITY is identical to PROOF_QUALITY, which is identical to CLEARTYPE_QUALITY.
See also
UseCompatibleTextRendering - Compatible with whaaaaaat?
Sorting it all out: A quick look at Whidbey's TextRenderer
MSDN: LOGFONT Structure
AppCompat Guy: GDI vs. GDI+ Text Rendering Performance
GDI+ Text, Resolution Independence, and Rendering Methods.
Or - Why does my text look different in GDI+ and in GDI?
This is what works for me.
using namespace Gdiplus;
HDC hDC = ::GetDC( NULL );
int nDPI = ::GetDeviceCaps( hDC, LOGPIXELSY );
::ReleaseDC( NULL, hDC );
REAL fFontHeight = 96 / (REAL)nDPI * 8;
FontFamily fontFamily( L"Arial" );
Gdiplus::Font font( &fontFamily, fFontHeight, UnitPixel );
REAL fMeasuredFontHeight = font.GetHeight( &gr );
It turns out that Gdiplus::Font, despite being specified in pixels, uses the user's DPI setting to adjust the resulting font (even when the font is to be used to draw in a bitmap!). The standard DPI of 96 is a good value to use determine the correct ratio to adjust the font size.
In the above snippet, the font height sought was 8 pixels high.
fMeasuredFontHeight remains nearly constant (at approx. 12), through all DPI settings.
I have an edit control which should only take integers betweeen 1 and 99.To achieve this, i used, modifystyle() and limittext().Is there a way to restrict 0 from being entered?
You do not have to control values limit it using your code.
It is easier if you subclass (add variable) edit control using wizard, you can choose UINT type and set minimum and maximum value here.
Also do nto forget to set style to ES_NUMBER (setting Number to True in edit control Properties).
If you absolutely need that, you must derive a class from CEdit and process the input accordingly. Rendering the baloon that says "Unacceptable Character" may be bit tricky to give online-error. The class would be useful only if you are planning to use such class (edit-control) at multiple places, preferably with different ranges.
This is completely different problem from than one in your original post.
Make sure that your spin control follows immediately edit control in the Z-order (tab order).
In the resource set spinner style to: UDS_AUTOBUDDY UDS_SETBUDDYINT, UDS_ALIGNRIGHT,.
This will causse spinner to: choose edit control as buddy, set integer in edit box, place itself inside edit control to the right edge.
To do that, in the properties for the spinner set: "Auto Buddy" True, "Set Buddy Integer" True and "Alingment" to Right Align.
You do nto have to set minimum and maximum for the edit control, handle it now in the command handler for EN_CHANGE notification.
Place this code in the handler.
void CYourDlg::OnEnChangeEditNum()
{
int iValue = GetDlgItemInt(IDC_EDIT1);
if(iValue < 1 || iValue > 99)
{
m_Edit.ShowBalloonTip(_T("Number Out of Range"), _T("Value must fall between 1 and 99."), TTI_INFO_LARGE);
}
}