Using Direct2D drawing with real-time data from a timer - c++

I'm using Direct2D with MFC and would like to know how to use real-time data to update a render target. For instance, I have the following AFX_WM_DRAW2D handler:
afx_msg LRESULT CTestView::OnDraw2d(WPARAM wParam, LPARAM lParam)
{
CString text;
CHwndRenderTarget* pRenderTarget = (CHwndRenderTarget*)lParam;
ASSERT_VALID(pRenderTarget);
// Clear window background
pRenderTarget->Clear(ColorF(ColorF::Beige));
// Draw text
CRect rect;
GetClientRect(rect);
text.Format(_T("%i"), value);
pRenderTarget->DrawText(text, rect, m_pBlueBrush, m_pTextFormat);
return TRUE;
}
The variable 'value' is updated globally by a timer:
void CTestView::OnTimer(UINT_PTR nIDEvent)
{
CRect rect;
this->GetWindowRect(&rect);
this->InvalidateRect(&rect);
if (value == NULL)
value = 0;
value++;
CView::OnTimer(nIDEvent);
}
Unfortunately I can't seem to figure out how to make the interface be redrawn with the updated variable displayed via Direct2D. What is the best way to do this? I've read that Direct2D is much faster than GDI so I figured I would give it a shot for dealing with constantly updated variables.
Thanks!

For one thing, drawing operations can only be issued between a BeginDraw() and EndDraw() call.

You have to call EnableD2DSupport(); That’s all. No need to create a render target, resize it, recreating it when nesessary, calling BeginDraw, EndDraw, etc. All is done in the MFC framework if D2D support is enabled for a window.

Related

Forcing a redraw of Direct2D

