由于操作系统提供的edit存在一系列的问题,如单行不居中,闪烁,无法在layerwnd中使用等,所以,基本上好的界面库都对edit进行了自行绘制。dragon数据使用editdata对数据进行封装。下面对这个类进行分析。
//
//<span style="white-space:pre"> </span>封装编辑框的数据
//
class EditData
{
public:
<span style="white-space:pre"> </span>EditData();
<span style="white-space:pre"> </span>~EditData();
public:
<span style="white-space:pre"> </span>void BindToEdit(Edit* pEdit);
<span style="white-space:pre"> </span>void SetText(const TCHAR*, bool& bUpdate);
<span style="white-space:pre"> </span>void ReplaceChar(const TCHAR& c, bool& bUpdate);
<span style="white-space:pre"> </span>void ReplaceString(const String& str, bool& bUpdate);
<span style="white-space:pre"> </span>void Delete(bool& bUpdate);
<span style="white-space:pre"> </span>void BackSpace(bool& bUpdate);
void DeleteSelectionText(bool& bUpdate);
<span style="white-space:pre"> </span>void SetCaret(int nCaret, bool bSetSelStart, bool& bUpdate);
<span style="white-space:pre"> </span>void CutToClipboard();
<span style="white-space:pre"> </span>void CopyToClipboard();
<span style="white-space:pre"> </span>void PasteFromClipboard();
<span style="white-space:pre"> </span>void GetPriorItemPos(int nCP, int* pPrior);
<span style="white-space:pre"> </span>void GetNextItemPos(int nCP, int* pNext);
<span style="white-space:pre"> </span>void SetMaxChar(int nMax);
<span style="white-space:pre"> </span>void SetMaxChar(int nMax, bool bUpdate);
<span style="white-space:pre"> </span>void SetInsertMode(bool bInsertOrOverride);
<span style="white-space:pre"> </span>bool GetInsertMode() { return m_bInsertMode; }
<span style="white-space:pre"> </span>void GetText(String& str) { str = m_strText; }
<span style="white-space:pre"> </span>const String& GetTextRef() { return m_strText; }
<span style="white-space:pre"> </span>const TCHAR* GetText() { return m_strText.c_str(); }
<span style="white-space:pre"> </span>int GetTextLength() { return (int)m_strText.length(); }
<span style="white-space:pre"> </span>int GetCaretIndex() { return m_nCaret; }
<span style="white-space:pre"> </span>int GetSelectionLength() { return abs(m_nCaret - m_nSelStart); }
<span style="white-space:pre"> </span>int GetTextWidth() { return m_nTextWidth; }
<span style="white-space:pre"> </span>void GetSelectionInfo(int& nLeft, int& nRight) const;
<span style="white-space:pre"> </span>void SetSelectionInfo(int nStart, int nEnd, bool& bUpdate);
<span style="white-space:pre"> </span>void GetSelectionText(String& str);
<span style="white-space:pre"> </span>bool IsSelectionExist();
bool Clear();
<span style="white-space:pre"> </span>bool CP2X(int nCP, int* pX);
<span style="white-space:pre"> </span>bool X2CP(int nX, int* pnCP, int* pbTrailOrLead);
<span style="white-space:pre"> </span>void DestroyStringAnalysis();
protected:
<span style="white-space:pre"> </span>void DeleteSelectionText(); // 该函数仅用于内部调用,不对数据进行处理,仅删除文本
<span style="white-space:pre"> </span>void Fire_Text_Changed(BOOL bSetText=FALSE);
<span style="white-space:pre"> </span>bool FilterString(const TCHAR* szSrc, String& strDest);
<span style="white-space:pre"> </span>bool FilterChar(const TCHAR& cSrc, TCHAR& cDest);
<span style="white-space:pre"> </span>bool StringAnalysis();
private:
<span style="white-space:pre"> </span>String m_strText; // 编辑框的内容
<span style="white-space:pre"> </span>int m_nMaxChar; // 允许输入的最大字符数,-1表示无限制
<span style="white-space:pre"> </span>int<span style="white-space:pre"> </span> m_nSelStart; // 选择的字符起点,当没有选区时<span style="white-space:pre"> </span>m_nSelStart==m_nCaret<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>int m_nCaret; // 当前光标的位置,也标志着选区的End pos
<span style="white-space:pre"> </span>bool m_bInsertMode; // 插入/重写模式
<span style="white-space:pre"> </span>// UniScribe相关变量
<span style="white-space:pre"> </span>SCRIPT_CONTROL<span style="white-space:pre"> </span>m_ScriptControl;<span style="white-space:pre"> </span>// For uniscribe
<span style="white-space:pre"> </span>SCRIPT_STATE<span style="white-space:pre"> </span>m_ScriptState;<span style="white-space:pre"> </span>// For uniscribe
<span style="white-space:pre"> </span>SCRIPT_STRING_ANALYSIS<span style="white-space:pre"> </span>m_Analysis; // For cp2x, x2cp...
<span style="white-space:pre"> </span>int m_nTextWidth; // 当前字符的总长度
<span style="white-space:pre"> </span>Edit* m_pEdit;
};
EditData::EditData()
{
m_pEdit = NULL;
m_nMaxChar = -1;
m_nSelStart = 0;
m_nCaret = 0;
ZeroMemory( &m_ScriptControl, sizeof( SCRIPT_CONTROL));
ZeroMemory( &m_ScriptState, sizeof( SCRIPT_STATE));
<span style="color:#ff6666;"> ScriptApplyDigitSubstitution( NULL, &m_ScriptControl, &m_ScriptState); //uniscribe相关,详见后续博文</span>
m_Analysis = NULL;
m_nTextWidth = 0;
m_bInsertMode = true;
}
void EditData::BindToEdit(Edit* pEdit)
{
m_pEdit = pEdit;
}
EditData::~EditData()
{
if (m_Analysis)
{
ScriptStringFree(&m_Analysis);
m_Analysis = NULL;
}
m_pEdit = NULL;
}
void EditData::SetText(const TCHAR* szText, bool& bUpdate)
{
bUpdate = false;
String strInput;
if (false == this->FilterString(szText, strInput))
return;
if (0 == m_nMaxChar)
return;
bUpdate = true;
if (m_nMaxChar != -1)
{
if ((int)strInput.length() > m_nMaxChar)
{
m_strText = strInput.substr(0, m_nMaxChar);
}
else
{
m_strText = strInput;
}
}
else
{
m_strText = strInput;
}
m_nSelStart = m_nCaret = m_strText.length();
this->Fire_Text_Changed(TRUE);
}
//
// 在当前位置上面插入一个字符
//
void EditData::ReplaceChar(const TCHAR& c, bool& bUpdate)
{
bUpdate = false;
TCHAR cInput = _T('');
if (false == this->FilterChar(c, cInput))
return;
bool bInsertOrOverride = m_bInsertMode;
// 如果当前有被选择的文字,那么得覆盖这些文字
if (IsSelectionExist())
{
bInsertOrOverride = true; // 如果是覆盖模式,在删除完选区之后,应该转为插入,形成覆盖选区的样子
bUpdate = true;
DeleteSelectionText();
}
if (m_nCaret >= (int)m_strText.length())
{
bInsertOrOverride = true; // 光标在文字末尾时,应该是插入
}
if (bInsertOrOverride)
{
// 长度限制
if ( m_nMaxChar >= 0 && (int)this->m_strText.length() >= (UINT)m_nMaxChar)
{
}
else
{
bUpdate = true;
this->m_strText.insert( m_nCaret, 1, cInput);
m_nCaret += 1;
m_nSelStart = m_nCaret;
}
}
else
{
// if (cInput != m_strText[m_nCaret]) 即使要覆盖的字符一样,也要发送改变的通知
{
bUpdate = true;
m_strText.replace(m_nCaret,1,1,cInput);
m_nCaret += 1;
m_nSelStart = m_nCaret;
}
}
if (bUpdate)
{
this->Fire_Text_Changed();
}
}
//
// 在当前位置上面插入字符,如Copy,Paste
//
void EditData::ReplaceString(const String& str, bool& bUpdate)
{
bUpdate = false;
String strInput;
if (false == this->FilterString(str.c_str(), strInput))
return;
bool bInsertOrOverride = m_bInsertMode;
// 如果当前有被选择的文字,那么得覆盖这些文字
if (IsSelectionExist())
{
bInsertOrOverride = true; // 如果是覆盖模式,在删除完选区之后,应该转为插入,形成覆盖选区的样子
bUpdate = true;
DeleteSelectionText();
}
if(m_nCaret >= (int)m_strText.length())
{
bInsertOrOverride = true; // 光标在文字末尾时,应该是插入
}
if (bInsertOrOverride)
{
// 长度限制
if (m_nMaxChar >= 0)
{
int nRemain = m_nMaxChar - this->m_strText.length();
if (0 == nRemain)
{
if (bUpdate)
{
this->Fire_Text_Changed(); // 由于删除了选区导致text changed
}
return;
}
if (nRemain < (int)strInput.length())
{
strInput = strInput.substr(0,nRemain);
}
}
this->m_strText.insert(m_nCaret, strInput);
}
else
{
// 长度限制
if ( m_nMaxChar >= 0)
{
int nRemain = m_nMaxChar - m_nCaret;
if (0 == nRemain)
{
if (bUpdate)
{
this->Fire_Text_Changed(); // 由于删除了选区导致text changed
}
return;
}
if (nRemain < (int)strInput.length())
{
strInput = strInput.substr(0,nRemain);
}
}
this->m_strText.replace( m_nCaret, strInput.length(), strInput.c_str());
}
bUpdate = true;
m_nCaret += strInput.length();
m_nSelStart = m_nCaret;
if (bUpdate)
{
this->Fire_Text_Changed();
}
}
//
// 往后删除
//
void EditData::Delete(bool& bUpdate)
{
bUpdate = true;
// 删除当前所选择的文字
if (IsSelectionExist())
{
this->DeleteSelectionText();
}
// 往后删除一个字符
else
{
if (this->m_strText.length() == 0 || m_nCaret >= (int)this->m_strText.length())
{
bUpdate = false;
}
else
{
this->m_strText.erase( m_nCaret, 1);
}
}
m_nSelStart = m_nCaret;
if (bUpdate)
{
this->Fire_Text_Changed();
}
}
//
// 往前删除
//
void EditData::BackSpace(bool& bUpdate)
{
bUpdate = true;
// 删除当前所选择的文字
if (IsSelectionExist())
{
this->DeleteSelectionText();
}
// 往前删除一个字符
else
{
if (m_nCaret <= 0 || this->m_strText.length() == 0)
{
bUpdate = false;
}
else
{
m_nCaret--;
this->m_strText.erase( m_nCaret, 1);
}
}
m_nSelStart = m_nCaret;
if (bUpdate)
{
this->Fire_Text_Changed();
}
}
//
// 设置光标的位置,同时设置选区开始的位置
//
void EditData::SetCaret(int nCaret, bool bSetSelStart, bool& bUpdate)
{
bUpdate = false;
int nOldSelStart = m_nSelStart;
int nOldCaret = m_nCaret;
if (nCaret < 0)
{
nCaret = 0;
}
else if (nCaret > (int)m_strText.length())
{
nCaret = m_strText.length();
}
m_nCaret = nCaret;
if (bSetSelStart)
{
m_nSelStart = m_nCaret;
}
if (m_nSelStart==m_nCaret && nOldCaret==nOldSelStart) // 没有选区的光标移动
{
}
else if (m_nSelStart == nOldCaret && m_nCaret == nOldSelStart) // 选择反向
{
}
else
{
bUpdate = true;
}
}
void EditData::GetPriorItemPos( int nCP, int* pPrior)
{
if (NULL == pPrior)
return ;
*pPrior = nCP; // Default is the char itself
if (NULL == m_Analysis)
{
if (false ==this->StringAnalysis())
return;
}
const SCRIPT_LOGATTR* pLogAttr = ScriptString_pLogAttr( m_Analysis);
if (!pLogAttr)
return;
if (!ScriptString_pcOutChars(m_Analysis))
return;
int nInitial = *ScriptString_pcOutChars(m_Analysis);
if (nCP - 1 < nInitial)
nInitial = nCP - 1;
for (int i = nInitial; i > 0; --i)
if (pLogAttr[i].fWordStop || // Either the fWordStop flag is set
( !pLogAttr[i].fWhiteSpace && // Or the previous char is whitespace but this isn't.
pLogAttr[i - 1].fWhiteSpace))
{
*pPrior = i;
return;
}
// We have reached index 0. 0 is always a break point, so simply return it.
*pPrior = 0;
}
void EditData::GetNextItemPos(int nCP, int* pNext)
{
if (NULL == pNext)
return;
*pNext = nCP; // Default is the char itself
if (NULL == m_Analysis)
{
if (false ==this->StringAnalysis())
return;
}
const SCRIPT_LOGATTR* pLogAttr = ScriptString_pLogAttr(m_Analysis);
if (!pLogAttr)
return;
if (!ScriptString_pcOutChars(m_Analysis))
return;
int nInitial = *ScriptString_pcOutChars(m_Analysis);
if (nCP + 1 < nInitial)
nInitial = nCP + 1;
int i = nInitial;
int limit = *ScriptString_pcOutChars(m_Analysis);
while (limit > 0 && i < limit - 1)
{
if (pLogAttr[i].fWordStop) // Either the fWordStop flag is set
{
*pNext = i;
return;
}
else if (pLogAttr[i].fWhiteSpace && // Or this whitespace but the next char isn't.
!pLogAttr[i + 1].fWhiteSpace)
{
*pNext = i + 1; // The next char is a word stop
return;
}
++i;
limit = *ScriptString_pcOutChars( m_Analysis);
}
// We have reached the end. It's always a word stop, so simply return it.
*pNext = *ScriptString_pcOutChars(m_Analysis) - 1;
}
//
// 删除当前选中文本,如果没有选区则不执行
//
void EditData::DeleteSelectionText(bool& bUpdate)
{
// 删除当前所选择的文字
if (IsSelectionExist())
{
bUpdate = true;
this->DeleteSelectionText();
m_nSelStart = m_nCaret;
this->Fire_Text_Changed();
}
}
//
// 删除当前选区内的文字
//
// 注:该函数不会触发text changed
//
void EditData::DeleteSelectionText()
{
if (IsSelectionExist())
{
if (m_nCaret > m_nSelStart) // 从前到后选择
{
this->m_strText.erase(m_nSelStart, m_nCaret-m_nSelStart);
m_nCaret = m_nSelStart;
}
else // 从后往前选择的
{
this->m_strText.erase(m_nCaret , m_nSelStart-m_nCaret);
m_nSelStart = m_nCaret;
}
}
}
bool EditData::IsSelectionExist()
{
return (m_nCaret!=m_nSelStart);
}
bool EditData::Clear()
{
if (m_strText.length() == 0)
return false;
m_strText.clear();
m_nSelStart = m_nCaret = 0;
this->Fire_Text_Changed();
return true;
}
//
// BOOL bSetText,表示是否是因为调用Edit.SetText而触发的Change
//
void EditData::Fire_Text_Changed(BOOL bSetText)
{
this->StringAnalysis();
this->CP2X(m_strText.length(), &m_nTextWidth);
UIMSG msg;
msg.pMsgFrom = m_pEdit->GetIEdit();
msg.message = UI_WM_NOTIFY;
msg.nCode = UI_EN_CHANGE;
msg.wParam = (WPARAM)bSetText;
m_pEdit->GetIEdit()->DoNotify(&msg);
}
bool EditData::FilterString(const TCHAR* szSrc, String& strDest)
{
if (szSrc)
strDest = szSrc; // TODO
return true;
}
bool EditData::FilterChar(const TCHAR& cSrc, TCHAR& cDest)
{
cDest = cSrc; // TODO
return true;
}
void EditData::SetMaxChar(int nMax)
{
bool bUpdate = false;
this->SetMaxChar(nMax, bUpdate);
}
void EditData::SetMaxChar(int nMax, bool bUpdate)
{
bUpdate = false;
m_nMaxChar = nMax;
if (-1 != m_nMaxChar && (int)m_strText.length() > m_nMaxChar)
{
bUpdate = true;
m_strText = m_strText.substr(0,m_nMaxChar);
this->Fire_Text_Changed();
}
}
void EditData::SetInsertMode( bool bInsertOrOverride)
{
m_bInsertMode = bInsertOrOverride;
}
// 当外部字体发生改变时,需要重新创建
void EditData::DestroyStringAnalysis()
{
if (m_Analysis)
{
ScriptStringFree(&m_Analysis);
m_Analysis = NULL;
}
}
//
// 初始化当前字符串m_Analysis
//
bool EditData::StringAnalysis()
{
if (m_Analysis)
{
ScriptStringFree(&m_Analysis);
m_Analysis = NULL;
}
IUIApplication* pUIApp = m_pEdit->GetIEdit()->GetUIApplication();
HDC hDC = pUIApp->GetCacheDC();
IRenderFont* pRenderFont = m_pEdit->GetIEdit()->GetRenderFont();
HFONT hFont = pRenderFont->GetHFONT();
HFONT hOldFont = (HFONT)::SelectObject(hDC, hFont);
HRESULT hr = ScriptStringAnalyse(
hDC,
this->m_strText.c_str(),
this->m_strText.length() + 1, // 加上NULL.保证光标可以到达最后一个字符的后面。
this->m_strText.length()*3/2 + 16, // MSDN推荐值
-1,
SSA_BREAK | SSA_GLYPHS | SSA_FALLBACK | SSA_LINK,
0,
&m_ScriptControl,
&m_ScriptState,
NULL,
NULL,
NULL,
&m_Analysis
);
::SelectObject(hDC, hOldFont);
pUIApp->ReleaseCacheDC(hDC);
if (FAILED(hr) || NULL == m_Analysis)
return false;
return true;
}
bool EditData::CP2X(int nCP, int* pX)
{
if (NULL == pX)
return false;
if (NULL == m_Analysis)
{
if (false ==this->StringAnalysis())
return false;
}
int nX = 0;
HRESULT hr = ScriptStringCPtoX(
m_Analysis,
nCP, // 要计算第一个字符
FALSE, // 光标在字符前面还是后面
pX // 返回值
);
if (FAILED(hr))
return false;
return true;
}
bool EditData::X2CP(int nX, int* pnCP, int* pbTrailOrLead)
{
if (NULL == pnCP || NULL == pbTrailOrLead)
return false;
if (NULL == m_Analysis)
{
if (false ==this->StringAnalysis())
return false;
}
HRESULT hr = ScriptStringXtoCP(
m_Analysis,
nX,
pnCP,
pbTrailOrLead // 光标在字符前面还是后面
);
if (FAILED(hr))
return false;
return true;
}
// If nStartChar is 0 and nEndChar is –1, all the text in the edit control is selected.
// If nStartChar is –1, any current selection is removed.
void EditData::SetSelectionInfo(int nStart, int nEnd, bool& bUpdate)
{
if (-1 == nStart)
{
// m_nSelStart = m_nCaret;
SetCaret(m_nCaret, true, bUpdate);
}
else if (-1 == nEnd)
{
// m_nSelStart = nStart;
// m_nCaret = m_strText.length();
SetCaret(nStart, true, bUpdate);
SetCaret(m_strText.length(), false, bUpdate);
}
else
{
// m_nSelStart = nStart;
// m_nCaret = nEnd;
SetCaret(nStart, true, bUpdate);
SetCaret(nEnd, false, bUpdate);
}
}
void EditData::GetSelectionInfo(int& nLeft, int& nRight) const
{
if (m_nSelStart < m_nCaret)
{
nLeft = m_nSelStart;
nRight = m_nCaret;
}
else
{
nLeft = m_nCaret;
nRight = m_nSelStart;
}
}
void EditData::GetSelectionText(String& str)
{
int nLeft = 0, nRight = 0;
this->GetSelectionInfo(nLeft, nRight);
str = m_strText.substr(nLeft, nRight-nLeft);
}
void EditData::CutToClipboard()
{
if (IsSelectionExist())
{
this->CopyToClipboard();
bool bUpdate = false;
this->Delete(bUpdate);
}
}
void EditData::CopyToClipboard()
{
if (IsSelectionExist() && OpenClipboard(NULL))
{
EmptyClipboard();
String strSelectionText;
this->GetSelectionText(strSelectionText);
int nSize = sizeof(TCHAR) * (strSelectionText.length() + 1);
HGLOBAL hBlock = GlobalAlloc(GMEM_MOVEABLE, nSize);
if (hBlock)
{
void* p = GlobalLock( hBlock);
memcpy(p, strSelectionText.c_str(), nSize);
GlobalUnlock( hBlock);
}
SetClipboardData (CF_UNICODETEXT, hBlock);
CloseClipboard();
// We must not free the object until CloseClipboard is called.
if (hBlock)
GlobalFree( hBlock);
}
}
void EditData::PasteFromClipboard()
{
DeleteSelectionText();
if (OpenClipboard(NULL))
{
HANDLE handle = GetClipboardData( CF_UNICODETEXT);
if (handle)
{
// Convert the ANSI string to Unicode, then
// insert to our buffer.
WCHAR* pwszText = ( WCHAR*)GlobalLock( handle);
if (pwszText)
{
// Copy all characters up to null.
bool bUpdate = false;
ReplaceString(String(pwszText), bUpdate);
GlobalUnlock(handle);
}
}
CloseClipboard();
}
}
这个类并不复杂,但其中可能大家对uniscribe并不熟悉,它其实是操作系统提供的一个组件,它的主要作用就是提供USP支持:USP其实是英语Unicode Scripts Processor的简称,意思就是“Unicode文字系统处理器”。它主要包括以下的部件:
1、把文字从输入次序重排成为显示次序
2、把文字按前文后理作出适当的变换
3、按文字显示的方向作出字符的替换
从代码中来看,通过它来实现字符和字符位置的相互转换,这到省了想法设法去计算。