How can I change the default CDialog font for a non-modal dialog? - c++

It is necessary to switch off the "ClearType" property of the default font for all dialog controls. It is possible to do that for one control by setting
logfont.lfQuality = ANTIALIASED_QUALITY
There are a lot of suggestion how to do the same for modal dialogs (http://neelaakash.wordpress.com/2007/12/31/change-default-dialog-font-of-cdialog/ and others), but that should be done for non-modal dialogs (are instantiated with new and Create(...) methods). I've tried to do that myself:
Override 'Create' method, and modify dialog template:
BOOL CActivationChildDialogLicenseInfo::Create(UINT nIDTemplate,
CWnd* pParentWnd)
{
CDialogTemplate dlt;
int nResult;
// load dialog template
if (!dlt.Load(MAKEINTRESOURCE(nIDTemplate))) return -1;
// set your own font, for example “Arial”, 10 pts.
dlt.SetFont(L"Arial", 12);
// get pointer to the modified dialog template
LPSTR pdata = (LPSTR)GlobalLock(dlt.m_hTemplate);
// let MFC know that you are using your own template
m_lpszTemplateName = NULL;
InitModalIndirect(pdata);
// display dialog box
nResult = CActivationChildDialog::Create(nIDTemplate, pParentWnd);
// unlock memory object
GlobalUnlock(dlt.m_hTemplate);
return nResult ;
}
Seems like this method do nothing (it is called, I've checked that with putting break-point inside).
I've tried to call
nResult = CActivationChildDialog::Create(NULL, pParentWnd);
...but got a lot of ASSERTs.
I've also tried to override the 'OnSetFont' method:
void CActivationChildDialogLicenseInfo::OnSetFont(CFont *pFont)
{
CActivationChildDialog::OnSetFont(pFont);
LOGFONT logfont;
pFont->GetLogFont(&logfont);
LOGFONT logfont2=logfont;
pFont->DeleteObject();
logfont2.lfItalic = true;
logfont2.lfQuality = ANTIALIASED_QUALITY;
pFont->CreateFontIndirect(&logfont2);
}
That causes ASSERT during run-time and resulted in a VERY big font being used (lost default font settings, doesn't accept new specified settings)... I don't know why.
Please advise, how can I change a default dialog font that will be "inherited" by all dialog controls?
Thank you very much.

First off: the simple, reliable way to do this is to create the dialog and then send WM_SETFONT (or call SetFont()) to the dialog and each control in it. I'll show you how to do that below, but first, here's why the two strategies you've already tried didn't (and can't) work:
Modifying the dialog template
First off, you should be calling CDialog::CreateIndirect() if you wish to use a dialog template that you've already loaded.
But don't bother. The dialog template contains only the face name and point size - it does not allow you to specify other LOGFONT values such as lfQuality. If it did, you could simply specify that in your resource definition and avoid writing any runtime code at all!
Intercepting WM_SETFONT
In theory, you could make this work. But it's not practical. Your code has several problems: first off, you'd have to intercept this message for every child control in order for it to do anything useful: the dialog itself probably doesn't render any text. But worse, you're passing the original font to the base class (which hands it to the default window procedure, which stores it internally for later use) and then immediately destroying it - this means the dialog (and everything else using that font, including all of the child controls) will be trying to draw text using a bogus font, and reverting to the default font as a result. Finally, you're creating a new font attached to a temporary object (pFont) created and destroyed by MFC - internally, the CFont object you're working with will be detached from the font handle and destroyed, leaking a handle to a font object that nothing uses.
Leaky abstractions: a note on HFONT and CFont
HFONT is a handle type that Windows uses to represent a font object. Like most of GDI, there are specific functions for creating fonts, and the general-purpose DeleteObject() function for destroying them.
CFont is a light-weight wrapper class for HFONTs. A CFont instance can be attached and detached from an existing HFONT, or used to create a new one. If a CFont instance is still attached to a HFONT when its deconstructor executes, it will call DeleteObject() to destroy the underlying object. Internally, MFC utilizes temporary CFont instances that are attached and detached from HFONTs when calling various message handlers (such as OnSetFont). It's worth remembering that internally, Windows knows nothing about CFont, and a single HFONT may belong to 0 or more CFont instances at any given point in time.
A note on fonts and WM_SETFONT
When you create a new font - whether or not it is wrapped in a CFont object - you are the owner of that font, and it is your responsibility to destroy it once you are finished using it. Passing it to WM_SETFONT (CWnd::SetFont()) doesn't change ownership! This is actually quite useful, as it allows you to pass the same font to multiple windows without worrying about which one will destroy it - you're still the owner, and so you can (and must) destroy it yourself (once there are no windows still using it).
Finally - how to quickly create and set a font on a dialog and all its children
So you should now have enough background to understand the necessary steps:
Create the dialog
Create the desired font
Pass the font to the dialog and its children (by either sending WM_SETFONT messages, or by calling CWnd::SetFont... which itself send a WM_SETFONT message).
When the dialog is destroyed, also destroy your font.
Example
// define this as a class member - class destructor then handles step four!
CFont m_nonCleartypeFont;
BOOL CActivationChildDialogLicenseInfo::Create(UINT nIDTemplate,
CWnd* pParentWnd)
{
// step one: create dialog normally
BOOL nResult = CActivationChildDialog::Create(nIDTemplate, pParentWnd);
// step two: create custom font
// relying on destructor to destroy font once we're done with it
// so be careful to only create it once!
if ( NULL == m_nonCleartypeFont.m_hObject )
{
CFont* pOriginalFont = GetFont(); // use template font as... template!
// pull information from original font
LOGFONT logfont;
pOriginalFont->GetLogFont(&logfont);
// make font adjustments:
// specify italics
logfont.lfItalic = true;
// and non-cleartype antialiasing
logfont.lfQuality = ANTIALIASED_QUALITY;
// create our font based on adjusted information
m_nonCleartypeFont.CreateFontIndirect(&logfont);
}
// step three: set our custom font on the dialog and all children
SetFont(&m_nonCleartypeFont, FALSE);
// Send message to quickly set this font for all children.
// See documentation for SendMessageToDescendants()
// - this is actually the example given!
SendMessageToDescendants(WM_SETFONT,
(WPARAM)m_nonCleartypeFont.m_hObject,
MAKELONG(FALSE, 0),
FALSE);
return nResult;
}

Related

I'm having a problem setting fonts for static text

My problem is that the font in the text won't show up as the font that I want it to be. I looked up the issue but I didn't see any solutions. The text is the "Sign In"
BOOL Account::OnInitDialog() {
CDialogEx::OnInitDialog();
CFont font;
VERIFY(font.CreatePointFont(160, _T("Arial")));
SignInStatic.SetFont(&font);
font.DeleteObject();
return TRUE;
}
When you call CWnd::SetFont(), the window you assigned the font to does not take ownership of the font. You are responsible to delete the font, but only when it is no longer in use.
As often, the documentation of the underlying Windows API, which is WM_SETFONT, provides more information than the MFC documentation:
The application should call the DeleteObject function to delete the
font when it is no longer needed; for example, after it destroys the
control.
As you are using the CFont class, you don't have to explicitly call DeleteObject(). CFont is a RAII class that automatically destroys its associated resource in its destructor.
All you have to care about is the scope of the CFont instance. Currently you create a local variable of CFont in the OnInitDialog method. Even when you remove the explicit DeleteObject call, the font will be destroyed when OnInitDialog returns and the window you assigned the font to now refers to an invalid font handle.
Solution
Declare an instance of CFont as a member variable of the Account class:
class Account : public CDialogEx
{
public:
// Some stuff
private:
CFont m_signInFont;
};
In OnInitDialog you have to use the member variable instead of the local variable and remove the DeleteObject call:
BOOL Account::OnInitDialog() {
CDialogEx::OnInitDialog();
VERIFY(m_signInFont.CreatePointFont(160, _T("Arial")));
SignInStatic.SetFont(&m_signInFont);
return TRUE;
}
Now the font object will exist for the whole livetime of the dialog, which includes its children. You could even assign it to other children, as needed.

Getting a debug assertion error when calling GetDC() (MFC)

I'm making a MFC application using the Doc/View architecture with Visual Studio 2017, and for some reason I get that error whenever I call GetDC() inside this function:
void CDigitRecognizerView::ClearScreen(void)
{
CDC* dc;
dc = GetDC(); // debug assertion error here
CBrush brush;
brush.CreateSolidBrush(0xFFFFFF);
dc->SelectObject(&brush);
CRect rect;
GetWindowRect(&rect);
dc->FillRect(&rect, &brush);
CDigitRecognizerDoc* pDocument = GetDocument();
ReleaseDC(dc);
}
This is the message map macro defined in the app class:
BEGIN_MESSAGE_MAP(CDigitRecognizerApp, CWinApp)
ON_COMMAND(ID_APP_ABOUT, &CDigitRecognizerApp::OnAppAbout)
ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
ON_COMMAND(ID_EDIT_CLEARSCREEN, CDigitRecognizerView::ClearScreen)
END_MESSAGE_MAP()
So whenever I select the "Clear Screen" option from the menu of the app, ClearScreen() gets called but I can't get the DC of the View, it crashes.
I have looked at the variables in the debugger and the window handle seems OK so I don't know really.
I am also wondering what other way I could call a function of the View class from the App class whenever I select a menu option because this doesn't seem to work.
How did you get the CWinApp message-map pointing to a CDigitRecognizerView function? I think the "wizard" wouldn't do this. Did you add the handler manually?
As for accessing Doc/View instances from the CWinApp class, there are some functions available:
GetFirstDocTemplatePosition() / GetNextDocTemplate(), members of CWinApp class. Alternatively you can simply store the pDocTemplate instance created in the InitInstance() function. Then call:
GetFirstDocPosition() / GetNextDoc(), members of the CDocTemplate class, and finally:
GetFirstViewPosition() / GenNextView(), members of the CDocument class
But this is normally not needed (the events could be handled in the Doc/View classes), unless you want to perform some operation(s) on all (or some of the) DocTemplate/Doc/View instances (which rather implies that you are developing an MDI application).

Qt Parent to delete variable after child has closed

I'm writing an application in C++ using the Qt library. There is a central window (the parent) and all the children are launched when needed. Since a number of these windows can be open multiple times, but display different data, I'm declaring the objects with new. Here is an example of what I've got:
Home_Window.hpp
View_Window *SomeWindow;
Home_Window.cpp
void Home_Window::on_WindowButton_clicked()
{
SomeWindow = new View_Window();
SomeWindow->show();
}
What I want to do, is delete the object, when the window is closed to reduce the memory footprint of the application. I've already been able to delete all of the objects contained in the child window to reduce memory usage, but the core object still lingers, and if, through a single day a user opens and closes 1000's of windows, and each object holds onto 40-50KB of memory, the footprint of the application goes from a couple of MBs of memory to 100's of MBs of Memory.
I've not been able to find a guide online that would allow me to achieve this. I was considering a slot and signal pair, as I know that when a window is closed, it sends the QObject::destroyed() signal. Only issue, is I've not tried to setup a signal and slot pair like this.
Any suggestions are appreciated.
to delete the window when it is closed, you can set the WA_DeleteOnClose attribute on it. Your on_WindowButton_clicked() should look something like:
void Home_Window::on_WindowButton_clicked()
{
View_Window* w= new View_Window();
w->setAttribute(WA_DeleteOnClose);
w->show();
}
This way, you don't have to worry about destroying w yourself, it will get deleted automatically when it is closed.
You need to do two things:
The window's lifetime must be managed even if the window isn't closed.
You can give it a parent that you know will end its life at some point. Or you can use a QScopedPointer or std::unique_ptr.
The window must delete itself when it's closed.
void Home_Window::on_WindowButton_clicked()
{
// parent window flags
// vvvv vvvvvvvvvv
auto window = new View_Window(this, Qt::Dialog); /* 1. */
window->setAttribute(Qt::WA_DeleteOnClose); /* 2. */
window->show();
}
At the very least, you should set the Qt::Window flag. The Qt::Dialog includes the Qt::Window flag, and also declares the window to be a dialog box - that fact has platform-specific interpretation; read more about window flags here.
If your View_Window's API is broken, and it doesn't take the window flags as the 2nd argument to the constructor, you need to set them separately:
window->setWindowFlags(Qt::Dialog);
Every widget should be taking Qt::WindowFlags as the optional, 2nd argument to the constructor, with a default value reflecting the usual use of the window. That's the expectation set by all of Qt's widgets.
You can try to delete the pointer to ViewWindow by using: delete SomeWindow;

Is it necessary to explicitly restore a control font when its parent window is destroyed?

I use the following code in my windows create method
HANDLE hFont = ::GetStockObject(DEFAULT_GUI_FONT);
m_InfoTab.SendMessage(WM_SETFONT, (WPARAM)hFont);
m_InfoTab is a standard Windows/MFC tab control. Is it necessary to save the original font and restore it when the parent window is destroyed?
It is not required to restore the original font. You are however responsible for managing the font object yourself, i.e. deleting it when it is no longer used. The documentation for WM_SETFONT is fairly explicit here:
The application should call the DeleteObject function to delete the font when it is no longer needed; for example, after it destroys the control.
In this particular case you will not run into any problems since you retrieved the font object through a call to GetStockObject(). These objects are controlled by the system and it is not required to call DeleteObject on them (although it is not harmful either).
Whether or not your application leaks GDI handles can easily be verified using Task Manager. Go to the Processes tab, select View -> Select Columns... and tick GDI Objects. With the monitoring in place change your code and install a timer using SetTimer() with uElapsed = 1000 (once a second). Add an OnTimer handler with the following code:
void CMyDialog::OnTimer(UINT_PTR nIDEvent)
{
// Retrieve the system font
HFONT hFontSystem = (HFONT)GetStockObject( DEFAULT_GUI_FONT );
LOGFONT lfSystem = { 0 };
GetObject( hFontSystem, sizeof( lfSystem ), &lfSystem );
// And construct and identical font object
HFONT hFontNew = CreateFontIndirect( &lfSystem );
// This will leak the font object
m_InfoTab.SendMessage( WM_SETFONT, (WPARAM)hFontNew );
__super::OnTimer(nIDEvent);
}
Now open Task Manager, start the application and watch the GDI Objects count increase by 1 each second.
No, the window in question (m_infoTab) is responsible for managing its fonts, and freeing the old one if necessary.

Getting ActiveX window handle

I have followed this link to get the window handle of a ActiveX control
Sample Code from microsoft's site
// The following code should return the actual parent window of the ActiveX control.
HWND CMyOleControl::GetActualParent()
{
HWND hwndParent = 0;
// Get the window associated with the in-place site object,
// which is connected to this ActiveX control.
if (m_pInPlaceSite != NULL)
m_pInPlaceSite->GetWindow(&hwndParent);
return hwndParent; // Return the in-place site window handle.
}
But in my case I keep finding that "m_pInPlaceSite" is always NULL. I'm trying to run this code in my controls FinalConstruct. Is there something else I need to implement for the m_pInPlaceSite to be given a value? Or do I need to Query to get the value.
Thanks
FinalConstruct is way too early. In FinalConstruct your class is just being created and is not yet initialized. There is no "in place" site, there is no yet site at all.
Your control will be called by its owner, it will be given its site, then activated - only then you will possibly have m_pInPlaceSite available.