How to glow the minimum. maximum and close button? - c++

I followed below guide to create a custom Aero Frame using DWM API.
Custom Window Frame Using DWM
My work:
void CMainFrame::OnActivate(UINT nState,CWnd* pWndOther,BOOL bMinimized )
{
CFrameWnd::OnActivate(nState,pWndOther,bMinimized);
BOOL fDwmEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
{
if(nState == WA_ACTIVE )
{
MARGINS margins ={-1};
HRESULT hr = DwmExtendFrameIntoClientArea(m_hWnd, &margins);
if (!SUCCEEDED(hr));
}
}
}
void CMainFrame::OnNcPaint(){
RECT rcClient;
GetWindowRect(&rcClient);
// Inform the application of the frame change.
SetWindowPos(
NULL,
rcClient.left, rcClient.top,
RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
SWP_FRAMECHANGED);
CFrameWnd::OnNcPaint();
CDC* dc = GetWindowDC();
dc->FillSolidRect(0,0,RECTWIDTH(rcClient),RECTHEIGHT(rcClient),RGB(0,0,0));
}
LRESULT CMainFrame::OnNcHitTest(CPoint p)
{
LRESULT r ;
r = CFrameWnd::OnNcHitTest( p);
if(r == HTMINBUTTON || r == HTMAXBUTTON || r == HTCLOSE)
return r;
else
r = HitTestNCA(m_hWnd,p); // this function is direct copied from above link.
return r;
}
Result:
I found out the minimum, maximum and close button that will not be glowed when I move the mouse on these buttons.
General situation:
How to fix this problem?
Best Regards,

DwmDefWindowProc is required to handle caption buttons. From msdn:
For caption button hit testing, DWM provides the DwmDefWindowProc
function. To properly hit test the caption buttons in custom frame
scenarios, messages should first be passed to DwmDefWindowProc for
handling. DwmDefWindowProc returns TRUE if a message is handled and
FALSE if it is not. If the message is not handled by DwmDefWindowProc,
your application should handle the message itself or pass the message
onto DefWindowProc.
In MFC it can work out as follows:
LRESULT cframeWnd::OnNcHitTest(CPoint p)
{
BOOL dwm_enabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&dwm_enabled)))
{
LRESULT result = 0;
if (!DwmDefWindowProc(m_hWnd, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y), &result))
result = HitTestNCA(m_hWnd, p);
if (result == HTNOWHERE && GetForegroundWindow() != this)
{
return HTCAPTION;
}
return result;
}
return CWnd::OnNcHitTest(p);
}
I added a fix with GetForegroundWindow(), because the HitTestNCA function from MSDN example is wrong, it doesn't return HTCLIENT when it should. So when another window has focus, it won't switch windows upon mouse click in client area.
Also, there is a leak in OnNcPaint:
CDC* dc = GetWindowDC();
Whenever GetWindowDC() is called it should be followed by ReleaseDC. Or just use CWindowDC which has automatic cleanup. You don't actually need to override OnNcPaint because frame has been extended to "client area".
Here is a full example:
class cglassWnd : public CWnd
{
void OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS FAR*);
LRESULT OnNcHitTest(CPoint p);
void OnNcMouseLeave();
int OnCreate(LPCREATESTRUCT lpCreateStruct);
void OnActivate(UINT state, CWnd* otherWnd, BOOL minimized);
void OnPaint();
CRect borders;
int titlebar_height;
DECLARE_MESSAGE_MAP()
public:
cglassWnd();
};
BEGIN_MESSAGE_MAP(cglassWnd, CWnd)
ON_WM_NCHITTEST()
ON_WM_NCCALCSIZE()
ON_WM_NCMOUSELEAVE()
ON_WM_ACTIVATE()
ON_WM_CREATE()
ON_WM_PAINT()
END_MESSAGE_MAP()
cglassWnd::cglassWnd()
{
BOOL dwm_enabled = FALSE;
DwmIsCompositionEnabled(&dwm_enabled);
if (!dwm_enabled)
TRACE("Error: don't use this class, add error handling...");
//modified height for the new title bar
titlebar_height = 60;
}
int cglassWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
int res = CWnd::OnCreate(lpCreateStruct);
//find border thickness
borders = { 0,0,0,0 };
if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_THICKFRAME)
{
AdjustWindowRectEx(&borders,
GetWindowLongPtr(m_hWnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
borders.left = abs(borders.left);
borders.top = abs(borders.top);
}
else if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_BORDER)
{
borders = { 1,1,1,1 };
}
//Extend caption in to client area
MARGINS margins = { 0 };
margins.cyTopHeight = titlebar_height;
DwmExtendFrameIntoClientArea(m_hWnd, &margins);
SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
return res;
}
void cglassWnd::OnPaint()
{
CPaintDC dc(this);
//paint titlebar area (this used to be the non-client area)
CRect rc;
GetClientRect(&rc);
rc.bottom = titlebar_height;
//see MSDN reference for explanation of this code
//upside-down bitmap is for the sake of DrawThemeTextEx
CDC memdc;
memdc.CreateCompatibleDC(&dc);
BITMAPINFOHEADER infhdr = { sizeof(infhdr), rc.right, -rc.bottom, 1, 32 };
HBITMAP hbitmap = CreateDIBSection(dc,(BITMAPINFO*)(&infhdr),DIB_RGB_COLORS,0,0,0);
auto oldbitmap = memdc.SelectObject(hbitmap);
//do extra titlebar painting here
//for example put DrawThemeTextEx for window's name
dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memdc, 0, 0, SRCCOPY);
memdc.SelectObject(oldbitmap);
DeleteObject(hbitmap);
//begin normal paint
//The new client area begins below titlebar_height which we define earlier
GetClientRect(&rc);
rc.top = titlebar_height;
dc.FillSolidRect(&rc, RGB(128, 128, 255));
}
void cglassWnd::OnNcCalcSize(BOOL validate, NCCALCSIZE_PARAMS FAR* sz)
{
if (validate)
{
sz->rgrc[0].left += borders.left;
sz->rgrc[0].right -= borders.right;
sz->rgrc[0].bottom -= borders.bottom;
}
else
{
CWnd::OnNcCalcSize(validate, sz);
}
}
LRESULT cglassWnd::OnNcHitTest(CPoint pt)
{
LRESULT result = 0;
//handle close/minimize/maximize button
if (DwmDefWindowProc(m_hWnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y), &result))
return result;
//cursor is over the frame or client area:
result = CWnd::OnNcHitTest(pt);
if (result == HTCLIENT)
{
ScreenToClient(&pt);
if (pt.y < borders.top) return HTTOP;
if (pt.y < titlebar_height) return HTCAPTION;
}
return result;
}
void cglassWnd::OnNcMouseLeave()
{
//This is for close/minimize/maximize/help buttons
LRESULT result;
DwmDefWindowProc(m_hWnd, WM_NCMOUSELEAVE, 0, 0, &result);
CWnd::OnNcMouseLeave();
}
void cglassWnd::OnActivate(UINT state, CWnd* otherWnd, BOOL minimized)
{
CWnd::OnActivate(state, otherWnd, minimized);
Invalidate(FALSE);
}

