YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   Home Products | Purchase Support | Downloads  
View in English
View in Japanese
View in
참고
View in Français
View in Italiano
View in 中文(繁體)
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
VX++ Cross-Platform C/C++
Overview
Download
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET

 Direct2D & DirectWrite, Draw and Rendering text C++ Source Code

1

This is the first article in a series intended to illustrate an approach to safer C++ development made possible by the C++11 standard, while building our code directly on top of the Windows C and COM based APIs.

In the next article Windows Development in C++, working with menus[^] we explore the Windows API for creating and handling menus, whith an eye towards how C++11 enables a safer programming model.

The article is really much more about the programming style made possible by using std::shared_ptr<>, and other smart pointers, than it is about Direct2D and DirectWrite. The library include a set of classes that wraps the functionality of Direct2D and DirectWrite, adding a few significant features:

  • Errors are converted into exceptions
  • Transparent management of COM interface lifetimes

The demo application implements the same functionality as one of the DirectWrite SDK examples, with a significant reduction in the size of the code.

Now, those of us that develop applications that display 3D content are used to having the power of the GPU at our disposal. While it’s certainly possible to use Direct3D to display 2D content, it’s not something most of us would use to render just a few lines of text, or anything else that can easily be implemented using GDI or GDI+.

Starting with Windows Vista Service Pack 2 and Windows 7, we now have a new set of APIs that facilitate 2D rendering using the GPU called Direct2D. At the same time Microsoft introduced another new API, DirectWrite, supporting text rendering, resolution-independent outline fonts, and full Unicode text and layout support.

While the examples included with the SDK for Direct2D and DirectWrite provide the basics we need to get started with the new APIs, they are somewhat cumbersome, and it’s my hope that you’ll find the approach I’m using somewhat easier to understand.

Currently the code is at a very early stage, meaning there are certainly some rough edges and unfinished parts, but from a design perspective it’s starting to get interesting.

Wouldn’t you like your wWinMain to look like this:

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    auto application = std::make_shared<Application>();
    auto form = std::make_shared<MyForm>();
    auto result = application->Run(form);

    return result;
}

The code relies on the Boost C++ libraries, which can be downloaded from http://www.boost.org/[^], so you need to download and build it, before updating the provided projects with the include and library paths matching your installation.

2

I’m sure you noticed the auto form = std::make_shared<MyForm>() statement above.

Now, std::make_shared<MyForm>() is a smart way of creating a std::share_ptr<MyForm> smart pointer to an object of the MyForm type; since it’s capable of allocating space both for the housekeeping information required for std::share_ptr<MyForm> and the MyForm object using a single allocation.

std::shared_ptr<>

The std::shared_ptr<> class template stores a pointer to a dynamically allocated object.std::shared_ptr<> guarantees that the object it points to will be deleted when the laststd::shared_ptr<> pointing to it is destroyed or reset.

The implementation of std::shared_ptr<> uses reference counting, and cycles of std::shared_ptr<>instances will not be destroyed. If a function holds a std::shared_ptr<> to an object that directly or indirectly holds a std::shared_ptr<> back to the object, the objects use count will be 2, and destruction of the originalstd::shared_ptr<> will keep the object hanging around with a use count of 1. To avoid this kind of circular references you can use std::weak_ptr<> to reference objects back up the object hierarchy.

The MyForm class declaration looks like this:

class MyForm : public Form
{
    graphics::Factory factory;
    graphics::WriteFactory writeFactory;
    graphics::WriteTextFormat textFormat;
    graphics::ControlRenderTarget renderTarget;
    graphics::SolidColorBrush blackBrush;
    float dpiScaleX;
    float dpiScaleY;
    String text;
public:
    typedef Form Base;

    MyForm();
protected:
    virtual void DoOnShown();
    virtual void DoOnDestroy(Message& message);
    virtual void DoOnDisplayChange(Message& message);
    virtual void DoOnPaint(Message& message);
    virtual void DoOnSize(Message& message);
private:
    void UpdateScale( );
};

MyForm is derived from Form, a class that represents a top level window, which is what we need for our example. The graphics::Factory class is a wrapper around the Direct2D ID2D1Factory interface, andgraphics::WriteFactory is a wrapper around the DirectWrite IDWriteFactory interface. Both are initialized in the constructor of MyForm:

MyForm::MyForm()
    : Base(),
      factory(D2D1_FACTORY_TYPE_SINGLE_THREADED),
      writeFactory(DWRITE_FACTORY_TYPE_SHARED),
      dpiScaleX(0),dpiScaleY(0),
      text(L"Windows Development in C++, rendering text with Direct2D & DirectWrite")
{
    SetWindowText(text);
    textFormat = writeFactory.CreateTextFormat(L"Plantagenet Cherokee",72);
    textFormat.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
    textFormat.SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);

    UpdateScale( );
}

