Wtl的sdi应用,视图默认铺满框架的客户区。视图通常用modeless对话框,所有的界面元素都拥挤在左上角,这明显很丑陋。我们尝试让视图居中显示,保持原始大小,这是个很典型的问题,看似简单,诸多细节,逐一解决后,对Wtl的理解程度,马上能达到通透的水平。

Wtl比较臭名昭著的一点:没有官方资料。许多问题只能靠分析源代码来解决。本文详细的描述整个解决过程,以及如何快速的阅读、分析Wtl源代码。

一、Google之路:
    本世纪只要有最低智商的人,首先的方式肯定是Google,我们来看看能否通过Google来找到答案。非常遗憾,我们只能找到一篇Mfc领域的文章:

Making the SDI view smaller than the CFrameWnd:

http://www.codeproject.com/Articles/13621/Making-the-SDI-view-smaller-than-the-CFrameWnd

这篇文章讲解了在mfc中,怎样让视图"比框架窗口小",他花费了大量的精力解决闪烁的问题...事后我发现他完全走错了路子:1、闪烁的问题并非因为他所理解的原因;2、他的解决方法,如果将视图设置得更小一些,仍然会闪烁。使用Wtl center view sdi之类的关键词,无论你怎么搜索,相信你也找不到一篇...任何一篇文章,说明如何处理。你不必再尝试,因为我整整花费了两个小时,专注,中途绝对没有转移视线。Google大师没有找到的,你肯定也找不到。偷懒没用的时候,你只有动用终极手段,读代码、理解,然后自己搞定。当然,这种终极手段远没有那么辛劳,只要随时注意大而化之...

二、第一步:创建时居中显示:
    在CMainFrame类的OnCreate函数中:MESSAGE_HANDLER(WM_CREATE, OnCreate)
    m_hWndClient=m_view.Create(m_hWnd);
    这里m_view创建了视图窗口,m_hWndClient保留了视图的句柄。我们在这里居中显示,试试看...
    m_view.CenterWindow(m_hWnd);//当然,这个实在整个窗体居中,我们可以自行写函数处理在客户区居中。为了快速实现,我们暂时忽略细节。
    你会很失望,因为运行之后,这行代码没有发生任何作用。原因何在?CMainFrame的基类,肯定对视图的显示做了处理,让对话框铺满窗体,需要改变其大小。我们先用一个暴力的方法,让基类不知道这是视图:将m_hWndClient=m_view.Create(m_hWnd)修改为m_view.Create(m_hWnd)。再看看...果然,对话框居中显示,很正常。基类明显针对m_hWndClent处理,当m_hWndClient为NULL的时候,代码也肯定做了判断,因此程序能正常运行。

我们当然不能用这种粗暴的方式,程序员一般总要装得绅士一些...那么雅致一点,就意味着大量的工作,我们首先要做的,是找到基类里这部份内容。

三、第二步:CMainFrame的继承关系
  先简单阅读一下向导生成的CMainFrame代码,方式很简单,看继承自哪些类,看消息映射,看函数的名字...除非必要,不要过多的看函数的细节。
  1、CMainFrame类的继承关系:
      在vs2013中,鼠标指向类名,然后右键在快捷菜单中点击"转向定义",很容易查出CMainFrame的继承关系。

class CMainFrame :
public CFrameWindowImpl<CMainFrame>,
public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler template <class T, class TBase = ATL::CWindow, class TWinTraits = ATL::CFrameWinTraits>
class ATL_NO_VTABLE CFrameWindowImpl : public CFrameWindowImplBase< TBase, TWinTraits > template <class TBase = ATL::CWindow, class TWinTraits = ATL::CFrameWinTraits>
class ATL_NO_VTABLE CFrameWindowImplBase : public ATL::CWindowImplBaseT< TBase, TWinTraits >

很清晰,CMainFrame<-CFrameWindowImpl<-CFrameWindowImplBase<-ATL::CWindowImplBaseT,到Atl一层我们暂时不用管了...Wtl的提供的框架基类,包括两层CFrameWindowImpl<-CFrameWindowImplBase,向导创建的CMainFrame和这两个基类,就是Wtl关于框架类的全部源代码。