Related

I am trying to check if the specified window is visible to the screen using Winapi but not sure how to approach when two windows are not overlapped

What I am trying to do is combining the regions of the top windows and checking if the specified window is present in the region if it is then displaying result. It does not giving results very accurately. And also I have problem when windows are non-overlapped. Does any one have any idea how to overcome this situation.
p is the foreground process
while (p != NULL)
{
if (IsWindowVisible(p)) {
if ((GetWindowLongPtr(p, GWL_STYLE) & WS_ICONIC) || GetWindowLong(p, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) {
}
else
{
RECT a;
if (GetWindowText(p, str, 255)) {
if ((_wcsicmp(str, _T("Program Manager"))))
{
GetWindowThreadProcessId(p, &proc_id);
proc_hnd = OpenProcess(PROCESS_ALL_ACCESS, TRUE, proc_id);
flag = GetProcessImageFileName(proc_hnd, proc_name, 1024);
wstring test(&proc_name[0]); //convert to wstring
string test2(test.begin(), test.end());
LOGGER->info(test2);
GetWindowRect(p, &a);
z = CreateRectRgn(a.left, a.top, a.right, a.bottom);
if (wcsstr(proc_name, L"chrome.exe")) {
RECT op;
GetWindowRect(p, &op);
HRGN j1 = CreateRectRgn(op.left, op.top, op.right, op.bottom);
CombineRgn(j1, j1, y, RGN_DIFF); emphasized text
CombineRgn(y, y, z, RGN_OR); //combining the region
switch (GetRgnBox(j1, &a))
{
case NULLREGION:
LOGGER->info("null region");
break;
case SIMPLEREGION:
LOGGER->info("simple region");
break;
case COMPLEXREGION:
LOGGER->info("complex region");
break;
default:
LOGGER->info("default region");
}
}
else
{
CombineRgn(y, y, z, RGN_OR); //combining the region
}
}
}
}
}
p = GetWindow(p, GW_HWNDNEXT); //getting the next window
}
I use the IsOverlapped sample here, but it is used to detect whether it is overlapped by any window. I modified this sample to check whether the window is visible:
BOOL IsVisible(HWND window)
{
HWND hWnd = window;
RECT thisRect;
if (!IsWindowVisible(window))
return FALSE;
GetWindowRect(window, &thisRect);
HWND explorer = FindWindow(L"Shell_TrayWnd", NULL);
DWORD explorer_pid;
GetWindowThreadProcessId(explorer, &explorer_pid);
HRGN hrgn_above = CreateRectRgn(0, 0, 0, 0);
HRGN hrgn_window = CreateRectRgnIndirect(&thisRect);
while ((hWnd = GetWindow(hWnd, GW_HWNDPREV)) != NULL)
{
DWORD pid;
GetWindowThreadProcessId(hWnd, &pid);
if (pid == explorer_pid || !IsWindowVisible(hWnd))
continue;
RECT rc = {};
GetWindowRect(hWnd, &rc);
HRGN hrgn_hWnd = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
CombineRgn(hrgn_above, hrgn_above, hrgn_hWnd, RGN_OR);
DeleteObject(hrgn_hWnd);
}
int ret = CombineRgn(hrgn_window, hrgn_window, hrgn_above, RGN_DIFF);
DeleteObject(hrgn_above);
DeleteObject(hrgn_window);
if (ret != NULLREGION && ret)
return TRUE;
return FALSE;
}
Use GetWindow + GW_HWNDPREV to get the windows list above the hWnd in Z order.

Set position of MFC dialog-based application window

I have a CDialog based MFC application. I save the window's current position when the program ends. When the program starts, I want to restore the previous position. I'm currently trying to do this in OnInitDialog(), however, the program asserts when I call SetWindowPos() from within OnInitDialog(). My call to SetWindowPos() is similar to:
SetWindowPos(&CWnd::wndTop, 10, 10, 500, 500, SWP_NOREDRAW | SWP_NOZORDER);
The assertion relates to a null m_hWnd handle.
Is this the correct place to re-position a dialog-based application's window?
Any ideas on why I'm asserting?
In the process of providing more information to my initial question, I discovered that calling SetWindowPos() passed messages to toolbars within the dialog that hadn't been created, yet. I moved the SetWindowPos() to the end of OnInitiDialog() and it worked.
Thanks for the encouragement, #zett42.
This is how I do it:
#include "stdafx.h"
#include "resource.h"
#include "ResizingDialog.h"
IMPLEMENT_DYNAMIC(CResizingDialog, CDialogEx)
CResizingDialog::CResizingDialog(const CString& strWindowID, UINT nIDTemplate, CWnd* pParent /* nullptr */, bool bOnlyStorePosition /* false */)
: CDialogEx(nIDTemplate, pParent)
, m_strWindowID(strWindowID)
, m_bOnlyStorePosition(bOnlyStorePosition)
, m_bDoNotShowResizeIcon(false)
{
m_rcInit.SetRect(0, 0, 0, 0);
}
CResizingDialog::~CResizingDialog()
= default;
void CResizingDialog::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CResizingDialog, CDialogEx)
ON_WM_GETMINMAXINFO()
ON_WM_DESTROY()
ON_WM_PAINT()
ON_WM_NCHITTEST()
ON_WM_SIZE()
END_MESSAGE_MAP()
BOOL CResizingDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Save Initial window size to m_rcInit
GetWindowRect(&m_rcInit);
//if (!m_bOnlyStorePosition && !m_bDoNotShowResizeIcon)
//InitialiseResizeIcon(m_bmpResize, m_lblResize, this);
RestoreWindowPosition(m_strWindowID, this, true);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CResizingDialog::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
// Set the minimum window size to initial size.
lpMMI->ptMinTrackSize.x = m_rcInit.Width();
lpMMI->ptMinTrackSize.y = m_rcInit.Height();
CDialogEx::OnGetMinMaxInfo(lpMMI);
}
void CResizingDialog::RestoreWindowPosition(CString strWindow, CWnd* pWindow, bool bOverrideState)
{
int max_x, max_y;
RECT rtWindow;
if (pWindow == nullptr)
return;
// Only restore if there is a previously saved position
if ((rtWindow.top = AfxGetApp()->GetProfileInt(strWindow, _T("Top"), -1)) != -1 &&
(rtWindow.left = AfxGetApp()->GetProfileInt(strWindow, _T("Left"), -1)) != -1 &&
(rtWindow.bottom = AfxGetApp()->GetProfileInt(strWindow, _T("Bottom"), -1)) != -1 &&
(rtWindow.right = AfxGetApp()->GetProfileInt(strWindow, _T("Right"), -1)))
{
max_x = rtWindow.right - rtWindow.left;
max_y = rtWindow.bottom - rtWindow.top;
// Get a handle to the monitor
HMONITOR hMonitor = ::MonitorFromPoint(
CPoint(rtWindow.left, rtWindow.top), MONITOR_DEFAULTTONEAREST);
// Get the monitor info
MONITORINFO monInfo;
monInfo.cbSize = sizeof(MONITORINFO);
if (::GetMonitorInfo(hMonitor, &monInfo) == 0)
AfxMessageBox(_T("GetMonitorInfo failed"));
else
{
// Adjust for work area
rtWindow.left += monInfo.rcWork.left - monInfo.rcMonitor.left;
rtWindow.top += monInfo.rcWork.top - monInfo.rcMonitor.top;
// Ensure top left point is on screen
if (CRect(monInfo.rcWork).PtInRect(CPoint(rtWindow.left, rtWindow.top)) == FALSE)
{
rtWindow.left = monInfo.rcWork.left;
rtWindow.top = monInfo.rcWork.top;
}
rtWindow.right = rtWindow.left + max_x;
rtWindow.bottom = rtWindow.top + max_y;
// Restore window size
pWindow->MoveWindow(&rtWindow, FALSE);
}
if (bOverrideState)
{
// Let us override by restoring the window state
int iState = AfxGetApp()->GetProfileInt(strWindow, _T("ShowCmd"), SW_SHOWNORMAL);
pWindow->ShowWindow(iState);
}
}
}
void CResizingDialog::SaveWindowPosition(CString strWindow, CWnd* pWindow)
{
WINDOWPLACEMENT wp;
if (pWindow == nullptr)
return;
pWindow->GetWindowPlacement(&wp);
// Commit to registry
AfxGetApp()->WriteProfileInt(strWindow, _T("Top"), wp.rcNormalPosition.top);
AfxGetApp()->WriteProfileInt(strWindow, _T("Left"), wp.rcNormalPosition.left);
AfxGetApp()->WriteProfileInt(strWindow, _T("Bottom"), wp.rcNormalPosition.bottom);
AfxGetApp()->WriteProfileInt(strWindow, _T("Right"), wp.rcNormalPosition.right);
AfxGetApp()->WriteProfileInt(strWindow, _T("ShowCmd"), wp.showCmd);
}
void CResizingDialog::InitialiseResizeIcon(CBitmap& rBmpResize, CStatic& rLblResize, CWnd* pDialog)
{
CRect rcIcon, rcClient;
if (pDialog != nullptr)
{
rBmpResize.LoadOEMBitmap(OBM_SIZE);
rLblResize.Create(nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP,
CRect(0, 0, 16, 16), pDialog, IDC_STATIC_RESIZE);
rLblResize.SetBitmap(rBmpResize);
//theApp.UpdateBitmapBackground(rLblResize.GetBitmap(), true, GetSysColor(COLOR_ACTIVECAPTION));
pDialog->GetClientRect(rcClient);
rLblResize.GetClientRect(rcIcon);
rLblResize.SetWindowPos(&CWnd::wndTop,
rcClient.right - rcIcon.Width(),
rcClient.bottom - rcIcon.Height(), 0, 0, SWP_NOSIZE);
CMFCDynamicLayout *pDynamicLayout = pDialog->GetDynamicLayout();
if (pDynamicLayout != nullptr)
{
CMFCDynamicLayout::MoveSettings moveSettings = CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100);
CMFCDynamicLayout::SizeSettings sizeSettings = CMFCDynamicLayout::SizeNone();
pDynamicLayout->AddItem(rLblResize.GetSafeHwnd(), moveSettings, sizeSettings);
}
}
}
void CResizingDialog::OnDestroy()
{
CDialogEx::OnDestroy();
SaveWindowPosition(m_strWindowID, this);
}
void CResizingDialog::DoNotShowResizeIcon()
{
m_bDoNotShowResizeIcon = true;
}
void CResizingDialog::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
// Do not call CDialogEx::OnPaint() for painting messages
if (!m_bOnlyStorePosition && !m_bDoNotShowResizeIcon)
{
CRect rc;
GetClientRect(&rc);
rc.left = rc.right - ::GetSystemMetrics(SM_CXHSCROLL);
rc.top = rc.bottom - ::GetSystemMetrics(SM_CYVSCROLL);
HTHEME ht = OpenThemeData(GetSafeHwnd(), L"STATUS");
if (ht)
{
DrawThemeBackground(ht, dc, SP_GRIPPER, 0, &rc, nullptr);
CloseThemeData(ht);
}
else
{
dc.DrawFrameControl(rc, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
}
}
}
LRESULT CResizingDialog::OnNcHitTest(CPoint point)
{
CRect rc;
GetWindowRect(rc);
rc.left = rc.right - ::GetSystemMetrics(SM_CXHSCROLL);
rc.top = rc.bottom - ::GetSystemMetrics(SM_CYVSCROLL);
if (rc.PtInRect(point))
return HTBOTTOMRIGHT;
return CDialogEx::OnNcHitTest(point);
}
void CResizingDialog::OnSize(UINT nType, int cx, int cy)
{
CDialogEx::OnSize(nType, cx, cy);
Invalidate(TRUE);
}
I don't know how you are storing the window position but my code factors in for multiple monitor configurations.

