I have an application that has drawn a grid using CDC (it has text, rectangle, and bitmaps). I want to take a screenshot of that finished grid when it is saved and use that screenshot as a "preview" for the file.
How can I take a screenshot of my application and save it?
Thank you,
Answer is here
void CScreenShotDlg::OnPaint()
{
// device context for painting
CPaintDC dc(this);
// Get the window handle of calculator application.
HWND hWnd = ::FindWindow( 0, _T( "Calculator" ));
// Take screenshot.
PrintWindow( hWnd,
dc.GetSafeHdc(),
0 );
}
Ultimately I ended up doing it this way because I wanted to capture even the hidden parts of the window (since the content extends beyond the screen and requires scrolling):
CDC* WindowToCaptureDC = AfxGetMainWnd()->GetWindowDC();
CDC CaptureDC;
CDC MemDC;
MemDC.CreateCompatibleDC(WindowToCaptureDC);
CaptureDC.CreateCompatibleDC(WindowToCaptureDC);
CBitmap CaptureBmp;
CBitmap ResizeBmp;
int pWidth = grid.tableWidth + grid.marginLeft*2;
int pHeight = grid.tableHeight + grid.marginBottom;
CaptureBmp.CreateCompatibleBitmap( WindowToCaptureDC, pWidth, pHeight);
CaptureDC.SelectObject(&CaptureBmp);
CBrush brush(RGB(255, 255, 255));
CaptureDC.SelectObject(&brush);
CaptureDC.Rectangle(0, 0, pWidth, pHeight);
///Drew items into CaptureDC like I did for OnDraw HERE///
double width = //desired width;
double height = //desired width;
//maintain aspect ratio
if(pWidth!=width || pHeight!=height)
{
double w = width/pWidth;
double h = height/pHeight;
if(w < h)
height = height*w;
else
width = width*h;
}
ResizeBmp.CreateCompatibleBitmap(WindowToCaptureDC, width, height);
MemDC.SelectObject(&ResizeBmp);
MemDC.StretchBlt(0, 0, width, height, &CaptureDC, 0, 0, pWidth, pHeight, SRCCOPY);
CImage TempImageObj;
TempImageObj.Attach((HBITMAP)ResizeBmp.Detach());
CString filePath = _T("LOCATION\\image.bmp");
TempImageObj.Save(filePath);
Related
Here's my objective: a) capture the device context for the entire screen; b) capture a background window device context (including the title bar); c) draw the desktop content on a buffer device context; d) draw the captured window content on the same buffer device context; e) display the drawn device context on a top window, simulating that the desired window is on top.
The issue is that the title bar isn't drawn on the new window, even though the GetWindowRect function seems to be working fine. That's because some other stuff is drawn instead of the title bar (usually the content of windows on the background when the target window is created).
This is my code right now:
case __SourceDC:
{
HDC desktop_dc = GetDC(NULL);
HWND target_hwnd = FindWindowA(NULL, "Everything");
HDC target_dc = GetWindowDC(target_hwnd);
RECT rc{};
GetWindowRect(target_hwnd, &rc);
RECT clrc{};
GetClientRect(target_hwnd, &clrc);
int width = rc.right - rc.left;
int height = rc.bottom - rc.top;
int x_dst = ((double)rc.left / SCREEN_WIDTH) * m_area.width;
int y_dst = ((double)rc.top / SCREEN_HEIGHT) * m_area.height;
int w_dst = ((double)width / SCREEN_WIDTH) * m_area.width;
int h_dst = ((double)height / SCREEN_HEIGHT) * m_area.height;
SetStretchBltMode(hdc, HALFTONE);
StretchBlt(hdc, 0, 0, m_area.width, m_area.height, desktop_dc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SRCCOPY);
StretchBlt(hdc, x_dst, y_dst, w_dst, h_dst, target_dc, 0, 0, width, height, SRCCOPY);
ReleaseDC(target_hwnd, target_dc);
ReleaseDC(NULL, desktop_dc);
}
break;
All the desktop content and the window content is correctly drawn, except for the title bar (and apparently the borders as well).
As you can see from the image, the myWnd window shows the desktop content, but the title bar is not displayed for the target window. Also, nothing changes when the target window is the foreground window.
What am I missing here? Is this behavior expected? Is StretchBlt working as intended by not capturing the content of the title bar?
Thanks in advance.
I am working on an application which is using emf for buffer drawing. I am trying to save this emf to a bitmap image file using BitBlt. But no drawings are saved to bitmap. I know I can use PlayEnhMetaFile() but I have to use BitBlt or GDI/GDI+ calls for this. As there will be some other drawing calls to emf after saving it to bitmap.
Sample code.
void CTestGUIApplicationView::OnDraw(CDC* pDC)
{
CRect oRect(0, 0, 640, 434);
HDC hdc = pDC->GetSafeHdc();
//Meta file creation
int iWidthMM = GetDeviceCaps(hdc, HORZSIZE);
int iHeightMM = GetDeviceCaps(hdc, VERTSIZE);
int iWidthPels = GetDeviceCaps(hdc, HORZRES);
int iHeightPels = GetDeviceCaps(hdc, VERTRES);
CRect rect;
rect.left = (oRect.left * iWidthMM * 100) / iWidthPels;
rect.top = (oRect.top * iHeightMM * 100) / iHeightPels;
rect.right = (oRect.right * iWidthMM * 100) / iWidthPels;
rect.bottom = (oRect.bottom * iHeightMM * 100) / iHeightPels;
HDC hMetaDC = CreateEnhMetaFile(hdc, NULL, &rect, NULL);
CDC *pMetaDC = CDC::FromHandle(hMetaDC);
//Drawing on meta file DC.
RECT drawingRect;
CPen penBlue, pen2, pen3, pen4;
penBlue.CreatePen(PS_SOLID | PS_COSMETIC, 1, RGB(0, 0, 255));
pen2.CreatePen(PS_SOLID | PS_COSMETIC, 1, RGB(0, 255, 255));
pen3.CreatePen(PS_SOLID | PS_COSMETIC, 1, RGB(255, 0, 255));
pen4.CreatePen(PS_SOLID | PS_COSMETIC, 1, RGB(128, 0, 56));
auto pOldPen = pMetaDC->SelectObject(&penBlue);
pMetaDC->Arc(oRect,
CPoint(oRect.right, oRect.CenterPoint().y),
CPoint(oRect.CenterPoint().x, oRect.right));
pMetaDC->SelectObject(&pen2);
pMetaDC->Ellipse(oRect.left + 50, oRect.top + 25, oRect.Width(), oRect.Height());
//copy meta file to DC.
CopyToBitMap(_T("StateImage_EMFDC.bmp"), pMetaDC, oRect);
//some other drawing calls on meta file DC.
pMetaDC->SelectObject(&pen3);
pMetaDC->Ellipse(oRect.left + oRect.Width() / 2, oRect.top + oRect.Height() / 2, oRect.Width(), oRect.Height());
pMetaDC->SelectObject(pOldPen); pOldPen = NULL;
//Copy meta file to window DC.
HENHMETAFILE hMeta = CloseEnhMetaFile(pMetaDC->GetSafeHdc());
if (hMeta != NULL)
{
//save meta file to disk to view its contents.
HENHMETAFILE hMeta2 = CopyEnhMetaFile(hMeta, _T("test.emf"));
DeleteEnhMetaFile(hMeta2);
PlayEnhMetaFile(hdc, hMeta, oRect);
DeleteEnhMetaFile(hMeta);
}
//some other drawing on Window DC.
pOldPen = pDC->SelectObject(&pen4);
pDC->Ellipse(oRect.left + 25, oRect.top + 50, oRect.Width() - 30, oRect.Height() - 60);
pDC->SelectObject(pOldPen);
CopyToBitMap(_T("StateImage_WindowDC.bmp"), pDC, oRect);
}
//Routine to copy data from DC to bitmap.
void CTestGUIApplicationView::CopyToBitMap(CString filePath, CDC* pDC, CRect & windRect)
{
//creating bitmap
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(pDC, windRect.Width(), windRect.Height());
CDC memDC;
memDC.CreateCompatibleDC(pDC);
CBitmap *oldBMP = memDC.SelectObject(&bitmap);
memDC.FillSolidRect(windRect, RGB(0, 255, 0));
BOOL result = BitBlt(memDC.GetSafeHdc(), 0, 0, windRect.Width() , windRect.Height(), pDC->GetSafeHdc(), 0, 0, SRCCOPY);
memDC.SelectObject(oldBMP);
//Saving bitmap to disk.
CImage image;
image.Attach(bitmap);
image.Save(filePath, Gdiplus::ImageFormatBMP);
image.Detach();
image.Destroy();
bitmap.DeleteObject();
}
"StateImage_WindowDC.bmp" have complete drawing. while "StateImage_EMFDC.bmp" is complete green image which should have drawing on it.
A metafile DC isn't like a regular DC, it doesn't draw on a bitmap; it's only used for recording drawing instructions. From Microsoft's "Enhanced Metafile Creation" page:
When CreateEnhMetaFile succeeds, it returns a handle that identifies a special metafile device context. A metafile device context is unique in that it is associated with a file rather than with an output device. When the system processes a GDI function that received a handle to a metafile device context, it converts the GDI function into an enhanced-metafile record and appends the record to the end of the enhanced metafile.
You say that you can't call PlayEnhMetaFile because you have additional drawing commands you want to append to the metafile after you capture it. In that case I can see two options:
Draw to a regular DC in parallel with the metafile DC.
Use GetEnhMetaFileBits and SetEnhMetaFileBits to make a copy of the metafile that you can use with PlayEnhMetaFile.
Well simply what I need to do is to create a grid of rectangle and fill any selected case of that grid at the user click. thus I save the parameters (color, position..). The problem is when I try to fill the case at the click event I can't any changes maybe because the device context is changed even if I use GetDC() method. So is there any way to save the current created on OnDraw() method for exmaple and use it somewhere in another function, I tried to use SaveDC() and RestoreDC() but in vain.
Here's some of my code:
void CXThiefManView::OnDraw(CDC* pDC)
{
CXThiefManDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CRect rcClient;
GetClientRect(&rcClient);
// Get the background color of the board
COLORREF clr = pDoc->GetBoardSpace(-1, -1);
// Draw the background first
pDC->FillSolidRect(&rcClient, clr);
// Create the brush for drawing
CBrush br;
br.CreateStockObject(HOLLOW_BRUSH);
CBrush* pbrOld = pDC->SelectObject(&br);
// Draw the squares
for (int row = 0; row < pDoc->GetRows(); row++)
{
for (int col = 0; col < pDoc->GetColumns(); col++)
{
// Get the color for this board space
clr = pDoc->GetBoardSpace(row, col);
// Calculate the size and position of this space
CRect rcBlock;
rcBlock.top = row * pDoc->GetHeight();
rcBlock.left = col * pDoc->GetWidth();
rcBlock.right = rcBlock.left + pDoc->GetWidth();
rcBlock.bottom = rcBlock.top + pDoc->GetHeight();
// Fill in the block with the correct color
pDC->FillSolidRect(&rcBlock, clr);
// Draw the block outline
pDC->Rectangle(&rcBlock);
}
}
saveState = pDC->SaveDC();
DrawItem(pDC, pDoc->GetThiefRow(), pDoc->GetThiefCol(), pDoc->GetThiefColor());
}
void CXThiefManView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CView::OnLButtonDown(nFlags, point);
CXThiefManDoc* pDoc = GetDocument();
// the draw item here to fill the case
DrawItem(GetDC(), 5, 5, RGB(0, 0, 0));
}
Underneath all those MFC function, the WINAPI functions of the same name are being called. In WINAPI, GetDC returns a new device context. SaveDC & RestoreDC allow you to save and restore the state (brush, font etc...) of a specific DC handle, but that doesn't cross to other handles.
There's nothing MFC can do about it, nor can you. You have to reconfigure the new DC handle for your needs.
Handle from GetDC has to be released by ReleaseDC. Please see GetDC
Or use CClientDC dc(this) which has automatic cleanup, it's usage is similar, for example Draw(&dc,...);.
As to your main question, you can save the drawing on a bitmap. The bitmap can be saved.
define:
CBitmap m_bitmap;
CDC m_memdc;
initialize the bitmap and dc:
CClientDC dc(this);
m_bitmap.CreateCompatibleBitmap(&dc, 2000, 1200);
//this creates a bitmap as big as the screen...
m_memdc.CreateCompatibleDC(&dc);
m_memdc.SelectObject(m_bitmap);
m_memdc.FillSolidRect(0, 0, 2000, 1200, RGB(255, 255, 255));
//draw on m_memdc, then draw memdc on to dc
Usage:
void CMyWindow::OnPaint()
{
CWnd::OnPaint();
CClientDC dc(this);
CRect rc;
GetClientRect(&rc);
dc.BitBlt(0, 0, rc.Width(), rc.Height(), &m_memdc, 0, 0, SRCCOPY);
}
void CMyWindow::OnMouseMove(UINT k, CPoint p)
{
CClientDC dc(this);
CRect rc;
GetClientRect(&rc);
if (k & MK_LBUTTON)
{
//draw on m_memdc, (instead of drawing on dc)
m_memdc.SetPixel(p.x, p.y, RGB(0,0,0));
m_memdc.SetPixel(p.x, p.y+1, RGB(0,0,255));
//when finished drawing, draw m_memdc on to dc
dc.BitBlt(0, 0, rc.Width(), rc.Height(), &m_memdc, 0, 0, SRCCOPY);
}
}
I import a picture(.bmp or .jpeg) as the background of a client view in MFC.
When I click Open, function CDrawToolView::OnFileOpen() open a window to choose a picture, then I use ShowBitmap(CDC* pDC,CString strPicPath) and ShowPic(CDC* pDC,CString strPicPath) to load the picture as backgroud and adjust the size of client view to fit the picture.
I want to set the picture translucent, so the background looks softer. Could some one help me or give some suggestion, thanks.
Here is my code:
void CDrawToolView::ShowBitmap(CDC* pDC,CString strPicPath)
{
HBITMAP hBitmap=(HBITMAP)LoadImage(NULL,strPicPath,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE);
m_bitmap.Detach();
m_bitmap.Attach(hBitmap);
CRect rect;
GetClientRect(&rect);
CDC dcImage;
if (!dcImage.CreateCompatibleDC(pDC))
{
return;
}
BITMAP bm;
m_bitmap.GetBitmap(&bm);
dcImage.SelectObject(&m_bitmap);
pDC->StretchBlt(0,0,rect.right,rect.bottom,&dcImage,0,0,bm.bmWidth,bm.bmHeight,SRCCOPY);
}
void CDrawToolView::ShowPic(CDC* pDC,CString strPicPath)
{
if(!m_MyImage.IsNull())
m_MyImage.Destroy();
HRESULT hResult=m_MyImage.Load(strPicPath);
int iWidth=m_MyImage.GetWidth();
int iHeight=m_MyImage.GetHeight();
m_MyImage.Draw(pDC->m_hDC,0,0,iWidth,iHeight);
CRect client(0, 0, iWidth, iHeight);
client.bottom=client.bottom+::GetSystemMetrics(SM_CYMENU)+::GetSystemMetrics(SM_CYEDGE)*2;
client.right=client.right+::GetSystemMetrics(SM_CXEDGE)*2;
CFrameWnd* pFrame = GetParentFrame();
pFrame->CalcWindowRect(&client);
int width = client.Width();
int height = client.Height();
int y = (::GetSystemMetrics(SM_CYSCREEN) - height) / 2 + 100;
int x = (::GetSystemMetrics(SM_CXSCREEN) - width) / 2;
pFrame->SetWindowPos( NULL, x, y, width, height, SWP_NOACTIVATE | SWP_NOZORDER );
}
Use AlphaBlend instead of StretchBlt
CDC::AlphaBlend
Set SourceConstantAlpha in your BLENDFUNCTION struct to something like 128 (halfway between transparent and opaque), then adjust until it looks good.
AlphaFormat should be zero unless your usa a 32-bit bitmap with an alpha channel.
I've made a screensaver that simply scrolls user-defined text from right to left, automatically jumping back to the right if it exceeds the left boundary.
It works with multiple monitors flawlessly, barring one exception: if the 'Main Display' is on the right (i.e. Monitor #2 is primary), then I do not get the scrolling text, however the monitor IS blacked out by the code. If the main display is #1, there's no problem.
I've been poring over the code for hours and cannot identify at what stage the issue arises; I can confirm the text is in the right position (I inserted logging code that verifies its current position), but it's as if one of the API calls simply erases it. I've read the documentation for them and all looks ok.
I create a custom DC in WM_CREATE via:
if (( hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL)) == NULL )
To prevent flicker, I create compatible objects to update:
void
TickerScreensaver::Paint_Prep(HDC hDC)
{
_devcon_mem = CreateCompatibleDC(hDC);
_devcon_orig = hDC;
_bmp_mem = CreateCompatibleBitmap(hDC, _width, _height);
}
and when painting in WM_PAINT (after BeginPaint, etc.), do a bit-block transfer to the actual device context:
void
TickerScreensaver::Paint(HDC hDC, RECT rect)
{
_bmp_orig = (HBITMAP)SelectObject(_devcon_mem, _bmp_mem);
FillRect(_devcon_mem, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));
if ( _gdiplus_token != NULL )
{
Graphics graphics(_devcon_mem);
SolidBrush brush(cfg.display.font_colour);
FontFamily font_family(cfg.display.font_family.c_str());
Font font(&font_family, cfg.display.font_size, FontStyleRegular, UnitPixel);
PointF point_f((f32)cfg.display.text_pos.x, (f32)cfg.display.text_pos.y);
RectF layout_rect(0, 0, 0, 0);
RectF bound_rect;
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.MeasureString(cfg.display.text.c_str(), cfg.display.text.length(), &font, layout_rect, &bound_rect);
cfg.display.offset.x = (DWORD)(0 - bound_rect.Width);
cfg.display.offset.y = (DWORD)(bound_rect.Height / 2);
graphics.DrawString(cfg.display.text.c_str(), cfg.display.text.length(), &font, point_f, &brush);
}
BitBlt(hDC, 0, 0, _width, _height, _devcon_mem, 0, 0, SRCCOPY);
SelectObject(_devcon_mem, _bmp_orig);
}
I calculate the dimensions like so:
void
TickerScreensaver::GetFullscreenRect(HDC hDC, RECT *rect)
{
RECT s = { 0, 0, 0, 0 };
if ( EnumDisplayMonitors(hDC, NULL, EnumMonitorCallback, (LPARAM)&s) )
{
CopyRect(rect, &s);
s.left < 0 ?
_width = s.right + (0 + -s.left) :
_width = s.right;
s.top < 0 ?
_height = s.bottom + (0 + -s.top) :
_height = s.bottom;
}
}
Please note that the calculated width, height, etc., are all 100% accurate; it is purely the drawing code that doesn't appear to be working on the main display, only when it is on the right (which sets the origin to {0,0}, monitor #1 then being negative values). It is also reproduceable on a tri-display, with the main being in the center.
Well, turns out it is nice and simple - in Paint(), we should use a rect using the real width and height, not the one retrieved containing the negative values (the one actually retrieved from the API functions):
RECT r = { 0, 0, _width, _height };
_bmp_orig = (HBITMAP)SelectObject(_devcon_mem, _bmp_mem);
FillRect(_devcon_mem, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));