四、第三步:找到修改视图大小的地方

1、查看CMainFrame和两个基类的消息映射表:
    可以看到CMainframe的映射表最后,链接了CFrameWindowImpl。同样,后者链接了CFrameWindowImplBase。解释一下所谓的链接

CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
    意思是,本类的消息映射表中没有处理的,将在链接的CFrameWindowImpl<CMainFrame>的映射表中继续响应。本类的消息映射表中处理了的,如果bHandled为false,表示没有处理完成,消息仍然会在CFrameWindowImpl<CMainFrame>的映射表中继续响应。如果为bHanded如果为true,则表示消息处理完成,映射表中就不会往下传递,即使链接了CFrameWindowImplBase,且CFrameWindowImplBase的消息映射表有响应函数,它也不会执行。
    可以直观的设置断点,然后单步执行,能看到在消息映射表中从上到下执行的过程。
    因此,消息映射表的顺序是非常重要的,如果CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)放在最前面...这个顺序会倒过来,派生类就不太好覆盖基类的处理。
2、分别查看三个类的消息映射表:

那么,和创建、位置有关的,我们在CFrameWindowImpl中,看到Onsize函数,视图的位置、大小就是在这里改变的:

OnSize(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled){
if(wParam != SIZE_MINIMIZED)
{
T* pT = static_cast<T*>(this);
pT->UpdateLayout();
}
bHandled = FALSE;
return ;
}

3、理解模板多态:

这部分代码比较好理解,这就是"模板多态" T* pT = static_cast<T*>(this);这里按继承关系,T是我们传入的CMainFrame类型,将this转化成CMainFrame类的指针,然后执行pT->UpdateLayout();而UpdateLayout()在CFrameWindowImplBase中实现,因此:
    首先,我们的CMainFrame中没有重新定义UpdateLayout,但由于CMainFrame归根揭底是从CFrameWindowImplBase中继承下来的,拥有这个函数
所以这种情形下,执行的是CFrameWindowImplBase的UpdateLayout()
    然后,假设我们在CMainFrame里定义了完全同型的UpdateLayout函数,那么,指向CMainFrame指针的pT,当然只会执行我们定义的UpdateLayout,基类定义的函数就成为摆设。这就是所谓的模板多态...基类不知道派生类会有多少种、各自什么名称,所以继承的时候要将派生类名称传递给基类
这也是这种继承方式的来由:class CMainFrame : public CFrameWindowImpl<CMainFrame>
我们可以看看UpdateLayout的代码,继续动用"转向定义"

4、UpdateLayout代码分析:
    多数情况下,我们在翻看代码的时候,不必深入函数的实现细节。比如UpdateLayout,从名字上可以看到,是更新窗体的布局。函数在父类CFrameWindowImpl中调用,在祖父类CFrameWindowImplBase实现,调用是采用模板多态。由于CMainFrame和父类中都没有覆盖,因此调用的就是祖父类CFrameWindowImplBase中定义的函数。一般情况下这么理解基本就可以了。
    只有在特殊情况下,我们才需要详细分析某个函数,因为我们要弄清它如何改变视图大小、我们也要阻止它。
   下面就是祖父类中的UpdateLayout的代码,注释比较清晰。