An Aero caption title bar issue using DWM API on the windows 10

In order to draw the icon on the caption title bar, I have refereed this MSDN article and used DWM API to create my customize client area by calling DwmExtendFrameIntoClientArea.
my code:
CMainFrame::CMainFrame()
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
BOOL fDwmEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
TRACE0("DWM is enabled\n");
TCHAR szLogoPath[MAX_PATH];
GetModuleFileName ( GetModuleHandle(NULL), szLogoPath, _countof(szLogoPath) );
PathRemoveFileSpec ( szLogoPath );
PathAppend ( szLogoPath, _T("lena.bmp") );
m_pLogoImage = m_pLogoImage->FromFile ( CT2CW(szLogoPath) );
if(NULL == m_pLogoImage)
TRACE0("load image fail\n");
}
void CMainFrame::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
int xFrame = 2;
int yFrame = 2;
int nTHight = 30;
NCCALCSIZE_PARAMS * p;
RECT * rc;
RECT aRect;
RECT bRect;
RECT acRect;
p = (NCCALCSIZE_PARAMS *)lpncsp;
CopyRect(&bRect,&p->rgrc[1]);
CopyRect(&aRect,&p->rgrc[0]);
acRect.left = aRect.left + xFrame;
acRect.top = aRect.top - nTHight;
acRect.right = aRect.right - xFrame;
acRect.bottom = aRect.bottom - yFrame;
CopyRect(&p->rgrc[0],&acRect);
CopyRect(&p->rgrc[1],&aRect);
CopyRect(&p->rgrc[2],&bRect);
CFrameWnd::OnNcCalcSize(TRUE, lpncsp);
}
LRESULT CMainFrame::OnNcHitTest(CPoint p)
{
BOOL dwm_enabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&dwm_enabled)))
{
LRESULT result = 0;
if (!DwmDefWindowProc(m_hWnd, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y), &result))
result = HitTestNCA(m_hWnd, p);
if (result == HTNOWHERE && GetForegroundWindow() != this)
{
return HTCAPTION;
}
return result;
}
return CWnd::OnNcHitTest(p);
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if(cs.hMenu!=NULL)
{
::DestroyMenu(cs.hMenu);
cs.hMenu = NULL ;
}
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.style = WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_OVERLAPPED| WS_SYSMENU | WS_THICKFRAME;
cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
cs.lpszClass = AfxRegisterWndClass(0);
return TRUE;
}
void CMainFrame::OnActivate(UINT nState,CWnd* pWndOther,BOOL bMinimized )
{
CFrameWnd::OnActivate(nState,pWndOther,bMinimized);
BOOL fDwmEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
{
if(nState == WA_ACTIVE )
{
MARGINS margins = {-1};
/*margins.cyTopHeight = 30;
margins.cxLeftWidth = 0;
margins.cxRightWidth = 0;
margins.cyBottomHeight = 0;*/
HRESULT hr = DwmExtendFrameIntoClientArea(m_hWnd, &margins);
if (!SUCCEEDED(hr))
TRACE0("Failed in DwmExtendFrameIntoClientArea\n");
}
}
}
void CMainFrame::OnNcPaint()
{
CFrameWnd::OnPaint();
CDC* dc = GetWindowDC();
RECT rcClient;
GetWindowRect(&rcClient);
dc->FillSolidRect(0,0,RECTWIDTH(rcClient),RECTHEIGHT(rcClient),RGB(255,0,0));
CPaintDC gdc(this); // device context for painting
Graphics gr(gdc.m_hDC);
gr.DrawImage ( m_pLogoImage, 0, 0 );
ReleaseDC(dc);
}
The result under Windows 7 is fine.
However, my window appears another unknown caption title bar under Windows 10.
I found out the unknown caption is caused by WS_THICKFRAME in the cs.style.
If I remove WS_THICKFRAME, the unknown cation bar will disappear, but I cannot resizing the border of my window. Furthermore, my program cannot capture the minimum, maximum and the close button message on my custom caption bar anymore.
I want to remove the unknown title bar without any side effect.
Does anyone could provide me a good solution or suggestion?
Best Regards,
When using DwmExtendFrameIntoClientArea, it means frame is extended in to client area. It is no longer in non-client area. So there is no need to override OnNcPaint, you can do all of the painting in OnPaint
void CMainFrame::OnPaint()
{
CPaintDC dc(this);
//paint titlebar area (this used to be the non-client area)
CRect rc;
GetClientRect(&rc);
rc.bottom = titlebar_height;
CDC memdc;
memdc.CreateCompatibleDC(&dc);
BITMAPINFOHEADER bmpInfoHeader = {
sizeof(BITMAPINFOHEADER), rc.Width(), -rc.Height(), 1, 32 };
HBITMAP hbitmap = CreateDIBSection(
dc, (BITMAPINFO*)(&bmpInfoHeader), DIB_RGB_COLORS, NULL, NULL, 0);
auto oldbitmap = memdc.SelectObject(hbitmap);
dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memdc, 0, 0, SRCCOPY);
memdc.SelectObject(oldbitmap);
DeleteObject(hbitmap);
//begin normal paint
//The new client area begins below titlebar_height which we define earlier
GetClientRect(&rc);
rc.top = titlebar_height;
dc.FillSolidRect(&rc, RGB(0, 0, 255));
Gdiplus::Image *image = Gdiplus::Image::FromFile(L"file.jpg");
Gdiplus::Graphics gr(dc);
gr.DrawImage(image, 0, 0);
delete image;
}
Use a member variable CRect m_border to keep track of border's thickness. You can use AdjustWindowRectEx to find the thickness of the borders.
void CMainFrame::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
CFrameWnd::OnActivate(nState, pWndOther, bMinimized);
titlebar_height = 100;
//find border thickness
if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_THICKFRAME)
{
m_border = { 0,0,0,0 };
AdjustWindowRectEx(&m_border, GetWindowLongPtr(m_hWnd,
GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
m_border.left = abs(m_border.left);
m_border.top = abs(m_border.top);
}
else if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_BORDER)
{
m_border = { 1,1,1,1 };
}
else
{
m_border = { 0,0,0,0 };
}
//Extend frame in to client area
MARGINS margins = { 0 };
margins.cyTopHeight = titlebar_height; //<<=== *** edited
DwmExtendFrameIntoClientArea(m_hWnd, &margins);
SetWindowPos(NULL, 0, 0, 0, 0,
SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
}
m_border will be for example {7,7,7,7};
Allow Windows to do the painting on left, right, bottom border. The top border is the only one changed
void CMainFrame::OnNcCalcSize(BOOL validate, NCCALCSIZE_PARAMS FAR* lpncsp)
{
if (validate)
{
lpncsp->rgrc[0].left += m_border.left;
lpncsp->rgrc[0].right -= m_border.right;
lpncsp->rgrc[0].bottom -= m_border.bottom;
}
else
{
CFrameWnd::OnNcCalcSize(validate, lpncsp);
}
}
see also How to glow the minimum. maximum and close button?

Issue in drawing color on propertysheet footer?

I designed a property sheet and painted its footer to some gradient in the OnPaint() event.
The footer looks like as below.Observe the button area circled in red colour.
In the OnPaint I was doing as follows,
//CMySheet is derived from CPropertySheet.
void CMySheet::OnPaint()
{
if(IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND,reinterpret_cast<WPARAM>(dc.GetSafeHdc()),0);
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1)/2;
int y = (rect.Height() - cyIcon + 1)/2;
}
else
{
CPaintDC dc(this);
UpdateData(false);
CRect Clientrect;
GetClientRect(&Clientrect);
LONG RectDifference = ((Clientrect.bottom - m_PageRectBottom)-2);//m_pageRectBottom is of page bottom rect
CRect rectFooter(Clientrect.top,(Clientrect.bottom - RectDifference),Clientrect.right,Clientrect.bottom);//638//520
//CRect rectFooter(0,390,640,445);
FillGradation(&dc,rectFooter,RGB(150,150,150),RGB(0,0,0),true);
}
}
}
void CMySheet::OnPaint(CDC* pDC, CRect rc, COLORREF colBegin, COLORREF colEnd, bool bV)
{
TRIVERTEX av[2] = {rc.left,rc.top,GetRValue(colBegin) << 8,GetGValue(colBegin) << 8,GetBValue(colBegin) << 8 ,0xff00,
rc.right,rc.bottom,GetRValue(colEnd) << 8 ,GetGValue(colEnd) << 8,GetBValue(colEnd) << 8,0xff00,};
GRADIENT_RECT gr = {0,1};
ULONG ulMode;
if(bV){
ulMode = GRADIENT_FILL_RECT_V;
}
else{
ulMode = GRADIENT_FILL_RECT_H;
}
GradientFill(pDC->GetSafeHdc(),av,2,&gr,1,ulMode);
}
The buttons are not transparent in the above image ,but actually the background of the button should look like as in below image.
The wizard buttons background or the footer area should look like the above image.But if you can have a look at the first image in that there is some white colour around the Back button ,Next and cancel buttons.
HBRUSH CMySheet::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CPropertySheet::OnCtlColor(pDC, pWnd, nCtlColor);
if((pWnd->GetDlgCtrlID() == ID_WIZBACK) || (pWnd->GetDlgCtrlID() == ID_WIZNEXT) ||
(pWnd->GetDlgCtrlID() == ID_WIZFINISH) || (pWnd->GetDlgCtrlID() == IDCANCEL))
{
return CreateSolidBrush(RGB(130,130,130));
}
return hbr;
}
If I am doing like this ,the Image is as follows with gray colour .But that colour should be gradient right,I am not able to create a Gradient brush.
I tried returning NULL in CtlColor but I could not see any difference.
Derived my own classes from CPropertySheet and CButton ,
//Overrided the DrawItem and PreSubclassWindow
void CPropSheetButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rect = lpDrawItemStruct->rcItem;
UINT state = lpDrawItemStruct->itemState;
if (state & ODS_SELECTED)
pDC->DrawFrameControl(rect, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
else
pDC->DrawFrameControl(rect, DFC_BUTTON, DFCS_BUTTONPUSH);
UINT uStyle = DFCS_BUTTONPUSH;
HTHEME hTheme = OpenThemeData(m_hWnd, L"BUTTON");
HRESULT hr = DrawThemeBackground(hTheme, lpDrawItemStruct->hDC, BP_PUSHBUTTON, PBS_DEFAULTED, &lpDrawItemStruct->rcItem, NULL);
// Get the button's text.
CString strText;
GetWindowText(strText);
CloseThemeData(hTheme);
::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(),
&lpDrawItemStruct->rcItem, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
int nMode = pDC->SetBkMode(TRANSPARENT);
pDC->SetBkMode(nMode);
}
void CPropSheetButton::PreSubclassWindow()
{
CButton::PreSubclassWindow();
ModifyStyle(0, BS_OWNERDRAW); // make the button owner drawn
}
//In the Sheet derived class OnInitDialog ,
BOOL CMySheetWizard::OnInitDialog()
{
CPropertySheet::OnInitDialog();
CMyButton backbutton;
BOOL bRet = backbutton.SubclassDlgItem(ID_WIZBACK,this);
}
Can anyone please let me know how I can remove the border around those buttons.
Using your painting code to render the background and some extra classes, I was able to achieve this...
I think this was what you were trying to achieve. I was able to accomplish this by doing the following:
Derive my own CPropertySheet and CButton classes.
Subclass the property sheet buttons and make them owner drawn.
And here's the code that draws the buttons from the button class.
void SheetButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
UINT uStyle = DFCS_BUTTONPUSH;
HTHEME hTheme = OpenThemeData(m_hWnd, L"BUTTON");
DrawThemeBackground(hTheme, lpDrawItemStruct->hDC, BP_PUSHBUTTON, PBS_DEFAULTED, &lpDrawItemStruct->rcItem, NULL);
// Get the button's text.
CString strText;
GetWindowText(strText);
// Draw the button text using the text color red.
COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(255, 0, 0));
::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(),
&lpDrawItemStruct->rcItem, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
::SetTextColor(lpDrawItemStruct->hDC, crOldColor);
CloseThemeData(hTheme);
}
BTW...you still need to add code to return a null brush for the buttons. I did not, however, account for the different states of the buttons in the drawing code. I left that up to you as an exercise.