I have a combo-box in MFC C++ application that I use for drawing. Now I am experimenting with Direct2D for drawing. However, I am not able to re-draw content after the initial drawing. Using the code below, my "OnAfxDraw2D" method is being called when the application starts and the Direct2D content appears as intended.
Later, when I want to return my combo box to the initial state, I call the DoD2D() method. If the method is implemented using the first option, it works as intended, but it sometimes produce an assertion error in Visual Studio (Winhand.cpp Line 208). If I use the second option of DoD2D() method - nothing happens (the graphical content is not drawn).
Am I missing something here?
RenderArea::RenderArea() : CButton()
{
BOOL bUseDCRenderTarget = TRUE;
EnableD2DSupport(TRUE, bUseDCRenderTarget);
}
void RenderArea::DoDataExchange(CDataExchange* pDX)
{
CButton::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(RenderArea, CButton)
ON_REGISTERED_MESSAGE(AFX_WM_DRAW2D, &RenderArea::OnAfxDraw2D)
END_MESSAGE_MAP()
void RenderArea::DoD2D()
{
if (!IsD2DSupportEnabled())
return;
// Option 1) Works, but fails an assertion in debug mode
Invalidate();
return;
// Option 2) Does not work.
CDCRenderTarget *pRenderTarget = GetDCRenderTarget();
pRenderTarget->BeginDraw();
DrawD2DResource(pRenderTarget);
pRenderTarget->EndDraw();
}
void RenderArea::DrawD2DResource(CDCRenderTarget* pDCRenderTarget)
{
ASSERT_VALID(pDCRenderTarget);
D2D1_COLOR_F crBack =
CRenderTarget::COLORREF_TO_D2DCOLOR(::GetSysColor(COLOR_WINDOW));
pDCRenderTarget->Clear(crBack);
CD2DSizeF sizeTrans(0.f, 105.f);
pDCRenderTarget->SetTransform(D2D1::Matrix3x2F::Translation(sizeTrans));
CD2DEllipse ellipse(CD2DRectF(10.f, 10.f, 200.f, 100.f));
CD2DSolidColorBrush brush(pDCRenderTarget, ::GetSysColor(COLOR_WINDOWTEXT));
pDCRenderTarget->DrawEllipse(ellipse, &brush);
CD2DPointF pointFrom(10.f, 10.f), pointTo(200.f, 100.f);
pDCRenderTarget->DrawLine(pointFrom, pointTo, &brush);
pDCRenderTarget->SetTransform(D2D1::IdentityMatrix());
return;
}
LRESULT RenderArea::OnAfxDraw2D(WPARAM wParam, LPARAM lParam)
{
CDCRenderTarget* pDCRenderTarget = (CDCRenderTarget*)lParam;
ASSERT_KINDOF(CDCRenderTarget, pDCRenderTarget);
DrawD2DResource(pDCRenderTarget);
HRESULT hr = pDCRenderTarget->EndDraw();
return static_cast<LRESULT>(TRUE);
}

C++: Trying to hook a message box and change its position

I recently started coding in C++ and I am very new to it. (I code in Javascript, PHP, Java and Obj-C more often)
I'm practicing how to hook a message box and change its position. This is what I have in my .cpp file (after reading this SO post).
#include <iostream>
#pragma comment(lib,"User32.lib")
#include <windows.h>
HHOOK hhookCBTProc = 0;
LRESULT CALLBACK pfnCBTMsgBoxHook(int nCode, WPARAM wParam, LPARAM lParam){
if (nCode == HCBT_CREATEWND)
{
CREATESTRUCT *pcs = ((CBT_CREATEWND *)lParam)->lpcs;
if ((pcs->style & WS_DLGFRAME) || (pcs->style & WS_POPUP))
{
HWND hwnd = (HWND)wParam;
SetWindowPos(hwnd, HWND_TOP,130,122, 0, 0,SWP_NOSIZE);
}
}
return (CallNextHookEx(hhookCBTProc, nCode, wParam, lParam));
}
int main(void)
{
hhookCBTProc = SetWindowsHookEx(WH_CBT,pfnCBTMsgBoxHook,
0, GetCurrentThreadId());
int sResult = MessageBox ( NULL, "Hooked!", "oh my", MB_OK );
UnhookWindowsHookEx(hhookCBTProc);
return 0;
}
For some reason the position of the message box isn't changing. Where did it go wrong?
(I know I can create a customized window or dialog. But I am doing it this way because I want to learn how to hook a message box and where I did wrong.)
Firstly you should check in the debugger that your hook is actually being called, if you haven't already.
Secondly, at the time the HCBT_CREATEWND hook event is triggered, the window has only just been created - the system has yet to size and position it. It will do this with the values in the CREATESTRUCT after the hook returns - overriding your SetWindowPos call.
See the docs from MSDN on the lParam value for this particular hook event:
Specifies a long pointer to a CBT_CREATEWND structure containing
initialization parameters for the window. The parameters include the
coordinates and dimensions of the window. By changing these
parameters, a CBTProc hook procedure can set the initial size and
position of the window.
Therefore, the correct way to use this hook to change a window's position is to modify the values in the CREATESTRUCT directly.
Also note that it's quite possible that the dialog manager sizes and positions the window after creation, so if you find that this still isn't working for you, you may need to try watching for the HCBT_MOVESIZE event instead.
From the docs
At the time of the HCBT_CREATEWND notification, the window has been
created, but its final size and position may not have been determined
and its parent window may not have been established.
Maybe try hooking into CBT_ACTIVATE instead.

WTL RedrawWindow parameterrs

I'm new to the WTL C++. I'm really confused about the parameters that go into the RedrawWindows function especially for the flags. I'm just trying to update a window everytime I draw a line, but I don't exactly understand how
LRESULT CDrawView::OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
int xPos= GET_X_LPARAM(lParam);
int yPos = GET_Y_LPARAM(lParam);
end.X = xPos;
end.Y = yPos;
Pen pen(Color(0, 0, 255));
m_GraphicsImage.DrawLine(&pen, start.X, start.Y, end.X, end.Y);
I try to call RedrawWindow here,
RedrawWIndow(NULL,NULL, NULL, RDW_INTERNALPAINT)
So everytime I release the left mouse button the window gets updated. I'm having a really hard time understanding the parameters that go into the Redraw Function. I tried putting them all null minus the last one but Visual studio says that the function doesn't take 4 parameters even though I read the msdn microsoft...
You are not calling the global RedrawWindow.
You're calling the member function CWindow::RedrawWindow, which takes 3 parameters.
BOOL RedrawWindow(
LPCRECT lpRectUpdate = NULL,
HRGN hRgnUpdate = NULL,
UINT flags = RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE
); throw()
Edit:
These three parameters all have default arguments, meaning they don't need to be supplied an RedrawWindow() alone should work.
This is not the way you should use and you are supposed to be using the API. Your mouse button handler should call Invalidate() or InvalidateRect with specific part of the window you are marking as needing an update. Your window will then receive WM_PAINT event at the first OS convenience and your paint handler would paint the line.
RedrawWindow might work out this time, however is likely to be a base of a next problem very soon because you are already on the wrong way.

CWnd with transparent background

I'd like to create a CWnd based class that will introduce a control with transparent background.
There is no big deal for me to create a control and draw its content with transparent background as long as the content is static.
The problem is when I want to create a control with changing content. It's becaue I don't know how to erase content of control with parent's background (which in general case may not be just a solid color).
So the goal I want to achieve is to erase control before painting its conent as the control was never there (parent, and maybe other controls may appear), and than paint control in this place.
Roel answer is fine if you want to create a top-level window. If you need to crate a child window (which must be the case if you are creating a control) you cannot use WS_EX_LAYERED (I think this has changed from Windows 8 on).
The easy trick is to draw parent as the control backgroud. So in the OnEraseBkgnd you can add this code:
BOOL uiBarcodeButton::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(rect);
return afxGlobalData.DrawParentBackground( this, pDC, rect);
}
Not sure if afxGlobalData global variable is just for MFC 2008 Feature Pack. If you are using a previous version of MFCs then you can use the code from DrawParentBackground:
ASSERT_VALID(pDC);
ASSERT_VALID(pWnd);
BOOL bRes = FALSE;
CRgn rgn;
if (rectClip != NULL)
{
rgn.CreateRectRgnIndirect(rectClip);
pDC->SelectClipRgn(&rgn);
}
CWnd* pParent = pWnd->GetParent();
ASSERT_VALID(pParent);
// In Windows XP, we need to call DrawThemeParentBackground function to implement
// transparent controls
if (m_pfDrawThemeBackground != NULL)
{
bRes = (*m_pfDrawThemeBackground)(pWnd->GetSafeHwnd(), pDC->GetSafeHdc(), rectClip) == S_OK;
}
if (!bRes)
{
CPoint pt(0, 0);
pWnd->MapWindowPoints(pParent, &pt, 1);
pt = pDC->OffsetWindowOrg(pt.x, pt.y);
bRes = (BOOL) pParent->SendMessage(WM_ERASEBKGND, (WPARAM)pDC->m_hDC);
pDC->SetWindowOrg(pt.x, pt.y);
}
pDC->SelectClipRgn(NULL);
return bRes;
You use WS_EX_LAYERED and the UpdateLayeredWindow() API to draw your window. See http://msdn.microsoft.com/en-us/library/ms997507.aspx .
I used below code for my custom Static control:
BOOL MyStaticText::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
pDC->SelectObject((HBRUSH)GetStockObject(NULL_BRUSH));
return pDC->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);
}