void UpdateLayout(BOOL bResizeBars = TRUE)
{
RECT rect = { };
GetClientRect(&rect); //获取整个应用的客户区rect,这只是除去窗口的标题、边框之后,剩下的窗体工作区域 // position bars and offset their dimensions
UpdateBarsPosition(rect, bResizeBars); //该rect减去菜单、工具栏、状态栏所占区域
//此处得到的rect是全部客户区,可以在这个范围内居中显示 //如果不要铺满视图,则注释掉下面的语句,会出现状态栏残痕,这是UpdateBarsPosition要处理的
// resize client window
if(m_hWndClient != NULL) //这里将客户区铺满。如果注释掉,则大小变化的时候,状态栏会出现异常,前面部分区域没有消除
::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOZORDER | SWP_NOACTIVATE);
} void UpdateBarsPosition(RECT& rect, BOOL bResizeBars = TRUE)
{
// resize toolbar
if(m_hWndToolBar != NULL && ((DWORD)::GetWindowLong(m_hWndToolBar, GWL_STYLE) & WS_VISIBLE))
{
if(bResizeBars != FALSE)
{
::SendMessage(m_hWndToolBar, WM_SIZE, , ); //相当于调用函数,消息执行完后才执行下一条,是同步代码,可以理解为调用某个函数
::InvalidateRect(m_hWndToolBar, NULL, TRUE);
}
RECT rectTB = { };
::GetWindowRect(m_hWndToolBar, &rectTB);
rect.top += rectTB.bottom - rectTB.top;
} // resize status bar
if(m_hWndStatusBar != NULL && ((DWORD)::GetWindowLong(m_hWndStatusBar, GWL_STYLE) & WS_VISIBLE))
{ //这里没让原来区域失效,因为铺满地窗体将覆盖它,但我们若没有铺满窗体,则这里必须同样失效。
if(bResizeBars != FALSE)
::SendMessage(m_hWndStatusBar, WM_SIZE, , ); RECT rectSB = { };
::GetWindowRect(m_hWndStatusBar, &rectSB);
rect.bottom -= rectSB.bottom - rectSB.top;
}
}

五、解决方案一:在CMainFrame中覆盖UpdateLayout
    我们将代码拷贝到CMainFrame,注释掉改变视图大小的几行语句

void UpdateLayout(BOOL bResizeBars = TRUE)
{
RECT rect = { };
GetClientRect(&rect); //获取整个应用的客户区rect,这只是除去窗口的标题、边框之后,剩下的窗体工作区域 // position bars and offset their dimensions
UpdateBarsPosition(rect, bResizeBars); //该rect减去菜单、工具栏、状态栏所占区域
//此处得到的rect是全部客户区,可以在这个范围内居中显示 //如果不要铺满视图,则注释掉下面的语句,会出现状态栏残痕,这是UpdateBarsPosition要处理的
// resize client window
//if(m_hWndClient != NULL) //这里将客户区铺满。如果注释掉,则大小变化的时候,状态栏会出现异常,前面部分区域没有消除
// ::SetWindowPos(m_hWndClient, NULL, rect.left, rect.top,
// rect.right - rect.left, rect.bottom - rect.top,
// SWP_NOZORDER | SWP_NOACTIVATE);
}

重新运行,显然,我们看到视图居中显示了。效果如下:

但遗憾的是,当我们将应用最大化,或者改变大小的时候,状态栏出现了残留痕迹,视图原来的位置也没有擦除,仍然保留残痕:

当然,改变大小后,视图保持了以前在框架中的位置,没有居中,这是因为我们没有在onsize中处理。我们先解决简单的,为CMainFrame响应WM_ONSIZE消息,在消息映射表加上MESSAGE_HANDLER(WM_SIZE, OnSize),然后消息处理函数:

LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (m_view.IsWindow())
{
m_view.CenterWindow(m_hWnd);
}
bHandled = False;
return ;
}

这里设置bHandled = False;这样基类的onsize会执行UpdateLayout,这里就不用多此一举。至于残留痕迹问题,我们继续看代码。

六、第四步:找出主窗体大小改变时,残痕产生的原因
    上面说的残痕,很明显,整个界面都紊乱了,我们首先要找到原因。按照上面同样的方式,我们可以看到,父类只有个OnSize函数,祖父类消息映射中则处理了两个:擦除背景的消息,是return 1,也就是说,如果存在视图,默认的背景擦除就不调用了,这里直接处理。

这等于屏蔽了背景擦除或者重画。

LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
if(m_hWndClient != NULL) // view will paint itself instead就是说这由视图来做
return ; bHandled = FALSE;
return ;
}

