Programming Direct2D – Part 2 – Basic Geometry

 

Few weeks ago, we’ve covered the basics of Direct2D API and usage. In this installment, let’s cover about basic primitives. The basic primitives are the mostly used shapes like lines, rectangle, rounded rectangle, circle etc. Direct2D can support more complex primitives ( like path geometries) but let’s get introduced to basic primitives.

ID2D1Geometry class act as the base (interface) class for all geometry classes in Direct2D. The base class provides a minimum set interfaces to handle the data management and drawing operations. The derived classes like ID2D1RectangleGeometry, ID2D1RoundedRectangleGeometry and ID2D1EllipseGeometry etc. are provide it the concrete implementation for each primitives. This is absolutely object oriented concept which help us to manage the code flexible and easy. The usage of interface classes are easy. To create the absolute parameters, several typesafe helper functions are available for creating rectangle, point values etc. (Same as RECT and POINT structure in Windows)

image

Here I demonstrate a sample program which can draw Rectangle, Rounded Rectangle and Ellipse n number of times in a multi document application.
The basic architecture remains same.

  • The view class uses the Direct2DHandler class for Drawing using Direct2D.
  • The user interaction is handled in the view class and corresponding interfaces are called on user action or on window operations(like paint resize etc).
  • Since this is MDI application each view/tab contains the object of Direct2DHandler object.

image

Direct2DHandler class

To support any number of primitives, the class contains a vector of IDirect2DGeometry pointers. Since this is an abstract class pointer, it can hold the any primitive classes derived from it. Upon calling the draw function, the scene is cleared and each primitives will draw again in a loop.
UI Operation
This is a very basic application which allow us to draw something in conventional way. The user can mark rectangle area using mouse and this will be passed to the interface for creating each primitive. The interfaces provided in the Direct2DHandler class will calculate the parameters from the given rectangle and create the geometry. The geometry will be inserted in the vector given as member class. This application doesn’t support to delete the drawn primitives.

Creating Rectangle Geometry

void Direct2DHandler::CreateRectangle( LPCRECT pRect, bool bFill )
{
    D2D1_RECT_F rectangle = D2D1::Rect( pRect->left, pRect->top, pRect->right, pRect->bottom );

    ID2D1RectangleGeometry* pRectangle;
    m_pDirect2dFactory->CreateRectangleGeometry( rectangle, &pRectangle );
    m_Geometries.push_back( pRectangle );
}

Creating Ellipse Geometry

void Direct2DHandler::CreateEllipse( LPCRECT pRectBoundingBox )
{
    int halfX = ( pRectBoundingBox->right - pRectBoundingBox->left ) /2;
    int halfY = ( pRectBoundingBox->bottom - pRectBoundingBox->top ) /2;
    D2D1_ELLIPSE ellipse = D2D1::Ellipse( D2D1::Point2( pRectBoundingBox->left + halfX, pRectBoundingBox->top + halfY), halfX, halfY );

    ID2D1EllipseGeometry* pEllipse;
    m_pDirect2dFactory->CreateEllipseGeometry( ellipse, &pEllipse );
    m_Geometries.push_back( pEllipse );
}

Create Rounded Rectangle Geometry

void Direct2DHandler::CreateRoundedRectangle(LPCRECT pRect, int radiusx, int radiusY, bool bFill )
{
    D2D1_ROUNDED_RECT rectangle = D2D1::RoundedRect( D2D1::Rect(pRect->left, pRect->top, pRect->right, pRect->bottom), radiusx, radiusY);
    ID2D1RoundedRectangleGeometry* pRRectangle;
    m_pDirect2dFactory->CreateRoundedRectangleGeometry( rectangle, &pRRectangle );
    m_Geometries.push_back( pRRectangle );
}

Primitives container

Primitive container is declared as member of class

std::vector<ID2D1Geometry*> m_Geometries;

Drawing

HRESULT Direct2DHandler::OnRender()
{
    HRESULT hr = S_OK;

    hr = CreateDeviceResources();

    if (SUCCEEDED(hr))
    {
        m_pRenderTarget->BeginDraw();
        m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
        m_pRenderTarget->Clear( D2D1::ColorF(D2D1::ColorF::Black, 1.0f));
        // Iterate and draw all primitives
        for( std::vector<ID2D1Geometry*>::iterator it = m_Geometries.begin();
            it != m_Geometries.end(); ++it )
        {
            m_pRenderTarget->DrawGeometry( *it, m_pLightSlateGrayBrush);
        }

        hr = m_pRenderTarget->EndDraw();

    }
    if (hr == D2DERR_RECREATE_TARGET)
    {
        hr = S_OK;
        DiscardDeviceResources();
    }
    return hr;
}

