Search code examples
winapidrawtext

DrawText with DT_CALCRECT and DT_RIGHT does not work


I have the following code in an otherwise default VisualStudio project. It passes DT_CALCRECT to DrawTextW to calculate the rectangle to draw some text, then it uses that rectangle to draw the text. To test it yourself just paste this code into a default VisualStudio project:

case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    // TODO: Add any drawing code here...

    {
        wchar_t txt[] = L"abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef\r\nabc\r\n123456";
        BOOL useDT_RIGHT = TRUE; // <<< ** SWITCH THIS BETWEEN TRUE AND FALSE **
        wchar_t buf1[100] = {0};
        wchar_t buf2[100] = {0};
        RECT r1 = {0, 0, 192, 1000};
        RECT r2 = {r1.right + 10, r1.top, r1.right + 400, r1.top + 100};
        int ret1, ret2;

        FillRect(hdc, &r1, (HBRUSH)GetStockObject(GRAY_BRUSH));

        ret1 = DrawTextW(hdc, txt, -1, &r1,
            DT_CALCRECT |
            DT_WORDBREAK |
            (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
        );
        if(ret1 == 0) MessageBoxW(NULL, L"ret1 == 0", NULL, MB_OK);

        wsprintfW(buf1, L"useDT_RIGHT = %i\r\nDT_CALCRECT returned %i %i %i %i\r\nret1 = %i\r\n", useDT_RIGHT, r1.left, r1.top, r1.right, r1.bottom, ret1);

        ret2 = DrawTextW(hdc, txt, -1, &r1,
            DT_WORDBREAK |
            (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
        );
        if(ret2 == 0) MessageBoxW(NULL, L"ret2 == 0", NULL, MB_OK);

        wsprintfW(buf2, L"%sret2 = %i", buf1, ret2);
        DrawTextW(hdc, buf2, -1, &r2, DT_LEFT);
    }

    EndPaint(hWnd, &ps);
    break;

In the code, if useDT_RIGHT is set to FALSE, the text is left aligned and DT_CALCRECT returns the correct rectangle, as shown below:

http://i64.tinypic.com/2ptw2dk.png

If useDT_RIGHT is set to TRUE, the text is right aligned but DT_CALCRECT returns an incorrect rectangle, as shown below:

http://i68.tinypic.com/nwx9co.png

Or rather, it may be returning a correct rectangle, and the subsequent call to actually draw the text is drawing it incorrectly, it's impossible to tell.

The docs for DT_CALCRECT say "If there are multiple lines of text, DrawText uses the width of the rectangle pointed to by the lpRect parameter and extends the base of the rectangle to bound the last line of text. If the largest word is wider than the rectangle, the width is expanded. If the text is less than the width of the rectangle, the width is reduced."

What I would expect is that the rectangle returned by DrawTextW would be the right size to draw the text (in the actual code, the rectangle is also used for positioning the surrounding controls, so just expanding it willy nilly won't really help). I would also expect the text to be properly right aligned (ie. the opposite of the left aligned text), and not the mess it is as shown in the second screenshot above. By properly right aligned I mean as shown in this screenshot of wordpad:

http: //i63.tinypic.com/qqya1u.png (Please remove the space from this link to make it work.)

What is wrong with this code? Why does DT_CALCRECT with DT_RIGHT not produce the expected results? Or, if it is, why is the second call to DrawTextW not drawing it correctly?


Solution

  • It seems that this behavior is either a bug or by design. Maybe DT_WORDBREAK removes spaces and that's why it produces a narrower rectangle when using DT_RIGHT. Anyway, here is a way to make DrawText behave the same way when using DT_CALCRECT with either DT_LEFT or DT_RIGHT, you can test this code (check the comment that starts with FIX):

    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        // TODO: Add any drawing code here...
    
        {
            wchar_t txt[] = L"abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef abcdef\r\nabc\r\n123456";
            BOOL useDT_RIGHT = FALSE; // TRUE; // <<< ** SWITCH THIS BETWEEN TRUE AND FALSE **
            wchar_t buf1[100] = { 0 };
            wchar_t buf2[100] = { 0 };
            RECT r1 = { 0, 0, 192, 1000 };
            RECT r2 = { r1.right + 10, r1.top, r1.right + 400, r1.top + 100 };
            int ret1, ret2;
    
            FillRect(hdc, &r1, (HBRUSH)GetStockObject(GRAY_BRUSH));
    
            ret1 = DrawTextW(hdc, txt, -1, &r1,
                DT_CALCRECT |
                DT_WORDBREAK | 
                (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT) 
                );
            if (ret1 == 0) MessageBoxW(NULL, L"ret1 == 0", NULL, MB_OK);
    
            // FIX: The following two lines make DrawText with DT_CALCRECT behave the same way for DT_LEFT and DT_RIGHT
            r1.right = 192;
            r1.bottom = ret1;
    
            wsprintfW(buf1, L"useDT_RIGHT = %i\r\nDT_CALCRECT returned %i %i %i %i\r\nret1 = %i\r\n", useDT_RIGHT, r1.left, r1.top, r1.right, r1.bottom, ret1);
    
            ret2 = DrawTextW(hdc, txt, -1, &r1,
                DT_WORDBREAK |
                (useDT_RIGHT == FALSE ? DT_LEFT : DT_RIGHT)
                );
            if (ret2 == 0) MessageBoxW(NULL, L"ret2 == 0", NULL, MB_OK);
    
            wsprintfW(buf2, L"%sret2 = %i", buf1, ret2);
            DrawTextW(hdc, buf2, -1, &r2, DT_LEFT);
        }
    
        EndPaint(hWnd, &ps);
        break;