1
votes

I have a CRichEditCtrl (actually I have a class that is a subclass of a CRichEditCtrl, a class that I defined) that is populated by many lines of text with both horizontal and vertical scroll bars. The purpose of this control is to display a string that is searched for in a larger text along with n characters to the right and left (e.g. if the user searches for "the" then they would get a list of all the instances of "the" in the text with (if n = 100) 100 characters to the left and right of each found instance to provide context).

The query string needs to be lined up between each row. Before this program had Unicode support, just setting the font to Courier did the trick, but now that I've enabled Unicode support, this no longer works.

I've tried using monospaced fonts, but as far as I can tell, there aren't any that are for all characters. It seems to me that the latin characters all have one size, and the Chinese characters have another (I've noticed lines of text with all latin characters line up and ones with all Chinese characters line up, but ones with both do not line up).

I've also tried center aligning the text. Since the query string in each line is in the exact center, they should all line up, but I cannot seem to get this to work, the SetParaFormat call seems to just get ignored. Here's the code I used for that:

long spos, epos;
GetSel(spos, epos);
PARAFORMAT Pfm;
GetParaFormat(Pfm);

Pfm.dwMask = (Pfm.dwMask | PFM_ALIGNMENT);
Pfm.wAlignment = PFA_CENTER;

SetSel(0, -1);
SetParaFormat(Pfm);

SetSel(spos, epos);

I do this everytime text is inserted in the ctrl, but it has no affect on the program.

Is there anyway to get the query word in each line of text to line up even when there are interspersed Chinese and latin characters? (and possibly any other character set)

2
Are the latin character and Chinese character sizes related?Mark Ransom
they appear to be just slightly off each other, latin might be something like 3/4's the width of the chinese, but I'm not sure on that at all.Rae_III

2 Answers

2
votes

See http://msdn.microsoft.com/en-us/library/bb787940(v=vs.85).aspx, in particular the cTabCount and rgxTabs members of the PARAFORMAT (or PARAFORMAT2) structure, which allow you to set tabstops.

0
votes

Okay so I managed to solve it. For future reference, here's what I did:

First, I tried to find a monospaced font, but I was unable to find any that were truly monospaced (had latin and chinese characters as the same width).

Next, I tried to center the text in the window. I was unable to do this until I realized that having Auto HScroll set to true (ES_AUTOHSCROLL defined for the rich edit control) caused setParaFormat to ignore me trying to center the text. After I disabled it and manually set the size of the drawable text area, I was able to center the text. Just in case anyone is curious, here's the code I used to set the width of the drawable area in the rich edit box:

CDC* pDC = GetDC();    
long lw = 99999999;
SetTargetDevice(*GetDC(), lw);

I just set lw arbitrarily large so I could test to see if centering the text worked. It did not. As it turns out, when the rich edit control centers the text, it bases it off the draw width of the text, not off the number of characters. I assumed that since there were the same number of characters on either side of the query string then that would cause the string to be centered, but this was not the case.

The final solution I tried was the one suggested by ymett. After some tweaking I came up with a function called alignText() that's called after all the text has been inserted into the rich edit control. Here's the function (Note: each line in the control had tabs inserted before this function was called: one at the beginning of the line and one after the query string e.g. "string1-query-string2" becomes "\tstring1-query\t-string2")

void CFormViewConcordanceRichEditCtrl::alignText()
{

    long maxSize = 0;
    CFont font;
    LOGFONT lf = {0};
    CHARFORMAT cf = {0};
    this->GetDefaultCharFormat(cf);

    //convert a CHARFORMAT struct into a LOGFONT struct
    if (cf.dwEffects & CFE_BOLD)
        lf.lfWeight = FW_BOLD;
    else
        lf.lfWeight = FW_NORMAL;
    if (cf.dwEffects & CFE_ITALIC)
        lf.lfItalic = true;
    if (cf.dwEffects & CFE_UNDERLINE)
        lf.lfUnderline = true;
    if (cf.dwEffects & CFE_STRIKEOUT)
        lf.lfStrikeOut = true;
    lf.lfHeight = cf.yHeight;
    _stprintf(lf.lfFaceName, _T("%s"), cf.szFaceName);
    lf.lfCharSet = DEFAULT_CHARSET;
    lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
    font.CreateFontIndirect(&lf);

    //create a display context
    CClientDC dc(this);
    dc.SetMapMode(MM_TWIPS);
    CFont *pOldFont = dc.SelectObject(&font);

    //find the line that as the longest preceding string when drawn
    for(int i = 0; i < m_pParent->m_nDataArray; i++)
    {
        CString text = m_pParent->DataArray[i].text.Left(BUFFER_LENGTH + m_pParent->generateText.GetLength());
        text.Replace(_T("\r"), _T(" "));
        text.Replace(_T("\n"), _T(" "));
        text.Replace(_T("\t"), _T(" "));

        CRect rc(0,0,0,0);
        dc.DrawText(text, &rc, DT_CALCRECT);

        int width = 1.0*cf.yHeight/fabs((double)rc.bottom - rc.top)*(rc.right - rc.left);
        width = dc.GetTextExtent(text).cx;
        if(width > maxSize)
            maxSize = width;
    }

    dc.SelectObject(pOldFont);

    //this calulates where to place the first tab. The 0.8 is a rought constant calculated by guess & check, it may be innacurate.
    long tab = maxSize*0.8;
    PARAFORMAT pf;
    pf.cbSize = sizeof(PARAFORMAT);
    pf.dwMask = PFM_TABSTOPS;
    pf.cTabCount = 2;
    pf.rgxTabs[0] = tab + (2 << 24); //make the first tab right-aligned
    pf.rgxTabs[1] = tab + 25;

    //this is to preserve the user's selection and scroll positions when the selection is changed
    int vScroll = GetScrollPos(SB_VERT);
    int hScroll = GetScrollPos(SB_HORZ);
    long spos, epos;
    GetSel(spos, epos);

    //select all the text
    SetSel(0, -1);
    //this call is very important, but I'm not sure why
    ::SendMessage(GetSafeHwnd(), EM_SETTYPOGRAPHYOPTIONS, TO_ADVANCEDTYPOGRAPHY, TO_ADVANCEDTYPOGRAPHY);
    this->SetParaFormat(pf);

    //now reset the user's selection and scroll positions
    SetSel(spos, epos);
    ::SendMessage(GetSafeHwnd(), 
        WM_HSCROLL, 
        (WPARAM) ((hScroll) << 16) + SB_THUMBPOSITION,
        (LPARAM) NULL);
    ::SendMessage(GetSafeHwnd(), 
        WM_VSCROLL, 
        (WPARAM) ((vScroll) << 16) + SB_THUMBPOSITION,
        (LPARAM) NULL);
}

Essentially what this function does is make the first tab stop right-aligned and set it at some point x to the right in the control. It then makes the second tab stop a small distance to the right of that, and makes it left-aligned. So all the text from the beginning of each line to the end of the query string (from the first \t to the second \t) is pushed toward the right against the first tab stop, and all the remaining text is pushed toward the left against the second tab stop, causing the query string to be aligned between all the lines in the control. The first part of the function finds out x (by finding out how long each line will be drawn and taking the max) and the second part sets the tab stops.

Thanks again to ymett for the solution.