views:

180

answers:

5

Hello :)

I've got a little text drawing puzzle under Win32. I'm trying to draw some instructions for users of my application at the top of the window.

Please refer to the following window (I've changed the background color on the text so you can see the boundaries)

Demonstration

I'm currently using DrawTextEx to draw the text to my window, but the problem is that it does not fill the entire RECTangle that I give it. Not drawing that area is just fine, until the window resizes:

Demonstration after resize

When the text is re wrapped due to the window sizing, because DrawTextEx doesn't clear it's background, these artifacts are leftover.

I tried using FillRect to fill in the area behind the text drawing call, which does eliminate the visual artifacts, but then causes the text to flicker constantly, as it is completely erased and then completely redrawn to the display.

Any ideas on how one might get the area not containing text to be drawn with the background color?

EDIT: I'd like to avoid having to double buffer the form if at app possible.

EDIT2: I solved the problem by only redrawing the text when I detect that the wrapping changes during a resize.

+3  A: 

Use double buffering?

Draw everything to a bitmap and draw the bitmap to the window. Flickering is commonly a double buffering issue.

Pedery
That would work, but I'd like to avoid having to double buffer everything for such a simple operation. I've worked very hard on the rest of the application to not need to double buffer. Seems a shame to waste all that for something so simple.
Billy ONeal
Is the CPU the limited resource or the memory? In case of the former, you can be very restrictive about *when* to update your window, i.e. not more often than every 40ms. If memory is your limited resource, I think we'd need to know more about your hardware limitations to solve your case.
Pedery
+1  A: 

There are many possible solutions and without seeing your code, it's hard to tell which method would be best so I'd suggest taking a look at this article on flicker free drawing

BarrettJ
I read that article -- the rest of the application follows the guidelines there. The text is the only thing that flickers. Everything else is just fine.
Billy ONeal
Do you mean a constant flicker, or only while resizing?
BarrettJ
Only while resizing.
Billy ONeal
A: 

SetBkMode + SetBkColor ?

Anders
The background is being drawn behind the text correctly. I need a way for the function to also draw the background to the left of each line to fill the rectangle where the instructions go.
Billy ONeal
A: 

Get/Calculate the rect used by the DrawText call and clip it with something like ExcludeClipRect before calling FillRect

Anders
Get/Calculate the rect used by the DrawText <-- Care to enlighten me as to how an irregular shape can be represented as a RECT?
Billy ONeal
You already have a RECT that you pass to DrawText (By first calling it with DT_CALCRECT or some "hardcoded" value)
Anders
A: 

Well since nobody seems to know what to do about it, I implemented it this way:

std::vector<std::wstring> wrapString(HDC hDC, const std::wstring& text, const RECT& targetRect, HFONT font)
{
    std::vector<std::wstring> result;
    RECT targetRectangle;
    CopyRect(&targetRectangle, &targetRect);

    //Calculate the width of the bounding rectangle.
    int maxWidth = targetRectangle.right - targetRectangle.left;

    //Draw the lines one at a time
    std::wstring currentLine;
    for(std::wstring::const_iterator it = text.begin(); it != text.end(); currentLine.push_back(*it), it++)
    {
     if(*it == L'\r' || *it == L'\n')
     { //Hard return
      while(it != text.end() && (*it == L'\r' || *it == L'\n')) it++;
      result.push_back(currentLine);
      currentLine.clear();
     }
     else
     { //Check for soft return
      SIZE sizeStruct;
      GetTextExtentPoint32(hDC, currentLine.c_str(), static_cast<int>(currentLine.length()), &sizeStruct);
      if (sizeStruct.cx > maxWidth)
      {
       std::wstring::size_type lineLength = currentLine.find_last_of(L' ');
       if (lineLength == currentLine.npos)
       { //Word is longer than a line.
        for(;it != text.end() && !iswspace(*it);it++) currentLine.push_back(*it);
       }
       else
       { //Clip word to line.
        //Backtrack our scan of the source text.
        it -= currentLine.length() - lineLength - 1;
        //Remove the clipped word
        currentLine.erase(lineLength);
       }
       result.push_back(currentLine);
       currentLine.clear();
      }
     }
    }
    //Last remaining text.
    result.push_back(currentLine);
    return result;
}

void DrawInstructionsWithFilledBackground(HDC hDC, const std::wstring& text, RECT& targetRectangle, HFONT font, COLORREF backgroundColor)
{
    //Set up our background color.
    int dcIdx = SaveDC(hDC);
    HBRUSH backgroundBrush = CreateSolidBrush(backgroundColor);
    SelectObject(hDC, backgroundBrush);
    SelectObject(hDC, font);
    SetBkColor(hDC, backgroundColor);

    std::vector<std::wstring> lines(wrapString(hDC, text, targetRectangle, font));
    for(std::vector<std::wstring>::const_iterator it = lines.begin(); it!=lines.end(); it++)
    {
     RECT backgroundRect = targetRectangle;
     DrawText(hDC, const_cast<LPWSTR>(it->c_str()), static_cast<int>(it->length()), &backgroundRect, DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE);
     backgroundRect.left = backgroundRect.right;
     backgroundRect.right = targetRectangle.right;
     if (backgroundRect.right >= backgroundRect.left)
     FillRect(hDC, &backgroundRect, backgroundBrush);
     ExtTextOut(hDC, targetRectangle.left, targetRectangle.top, ETO_OPAQUE, NULL, it->c_str(), static_cast<UINT>(it->length()), NULL);
     targetRectangle.top += backgroundRect.bottom - backgroundRect.top;
    }
    instructionsWrap = lines;
    //Restore the DC to it's former glory.
    RestoreDC(hDC, dcIdx);
    DeleteObject(backgroundBrush);
}
Billy ONeal