Ribbon button items with large images and checkboxes

I've got a menu attached to a split ribbon button like this (VS2008, Feature Pack):
std::auto_ptr<CMFCRibbonButton> apBtn3(new CMFCRibbonButton(ID_RIBBON_BTN_3, _T("Split Button"), 2, 2));
apBtn3->SetMenu(IDR_RIBBON_MENU_1, TRUE);
apBtn3->SetAlwaysLargeImage();
apBtn3->RemoveSubItem(0);
std::auto_ptr<CMFCRibbonButton> apSubButton(new CMFCRibbonButton(ID_RIBBON_MBTN_1, _T("Item 1"), 2, 2));
apSubButton->SetAlwaysLargeImage();
apBtn3->AddSubItem(apSubButton.release(), 0);
pPanel1->Add(apBtn3.release());
I want to put checkboxes in front of each menu item and have provided SetCheck() calls in the CN_UPDATE_COMMAND_UI handler but checkboxes will only show up, if I disable the large icons.
Is there any way to use checkboxes along with large icons in CMFCRibbonButton menus? If not, what would be the best possible workaround?
I found a usable workaround to check large icons by alpha-blending a green circle with a checkmark in it over the icon -- the same solution Windows uses to mark the default audio device or printer in the control panel:
This is my CMFCRibbonButtonEx class declaration:
class CMFCRibbonButtonEx : public CMFCRibbonButton
{
// Construction
public:
CMFCRibbonButtonEx();
CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, int nSmallImageIndex = -1, int nLargeImageIndex = -1, BOOL bAlwaysShowDescription = FALSE);
CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, HICON hIcon, BOOL bAlwaysShowDescription = FALSE, HICON hIconSmall = NULL, BOOL bAutoDestroyIcon = FALSE, BOOL bAlphaBlendIcon = FALSE);
// Overridden
void SetCheck(BOOL bCheck = TRUE);
void DrawImage(CDC* pDC, RibbonImageType type, CRect rectImage);
// Attributes
private:
BOOL m_bChecked;
// Helper
private:
void DrawCheckmark(CDC* pDC, int CheckmarkResourceBitmapID, RECT *r);
void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp);
};
Here are the class function definitions:
CMFCRibbonButtonEx::CMFCRibbonButtonEx() : CMFCRibbonButton() { }
CMFCRibbonButtonEx::CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, int nSmallImageIndex, int nLargeImageIndex, BOOL bAlwaysShowDescription)
: CMFCRibbonButton(nID, lpszText, nSmallImageIndex, nLargeImageIndex, bAlwaysShowDescription) { }
CMFCRibbonButtonEx::CMFCRibbonButtonEx(UINT nID, LPCTSTR lpszText, HICON hIcon, BOOL bAlwaysShowDescription, HICON hIconSmall, BOOL bAutoDestroyIcon, BOOL bAlphaBlendIcon)
: CMFCRibbonButton(nID, lpszText, hIcon, bAlwaysShowDescription , hIconSmall, bAutoDestroyIcon, bAlphaBlendIcon) { }
void CMFCRibbonButtonEx::SetCheck(BOOL bCheck)
{
m_bChecked = bCheck;
}
void CMFCRibbonButtonEx::DrawImage(CDC* pDC, RibbonImageType type, CRect rectImage)
{
CMFCRibbonButton::DrawImage(pDC, type, rectImage);
if (type == RibbonImageLarge && m_bChecked)
DrawCheckmark(pDC, IDB_BIG_ICON_CHECKMARK, &rectImage);
}
void CMFCRibbonButtonEx::DrawCheckmark(CDC* pDC, int CheckmarkResourceBitmapID, RECT *r)
{
HDC hdc;
CDC *dc;
CDC dcMem;
CBitmap cbm;
VERIFY(hdc = pDC->m_hDC);
VERIFY(dc = pDC);
dcMem.CreateCompatibleDC(dc);
cbm.LoadBitmap(CheckmarkResourceBitmapID);
PremultiplyBitmapAlpha(dcMem.m_hDC, cbm);
SelectObject(dcMem.m_hDC, cbm.m_hObject);
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 255;
bf.AlphaFormat = AC_SRC_ALPHA;
::AlphaBlend(hdc, r->left, r->top, r->right-r->left, r->bottom-r->top, dcMem, 0, 0, 32, 32, bf);
VERIFY(dcMem.DeleteDC());
}
void CMFCRibbonButtonEx::PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
BITMAP bm = { 0 };
GetObject(hBmp, sizeof(bm), &bm);
BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
if( pBitData == NULL ) return;
LPBYTE pData = pBitData;
::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
for( int y = 0; y < bm.bmHeight; y++ ) {
for( int x = 0; x < bm.bmWidth; x++ ) {
pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
pData += 4;
}
}
::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
::LocalFree(pBitData);
}
IDB_BIG_ICON_CHECKMARK is a 32bit bmp with alpha channel: Download here
Sadly, in OnCmdMsg only a fake CMFCRibbonButton created by the ribbon framework is being maintained, so SetCheck() doesn't have any effect on an IsChecked() call in DrawImage(). You just have to find the real CMFCRibbonButtonEx in the subitems of the menu like this:
BOOL CEasyCashView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
...
else if (nID >= ID_MY_ITEMS_BASE && nID < ID_ITEMS_BASE+MAX_MY_ITEMS)
{
if (nCode == CN_COMMAND)
{
OnMyItemsCommand(nID);
}
else if (nCode == CN_UPDATE_COMMAND_UI)
{
((CCmdUI*)pExtra)->SetCheck(myItemsCheckedArray[nID-ID_MY_ITEMS_BASE] == TRUE);
// this won't have any effect, use code below
CMFCRibbonButton* pMyMainMenuButton;
if (pMyMainMenuButton = ((CMainFrame*)AfxGetMainWnd())->m_pMyMainMenuButton)
{
int i;
for (i = 0; i < pMyMainMenuButton->GetSubItems().GetCount(); i++)
if (pMyMainMenuButton->GetSubItems()[i]->GetID() == nID)
{
((CMFCRibbonButtonEx*)pMyMainMenuButton->GetSubItems()[i])->SetCheck(myItemsCheckedArray[nID-ID_MY_ITEMS_BASE] == TRUE);
break;
}
}
return TRUE;
}
}
Should anyone know how to connect the fake CMFCRibbonButton (or CMFCRibbonBaseElement) maintained by CCmdUI to the originally created CMFCRibbonButtonEx, please leave me a comment.
"checkboxes in front of each menu item"
Is the below picture what you're looking for? If yes, this can be achieved by editing the ribbon xml file.