Strategy for creating a layered window with child windows (controls)

I want to create an irregularily shaped/skinned window (just with rounded, alpha blended corners for now). Upon creating the top-level window i am processing the WM_CREATE message and do the following:
Create a compatible memory DC
Create a DIB section compatible with the window DC
Select DIB into memory DC
Do the drawing of my backdrop
Apply alpha channel and premultiply RGB values
Call UpdateLayeredWindow()
Later on I am planning on rounding of the edges by setting the alpha channel and premultiplying the bits in question to make that happen.
Now, if I create for instance a button in my window, it will not render itself. I know why, and I am trying to come up with an elegant solution to making my custom controls (child windows) here.
My initial approach was to ditch using child windows in the first place and just let the top level window do all the drawing and also input handling, hit testing, and so on. I found this to be way to tedious and instead I want to let Windows handle all this for me.
Now, I know if I create a child window, it of course behaves normally (e.g. reacting to user input), and I want to leverage this. I am planning on creating the child windows (custom controls) normally using CreateWindowEx() so they get a window handle, and recieve window messages without me having to worry about passing them manually.
Somehow I need to get these windows painted, and as I see it, the only possible way to do this is from the routine that paints the whole top level window. I need to invent some kind of logic to get the top level window's paint routine to paint my child windows whenever necessary. As far as I understand the UpdateLayeredWindow() function need to redraw the whole window.
Is it sensible to for instance have the child controls render an image of themselves that are sent to the top level window's paint routine? Like for instance the child window sending a user WM to the top level window passing pointer to its rendered bitmap as a WPARAM and pointer to a structure defining its position and size as a LPARAM.
Any better ideas? Does this make any sense at all?
Thanks,
Eirik
I was trying to do a very similar thing.
From reading this and other searching web. It seams the recommended mechanism for drawing a CWnd (or HWND) and it's children onto your own CDC (or HDC) is to use the printing API.
CWnd has methods Print and PrintClient and which send WM_PRINT correctly. There is also the Win32 methods: PrintWindow.
I had trouble getting this to work at first but I eventually got the right method and parameters. The code that worked for me was:
void Vg2pImageHeaderRibbon::Update() {
// Get dimensions
CRect window_rect;
GetWindowRect(&window_rect);
// Make mem DC + mem bitmap
CDC* screen_dc = GetDC(); // Get DC for the hwnd
CDC dc;
dc.CreateCompatibleDC(screen_dc);
CBitmap dc_buffer;
dc_buffer.CreateCompatibleBitmap(screen_dc, window_rect.Width(), window_rect.Height());
auto hBmpOld = dc.SelectObject(dc_buffer);
// Create a buffer for manipulating the raw bitmap pixels (per-pixel alpha).
// Used by ClearBackgroundAndPrepForPerPixelTransparency and CorrectPerPixelAlpha.
BITMAP raw_bitmap;
dc_buffer.GetBitmap(&raw_bitmap);
int bytes = raw_bitmap.bmWidthBytes * raw_bitmap.bmHeight;
std::unique_ptr<char> bits(new char[bytes]);
// Clears the background black (I want semi-transparent black background).
ClearBackgroundAndPrepForPerPixelTransparency(dc, raw_bitmap, bytes, bits.get(), dc_buffer);
// To get the window and it's children to draw using print command
Print(&dc, PRF_CLIENT | PRF_CHILDREN | PRF_OWNED);
CorrectPerPixelAlpha(dc, raw_bitmap, bytes, bits.get(), dc_buffer);
// Call UpdateLayeredWindow
BLENDFUNCTION blend = {0};
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
CPoint ptSrc;
UpdateLayeredWindow(
screen_dc,
&window_rect.TopLeft(),
&window_rect.Size(),
&dc,
&ptSrc,
0,
&blend,
ULW_ALPHA
);
SelectObject(dc, hBmpOld);
DeleteObject(dc_buffer);
ReleaseDC(screen_dc);
}
This worked for me just as is. But incase you window or children don't support WM_PRINT I looked at how it was implemented for CView class I discovered that this class provides a virtual method called OnDraw(CDC* dc) that is provided with a DC to draw with. WM_PAINT is implemented something like this:
CPaintDC dc(this);
OnDraw(&dc);
And the WM_PAINT is implemented:
CDC* dc = CDC::FromHandle((HDC)wParam);
OnDraw(dc);
So the WM_PAINT and WM_PRINT results an OnDraw(), and the drawing code implemented once.
You can basically add this same logic your own CWnd derived class. This may not be possible using visual studio's class wizards. I had to add the following to message map block:
BEGIN_MESSAGE_MAP(MyButton, CButton)
...other messages
ON_MESSAGE(WM_PRINT, OnPrint)
END_MESSAGE_MAP()
And my handler:
LRESULT MyButton::OnPrint(WPARAM wParam, LPARAM lParam) {
CDC* dc = CDC::FromHandle((HDC)wParam);
OnDraw(dc);
return 0;
}
NOTE: If you add a custom WM_PRINT handler on a class that already supports this automatically then you loose the default implementation. There isn't a CWnd method for OnPrint so you have to use the Default() method to invoke the default handler.
I have't tried the following but I expect it works:
LRESULT MyCWnd::OnPrint(WPARAM wParam, LPARAM lParam) {
CDC* dc = CDC::FromHandle((HDC)wParam);
// Do my own drawing using custom drawing
OnDraw(dc);
// And get the default handler to draw children
return Default();
}
Above I defined some strange methods: ClearBackgroundAndPrepForPerPixelTransparency and CorrectPerPixelAlpha. These allow me to set the background of my dialog be semi-transparent when having the child controls be full opaque (this is my per-pixel transparency).
// This method is not very efficient but the CBitmap class doens't
// give me a choice I have to copy all the pixel data out, process it and set it back again.
// For performance I recommend using another bitmap class
//
// This method makes every pixel have an opacity of 255 (fully opaque).
void Vg2pImageHeaderRibbon::ClearBackgroundAndPrepForPerPixelTransparency(
CDC& dc, const BITMAP& raw_bitmap, int bytes, char* bits, CBitmap& dc_buffer
) {
CRect rect;
GetClientRect(&rect);
dc.FillSolidRect(0, 0, rect.Width(), rect.Height(), RGB(0,0,0));
dc_buffer.GetBitmapBits(bytes, bits);
UINT* pixels = reinterpret_cast<UINT*>(bits);
for (int c = 0; c < raw_bitmap.bmWidth * raw_bitmap.bmHeight; c++ ){
pixels[c] |= 0xff000000;
}
dc_buffer.SetBitmapBits(bytes, bits);
}
// This method is not very efficient but the CBitmap class doens't
// give me a choice I have to copy all the pixel data out, process it and set it back again.
// For performance I recommend using another bitmap class
//
// This method modifies the opacity value because we know GDI drawing always sets
// the opacity to 0 we find all pixels that have been drawn on since we called
// For performance I recommend using another bitmap class such as the IcfMfcRasterImage
// ClearBackgroundAndPrepForPerPixelTransparency. Anything that has been drawn on will get an
// opacity of 255 and all untouched pixels will get an opacity of 100.
void Vg2pImageHeaderRibbon::CorrectPerPixelAlpha(
CDC& dc, const BITMAP& raw_bitmap, int bytes, char* bits, CBitmap& dc_buffer
) {
const unsigned char AlphaForBackground = 100; // (0 - 255)
const int AlphaForBackgroundWord = AlphaForBackground << 24;
dc_buffer.GetBitmapBits(bytes, bits);
UINT* pixels = reinterpret_cast<UINT*>(bits);
for (int c = 0; c < raw_bitmap.bmWidth * raw_bitmap.bmHeight; c++ ){
if ((pixels[c] & 0xff000000) == 0) {
pixels[c] |= 0xff000000;
} else {
pixels[c] = (pixels[c] & 0x00ffffff) | AlphaForBackgroundWord;
}
}
dc_buffer.SetBitmapBits(bytes, bits);
}
Here is a screen shot of my test application. When the user hovers the mouse over the "more buttons" button the dialog box is created with a semi-transparent background. The buttons "B1" to "B16" are child controls derived from CButton and are being drawn using the Print() call show above. You can see the semi-transparent background at the right hand edge of the view and between the buttons.
I think I'm going to go for this solution:
Top level window
The top level window maintains two bitmaps. One which is the displayed window and one without any of the child controls rendered. The latter one will only need redrawing when the window changes size. The window will have a message handler that renders a child control on the displayed bitmap. The message handler will expect a pointer to either a DIB containing the child control, or to the actual bits (not sure which is best at the moment), as the WPARAM, and a pointer to a structure containing the rectangle that the child shall be drawn into as the LPARAM. A call to BitBlt() will be made to clear out the underlying surface (this is where the other bitmap comes in) prior to an AlphaBlend() call for rendering the child control bitmap onto the displayed bitmap surface.
The parent window will call the EnumChildWindows whenever it is resized or for some reason need to redraw its children. There could of course be some kind of invalidation regime enforced here to reduce unnecessary rendering of the child controls. Not sure if the speed increase is worth the effort, though.
Child windows
Upon creation of the child control instance, an internal bitmap compatible with that of the top-level window is created. The child renders itself into this internal bitmap and whenever it needs redrawing it notifies its parent window via the SendMessage() function, passing a pointer to its bitmap as the WPARAM, and a RECT as the LPARAM defining its position and dimensions. If the parent needs redrawing, it issues a message down to all its child windows requesting their bitmap. Childs will then respond with the same message that they normally would send when they decide they need redrawing themselves.
Eirik
To quote the MSDN page for WM_PAINT:
The WM_PAINT message is generated by the system and should not be sent by an application. To force a window to draw into a specific device context, use the WM_PRINT or WM_PRINTCLIENT message. Note that this requires the target window to support the WM_PRINTCLIENT message. Most common controls support the WM_PRINTCLIENT message.
So it looks like you can iterate through all the child windows with EnumChildWindows and send them WM_PRINT with your memory DC between steps 5 and 6.
It will look something like this:
static BOOL CALLBACK MyPaintCallback(HWND hChild,LPARAM lParam) {
SendMessage(hChild,WM_PRINT,(WPARAM)lParam,(LPARAM)(PRF_CHECKVISIBLE|PRF_CHILDREN|PRF_CLIENT));
return TRUE;
}
void MyPaintMethod(HWND hWnd) {
//steps 1-5
EnumChildWindows(hWnd,MyPaintCallback,MyMemoryDC);
//step 6
}