14.2 选词控件的渲染
这个控件由于使用了三个子窗口来实现功能,所以它的渲染窗口实现非常简单,只是负责描绘背景。代码如下。
void FalgardIMEShowWindow::render()
{
IMEShowWindow* w = (IMEShowWindow*)d_window;
const WidgetLookFeel& wlf = getLookNFeel();
wlf.getImagerySection("background").render(*w, 0);
}
读者可能非常奇怪,为什么就这么简单的描绘背景,就会描绘出这么复杂的选词窗口呢。因为我们这里只是负责渲染自己,子窗口的渲染在他们自己的对应渲染窗口中进行。他们的已经被CEGUI实现了,他们是比较复杂的。有兴趣的读者可以自己阅读相关的代码。
下面简单介绍选词控件的外观定义。(第14.1节已经介绍了一部分)。
我们的控件使用了属性的链接的定义。由于我们的需要通过设置控件的属性就能够设置子窗口的属性,所以我们需要定义属性链接。定义如下。
<PropertyLinkDefinition name="IMENameColors" widget="__auto_imename__" targetProperty="TextColours" initialValue="tl:FFFFFF00 tr:FFFFFF00 bl:FF00FF00 br:FF00FF00" redrawOnWrite="true" />
<PropertyLinkDefinition name="InputNameColors" widget="__auto_inputcode__" targetProperty="TextColours" initialValue="tl:FFFFFF00 tr:FFFFFF00 bl:FF00FFFF br:FF00FF00" redrawOnWrite="true" />
这是两个颜色的属性链接。控件中定义了一个名为IMENameColors的属性,它指向子窗口后缀名为"__auto_imename__"的子窗口中的TextColours属性。也就是说如果操作了控件的IMENameColors属性实际上是在操作这个子窗口中的TextColours属性。而这个属性正式子窗口文本的颜色矩形。我们说过CEGUI支持文本的过渡色。只要设置控件的这个属性就可以实现不同的过渡色。读者可以自己试验。tl表示左上,t表示top,l表示left,其他类似。指定redrawOnWrite为真,当这个值被修改后重绘控件。另外一个属性和我们介绍的非常类似,这里就不在介绍了。
背景图片的外观的定义是从外观Vanilla/Shared中的BackgroundOnly图像段中拷贝过来的。它定义了一个图像的框架作为背景。这个框架支持各向的拉伸操作。因此非常时候我们的控件,因为我们需要随时的调整控件的大小。
14.3 使用选词控件
控件设计好了,哪么如何使用他呢。我们说过选词控件需要响应第13章实现的IME消息。把IME类获取的各种文本添加到控件中。
首先,我们要定义IME的实例,并且处理各种IME消息。我们定义了一个名为g_IME的全局变量,并且在Windows窗口过程中调用这个实例的MessageProc函数来处理IME消息。定义和消息处理如下所示。
//全局变量的声明
IME g_IME;
//处理消息,如果处理的话就返回,不在被其他函数处理
if (g_IME.MessageProc(hwnd, uMsg, wParam, lParam))
{
return 0;
}
其次,创建我们的选词控件,我们在程序里创建。代码如下。
//创建一个选词窗口
Window* pIME = winMgr.createWindow("Vanilla/ImeWindow", "IMEShow");
if (pIME)
{
//设置其实位置,其实这一步也没什么用,控件或自己调整位置和大小
pIME->setArea(UVector2(UDim(0, 150), UDim(1, -400)),
UVector2(UDim(0,200), Dim(0,260)));
//添加到背景窗口
background->addChildWindow(pIME);
//默认隐藏窗口
pIME->hide();
//保存到全局变量中,在IME类中使用
g_IMEShower = (IMEShowWindow*)pIME;
}
再次,我们需要修改IME类,响应IME类提供的各种函数来控制选词窗口。
当开始文本混合的时候我们显示选词窗口。
void IME::onCompositionBegin()
{
if (g_IMEShower == NULL)
{
return;
}
//显示窗口
g_IMEShower->show();
//获取当前的激活窗口
CEGUI::Window* pWnd = CEGUI::System::getSingleton().getGUISheet()-> getActiveChild();
//设置这个窗口为跟踪窗口
g_IMEShower->TraceWindow(pWnd);
}
当混合结束后我们隐藏选词窗口。
void IME::onCompositionEnd()
{
if (g_IMEShower == NULL)
{
return;
}
g_IMEShower->hide();
}
当在混合过程中的时候,我们响应各种操作。
void IME::onCompositionStr(LONG flag)
{
if (g_IMEShower == NULL)
{
return;
}
//如果混合结束后,我们注入结果字符串到CEGUI中
if (flag & GCS_RESULTSTR)
{
for (int i=0; i<wcslen(d_pResultStr); i++)
{
CEGUI::System::getSingleton().injectChar(d_pResultStr[i]);
}
}
//如果混合编码变化我们修改混合编码
if (flag&GCS_COMPSTR )
{
if (wcslen(d_pCompStr) <= 0)
{
return;
}
g_IMEShower->setInputCode(ucs_to_utf8(d_pCompStr));
}
//暂不处理这个消息
if (flag&GCS_CURSORPOS )
{
}
获取激活的窗口
CEGUI::Window* pWnd = CEGUI::System::getSingleton().getGUISheet()-> getActiveChild();
//追踪这个窗口
g_IMEShower->TraceWindow(pWnd);
}
当输入法的状态发生变化的时候。修改输入法的名称和全角半角状态。
void IME::onIMEStatusChanged()
{
if (g_IMEShower == NULL)
{
return;
}
STRING str = d_imeName ;
str += L"(" ;
str += (d_bSharp ? L"全角" : L"半角") ;
str += L")";
g_IMEShower->setInputName(ucs_to_utf8(str.c_str()));
g_IMEShower->hide();
}
当选词列表发生变化的时候。
void IME::onCandidateListChanged()
{
if (g_IMEShower == NULL)
{
return;
}
//先清空列表
g_IMEShower->clearWordList();
//然后获取选词窗口的文本,然后注入到CEGUI系统中
for (int i=0; i<d_candidateArray.size(); i++)
{
g_IMEShower->addWord(ucs_to_utf8(d_candidateArray[i].c_str()));
}
}
读者可能不明白ucs_to_utf8函数的作用。它将Unicode的字符串转化为utf8的字符串。我们说过String类接受的Unicode编码格式是UTF8格式。原始的Unicode编码不直接支持。读者可以写一个支持Unicode格式的构造函数,哪么这里就不需要这一步转化了。转化函数如下所示。
const CEGUI::utf8* ucs_to_utf8(const wchar_t* pucs)
{
static char tmpbuf[1024];
memset(tmpbuf, 0, sizeof(tmpbuf));
WideCharToMultiByte(CP_UTF8, 0, pucs, (int)wcslen(pucs), tmpbuf, 1023,0,0);
return (CEGUI::utf8*)tmpbuf;
}
这个函数调用了Window提供的转化函数将Unicode编码转化为UTF8编码。静态缓冲tmpbuf有1024个字节一般来说不可能超过它,所以不必检查缓冲区的溢出。本书的很多代码都是为例子程序而写,可能不是很注重效率以及安全性。读者在使用的时候需要自己考虑。
14.4 本章小结
本章介绍了选词控件的实现,并且介绍了如何使用这个控件。结合第13章介绍的输入法的支持,本章完整的实现了一个输入法的选词控件。读者可以打开例子程序体验,感觉一定很不错哦。