I am trying to draw text on a simple splash screen that displays a bitmap and nothing else. Below is my code. The bitmap displays properly but no text. What am I doing wrong?
I am required to add some more "details" before posting because this is mostly code: I am not sure where the author got this example. I can't find an example of drawing text on a bitmap that fits the way this is done, so need some help.
Thanks
Splash.cpp
#include "stdafx.h"
#include "Splash.h"
Splash::Splash()
{
bool b = false; //debugging
}
Splash::~Splash()
{
DestroyWindow(hSplashWnd);
}
void Splash::Init(HWND hWnd,HINSTANCE hInst,int resid)
{
hParentWindow=hWnd;
hSplashWnd=CreateWindowEx(WS_EX_CLIENTEDGE,"STATIC","",
WS_POPUP|WS_DLGFRAME|SS_BITMAP,300,300,300,300,hWnd,NULL,hInst,NULL);
SendMessage(hSplashWnd,STM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(hInst,MAKEINTRESOURCE(resid)));
this->SHOWING = FALSE;
}
void Splash::Show()
{
//get size of hSplashWnd (width and height)
int x, y;
int pwidth, pheight;
int tx, ty;
HDWP windefer;
RECT rect, prect;
GetClientRect(hSplashWnd,&rect);
x=rect.right;
y=rect.bottom;
//get parent screen coordinates
GetWindowRect(this->hParentWindow,&prect);
//center splash window on parent window
pwidth=prect.right-prect.left;
pheight=prect.bottom - prect.top;
int iScreenWidth, iScreenHeight, iSplashLeft, iSplashTop;
iScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
iScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
if(prect.left > iScreenWidth)
{
//On second monitor
iSplashLeft = iScreenWidth + (iScreenWidth / 2) - ((rect.right - rect.left) / 2);
}
else
{
//On first monitor
iSplashLeft = (iScreenWidth / 2) - ((rect.right - rect.left) / 2);
}
iSplashTop = (iScreenHeight / 2) - ((rect.bottom - rect.top) /2);
tx = iSplashLeft;
ty = iSplashTop;
windefer=BeginDeferWindowPos(1);
DeferWindowPos(windefer,hSplashWnd,HWND_NOTOPMOST,tx,ty,50,50,SWP_NOSIZE|SWP_SHOWWINDOW|SWP_NOZORDER);
EndDeferWindowPos(windefer);
BOOL isValidWindow = IsWindow(hSplashWnd);
//##################### Draw text on the bitmap ###############
CBrush brush;
brush.CreateSolidBrush(RGB(255,0,0));
HDC hdc = GetDC(hSplashWnd);
char *text = "HELLO HELLO HELLO HELLO HELLO HELLO";
SelectObject(hdc, brush);
SetTextColor(hdc, RGB(0,255,0));
DrawTextA(hdc, text, strlen(text), &rect, DT_SINGLELINE | DT_LEFT); //DT_CENTER | DT_VCENTER );
ShowWindow(hSplashWnd,SW_SHOWNORMAL);
UpdateWindow(hSplashWnd);
this->SHOWING = TRUE;
}
void Splash::Hide()
{
ShowWindow(hSplashWnd,SW_HIDE);
this->SHOWING = FALSE;
}
Splash.h
#if !defined(AFX_SPLASH_H_INCLUDED)
#define AFX_SPLASH_H_INCLUDED
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class Splash
{
public:
void Hide();
void Show();
void Init(HWND hWnd,HINSTANCE hInst,int resid);
BOOL SHOWING;
Splash();
virtual ~Splash();
private:
UINT TimerID;
HWND hParentWindow;
HWND hSplashWnd;
};
#endif
Draw the splash screen:
Splash Splash1;
Splash1.Init( m_pMainWnd->m_hWnd, this->m_hInstance, IDB_BITMAP_SPLASH );
Splash.Show();
Sleep(3000);
Splash1.Hide;
Move the call to UpdateWindow(hSplashWnd); to after your GetDC(..)
The WM_PAINT message gets your bitmap drawn, and then you want to draw your text after that.
HDC hdc = GetDC(hSplashWnd);
UpdateWindow(hSplashWnd);
wchar_t * text = L"HELLO";
//SelectObject(hdc, brush);
SetTextColor(hdc, RGB( 0, 255, 0 ) );
DrawText(hdc, text, 200, &rect, DT_SINGLELINE | DT_LEFT); //DT_CENTER | DT_VCENTER );
ShowWindow(hSplashWnd,SW_SHOWNORMAL);
Related
I have two methods, paintDoubleBuffered and paint. They are both supposed to draw this image on the screen:
The image is made up of approximately 12 smaller images each sized 256x256 tiled together.
My standard painting method works as expected. Here it is:
void MainWindow::paint(HWND hwnd) {
HDC hdc{GetDC(hwnd)};
paint(hwnd, hdc);
ReleaseDC(hwnd, hdc);
}
void MainWindow::paint(HWND hwnd, HDC hdc) {
constexpr INT img_width{ MDNR_Map::pannel_width };
constexpr INT img_height{ MDNR_Map::pannel_height };
const INT width{ GetDeviceCaps(hdc, HORZRES) };
const INT height{ GetDeviceCaps(hdc, VERTRES) };
const INT num_width_pannels{ (width / img_width) + 1 };
const INT num_height_pannels{ (height / img_height) + 1 };
Gdiplus::Graphics g(hdc);
g.SetCompositingMode(CompositingMode::CompositingModeSourceCopy);
g.SetInterpolationMode(InterpolationMode::InterpolationModeNearestNeighbor);
for (INT y = 0; y < num_height_pannels; y++) {
for (INT x = 0; x < num_width_pannels; x++) {
Location_t get_loaction(x + map_location.x, y + map_location.y, map_location.layer);
const IMG_t v{ mdnr_map.get(get_loaction) };
const Point drawPoint((INT)(img_width * x), (INT)(img_height * y));
Status stat{ g.DrawImage(v, drawPoint) };
if (stat != Status::Ok)
{
throw std::runtime_error(":(");
}
}
}
}
The issue with that paint method is that mdnr_map.get is an io bound call and may take several micro seconds. Because I need to call it about 12 times, it can lead to flickering.
To solve this, I attempted to write a double-buffered paint method, which is as follows:
void MainWindow::paintDoubleBuffered(HWND hwnd) {
// Get DC for window
HDC hdc{ GetDC(hwnd) };
const INT win_width{ GetDeviceCaps(hdc, HORZRES) };
const INT win_height{ GetDeviceCaps(hdc, VERTRES) };
// Create an off-screen DC for double-buffering
HDC hdcMem{ CreateCompatibleDC(hdc) };
HBITMAP hbmMem{ CreateCompatibleBitmap(hdc, win_width, win_height) };
HANDLE hOld{ SelectObject(hdcMem, hbmMem) };
// Draw into hdcMem here
paint(hwnd, hdcMem);
// Transfer the off-screen DC to the screen
BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
}
However, this does not work and instead produces this abombination of an image:
With a little poking and proding, I was able to discover that if I changed my double buffered paint method by multiplying the image size by 1.5, the image was no longer so garbled, but was now zoomed in by a factor of 1.5
void MainWindow::paintDoubleBuffered(HWND hwnd) {
// Get DC for window
HDC hdc{ GetDC(hwnd) };
const INT win_width{ GetDeviceCaps(hdc, HORZRES) };
const INT win_height{ GetDeviceCaps(hdc, VERTRES) };
// Create an off-screen DC for double-buffering
HDC hdcMem{ CreateCompatibleDC(hdc) };
HBITMAP hbmMem{ CreateCompatibleBitmap(hdc, win_width, win_height) };
HANDLE hOld{ SelectObject(hdcMem, hbmMem) };
// Draw into hdcMem here
constexpr INT img_width{ MDNR_Map::pannel_width + 128 }; // MDNR_Map::pannel_width is 256
constexpr INT img_height{ MDNR_Map::pannel_height + 128}; // MDNR_Map::pannel_height is 256
const INT num_width_pannels{ (win_width / img_width) + 1 };
const INT num_height_pannels{ (win_height / img_height) + 1 };
Gdiplus::Graphics g(hdcMem);
g.SetCompositingMode(CompositingMode::CompositingModeSourceCopy);
g.SetInterpolationMode(InterpolationMode::InterpolationModeNearestNeighbor);
for (INT y = 0; y < num_height_pannels; y++) {
for (INT x = 0; x < num_width_pannels; x++) {
Location_t get_loaction(x + map_location.x, y + map_location.y, map_location.layer);
Gdiplus::Bitmap* pannel{ mdnr_map.get(get_loaction) };
const Point drawPoint((INT)(img_width * x), (INT)(img_height * y));
Status stat{ g.DrawImage(pannel, drawPoint) };
if (stat != Status::Ok)
{
throw std::runtime_error(":(");
}
}
}
// Transfer the off-screen DC to the screen
BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(hdcMem, hOld);
DeleteObject(hbmMem);
DeleteDC(hdcMem);
}
My question is why does drawing to the HDC returned by CreateCompatibleBitmap produce a different result than drawing to the HDC returned by GetDC?
I have tried:
All raster-operation codes for BltBlt.
I have checked that the temporary HDC is the same size as the window.
I have tried replacing the code snippet
const INT win_width{ GetDeviceCaps(hdc, HORZRES) };
const INT win_height{ GetDeviceCaps(hdc, VERTRES) };
with
RECT rect;
GetWindowRect(hwnd, &rect);
const INT win_width{ rect.right - rect.left };
const INT win_height{ rect.bottom - rect.top };
I have also called SetProcessDPIAware() before drawing.
Upon feedback from #Paul Sanders, I rewrote my paintDoubleBuffered method as follows, NOTE, I have called BufferedPaintInit in the object constructor:
void MainWindow::paintDoubleBuffered(HWND hwnd) {
PAINTSTRUCT ps;
HDC hdc{ BeginPaint(hwnd, &ps)};
RECT sz;
GetWindowRect(hwnd, &sz);
BP_PAINTPARAMS paintParams = { 0 };
paintParams.cbSize = sizeof(paintParams);
paintParams.dwFlags = BPPF_ERASE;
paintParams.pBlendFunction = NULL;
paintParams.prcExclude = NULL;
HDC hdcBuffer;
HPAINTBUFFER hBufferedPaint = BeginBufferedPaint(hdc, &sz, BPBF_COMPATIBLEBITMAP, &paintParams, &hdcBuffer);
if (hBufferedPaint && this->bufferedInitResult == Ok) {
// Application specific painting code
paint(hwnd, hdcBuffer);
EndBufferedPaint(hBufferedPaint, TRUE);
}
else{
paint(hwnd, hdc);
}
ReleaseDC(hwnd, hdc);
}
Unfortunately, this did not work and the resultant screen looks like this:
The issue ended up being not in the approach to double buffering, but in the call to Graphics::DrawImage(Image*, Gdiplus::Point) in my paint method. Changing to DrawImage(Image* image, INT x, INT y, INT width, INT height) fixed the scaling issue.
I am making a game by Direct2D and I need some help. In this game, I need to move a bitmap on the window slowly and rotates. However, when I ran the code, the bitmap didn't show on the window, if I wait a moment, it shows. I don't know how to deal with the problem because if it sleeps after it moves, the game suspended animation. And this is the following code:
VOID Rotate()
{
ID2D1BitmapBrush* pBitmapBrush = NULL;
g_pRenderTarget->CreateBitmapBrush(g_pMainRole, &pBitmapBrush);
pBitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_CLAMP);
pBitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_CLAMP);
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(g_hGameWnd, &ps);
g_pRenderTarget->BeginDraw();
DOUBLE dCurrentX = 0;
UINT uCurrentAngle = 0;
RECT rc = { 0 };
GetClientRect(g_hGameWnd, &rc);
while (dCurrentX <= rc.right - 128)
{
pBitmapBrush->SetTransform(
D2D1::Matrix3x2F::Rotation(uCurrentAngle % 360, D2D1::Point2F(64, 64)) *
D2D1::Matrix3x2F::Translation(D2D1::SizeF(dCurrentX, 60))
);
g_pRenderTarget->DrawBitmap(g_pBgBitmap, D2D1::RectF(0, 0,
g_pRenderTarget->GetSize().width, g_pRenderTarget->GetSize().height));
D2D1_RECT_F rc = D2D1::RectF(350, 150, 1200, 700);
g_pRenderTarget->DrawRoundedRectangle(D2D1::RoundedRect(rc, 10, 10), g_pBrush, 10.0F);
if (pBitmapBrush != NULL)
{
g_pRenderTarget->FillRectangle(D2D1::RectF(dCurrentX, 60, 1920, 1080), pBitmapBrush);
}
pBitmapBrush->SetTransform(D2D1::Matrix3x2F::Identity());
dCurrentX += 0.1;
uCurrentAngle++;
// Sleep(10);
// This code makes my game suspend animation.
}
g_pRenderTarget->EndDraw();
EndPaint(g_hGameWnd, &ps);
SafeRelease(pBitmapBrush); // This is my inline function
// It calls pBitmapBrush->Release() if pBitmapBrush is not null.
}
Thanks.
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?
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);
}
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.