Cleanup

Release all the allocated primitives during exit( destructor).

Direct2DHandler::~Direct2DHandler(void)
{
    for( std::vector<ID2D1Geometry*>::iterator it = m_Geometries.begin();
            it != m_Geometries.end(); ++it )
    {
            (*it)->Release();

    }

    m_Geometries.clear();

    SafeRelease(&m_pDirect2dFactory);
    SafeRelease(&m_pRenderTarget);
    SafeRelease(&m_pLightSlateGrayBrush);
    CoUninitialize();
}

View code

Initialize Direct2DHandler Object

int CDirect2DGeometrySampleView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CView::OnCreate(lpCreateStruct) == -1)
		return -1;

	m_pRender = new Direct2DHandler( m_hWnd );
	m_pRender->Initialize();

	return 0;
}

Delegate the painting operation to Direct2DHandler class

// CDirect2DGeometrySampleView drawing
void CDirect2DGeometrySampleView::OnDraw(CDC* /*pDC*/)
{
    if( m_pRender )
        m_pRender->OnRender();
}

Call Resize the render scene on resizing the Window

void CDirect2DGeometrySampleView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);

    if( m_pRender && cx && cy )
        m_pRender->OnResize( cx, cy );
}

Handle the menu commands to select the shape. The enumerations are locally defined.

void CDirect2DGeometrySampleView::OnShapeRrect()
{
	m_eShape = SHAPE_RRECT;
}

void CDirect2DGeometrySampleView::OnShapeRectangle()
{
	m_eShape = SHAPE_RECT;
}

void CDirect2DGeometrySampleView::OnShapeEllipse()
{
	m_eShape = SHAPE_ELLIPSE;
}

Call the handler interface to create geometry on mouse operation

void CDirect2DGeometrySampleView::OnLButtonUp(UINT nFlags, CPoint point)
{
	m_ptEnd = point;

	if( SHAPE_RECT == m_eShape )
		m_pRender->CreateRectangle( CRect( m_ptBeg, point ), true );
	else if( SHAPE_RRECT == m_eShape )
		m_pRender->CreateRoundedRectangle( CRect( m_ptBeg, point),10,10, true );
	else if( SHAPE_ELLIPSE == m_eShape )
		m_pRender->CreateEllipse( CRect( m_ptBeg, point ));
	else
		return;

	RedrawWindow();
}

The code is self explaining to understand the operations. However please find the the full project source code here (GitHub)

 

How to restrict Window Movement?

 

This is beginner level post. How we can make a Window immovable?

Roughly we’ve two methods to do this.

Method #1. Handle the NCHITTEST message and ignore while user click on the caption area (Titlebar). This method doesn’t work well in Windows 7/Vista if Aero is enabled. This method works with any type of Window having titlebar. The disadvantage of this method is , the user will still able to move using keyboard, if system menu is available.

 

LRESULT CMoveWindowSampleDlg::OnNcHitTest(CPoint point)
{
    UINT nHitTest = CDialogEx::OnNcHitTest(point);

    if( HTCAPTION == nHitTest )
        nHitTest = HTNOWHERE;

    return nHitTest;
}

Method #2 – Remove the Move command from System Menu (Works with windows having system menu stle (WS_SYSMENU) ). The the Move command in the system menu will be removed in this case. Windows will internally disable the movement of this kind of Windows.

BOOL CMoveWindowSampleDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    CMenu* pSysMenu = GetSystemMenu(FALSE);

    if (pSysMenu != NULL)
    {
        pSysMenu->RemoveMenu( SC_MOVE, MF_BYCOMMAND );
       }
...

       return TRUE;
}

PS: Using method 2, this is the same method we use to disable the close button of a Window. For disabling a menu/titlebar button. Call EnableMenu function with relevant parameters.

 

Windows 7: Task Dialog Part 2 – A more detailed task dialog

 

In the last installment, we’ve seen using the basic version of task dialog. But usually when we see the task dialogs in Windows Vista or 7, it’s more detailed and can have flashy icons etc. Let’s see how to take more control over the task dialogs.