Since our application is single threaded and we have full control of how the objects interact, and what state they are in, we create a single threaded ID2D1Factory and a shared IDWriteFactory.

Inside the constructor we use the writeFactory to create a graphics::WriteTextFormat object. Agraphics::WriteTextFormat object describes the format for text and is used when an entire string is to be rendered using the same font size, style, weight, alignment etc.

We also want our little application to be able to render correctly on high DPI devices, and UpdateScale calculates factors, based on the resolution of the desktop, that are later used scale the rending rectangle for text output.

void MyForm::UpdateScale( )
{
    factory.GetDesktopDpi(dpiScaleX,dpiScaleY);
    dpiScaleX /= 96.0f;
    dpiScaleY /= 96.0f;
}

At this point we have a fully initialized MyForm object, which we pass to the Run method of the Applicationobject.

auto result = application->Run(form);

Now we have a running Windows desktop application, and it’s time to look at the 5 virtual methods declared in the MyForm class. These methods override methods declared in the Form class, or in the Control class, the ancestor of the Form class.

The DoOnShown method is only called the first time a form is displayed – and any later minimizing, maximizing, restoring, hiding, showing, or invalidating and repainting will not cause this method to be called again. So it’s a good opportunity to initialize objects that relies on a valid window handle.

void MyForm::DoOnShown()
{
    Base::DoOnShown();

    renderTarget = factory.CreateControlRenderTarget(shared_from_this());
    blackBrush = renderTarget.CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black));
}

renderTarget is a ControlRenderTarget object, which is a wrapper around the Direct2D ID2D1HwndRenderTarget interface, and we use this object to render the text on our DoOnPaint method:

void MyForm::DoOnPaint(Message& message)
{
    Base::DoOnPaint(message);
    ValidateRect();
    RECT rc = GetClientRect();

    renderTarget.BeginDraw();

    renderTarget.SetTransform(D2D1::IdentityMatrix());
    renderTarget.Clear(D2D1::ColorF(D2D1::ColorF::White));
    
    D2D1_RECT_F layoutRect = D2D1::RectF(rc.top * dpiScaleY,rc.left * dpiScaleX,
        (rc.right - rc.left) * dpiScaleX,(rc.bottom - rc.top) * dpiScaleY);
    
    renderTarget.DrawText(text.c_str(),text.length(),textFormat,layoutRect,blackBrush);

    renderTarget.EndDraw();
}

As you see we call the BeginDraw method of the renderTarget object before issuing drawing commands, and after we’ve finished the drawing, we call the EndDraw method, indicating that drawing is finished.

Direct2D ID2D1HwndRenderTarget objects are double buffered, and drawing commands issued do not appear immediately, as they are performed on an offscreen surface. EndDraw causes the offscreen buffer to be presented onscreen.

Note that we call ValidateRect to tell windows that the entire client area is now valid.

By calling renderTarget.SetTransform(D2D1::IdentityMatrix()); we ensure that no transformation – such as rotation, skewing or scaling – takes place, and Clear draws our beautiful white background. Next, layoutRect is calculated using the scaling factors previously calculated by UpdateScale before calling DrawText to render the text using the textFormat created in the contructor and the black brush created in the DoOnShownmethod.

As mentioned, the renderTarget uses an offscreen surface, and the size of that surface is set in the DoOnSizemethod:

void MyForm::DoOnSize(Message& message)
{
    Base::DoOnSize(message);
    if(renderTarget)
    {
        D2D1_SIZE_U size;

        size.width = LOWORD(message.lParam);
        size.height = HIWORD(message.lParam);
        renderTarget.Resize(size);
    }
}

While the DoOnDisplayChange method:

void MyForm::DoOnDisplayChange(Message& message)
{
    UpdateScale( );
    InvalidateRect();
}

allows the application to handle changes to the display configuration. Lastly the DoOnDestroy method is used to clean up the rendering target when the window closes:

void MyForm::DoOnDestroy(Message& message)
{
    Base::DoOnDestroy(message);
    blackBrush.Reset();
    renderTarget.Reset();
}

Except for a few include statements; we’ve now gone through the complete source code for an application that provides functionality similar to the DirectWrite Simple Hello World Sample, that can be found athttp://msdn.microsoft.com/en-us/library/dd742738(VS.85).aspx[^]

3

I’m sure you noticed that there are no calls to Release, but that does not mean that the program does not release the interfaces in an appropriate manner.

Since we are working with DirectX based APIs it’s useful to have a class that wraps a pointer to the IUnknowninterface, and surprisingly I called this wrapper Unknown:

class Unknown
    {
    protected:
        IUnknown* unknown;
    public:
        Unknown();
        explicit Unknown(IUnknown* unknown);
        Unknown(const Unknown& other);
        Unknown(Unknown&& other);
        ~Unknown();
        operator bool() const;
        Unknown& operator = (const Unknown& other);
        Unknown& operator = (Unknown&& other);
        Unknown& Reset(IUnknown* other = nullptr);
    };

