1

我有一个 CRichEditCtrl(实际上我有一个类,它是 CRichEditCtrl 的一个子类,这是我定义的一个类),它由多行带有水平和垂直滚动条的文本填充。此控件的目的是显示在较大文本中搜索的字符串以及左右 n 个字符(例如,如果用户搜索“the”,那么他们将获得“the”的所有实例的列表" 在文本中(如果 n = 100)在每个找到的实例的左右各有 100 个字符以提供上下文)。

查询字符串需要在每一行之间排列。在这个程序支持 Unicode 之前,只需将字体设置为 Courier 就可以了,但现在我启用了 Unicode 支持,这不再有效。

我尝试过使用等宽字体,但据我所知,没有任何字体适用于所有字符。在我看来,拉丁字符都有一个大小,而汉字有另一个(我注意到所有拉丁字符的文本行排成一行,所有汉字排成一行,但两者都没有排成一行)。

我也试过居中对齐文本。由于每行中的查询字符串都在确切的中心,它们应该都排成一行,但我似乎无法让它工作,SetParaFormat 调用似乎只是被忽略了。这是我用于此的代码:

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);

每次在 ctrl 中插入文本时我都会这样做,但它对程序没有影响。

即使有穿插的中文和拉丁字符,是否也可以让每行文本中的查询词排成一行?(可能还有任何其他字符集)

4

2 回答 2

2

请参阅http://msdn.microsoft.com/en-us/library/bb787940(v=vs.85).aspx,特别是(or ) 结构的cTabCountandrgxTabs成员,它允许您设置制表位。PARAFORMATPARAFORMAT2

于 2011-08-04T15:04:50.340 回答
0

好的,所以我设法解决了它。为了将来参考,这就是我所做的:

首先,我试图找到一个等宽字体,但我找不到任何真正等宽的字体(拉丁字符和中文字符的宽度相同)。

接下来,我尝试使文本在窗口中居中。我无法做到这一点,直到我意识到将 Auto HScroll 设置为 true(为富编辑控件定义 ES_AUTOHSCROLL)导致 setParaFormat 忽略我试图使文本居中。在我禁用它并手动设置可绘制文本区域的大小后,我能够使文本居中。以防万一有人好奇,下面是我用来在富编辑框中设置可绘制区域宽度的代码:

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

我只是将 lw 设置为任意大,因此我可以测试以查看文本居中是否有效。它没有。事实证明,当富编辑控件使文本居中时,它基于文本的绘制宽度,而不是字符数。我假设由于查询字符串的任一侧都有相同数量的字符,那么这将导致字符串居中,但事实并非如此。

我尝试的最终解决方案是 ymett 建议的解决方案。经过一些调整后,我想出了一个名为 alignText() 的函数,该函数在所有文本都插入富编辑控件后调用。这是函数(注意:控件中的每一行在调用此函数之前都插入了选项卡:一个在行首,一个在查询字符串之后,例如“string1-query-string2”变为“\tstring1-query\t-字符串 2")

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);
}

本质上,此函数的作用是使第一个制表位右对齐并将其设置在控件右侧的某个点 x 处。然后它使第二个制表位在其右侧一小段距离,并使其左对齐。因此,从每行开头到查询字符串结尾(从第一个 \t 到第二个 \t)的所有文本都被推向右侧,靠着第一个制表位,所有剩余的文本都被推向左侧针对第二个制表位,导致查询字符串在控件中的所有行之间对齐。该函数的第一部分找出 x(通过找出每条线将绘制多长时间并取最大值),第二部分设置制表位。

再次感谢 ymett 的解决方案。

于 2011-08-13T23:45:15.950 回答