好的,所以我设法解决了它。为了将来参考,这就是我所做的:
首先,我试图找到一个等宽字体,但我找不到任何真正等宽的字体(拉丁字符和中文字符的宽度相同)。
接下来,我尝试使文本在窗口中居中。我无法做到这一点,直到我意识到将 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 的解决方案。