Is there an easy way to remove the close button from an MFC feature pack caption bar?
(I don't mean the window caption, I'm talking about the little information bar that can appear at the top of the client area in these apps - ie:CMFCCaptionBar)
Thanks
Figured out one way...
class CNoCloseCaptionBar : public CMFCCaptionBar
{
public:
CNoCloseCaptionBar()
{
}
virtual void RecalcLayout()
{
__super::RecalcLayout();
m_rectClose.SetRectEmpty();
}
};
Removing the bitmap worked for me. See the MSOffice2007Demo example in the Visual C++ 2008 Feature Pack.
Comment out the following line in CMainFrame:CreateMessageBar()
//m_wndMessageBar.SetBitmap(IDB_INFO, RGB(255, 255, 255), FALSE, CMFCCaptionBar::ALIGN_LEFT);
Caption Bar without close button
Related
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.
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();
}
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 found a problem. I can't change default sort arrows in header of CMFCListCtrl.
I found post on msdn about CMFCHeaderCtrl::OnDrawSortArrow but it didn't help because no examples there.
I tried simple method how I set arrows to CListCtrl header through the CimageList and HDITEM but those arrow sets only to left side because right side has already arrow default.
Google is empty on solutions how change default arrows on CMFCListCTrl.
Please help me)
Thanks!
PS. Please note that this is CMFCListCtrl not a CListCtrl where I can add arrows very easly.
Because CMFCHeaderCtrl is a member inside CMFCListCtrl you can not overwrite it.
Try to derive yor own CMFCListCtrl class, with your own CMFCHeaderCtrl class, that overwrites OnDrawSortHeader OnDrawSortArrow.
Overwrite CMFCListCtrl::InitHeader and subclass to your header control class.
If you start form scratch with a CListCtrl you can directly subclass the header control. The complete stuff inside CMFCListCtrl isn't so complicated and easy to reimplement. Depends what functionality you need.
The CMFCHeaderCtrl calls the currently active Visual Manager to do the actual drawing of sort arrows. It is easy to implement a customized Visual manager which overrides the arrow drawing method in the base class.
class CMyVisualManager:public CMFCVisualManagerOffice2007
{
virtual void OnDrawHeaderCtrlSortArrow(CMFCHeaderCtrl* pCtrl, CDC* pDC, CRect& rect, BOOL bIsUp);
};
void CMyVisualManager::OnDrawHeaderCtrlSortArrow(CMFCHeaderCtrl* pCtrl, CDC* pDC, CRect& rectArrow, BOOL bIsUp)
{
BOOL bDlgCtrl = pCtrl->IsDialogControl();
CPen penDark(PS_SOLID, 1, bDlgCtrl ? afxGlobalData.clrBtnDkShadow : afxGlobalData.clrBarDkShadow);
CPen* pPenOld = pDC->SelectObject(&penDark);;
ASSERT_VALID(pPenOld);
if (!bIsUp)
{
pDC->MoveTo(rectArrow.CenterPoint().x, rectArrow.bottom);
pDC->LineTo(rectArrow.CenterPoint().x, rectArrow.top);
pDC->MoveTo(rectArrow.CenterPoint().x-2, rectArrow.top+4);
pDC->LineTo(rectArrow.CenterPoint().x+3, rectArrow.top+4);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.top+3);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.top+3);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.top+2);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.top+2);
}
else
{
pDC->MoveTo(rectArrow.CenterPoint().x, rectArrow.top);
pDC->LineTo(rectArrow.CenterPoint().x, rectArrow.bottom);
pDC->MoveTo(rectArrow.CenterPoint().x-2, rectArrow.bottom-4);
pDC->LineTo(rectArrow.CenterPoint().x+3, rectArrow.bottom-4);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.bottom-3);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.bottom-3);
pDC->MoveTo(rectArrow.CenterPoint().x-1, rectArrow.bottom-2);
pDC->LineTo(rectArrow.CenterPoint().x+2, rectArrow.bottom-2);
}
pDC->SelectObject(pPenOld);
}
I've seen that some apps (maybe not .NET apps) that have an extra button on the left from the minimize button on the form's title bar? How can I achieve this in C#?
UPDATE: Added a solution that will work with Aero enabled for Windows Vista and Windows 7
***Non-Aero Solution***
The non-client area of a window interaction is managed by a series of non-client specfic messages. For example WM_NCPAINT message is sent to the window procedure to paint the non-client area.
I have never done this from .NET, but I suspect you can overide the WndProc and handle the WM_NC* messages to achieve what you want.
Update: Since I never tried this from .NET I got a few minutes and thought I would give it a quick try.
Trying this on Windows 7, I found that I needed to disable the Themes for the Window if I wanted to OS to do the base rendering of the non-client area. So here is a short test. I used GetWindowDC to get the DC of the entire window rather than GetDCEx, that was just because I could interop that from memory and did not have lookup all the flag constants for GetDcEx. And of course the code could do with more error checking.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
public partial class CustomBorderForm : Form
{
const int WM_NCPAINT = 0x85;
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetWindowDC(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
[DllImport("user32.dll", SetLastError = true)]
public static extern void DisableProcessWindowsGhosting();
[DllImport("UxTheme.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr SetWindowTheme(IntPtr hwnd, string pszSubAppName, string pszSubIdList);
public CustomBorderForm()
{
// This could be called from main.
DisableProcessWindowsGhosting();
InitializeComponent();
}
protected override void OnHandleCreated(EventArgs e)
{
SetWindowTheme(this.Handle, "", "");
base.OnHandleCreated(e);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case WM_NCPAINT:
{
IntPtr hdc = GetWindowDC(m.HWnd);
using (Graphics g = Graphics.FromHdc(hdc))
{
g.FillEllipse(Brushes.Red, new Rectangle((Width-20)/2, 8, 20, 20));
}
ReleaseDC(m.HWnd, hdc);
}
break;
}
}
}
}
Btw. I called DisableProcessWindowsGhosting, this will stop the OS from drawing the non-client area if the application takes too long to respond to windows messages. If you do not do this, then in some situations the border will be renderd but your adornments will not be shown. So that depends on your requirements it that is right for you or not.
***Aero supported solution***
Prompted by the comment from #TheCodeKing, I thought I would take another look at this. It turns out this can be done in a fully documented way while supporting Aero. But it is not for the faint of heart. I will not provide a complete solution here, there are still some kinks to workout, but it does the basics.
This code/solution is based off the Win32 example which can be found at the following location
http://msdn.microsoft.com/en-us/library/bb688195(VS.85).aspx
In principal what you need to do is the following.
Extend the client area of the window to cover the Frame. This is done by handling the WM_NCCALCSIZE message and returning 0. This gives the Non-Client area a size of 0 and therefore the client area now covers the entire window.
Extend the Frame into the client area using DwmExtendFrameIntoClientArea. This gets the OS to render the Frame over the client area.
The above steps will give you a windows with the standard glass frame excluding the system menu (Window Icon) and the title. The minimize, maximize and close buttons will still be drawn and will work. What you will not be able to do is drag or resize the window, this is because the frame is not really there, remember the client area covers the whole window, we have just asked the OS to draw the frame onto the client area.
Now you can draw on the window as normal, even on top of the frame. You can even put controls in the caption area.
Finally, allow the DWM to handle hit-testing for you, by calling DwmDefWindowProc from your WndProc (before you've processed it). It returns a boolean indicating whether the DWM handled the message for you.
Simple Solution:
Step 1: Create a Windows Form (this will be your custom title bar)
-Set Form Border Style to None
-Add whatever controls you would like to this
-I will name this custom form "TitleBarButtons"
Step 2. In the from that you want to use this custom control in add
titleBarBtn = new TitleBarButtons();
titleBarBtn.Location = new Point(this.Location.X + 100, this.Location.Y+5);
titleBarBtn.Show();
titleBarBtn.Owner = this;
To your constructor... you can mess with the offsets this just fit in a nice position for my app
Step 3. Add the move event to your main form
private void Form14_Move(object sender, EventArgs e)
{
titleBarBtn.Location = new Point(this.Location.X + 100, this.Location.Y+5);
}
Please let me know if you would like a better explanation of any of the above code.
I think a way to do this would be to handle WM_NCPAINT message (non-client paint) to draw the button, and to handle non-client mouse clicks to know someone clicked on the "button".