If I create normal CButtons they look like this:
I accidentally created some older looking buttons when I did the following:
class CClickButton : public CButton
{
afx_msg int OnCreate (LPCREATESTRUCT lpcs);
DECLARE_DYNAMIC(CClickButton);
DECLARE_MESSAGE_MAP();
};
IMPLEMENT_DYNAMIC(CClickButton, CButton);
BEGIN_MESSAGE_MAP(CClickButton, CButton)
ON_WM_CREATE()
END_MESSAGE_MAP()
int CClickButton::OnCreate (LPCREATESTRUCT lpcs)
{
return 0;
}
Now I would like to create buttons in this style. (Because I want to add a bitmap. And only when using this style, they give a visual feedback of getting 'pushed down'. The new style just tints the background blue and this is hidden by the bitmap on top of the button. An alternative question would be, if there is an easy way to also tint the image when the button gets pressed.)
What is the proper way to tell MFC to create those kind of buttons? Just omitting the the OnCreate message to the base class feels wrong to me. And I am not sure if that leads to other side effects I am not aware of yet.
I found some information on how to change the visual style for a whole program. But I only want to change it for some selected buttons.
Visual styles can be enabled and disabled on a per-window basis. The MSDN provides information on how to Turn Off Visual Styles:
You can turn off visual styles for a control or for all controls in a window by calling the SetWindowTheme function as follows:
SetWindowTheme(hwnd, L" ", L" ");
To implement this in your MFC CButton-derived class, put the code in your OnCreate-handler:
int CClickButton::OnCreate( LPCREATESTRUCT lpcs ) {
::SetWindowTheme( m_hWnd, L" ", L" " );
return CButton::OnCreate( lpcs );
}
If you are using a dialog resource to build your GUI and attach your CClickButton to an existing standard button control using the DDX_Control function after the dialog (and its controls) have been created, your OnCreate-handler will not get called. For this specific scenario you will have to turn off visual styles at a later point. An ideal candidate is overriding the virtual PreSubclassWindow method:
void CClickButton::PreSubclassWindow() {
::SetWindowTheme( m_hWnd, L" ", L" " );
CButton::PreSubclassWindow();
}
Related
I'm coding a custom Win32 UI control that I want to incorporate visual themes in. I load themes in its WM_NCCREATE as such:
case WM_NCCREATE:
{
HTHEME hTheme = ::OpenThemeData(hWnd, L"EDIT");
assert(hTheme);
assert(::GetWindowTheme(hWnd) != 0);
}
return 1;
and then release them when control is destroyed:
case WM_DESTROY:
{
HTHEME hTheme = ::GetWindowTheme(hWnd);
assert(hTheme);
if(::CloseThemeData(hTheme) != S_OK)
{
assert(NULL);
}
}
break;
This works well, until someone tries to change that control's styles. The following call (just by itself without even changing any styles):
::SetWindowLongPtr(hChildWnd, GWL_STYLE, dwStyle);
will make GetWindowTheme on hChildWnd return NULL.
So, is it a bug or a feature?
PS. To make a reproducible Win32 example I had to adjust the stock Win32 solution from the VS 2017. (Here is its full source code.) The way it works is this: in it I create a small child control (shown in gray below) that has theme in question:
Then when you click on the white area of the main window, I try to change its styles and its theme disappears:
To see the full Win32 code for that project, I also posted it on PasteBin.
According to Window Styles document:
"After the window has been created, these styles cannot be modified,
except as noted."
Because this is not permitted, the theme engine does not always check for changed styles and in some circumstances will draw the caption based on old data. And the only guaranteed and supportable solution is for the application to destroy the window and recreate it with the new styles rather than trying to change them on the fly.
A similar discussion can be found:
http://social.msdn.microsoft.com/Forums/en/windowscompatibility/thread/7b5ef777-ff0d-4f15-afed-5588f93f0e23
I'm not sure why am I getting this visual artifact?
Here's how to repro:
I'm using Visual Studio 2017 Community. Create a new C++ -> MFC project:
Then specify "dialog based":
Then build as "Debug" x86 app and run it.
So I'm running it on Windows 10.
When this dialog-based process has focus, it looks as I would expect it:
but if I switch keyboard focus to some other app (by clicking on it), this dialog-based process still retains its title bar color:
I'm not sure if it's just a matter of a visual glitch or if there's a deeper mess-up with the window message handling. How do I correct it? (This wasn't an issue with older MFC projects.)
I managed to replicate your problem and found a quick fix for it.
You need to add the WM_ACTIVATE message handler to your main dialog, comment out the base class OnActivate and modify it like this:
void CMFCApplication1Dlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
//CDialogEx::OnActivate(nState, pWndOther, bMinimized);
// TODO: Add your message handler code here
this->Default();
}
CWnd::Default call is needed to keep the active/inactive visualization of the default button.
OK, as much as I appreciate #VuVirt's solution, it doesn't completely remove all the bugs that are shipped in the default Dialog-based solution in VS2017. It solves the title bar focus issue, but while continuing to develop my project I encountered another bug. So I'm copy-and-pasting it from my comment to his answer:
There's still some kinda screw-up there. I'm not sure if it's related to this fix or not. Ex: If you create a button and then in its handler try to do: CFileDialog d(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_EXPLORER, NULL, this); d.DoModal(); to open a file picker dialog. When file picker opens up, close it and see if the title bar of the parent MFC dialog window goes back to being active. In my case it remains inactive until I click onto the Windows taskbar and then back onto that MFC app.
After banging my head against the wall trying to see what is going on there, I decided to try an earlier solution proposed by #zett42 in the comments to my original question (i.e. to replace CDialogEx with CDialog) and it worked! All the bugs are gone!
So here's my verdict: CDialogEx is buggy af.
The resolution is quite simple: When you create a new dialog-based project use project-wide find-and-replace (in the Edit menu) and replace all occurrences of CDialogEx with CDialog. And that is it. (I tried to use VS2017's refactoring tool for that but it messed it up and didn't replace it all. So simple search-and-replace does the job.)
And if you think that you'll be missing some functionality without CDialogEx, then you won't. All it does (besides introducing bugs) is that it adds background images and colors to the dialog.
So until MS fixes those glaring bugs in their templates I'm sticking with this approach.
This seems to be a bug in CDialogImpl::OnActivate and CDialogImpl::OnNcActivate:
void CDialogImpl::OnNcActivate(BOOL& bActive)
{
if (m_Dlg.m_nFlags & WF_STAYACTIVE)
bActive = TRUE;
if (!m_Dlg.IsWindowEnabled())
bActive = FALSE;
}
void CDialogImpl::OnActivate(UINT nState, CWnd* pWndOther)
{
m_Dlg.m_nFlags &= ~WF_STAYACTIVE;
CWnd* pWndActive = (nState == WA_INACTIVE) ? pWndOther : &m_Dlg;
if (pWndActive != NULL)
{
BOOL bStayActive = (pWndActive->GetSafeHwnd() == m_Dlg.GetSafeHwnd()
|| pWndActive->SendMessage(WM_FLOATSTATUS, FS_SYNCACTIVE));
if (bStayActive)
m_Dlg.m_nFlags |= WF_STAYACTIVE;
}
else
{
m_Dlg.SendMessage(WM_NCPAINT, 1);
}
}
This is meant to give CDialogEx the ability to stay active, for example, when CMFCPopupMenu is shown.
But m_Dlg.SendMessage(WM_NCPAINT, 1) is a suspicious call. The usage doesn't match the documentation for WM_NCPAINT:
Parameters
wParam
A handle to the update region of the window. The update region is clipped to the window frame.
lParam
This parameter is not used.
Additionally, OnNcActivate has an override based on IsWindowEnabled(). This seems to be a patch to fix the earlier problem in OnActivate. But it causes problems elsewhere, for example when using CFileDialog in CDialogEx
Suggested solution:
Modify CDialogEx::OnActivate so that it runs the default procedure. Or, change it such that it will force repaint.
BOOL CDialogEx::OnNcActivate(BOOL active)
{
if(m_nFlags & WF_STAYACTIVE)
active = TRUE;
return(BOOL)DefWindowProc(WM_NCACTIVATE, active, 0L);
}
void CDialogEx::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
Default();
}
or
void CDialogEx::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
Default();
//save the previous flag
UINT previous_flag = m_nFlags;
m_nFlags &= ~WF_STAYACTIVE;
// Determine if this window should be active or not:
CWnd* pWndActive = (nState == WA_INACTIVE) ? pWndOther : this;
if(pWndActive != NULL)
{
BOOL bStayActive = pWndActive->GetSafeHwnd() == GetSafeHwnd() ||
pWndActive->SendMessage(WM_FLOATSTATUS, FS_SYNCACTIVE);
if(bStayActive)
m_nFlags |= WF_STAYACTIVE;
}
if(previous_flag != m_nFlags && previous_flag & WF_STAYACTIVE)
{
//if the flag is changed,
//and if WF_STAYACTIVE was previously set,
//then OnNcActivate had handled it wrongly, do it again
SendMessage(WM_NCACTIVATE, FALSE); //<- less wrong!
}
}
This should work with CMFCPopupMenu for example. The MFC menu will open without deactivating the dialog.
I am not sure what SendMessage(WM_FLOATSTATUS, FS_SYNCACTIVE) is for, I haven't been able to test it... If it's necessary, it seems the code could be added on OnNcActivate, and then OnActivate is left alone.
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).
I am creating a simple clock application using MFC. My application title appears as follows : "CLOCK - [CLOCK1]". How do I reset it to simply "CLOCK"?
FYI, I have enabled the Document-View architecture.
Put in this override of the MFC title:
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
SetWindowText(L"CLOCK");
}
There's an answer here, but I feel that the following solution is more "proper".
In addition to overriding CMainFrame::OnUpdateFrameTitle(), you also need to override CMainFrame::PreCreateWindow() as below:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{ cs.style &= ~FWS_ADDTOTITLE;
return CFrameWndEx::PreCreateWindow(cs); // replace CFrameWndEx by CFrameWnd if
} // your CMainFrame is based on CFrameWnd
A note: it is better to use AfxSetWindowText(m_hWnd, _T("foo")) instead of SetWindowText(_T("foo")) to avoid excessive flicker, it checks that the text is different before setting the window text.
You can change it in visual editor by clicking on a your window and typing a title.
Or you can add this code in function OnInitDialog
this->SetWindowText(L"CLOCK");
I've created a read-only edit box in an MFC dialog box. I'm trying to have it so a user clicks in the edit box, which is read-only, it opens a file dialog, and then puts this value into the text box using UpdateData. I'm catching the ON_EN_SETFOCUS message but pressing OK on the file dialog respawns it, so I get caught in an infinite loop.
UpdateData(TRUE);
CFileDialog fileDialog(TRUE,NULL, NULL,OFN_HIDEREADONLY|OFN_FILEMUSTEXIST, _T("Text Files(*.txt)|*.txt||"));
if( fileDialog.DoModal() == IDOK )
{
configFile=fileDialog.GetPathName(); //Note to self, this includes filename, getPathName includes filename and path.
}
else
{
return;
}
UpdateData(FALSE);
If you've got any ideas on how this should be done, I would be very grateful.
Alright Mr. Lister I guess I'll add an answer.
First off I would preface this with I would probably simply add a button name "..." to launch the file dialog to the right of the edit box for opening the file dialog as that's the simplest solution and what most windows users will expect.
Another option however is to extend an MFC control. When deciding to extend a control you want to pick one that mostly has the desired behavior and that has a virtual destructor which lends itself to being a subclass. Since you want button like behavior CButton may be a good choice.
Your class interface might look something like this:
class CPathButton : public CButton
{
public:
enum { ID /*= IDC_BUTTON1*/ };
const CString GetPath() const;
const CString GetFileName() const;
const CString GetDirectory() const;
const CString GetExtension() const;
// other useful methods for setting file filters etc
protected:
// add ON_CONTROL(BN_CLICKED, ID, &OnClick) or ON_BN_CLICKED(ID, &OnClick)
DECLARE_MESSAGE_MAP()
// CFileDialog fdlg.DoModal(), m_path = fdlg.GetPathName(), SetWindowText(fdlg.GetFileTitle()), etc
afx_msg void OnClick();
// additional message handlers etc
private:
CString m_path; // save full path for after dialog is closed
};
You can add as much or as little customization as you want depending on if the control will be created dynamically, via the resource file, or whatever. The basic idea being that you display the currently selected file name on the button while storing the full path for other uses as a member so the user doesn't need to see the clutter of a long path with nested directories.
If you don't like the way it looks by default you can override OnPaint and handle WM_PAINT messages and use a custom font, size, or add ellipsis for a long file title. You could also handle re-sizing the button to fit the file title by using text metrics and GetTextExtent to ensure the name fits or simply display a CToolTipCtrl when they hover the mouse over the button so they can see the full name. The CMFCButton from the MFC feature pack in VS2008+ has tool tip functionality built in so if you inherit from that instead of CButton displaying a tool tip would be as simple as calling SetTooltip(m_path)
If you want to get really fancy you could use some of the uxtheme API or new windows animation API.
You can override PreTranslateMessage() in your dialog class, and determine if the edit control was clicked that way:
CEdit m_CEditCtrl;
// ...
BOOL YourDialogClass::PreTranslateMessage(MSG *pMsg)
{
if((pMsg->wParam == VK_LBUTTON) && (m_CEditCtrl.m_hWnd == pMsg->hwnd))
{
// open your file dialog
return TRUE; // Return that the message was translated and doesn't need to be dispatched
}
return CDialog::PreTranslateMessage(pMsg);
}
Update: You can also (and it may be a better idea) to override your CEdit control's CWnd::PreTranslateMessage() function. This would require deriving a class from CEdit.
If you are using VS2008 SP1 or above, the easiest way to ask for a path is with CMFCEditBrowseCtrl. It displays an edit control with a button. The steps to use it are:
Change your edit control's class to CMFCEditBrowseCtrl
Call EnableFileBrowseButton to tell it that you want to browse for files, not folders (you can set a filter and default extension)
When the user clicks the button, a file dialog appears, and when you click OK in it, the selected path is written in the edit control.