为什么要屏蔽?因为,视图铺满客户区的情形下...根本无需擦除背景。同时,前面在UpdateLayout中,状态栏没有发消息重画,也是同样的原因。所以,这里可以看出,Wtl的Frame类设计的基础,就是视图铺满客户区。
    题外话,祖父类中还处理了一个消息OnSetFocus,即程序启动之后,视图即获得焦点

LRESULT OnSetFocus(UINT, WPARAM, LPARAM, BOOL& bHandled)
{
if(m_hWndClient != NULL)
::SetFocus(m_hWndClient); bHandled = FALSE;
return ;
}

七、解决方案之二:解决背景擦除问题,让祖父类的OnEraseBackground失去作用
    我们为CMainFrame响应WM_ERASEBKGND消息

MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
return DefWindowProc(uMsg, wParam, lParam);
}

这里的代码很简单,即对于WM_ERASEBKGND消息,调用默认的消息处理函数。注意这是一个宏,调用的也不是win32 api而是基类的函数。由于bHandle默认为true,因此该消息对Chain的父类、祖父类不再可见。这样,重新运行,不错,现在视图能够正常的居中,无论最大化、改变主窗体大小,都能正常居中,不再有残影。到此为止,我们的居中小视图事业...算是圆满解决?

No,接下来还剩下一个大的问题,闪烁!
    我们最大化主窗体,或者改变大小,都能看到明显的闪烁。细节看出态度,态度决定一切...我们,怎么能闪烁不已呢?

八、第五步:找出闪烁产生的原因
    首先,闪烁之Google大计。很痛苦,很痛苦,当百度活得滋润的时候,你完全是在受它的折磨和蹂躏。当我终于发现bing也能找到不少国外的资料时,你发现它确实很弱智。最终,你还得使用Google,无论你用什么办法。一时手痒,搜索了一下闪烁,哗啦啦,铺天盖地,无论是Win32、mfc、Wtl、Duilib、Qt...闪烁无所不在。
    同时,解决的方法也无奇不有,双缓冲?自行处理擦除?局部擦除?尽量避免重画?一个悲哀的结论是:即使微软自己的程序,闪烁也几乎无所不在。
    我悄悄地尝试了各种方式...对不起,没有一种能够消除刚才的闪烁...上面提到的哪篇Mfc居中显示视图的文章,用Wtl原样实现,闪烁还是很明显,毫无变化。虽然奇怪,后来发现,他的视图设置得比较大,几乎铺满了框架...而我的视图很小,遮挡不住啊。
    将这位老先生的视图改小,我那个...去!这位费劲九牛二虎之力,致力于消除闪烁,号称比微软普通软件都要不闪亮的兄弟...这视图仍然是闪烁滴,你说这事儿闹的。

既然各种方法没用,我们反过来思考,多数闪烁现象,是因为窗体控件太多,在屏幕不同刷新周期显示,各种法门大体从快速、一次显示角度出发,或者减少擦除出发。但我们这里遇到的问题,整个框架,只有一个窗体,也就是我们的视图,没道理闪烁。

我再仔细观察了一下,闪烁的现象:最大化时,显示视图的同时,视图原来的位置跳动了一下,看到原位置视图、视图内的文字都跳动一下然后消失,再正常的显示居中的视图本身。这说明什么呢?月黑风高,一道闪电从窗外怯生生的探进头来...Onsize中居中,此时在正确的位置显示视图。但居中之前,很明显在原来的位置已经显示了视图,只是瞬间被擦除。
    瞬间,自动擦除背景、原位置显示再瞬间消失...这样怎可能不闪烁?

九、解决方案之三:消除闪烁
    那么...当窗体大小变化时,我们先隐藏之...OnSize先令其居中,然后显示之...问题岂非解决?