TaskDialogIndirect function can be used to have more options with task dialogs. TASKDIALOGCONFIG structure is used along with TaskDialogIndirect API.

clip_image001

As you’re seeing above the task dialog contains different type of controls, icons and capable of displaying more information to the user. It can have lengthy big buttons, radio buttons, checkbox, footer area, progressbar, custom icon, displaying predefined buttons like OK, Cancel, Yes, No etc. even we can have control over the buttons in the titlebar( minimize, maximize button etc)

The following code describes creating a task dialog with more flexible options. User can specify the callback functions which can be used to control the behavior if the controls and contents in the task bar. Filling the TASKDIALOGCONFIG structure is simple and straight forward as we’re seeing the code. The detailed option can be obtained from MSDN page.


HRESULT CALLBACK CTaskDialogSampleDlg::TaskDialogCallbackProc(
  __in  HWND hwnd,
  __in  UINT uNotification,
  __in  WPARAM wParam,
  __in  LPARAM lParam,
  __in  LONG_PTR dwRefData
)
{
	if( TDN_CREATED == uNotification )
	{
		::SendMessage( hwnd, TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE, IDOK, TRUE );
		::SendMessage( hwnd, TDM_SET_PROGRESS_BAR_RANGE, 0, 100 );
	}
	else if( TDN_HYPERLINK_CLICKED == uNotification )
	{
		ShellExecute( 0, L"open", (LPCTSTR) lParam, 0, 0, SW_SHOW );
	}
	else if( TDN_TIMER == uNotification )
	{
		static int i = 0;
		::SendMessage( hwnd, TDM_SET_PROGRESS_BAR_POS, i++,0 );
	}

	return 0;
}

void CTaskDialogSampleDlg::OnBnClickedButton1()
{
	int nButtonPressed                  = 0;
	TASKDIALOGCONFIG config             = {0};
	const TASKDIALOG_BUTTON buttons[]   = {
		{ IDOK, L"Elevate Privilege" },
		{ IDCANCEL, L"Run with user privilege" }
	};

	const TASKDIALOG_BUTTON radiobuttons[] = {
		{ IDCANCEL, L"Test Radio" }};
	config.cbSize                       = sizeof(config);
	config.hInstance                    = AfxGetApp()->m_hInstance;
	config.dwCommonButtons              = TDCBF_OK_BUTTON | TDCBF_CANCEL_BUTTON;
	config.pszMainIcon                  = TD_SHIELD_ICON;
	config.pszMainInstruction           = L"Main Instruction";
	config.pszContent                   = L"This is the content.";
	config.pszVerificationText          = L"Conifirm license agreement";
	config.pButtons                     = buttons;
	config.cButtons                     = ARRAYSIZE(buttons);
	config.pRadioButtons				= radiobuttons;
	config.cRadioButtons				= ARRAYSIZE( radiobuttons );
	config.dwFlags						= TDF_SHOW_PROGRESS_BAR |
		TDF_EXPAND_FOOTER_AREA | TDF_ENABLE_HYPERLINKS | TDF_CAN_BE_MINIMIZED
		| TDF_USE_COMMAND_LINKS | TDF_CALLBACK_TIMER;
	config.pszExpandedInformation		= _T( "<a href=\"http://codereflect.com/\" >Codereflect.com</a>" );
	config.pfCallback = TaskDialogCallbackProc;
	BOOL bVerification = FALSE;
	TaskDialogIndirect(&config, &nButtonPressed, NULL, &bVerification);

	switch (nButtonPressed)
	{
	case IDOK:
		break; // the user pressed button 0 (change password).
	case IDCANCEL:
		break; // user canceled the dialog
	default:
		break; // should never happen
	}
}

The callback function is also easy to manage. There are predefined set of events for each type of control and we can simply make use of these controls by sending various messages to update its state and values. One of the best example is updating the progressbar during the lifetime of messagebox. Once the timer is enabled, the callback function will be automatically fired on discrete time interval. The notify messages are specified in detail in MSDN Documentation. Please check it.

To compile this source code, please use latest version of Visual Studio 2010 or any prior version with Windows Vista/7 SDK. You can also use express edition of Visual C++ to try this API.

 

Proudly powered by WordPress
Theme: Esquire by Matthew Buchanan.