It’s pretty much a minimal implementation of a smart pointer to COM based objects, and it’s used as a base class for the various interface wrappers in the harlinn::windows::graphics namespace, so it’s worth looking at the implementation details.

The default constructor does pretty much what one would expect, as it just sets unknown to nullptr:

Unknown()
    : unknown(nullptr)
{}

Then we have a constructor that takes a pointer to an IUnknown:

explicit Unknown(IUnknown* unknown)
    : unknown(unknown)
{}

It’s declared explicit because I don’t want the compiler to automagically generate instances of the class. Please note that the implementation does not call AddRef on the interface.

Next we have the copy constructor, which do call AddRef – otherwise the whole thing would be rather pointless:

Unknown(const Unknown& other)
    : unknown(other.unknown)
{
    if(unknown)
    {
        unknown->AddRef();
    }
}

And then we have the move constructor:

Unknown(Unknown&& other)
    : unknown(0)
{
    if(other.unknown)
    {
        unknown = other.unknown;
        other.unknown = nullptr;
    }
}

Which copies the pointer managed by the argument, and sets the unknown field of the argument to nullptr, preventing a call to Release from the argument when that object goes out of scope.

~Unknown()
{
    IUnknown* tmp = unknown;
    unknown = nullptr;
    if(tmp)
    {
        tmp->Release();
    }
}

In MyForm::DoOnSize you saw this test if(renderTarget) which uses this operator:

operator bool() const
{
    return unknown != nullptr;
}

The copy assignment operator looks like this:

Unknown& operator = (const Unknown& other)
{
    if(unknown != other.unknown)
    {
        if(unknown)
        {
            IUnknown* tmp = unknown;
            unknown = nullptr;
            tmp->Release();
        }
        unknown = other.unknown;
        if(unknown)
        {
            unknown->AddRef();
        }
    }
    return *this;
}

while the move assignment operator is implemented like this:

Unknown& operator = (Unknown&& other)
{
    if (this != &other)
    {
        IUnknown* tmp = unknown;
        unknown = nullptr;
        if(tmp)
        {
            tmp->Release();
        }
        unknown = other.unknown;
        other.unknown = nullptr;
    }
    return *this;
}

It’s worth noting that both the copy assignment operator and the move assignment operator guards against self-assignment that would result in a premature call to Release, and the Reset method is implemented similarly:

Unknown& Reset(IUnknown* other = nullptr)
{
    if(unknown != other)
    {
        if(unknown)
        {
            IUnknown* tmp = unknown;
            unknown = nullptr;
            tmp->Release();
        }
        unknown = other;
    }
    return *this;
}

Also note that the Reset method does not call AddRef on the passed interface.

4

Remember the MyForm::DoOnDestroy method?

void MyForm::DoOnDestroy(Message& message)
{
    Base::DoOnDestroy(message);
    blackBrush.Reset();
    renderTarget.Reset();
}

Perhaps you wondered why we made a call to the DoOnDestroy method of the base class. The Control class implements the DoOnDestroy method like this:

HWIN_EXPORT void Control::DoOnDestroy(Message& message)
{
    OnDestroy(message);
}

Where OnDestroy is not another method, but a signal from the boost::signals2[^] library, declared like this:

signal<void (Message& message)> OnDestroy;

Signals provides functionality that are in many ways similar to .Net events, something that the Application::Runmethod puts to good use by connecting a lambda expression to the OnDestroy signal:

HWIN_EXPORT int Application::Run(std::shared_ptr<Form> mainform, std::shared_ptr<MessageLoop> messageLoop)
{
    if(mainform)
    {
        mainform->OnDestroy.connect( [=](Message& message)
            {
                ::PostQuitMessage(-1);
            });
        mainform->Show();

        int result = messageLoop->Run();
        return result;
    }
    return 0;
}

The lambda expression calls PostQuitMessage, causing the message loop to terminate when the application causes the DoOnDestroy method, usually in response to a WM_DESTROY message, to be called for the argument form only, so the lifetime of the message loop is tied to the lifetime of the window.

News:

1 UCanCode Advance E-XD++ CAD Drawing and Printing Solution Source Code Solution for C/C++, .NET V2024 is released!

2 UCanCode Advance E-XD++ HMI & SCADA Source Code Solution for C/C++, .NET V2024 is released!

3 UCanCode Advance E-XD++ GIS SVG Drawing and Printing Solution Source Code Solution for C/C++, .NET V2024 is released!

 


Contact UCanCode Software

To buy the source code or learn more about with:

Next--> Promotional personalized database document printing Solution

Ask any questions by MSN: ucancode@hotmail.com Yahoo: ucan_code@yahoo.com


 

Copyright ?1998-2024 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@ucancode.net