//Hide it here
LRESULT OnGetMinMaxInfo(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (m_view.IsWindow())
{
m_view.ShowWindow(SW_HIDE);
}
bHandled = false;
return TRUE;
} //center it here
LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if (m_view.IsWindow())
{
m_view.CenterWindow(m_hWnd);
m_view.ShowWindow(SW_SHOW);
}
bHandled = False;
return ;
} //fix position changed here
//
LRESULT OnWindowPosChanged(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
{
if (m_view.IsWindow() && !m_view.IsWindowVisible())
{
m_view.ShowWindow(SW_SHOW);
}
bHandled = False;
return ;
}

解决了这个问题后,很悲哀的看到了一篇:duilib启动程序时会闪一下(一闪而过)的解决方案。这也说明,但凡Win32编程,道理都是互通的。
这个几乎是相同的问题...但这哥们只是处理了创建之初,没有遇到主窗体大小改变的情形。
    所以,设计器中创建modeless对话框的时候,默认visble属性为false,不是没有道理的,我们手工的Show...可以避免这类启动时的问题。最后的效果,在原来居中的情形下,主窗体变大后,正常的居中显示视图:

十、留下作业?
    我们当然不是打算仅仅使视图居中,我们还需要切换不同的视图,这些视图基本上是modeless对话框,这就是简单的界面框架,让wtl能够实现主要流行界面。那么接下来我们还剩下哪些工作?
    1、将m_view改为指针?这意味着视图必需自删除
    2、切换视图,这需要有通用的方式处理PreTranslateMessage
    3、视图能够在框架中指定位置,并随大小移动?
        这需要提供相对位置、相对大小的函数,或者在CMainFrame中使用CDialogResize
    4、最最重要的是:将上述内容写成嵌入类?
        这绝对是必要的...但大家能够看到,我一向遵循从具体到抽象、有必要才抽象的次序。尤其是界面相关的编程中,先实现效果,再抽象就是件很简 单的事情。嵌入类是什么?前面我们看了两个基类的代码,嵌入类就很明显...使用模板多态的类。我们CMainFrame继承自该类,并将消息映射Chain到该类...上面出现的大量重载函数、消息映射、消息处理函数,就不用再写了。当然,要保证消息映射表中,嵌入类在 CFrameWindowImpl之前。

十一、有Wtl的书籍吗?
    我还真没找到,即使可怜的如Mfc程序员的Wtl指南、Wtl指导教程之类,都存在版本严重滞后的问题,也存在知识陈旧的问题,Win98干我们甚是?
C++ 11之后,Wtl也成为较好的UI选择之一,模板编程的思维比较接近。
    如果真没有...或许空闲的时候,我准备就Wtl最新的版本,结合C++ 11,结合一个具体项目的一部分...用本文的风格来完成?考虑到Wtl的冷僻程度,这或许是个注定亏本的买卖。没有契机...很遗憾,Wtl的小世界,暂时,仍然要生活在碎片化资料、不同版本资料之中,与Google相伴。

本文作者:毕丹军(11084184@qq.com),转载请略礼貌些,保留出处。

Wtl之奇技淫巧篇:一、SDI如何居中显示视图的更多相关文章

  1. div中img依据不同分辨率居中显示,超出部分隐藏

    在做banner居中时 碰到的问题,知道可以用背景图实现居中显示,但是内心是想深究下的,故找到几种办法收集一下,后面两种真的是奇技淫巧 来着下面两处 https://www.zhihu.com/que ...

  2. VC++环境下单文档SDI与OpenGL多视图分割窗口的实现-类似3DMAX的主界面

    本文主要讲述如何在VC++环境下实现单文档SDI与OpenGL多视图分割窗口,最终的界面类似3DMAX的主界面.首先给出我实现的效果图: 整个实现过程网络上有很多零散的博文,请各位自行搜索,在基于对话 ...

  3. 清除li内a标签的float=left实现a标签在li内居中显示(ul内li不居中显示)

    写在前面: 修改cnblogs主页面菜单显示问题. 问题描述:在给主菜单添加hover样式后发现菜单内容并未居中.见图1. 网上搜索到资料其中一篇讲的可以说简明扼要了,也是伸手党的福利(点我查看原文) ...

  4. 通过vertical-align属性实现“竖向居中”显示

    自学编程大概有大半年的时间了,从15年7月开始学习使用人数最多的JAVA,到后来喜欢上了前端,但由于之间在建筑设计院的工作加班颇为频繁,每天刨去工作,基本没有多少自己个人的时间,只能每天6,7点起床, ...

  5. jQery使网页在显示器上居中显示适用于任何分辨率

    这篇文章主要介绍了jQery使网页在任何分辨率的显示器上居中显示的方法,需要的朋友可以参考下 检测屏幕宽度,并设置为id为frame的div宽度, 根据自己网页的最大宽度来调节,小demo最大宽度为1 ...

  6. CSS基础之居中显示

    这些天忙完了一些项目后,终于有时间来总结一下了.自己就把做项目过程中的体会和理解到的一些东西和大家分享一下.有错请指正!! 在css中,元素居中显示,是基础也是一个小难点.我们经常不知为何总是不能把元 ...

  7. C#怎样保证弹出窗体是唯一并居中显示

    Winform窗体中,假如我从Form1窗体要弹出Form2窗体,写法是这样的: Form2 f2 = new Form2(); f2.Show(); 1.如何使窗体打开时居中显示 //初始化默认窗体 ...

  8. Android Hack1 使用weight属性实现视图的居中显示

    本文地址:http://www.cnblogs.com/wuyudong/p/5898403.html,转载请注明源地址. 如果要实现如下图所示的将按钮居中显示,并且占据父视图的一半,无论屏幕是否旋转 ...

  9. css 图片内容在不同分辨率下居中显示(演示的图片宽度是1920px,当图片宽度大于显示屏的宽度时)

    1.img 图片内容在不同分辨率下居中显示(如果隐藏多余,在img外面套一个div  设定overflow: hidden.div的大小就是img显示区域的大小) <!DOCTYPE html& ...

随机推荐

  1. LINQ to SQL大全

    LINQ to SQL语句 (1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的 ...

  2. SOA问题处理

    R12.1: How To Generate SOA Log For Debugging SOA Provider Issues (文档 ID 828753.1) 转到底部 In this Docum ...

  3. libqrencode 3.4.3 发布,二维码的C解析库

    libqrencode 3.4.3 的命令行增加了 --rle 参数,修复了开发库和命令行工具的一些小 bug. libqrencode (QRencode) 是一个用C语言编写的用来解析二维条形码( ...

  4. 转载:开发者眼中最好的 22 款 GUI 测试工具

    对于很多同学来说gui程序的测试是一个难点,所以我从网上转载了一篇关于gui测试的一篇文章,里面罗列的很多工具,大家可以尝试一下学习学习. 英文原文:22 best GUI testing tools ...

  5. Android布局中涉及的一些属性

    Android:gravity属性 线性布局常见的就是利用LinearLayout进行布局,其中有个比较重要的属性就是android:gravity,在官方文档中是这么描述这个属性的:指定一个元素怎么 ...

  6. [ACM_水题] Yet Another Story of Rock-paper-scissors [超水 剪刀石头布]

    Description Akihisa and Hideyoshi were lovers. They were sentenced to death by the FFF Inquisition. ...

  7. [JAVA] java_实例 获得系统字体

    这个代码可以帮助理解java是如何获取系统字体并设置文字字体: import java.awt.*; import java.awt.event.*; import javax.swing.JComb ...

  8. adblockTester通过js检测用户浏览器是否安装了AdBlock

    adblockTester 简介 首先有必要介绍一下AdBlock,它是一款知名网页广告屏蔽插件,在各大主流浏览器上均有AdBlock插件. AdBlock为用户带来了一片蓝天,却苦了站长,尤其是苦逼 ...

  9. 如何在ASP.NET中用C#将XML转换成JSON

    本文旨在介绍如果通过C#将获取到的XML文档转换成对应的JSON格式字符串,然后将其输出到页面前端,以供JavaScript代码解析使用.或许你可以直接利用JavaScript代码通过Ajax的方式来 ...

  10. C#与数据库访问技术总结(九)之实例

    实例 更新记录 在本例子中,建立一个供用户输入学生学号和姓名的文本框和几个对应不同操作类型的更新信息按钮,当用户输入信息以后单击相应的按钮则执行相应的操作.在此实例中还将接触到服务器信息验证的相关知识 ...