2010年9月6日星期一

MFC VC 控件

4.1 Button

按钮窗口(控件)在MFC中使用CButton表示,CButton包含了三种样式的按钮,Push Button,Check Box,Radio Box。所以在利用CButton对象生成按钮窗口时需要指明按钮的风格。

创建按钮:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中lpszCaption是按钮上显示的文字,dwStyle为按钮风格,除了Windows风格可以使用外(如 WS_CHILD|WS_VISUBLE|WS_BORDER)还有按钮专用的一些风格。

  • BS_AUTOCHECKBOX 检查框,按钮的状态会自动改变 Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box.

  • BS_AUTORADIOBUTTON 圆形选择按钮,按钮的状态会自动改变 Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group.

  • BS_AUTO3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a three-state check box, except that the box changes its state when the user selects it.

  • BS_CHECKBOX 检查框 Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style).

  • BS_DEFPUSHBUTTON 默认普通按钮 Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option).

  • BS_LEFTTEXT 左对齐文字 When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box.

  • BS_OWNERDRAW 自绘按钮 Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class.

  • BS_PUSHBUTTON 普通按钮 Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button.

  • BS_RADIOBUTTON 圆形选择按钮 Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices.

  • BS_3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled.

rect为窗口所占据的矩形区域,pParentWnd为父窗口指针,nID为该窗口的ID值。

获取/改变按钮状态:对于检查按钮和圆形按钮可能有两种状态,选中和未选中,如果设置了BS_3STATE或BS_AUTO3STATE风格就可能出现第三种状态:未定,这时按钮显示灰色。通过调用int CButton::GetCheck( ) 得到当前是否被选中,返回0:未选中,1:选中,2:未定。调用void CButton::SetCheck( int nCheck );设置当前选中状态。

处理按钮消息:要处理按钮消息需要在父窗口中进行消息映射,映射宏为ON_BN_CLICKED( id, memberFxn )id为按钮的ID值,就是创建时指定的nID值。处理函数原型为afx_msg void memberFxn( );

4.2 Static Box

静态文本控件的功能比较简单,可作为显示字符串,图标,位图用。创建一个窗口可以使用成员函数:
BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对静态控件指明专门的风格。

  • SS_CENTER,SS_LEFT,SS_RIGHT 指明字符显示的对齐方式。
  • SS_GRAYRECT 显示一个灰色的矩形
  • SS_NOPREFIX 如果指明该风格,对于字符&将直接显示,否则&将作为转义符,&将不显示而在其后的字符将有下划线,如果需要直接显示&必须使用&&表示。
  • SS_BITMAP 显示位图
  • SS_ICON 显示图标
  • SS_CENTERIMAGE 图象居中显示

控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。

控制显示的图标利用成员函数SetIcon/GetIcon用于设置/得到当前显示的图标。

控制显示的位图利用成员函数SetBitmap/GetBitmap用于设置/得到当前显示的位图。下面一段代码演示如何创建一个显示位图的静态窗口并设置位图

CStatic* pstaDis=new CStatic; pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE, CRect(0,0,40,40),pWnd,1); CBitmap bmpLoad; bmpLoad.LoadBitmap(IDB_TEST); pstaDis->SetBitmap(bmpLoad.Detach()); 

4.3 Edit Box

Edit窗口是用来接收用户输入最常用的一个控件。创建一个输入窗口可以使用成员函数:
BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对输入控件指明专门的风格。

  • ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明输入文字超出显示范围时自动滚动。
  • ES_CENTER,ES_LEFT,ES_RIGHT 指定对齐方式
  • ES_MULTILINE 是否允许多行输入
  • ES_PASSWORD 是否为密码输入框,如果指明该风格则输入的文字显示为*
  • ES_READONLY 是否为只读
  • ES_UPPERCASE,ES_LOWERCASE 显示大写/小写字符

控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。

通过GetLimitText/SetLimitText可以得到/设置在输入框中输入的字符数量。

由于在输入时用户可能选择某一段文本,所以通过void CEdit::GetSel( int& nStartChar, int& nEndChar )得到用户选择的字符范围,通过调用void CEdit::SetSel( int nStartChar, int nEndChar, BOOL bNoScroll = FALSE )可以设置当前选择的文本范围,如果指定nStartChar=0 nEndChar=-1则表示选中所有的文本。void ReplaceSel( LPCTSTR lpszNewText, BOOL bCanUndo = FALSE )可以将选中的文本替换为指定的文字。

此外输入框还有一些和剪贴板有关的功能,void Clear( );删除选中的文本,void Copy( );可将选中的文本送入剪贴板,void Paste( );将剪贴板中内容插入到当前输入框中光标位置,void Cut( );相当于Copy和Clear结合使用。

最后介绍一下输入框几种常用的消息映射宏:

  • ON_EN_CHANGE 输入框中文字更新后产生
  • ON_EN_ERRSPACE 输入框无法分配内存时产生
  • ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在输入框失去/得到输入焦点时产生

使用以上几种消息映射的方法为定义原型如:afx_msg void memberFxn( );的函数,并且定义形式如ON_Notification( id, memberFxn )的消息映射。如果在对话框中使用输入框,Class Wizard会自动列出相关的消息,并能自动产生消息映射代码。

4.4 Scroll Bar

Scroll Bar一般不会单独使用,因为SpinCtrl可以取代滚动条的一部分作用,但是如果你需要自己生成派生窗口,滚动条还是会派上一些用场。创建一个滚动条可以使用成员函数: :
BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对滚动条指明专门的风格。

  • SBS_VERT 风格将创建一个垂直的滚动条。
  • SBS_HORZ 风格将创建一个水平的滚动条。

在创建滚动条后需要调用void SetScrollRange( int nMinPos, int nMaxPos, BOOL bRedraw = TRUE )设置滚动范围,
int GetScrollPos( )/int SetScrollPos( )用来得到和设置当前滚动条的位置。

void ShowScrollBar( BOOL bShow = TRUE );用来显示/隐藏滚动条。

BOOL EnableScrollBar( UINT nArrowFlags = ESB_ENABLE_BOTH )用来设置滚动条上箭头是否为允许状态。nArrowFlags可取以下值:

  • ESB_ENABLE_BOTH 两个箭头都为允许状态
  • ESB_DISABLE_LTUP 上/左箭头为禁止状态
  • ESB_DISABLE_RTDN 下/右箭头为禁止状态
  • ESB_DISABLE_BOTH 两个箭头都为禁止状态

如果需要在滚动条位置被改变时得到通知,需要在父窗口中定义对消息WM_VSCROLL/WM_HSCROLL的映射。方法为在父窗口类中重载
afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )/afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar )
所使用的消息映射宏为:ON_WM_VSCROLL( ),ON_WM_HSCROLL( ),在映射宏中不需要指明滚动条的ID,因为所有滚动条的滚动消息都由同样的函数处理。在OnHScroll/OnVScroll的第三个参数会指明当前滚动条的指针。第一个参数表示滚动条上发生的动作,可取以下值:

  • SB_TOP/SB_BOTTOM 已滚动到顶/底部
  • SB_LINEUP/SB_LINEDOWN 向上/下滚动一行
  • SB_PAGEDOWN/SB_PAGEUP 向上/下滚动一页
  • SB_THUMBPOSITION/SB_THUMBTRACK 滚动条拖动到某一位置,参数nPos指明当前位置(参数nPos在其它的情况下是无效的)
  • SB_ENDSCROLL 滚动条拖动完成(用户松开鼠标)

4.5 List Box/Check List Box

ListBox窗口用来列出一系列的文本,每条文本占一行。创建一个列表窗口可以使用成员函数:
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对列表控件指明专门的风格。

  • LBS_MULTIPLESEL 指明列表框可以同时选择多行
  • LBS_EXTENDEDSEL 可以通过按下Shift/Ctrl键选择多行
  • LBS_SORT 所有的行按照字母顺序进行排序

在列表框生成后需要向其中加入或是删除行,可以利用:
int AddString( LPCTSTR lpszItem )添加行,
int DeleteString( UINT nIndex )删除指定行,
int InsertString( int nIndex, LPCTSTR lpszItem )将行插入到指定位置。
void ResetContent( )可以删除列表框中所有行。
通过调用int GetCount( )得到当前列表框中行的数量。

如果需要得到/设置当前被选中的行,可以调用int GetCurSel( )/int SetCurSel(int iIndex)。如果你指明了选择多行的风格,你就需要先调用int GetSelCount( )得到被选中的行的数量,然后int GetSelItems( int nMaxItems, LPINT rgIndex )得到所有选中的行,参数rgIndex为存放被选中行的数组。通过调用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框内指定行的字符串。

此外通过调用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在当前所有行中查找指定的字符传的位置,nStartAfter指明从那一行开始进行查找。
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以选中包含指定字符串的行。

在MFC 4.2版本中添加了CCheckListBox类,该类是由CListBox派生并拥有CListBox的所有功能,不同的是可以在每行前加上一个检查框。必须注意的是在创建时必须指明LBS_OWNERDRAWFIXED或LBS_OWNERDRAWVARIABLE风格。

通过void SetCheckStyle( UINT nStyle )/UINT GetCheckStyle( )可以设置/得到检查框的风格,关于检查框风格可以参考4.1 Button中介绍。通过void SetCheck( int nIndex, int nCheck )/int GetCheck( int nIndex )可以设置和得到某行的检查状态,关于检查框状态可以参考4.1 Button中介绍。

最后介绍一下列表框几种常用的消息映射宏:

  • ON_LBN_DBLCLK 鼠标双击
  • ON_EN_ERRSPACE 输入框无法分配内存时产生
  • ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在输入框失去/得到输入焦点时产生
  • ON_LBN_SELCHANGE 选择的行发生改变

使用以上几种消息映射的方法为定义原型如:afx_msg void memberFxn( );的函数,并且定义形式如ON_Notification( id, memberFxn )的消息映射。如果在对话框中使用列表框,Class Wizard会自动列出相关的消息,并能自动产生消息映射代码。

4.6 Combo Box/Combo Box Ex

组合窗口是由一个输入框和一个列表框组成。创建一个组合窗口可以使用成员函数:
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );
其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对列表控件指明专门的风格。

  • CBS_DROPDOWN 下拉式组合框
  • CBS_DROPDOWNLIST 下拉式组合框,但是输入框内不能进行输入
  • CBS_SIMPLE 输入框和列表框同时被显示
  • LBS_SORT 所有的行按照字母顺序进行排序

由于组合框内包含了列表框,所以列表框的功能都能够使用,如可以利用:
int AddString( LPCTSTR lpszItem )添加行,
int DeleteString( UINT nIndex )删除指定行,
int InsertString( int nIndex, LPCTSTR lpszItem )将行插入到指定位置。
void ResetContent( )可以删除列表框中所有行。
通过调用int GetCount( )得到当前列表框中行的数量。

如果需要得到/设置当前被选中的行的位置,可以调用int GetCurSel( )/int SetCurSel(int iIndex)。通过调用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框内指定行的字符串。

此外通过调用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在当前所有行中查找指定的字符传的位置,nStartAfter指明从那一行开始进行查找。
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以选中包含指定字符串的行。

此外输入框的功能都能够使用,如可以利用:
DWORD GetEditSel( ) /BOOL SetEditSel( int nStartChar, int nEndChar )得到或设置输入框中被选中的字符位置。
BOOL LimitText( int nMaxChars )设置输入框中可输入的最大字符数。
输入框的剪贴板功能Copy,Clear,Cut,Paste动可以使用。

最后介绍一下列表框几种常用的消息映射宏:

  • ON_CBN_DBLCLK 鼠标双击
  • ON_CBN_DROPDOWN 列表框被弹出
  • ON_CBN_KILLFOCUS / ON_CBN_SETFOCUS 在输入框失去/得到输入焦点时产生
  • ON_CBN_SELCHANGE 列表框中选择的行发生改变
  • ON_CBN_EDITUPDATE 输入框中内容被更新

使用以上几种消息映射的方法为定义原型如:afx_msg void memberFxn( );的函数,并且定义形式如ON_Notification( id, memberFxn )的消息映射。如果在对话框中使用组合框,Class Wizard会自动列出相关的消息,并能自动产生消息映射代码。

4.7 Tree Ctrl

树形控件TreeCtrl和下节要讲的列表控件 ListCtrl在系统中大量被使用,例如Windows资源管理器就是一个典型的例子。

树形控件可以用于树形的结构,其中有一个根接点(Root)然后下面有许多子结点,而每个子结点上有允许有一个或多个或没有子结点。MFC中使用CTreeCtrl类来封装树形控件的各种操作。通过调用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些树形控件的专用风格:

  • TVS_HASLINES 在父/子结点之间绘制连线

  • TVS_LINESATROOT 在根/子结点之间绘制连线

  • TVS_HASBUTTONS 在每一个结点前添加一个按钮,用于表示当前结点是否已被展开

  • TVS_EDITLABELS 结点的显示字符可以被编辑

  • TVS_SHOWSELALWAYS 在失去焦点时也显示当前选中的结点

  • TVS_DISABLEDRAGDROP 不允许Drag/Drop

  • TVS_NOTOOLTIPS 不使用ToolTip显示结点的显示字符

在树形控件中每一个结点都有一个句柄(HTREEITEM),同时添加结点时必须提供的参数是该结点的父结点句柄,(其中根Root结点只有一个,既不可以添加也不可以删除)利用
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一个结点,pszItem为显示的字符,hParent代表父结点的句柄,当前添加的结点会排在hInsertAfter表示的结点的后面,返回值为当前创建的结点的句柄。下面的代码会建立一个如下形式的树形结构:

+--- Parent1 +--- Child1_1 +--- Child1_2 +--- Child1_3 +--- Parent2 +--- Parent3 /*假设m_tree为一个CTreeCtrl对象,而且该窗口已经创建*/ HTREEITEM hItem,hSubItem; hItem = m_tree.InsertItem("Parent1",TVI_ROOT); 在根结点上添加Parent1 hSubItem = m_tree.InsertItem("Child1_1",hItem); //在Parent1上添加一个子结点 hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem); //在Parent1上添加一个子结点,排在Child1_1后面 hSubItem = m_tree.InsertItem("Child1_3",hItem,hSubItem); hItem = m_tree.InsertItem("Parent2",TVI_ROOT,hItem); hItem = m_tree.InsertItem("Parent3",TVI_ROOT,hItem); 

如果你希望在每个结点前添加一个小图标,就必需先调用CImageList* SetImageList( CImageList * pImageList, int nImageListType );指明当前所使用的ImageList,nImageListType为TVSIL_NORMAL。在调用完成后控件中使用图片以设置的 ImageList中图片为准。然后调用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加结点,nImage为结点没被选中时所使用图片序号,nSelectedImage为结点被选中时所使用图片序号。下面的代码演示了ImageList的设置。

/*m_list 为CImageList对象 IDB_TREE 为16*(16*4)的位图,每个图片为16*16共4个图标*/ m_list.Create(IDB_TREE,16,4,RGB(0,0,0)); m_tree.SetImageList(&m_list,TVSIL_NORMAL); m_tree.InsertItem("Parent1",0,1); //添加,选中时显示图标1,未选中时显示图标0 

此外CTreeCtrl还提供了一些函数用于得到/修改控件的状态。
HTREEITEM GetSelectedItem( );将返回当前选中的结点的句柄。BOOL SelectItem( HTREEITEM hItem );将选中指明结点。
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某结点所使用图标索引。
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一结点的显示字符。
BOOL DeleteItem( HTREEITEM hItem );用于删除某一结点,BOOL DeleteAllItems( );将删除所有结点。

此外如果想遍历树可以使用下面的函数:
HTREEITEM GetRootItem( );得到根结点。
HTREEITEM GetChildItem( HTREEITEM hItem );得到子结点。
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明结点的上/下一个兄弟结点。
HTREEITEM GetParentItem( HTREEITEM hItem );得到父结点。

树形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于树形控件可能取值和对应的数据结构为:

  • TVN_SELCHANGED 在所选中的结点发生改变后发送,所用结构:NMTREEVIEW

  • TVN_ITEMEXPANDED 在某结点被展开后发送,所用结构:NMTREEVIEW

  • TVN_BEGINLABELEDIT 在开始编辑结点字符时发送,所用结构:NMTVDISPINFO

  • TVN_ENDLABELEDIT 在结束编辑结点字符时发送,所用结构:NMTVDISPINFO

  • TVN_GETDISPINFO 在需要得到某结点信息时发送,(如得到结点的显示字符)所用结构:NMTVDISPINFO

关于ON_NOTIFY有很多内容,将在以后的内容中进行详细讲解。

关于动态提供结点所显示的字符:首先你在添加结点时需要指明lpszItem参数为:LPSTR_TEXTCALLBACK。在控件显示该结点时会通过发送 TVN_GETDISPINFO来取得所需要的字符,在处理该消息时先将参数pNMHDR转换为LPNMTVDISPINFO,然后填充其中 item.pszText。但是我们通过什么来知道该结点所对应的信息呢,我的做法是在添加结点后设置其lParam参数,然后在提供信息时利用该参数来查找所对应的信息。下面的代码说明了这种方法:

char szOut[8][3]={"No.1","No.2","No.3"}; //添加结点 HTREEITEM hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 0 ); hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 1 ); //处理消息 void CParentWnd::OnGetDispInfoTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; pTVDI->item.pszText=szOut[pTVDI->item.lParam]; //通过lParam得到需要显示的字符在数组中的位置 *pResult = 0; } 

关于编辑结点的显示字符:首先需要设置树形控件的TVS_EDITLABELS风格,在开始编辑时该控件将会发送TVN_BEGINLABELEDIT,你可以通过在处理函数中返回TRUE来取消接下来的编辑,在编辑完成后会发送TVN_ENDLABELEDIT,在处理该消息时需要将参数pNMHDR转换为 LPNMTVDISPINFO,然后通过其中的item.pszText得到编辑后的字符,并重置显示字符。如果编辑在中途中取消该变量为NULL。下面的代码说明如何处理这些消息:

//处理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.lParam==0);//判断是否取消该操作 *pResult = 1; else *pResult = 0; } //处理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.pszText==NULL);//判断是否已经取消取消编辑 m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText); //重置显示字符 *pResult = 0; } 

上面讲述的方法所进行的消息映射必须在父窗口中进行(同样WM_NOTIFY的所有消息都需要在父窗口中处理)。

4.8 List Ctrl

列表控件可以看作是功能增强的ListBox,它提供了四种风格,而且可以同时显示一列的多中属性值。MFC中使用CListCtrl类来封装列表控件的各种操作。通过调用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些列表控件的专用风格:

  • LVS_ICON LVS_SMALLICON LVS_LIST LVS_REPORT 这四种风格决定控件的外观,同时只可以选择其中一种,分别对应:大图标显示,小图标显示,列表显示,详细报表显示

  • LVS_EDITLABELS 结点的显示字符可以被编辑,对于报表风格来讲可编辑的只为第一列。

  • LVS_SHOWSELALWAYS 在失去焦点时也显示当前选中的结点

  • LVS_SINGLESEL 同时只能选中列表中一项

首先你需要设置列表控件所使用的ImageList,如果你使用大图标显示风格,你就需要以如下形式调用:
CImageList* SetImageList( CImageList* pImageList, LVSIL_NORMAL);
如果使用其它三种风格显示而不想显示图标你可以不进行任何设置,否则需要以如下形式调用:
CImageList* SetImageList( CImageList* pImageList, LVSIL_SMALL);

通过调用 int InsertItem( int nItem, LPCTSTR lpszItem );可以在列表控件中nItem指明位置插入一项,lpszItem为显示字符。除LVS_REPORT风格外其他三种风格都只需要直接调用 InsertItem就可以了,但如果使用报表风格就必须先设置列表控件中的列信息。

通过调用 int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat , int nWidth, int nSubItem);可以插入列。iCol为列的位置,从零开始,lpszColumnHeading为显示的列名,nFormat为显示对齐方式, nWidth为显示宽度,nSubItem为分配给该列的列索引。

在有多列的列表控件中就需要为每一项指明其在每一列中的显示字符,通过调用
BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );可以设置每列的显示字符。nItem为设置的项的位置,nSubItem为列位置,lpszText为显示字符。下面的代码演示了如何设置多列并插入数据:

m_list.SetImageList(&m_listSmall,LVSIL_SMALL);//设置ImageList m_list.InsertColumn(0,"Col 1",LVCFMT_LEFT,300,0);//设置列 m_list.InsertColumn(1,"Col 2",LVCFMT_LEFT,300,1); m_list.InsertColumn(2,"Col 3",LVCFMT_LEFT,300,2); m_list.InsertItem(0,"Item 1_1");//插入行 m_list.SetItemText(0,1,"Item 1_2");//设置该行的不同列的显示字符 m_list.SetItemText(0,2,"Item 1_3"); 

此外CListCtrl还提供了一些函数用于得到/修改控件的状态。
COLORREF GetTextColor( )/BOOL SetTextColor( COLORREF cr );用于得到/设置显示的字符颜色。
COLORREF GetTextBkColor( )/BOOL SetTextBkColor( COLORREF cr );用于得到/设置显示的背景颜色。
void SetItemCount( int iCount );用于得到添加进列表中项的数量。
BOOL DeleteItem(int nItem);用于删除某一项,BOOL DeleteAllItems( );将删除所有项。
BOOL SetBkImage(HBITMAP hbm, BOOL fTile , int xOffsetPercent, int yOffsetPercent);用于设置背景位图。
CString GetItemText( int nItem, int nSubItem );用于得到某项的显示字符。

列表控件的消息映射同样使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXList(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于列表控件可能取值和对应的数据结构为:

  • LVN_BEGINLABELEDIT 在开始某项编辑字符时发送,所用结构:NMLVDISPINFO

  • LVN_ENDLABELEDIT 在结束某项编辑字符时发送,所用结构:NMLVDISPINFO

  • LVN_GETDISPINFO 在需要得到某项信息时发送,(如得到某项的显示字符)所用结构:NMLVDISPINFO

关于ON_NOTIFY有很多内容,将在以后的内容中进行详细讲解。

关于动态提供结点所显示的字符:首先你在项时需要指明lpszItem参数为:LPSTR_TEXTCALLBACK。在控件显示该结点时会通过发送 TVN_GETDISPINFO来取得所需要的字符,在处理该消息时先将参数pNMHDR转换为LPNMLVDISPINFO,然后填充其中 item.pszText。通过item中的iItem,iSubItem可以知道当前显示的为那一项。下面的代码演示了这种方法:

char szOut[8][3]={"No.1","No.2","No.3"}; //添加结点 m_list.InsertItem(LPSTR_TEXTCALLBACK,...) m_list.InsertItem(LPSTR_TEXTCALLBACK,...) //处理消息 void CParentWnd::OnGetDispInfoList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; pLVDI->item.pszText=szOut[pTVDI->item.iItem]; //通过iItem得到需要显示的字符在数组中的位置 *pResult = 0; } 

关于编辑某项的显示字符:(在报表风格中只对第一列有效)首先需要设置列表控件的LVS_EDITLABELS风格,在开始编辑时该控件将会发送 LVN_BEGINLABELEDIT,你可以通过在处理函数中返回TRUE来取消接下来的编辑,在编辑完成后会发送LVN_ENDLABELEDIT,在处理该消息时需要将参数pNMHDR转换为LPNMLVDISPINFO,然后通过其中的item.pszText得到编辑后的字符,并重置显示字符。如果编辑在中途中取消该变量为NULL。下面的代码说明如何处理这些消息:

//处理消息 LVN_BEGINLABELEDIT void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; if(pLVDI->item.iItem==0);//判断是否取消该操作 *pResult = 1; else *pResult = 0; } //处理消息 LVN_BEGINLABELEDIT void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; if(pLVDI->item.pszText==NULL);//判断是否已经取消取消编辑 m_list.SetItemText(pLVDI->item.iItem,0,pLVDI->pszText); //重置显示字符 *pResult = 0; } 

上面讲述的方法所进行的消息映射必须在父窗口中进行(同样WM_NOTIFY的所有消息都需要在父窗口中处理)。

如何得到当前选中项位置:在列表控件中没有一个类似于ListBox中GetCurSel()的函数,但是可以通过调用GetNextItem( -1, LVNI_ALL | LVNI_SELECTED);得到选中项位置。

4.9 Tab Ctrl

Tab属性页控件可以在一个窗口中添加不同的页面,然后在页选择发生改变时得到通知。MFC中使用CTabCtrl类来封装属性页控件的各种操作。通过调用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );创建一个窗口,dwStyle中可以使用以下一些属性页控件的专用风格:

TCS_BUTTONS 使用按钮来表示页选择位置
TCS_MULTILINE 分行显示页选择位置
TCS_SINGLELINE 只使用一行显示页选择位置
在控件创建后必需向其中添加页面才可以使用,添加页面的函数为:
BOOL InsertItem( int nItem, LPCTSTR lpszItem );nItem为位置,从零开始,lpszItem为页选择位置上显示的文字。如果你希望在页选择位置处显示一个图标,你可以调用
BOOL InsertItem( int nItem, LPCTSTR lpszItem, int nImage );nImage指明所使用的图片位置。(在此之前必须调用CImageList * SetImageList( CImageList * pImageList );设置正确的ImageList)

此外CTabCtrl还提供了一些函数用于得到/修改控件的状态。
int GetCurSel( )/int SetCurSel( int nItem );用于得到/设置当前被选中的页位置。
BOOL DeleteItem( int nItem )/BOOL DeleteAllItems( );用于删除指定/所有页面。
void RemoveImage( int nImage );用于删除某页选择位置上的图标。

属性页控件的消息映射同样使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode为通知代码,id为产生该消息的窗口ID,memberFxn为处理函数,函数的原型如同void OnXXXTab(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR为一数据结构,在具体使用时需要转换成其他类型的结构。对于列表控件可能取值和对应的数据结构为:

TCN_SELCHANGE 在当前页改变后发送,所用结构:NMHDR
TCN_SELCHANGING 在当前页改变时发送可以通过返回TRUE来禁止页面的改变,所用结构:NMHDR

一般来讲在当前页发生改变时需要隐藏当前的一些子窗口,并显示其它的子窗口。下面的伪代码演示了如何使用属性页控件:

CParentWnd::OnCreate(...)
{
m_tab.Create(...);
m_tab.InsertItem(0,"Option 1");
m_tab.InsertItem(1,"Option 2");
Create a edit box as the m_tab's Child
Create a static box as the m_tab's Child
edit_box.ShowWindow(SW_SHOW); // edit box在属性页的第一页
static_box.ShowWindow(SW_HIDE); // static box在属性页的第二页
}
void CParentWnd::OnSelectChangeTab(NMHDR* pNMHDR, LRESULT* pResult)
{//处理页选择改变后的消息
if(m_tab.GetCurSel()==0)
{//根据当前页显示/隐藏不同的子窗口
edit_box.ShowWindow(SW_SHOW);
static_box.ShowWindow(SW_HIDE);
}
else
{//
edit_box.ShowWindow(SW_HIDE);
static_box.ShowWindow(SW_SHOW);
}
}

4.A Tool Bar

工具条也是常用的控件。MFC中使用CToolBar类来封装工具条控件的各种操作。通过调用
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP, UINT nID = AFX_IDW_TOOLBAR );创建一个窗口,dwStyle中可以使用以下一些工具条控件的专用风格:

  • CBRS_TOP 工具条在父窗口的顶部

  • TCBRS_BOTTOM 工具条在父窗口的底部

  • CBRS_FLOATING 工具条是浮动的

创建一个工具条的步骤如下:先使用Create创建窗口,然后使用BOOL LoadToolBar( LPCTSTR lpszResourceName );直接从资源中装入工具条,或者通过装入位图并指明每个按钮的ID,具体代码如下:

UINT uID[5]={IDM_1,IDM_2,IDM_3,IDM_4,IDM_5}; m_toolbar.Create(pParentWnd); m_toolbar.LoadBitmap(IDB_TOOLBAR); m_toolbar.SetSizes(CSize(20,20),CSize(16,16));//设置按钮大尺寸和按钮上位图的尺寸 m_toolbar.SetButtons(uID,5); 

AppWizard在生成代码时也会同时生成工具条的代码,同时还可以支持停靠功能。所以一般是不需要直接操作工具条对象。

工具条上的按钮被按下时发送给父窗口的消息和菜单消息相同,所以可以使用ON_COMMAND宏进行映射,同样工具条中的按钮也支持 ON_UPDATE_COMMAND_UI的相关操作,如SetCheck,Enable,你可以将按钮的当作菜单上的一个具有相同ID菜单项。

在以后的章节4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar会给出使用的方法。

4.B Status Bar

状态条用于显示一些提示字符。MFC中使用CStatusBar类来封装状态条控件的各种操作。通过调用
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );创建一个窗口,dwStyle中可以使用以下一些状态条控件的专用风格:

  • CBRS_TOP 状态条在父窗口的顶部

  • TCBRS_BOTTOM 状态条在父窗口的底部

创建一个状态条的步骤如下:先使用Create创建窗口,然后调用BOOL SetIndicators( const UINT* lpIDArray, int nIDCount );设置状态条上各部分的ID,具体代码如下:

UINT uID[2]={ID_SEPARATOR,ID_INDICATOR_CAPS}; m_stabar.Create(pParentWnd); m_stabar.SetIndicators(uID,2); 

通过CString GetPaneText( int nIndex )/BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE )可以得到/设置状态条上显示的文字。

Tip:在创建状态条时最好将状态条中所有的部分ID(除MFC自定义的几个用于状态条的ID外)都设置为ID_SEPARATOR,在生成后调用
void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );改变其风格,ID和宽度。

AppWizard在生成代码时也会同时生成状态条的代码。所以一般是不需要直接创建状态条对象。此外状态条上会自动显示菜单上的命令提示(必须先在资源中定义),所以也不需要人为设置显示文字。

状态条支持ON_UPDATE_COMMAND_UI的相关操作,如SetText,Enable。

在以后的章节4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar会给出使用的方法。

4.C Dialog Bar

Dialog Bar类似一个静态的附在框架窗口上的对话框,由于Dialog Bar可以使用资源编辑器进行编辑所以使用起来就很方便,在设计时就可以对Dialog Bar上的子窗口进行定位。用于显示一些提示字符。MFC中使用CDialogBar类来Dialog Bar控件的各种操作。通过调用
BOOL Create( CWnd* pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID );创建一个窗口,nIDTemplate为对话框资源,nID为该Dialog Bar对应的窗口ID,nStyle中可以使用以下一些状态条控件的专用风格:

  • CBRS_TOP Dialog Bar在父窗口的顶部
  • TCBRS_BOTTOM Dialog Bar在父窗口的底部
  • CBRS_LEFT Dialog Bar在父窗口的左部
  • CBRS_RIGHT Dialog Bar在父窗口的右部

对于Dialog Bar的所产生消息需要在父窗口中进行映射和处理,例如Dialog Bar上的按钮,需要在父窗口中进行ON_BN_CLICKED或ON_COMMAND映射,Dialog Bar上的输入框可以在父窗口中进行ON_EN_CHANGE,ON_EN_MAXTEXT等输入框对应的映射。

Dialog Bar支持ON_UPDATE_COMMAND_UI的相关操作,如SetText,Enable。

在以后的章节4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar会给出使用的方法

4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar

运行时程序界面如界面图,该程序拥有一个工具条用于显示两个命令按钮,一个用于演示如何使按钮处于检查状态,另一个根据第一个按钮的状态来禁止/允许自身。(设置检查状态和允许状态都通过OnUpdateCommand实现)此外Dialog Bar上有一个输入框和按钮,这两个子窗口的禁止/允许同样是根据工具条上的按钮状态来确定,当按下Dialog Bar上的按钮时将显示输入框中的文字内容。状态条的第一部分用于显示各种提示,第二部分用于利用OnUpdateCommand显示当前时间。同时在程序中演示了如何设置菜单项的命令解释字符(将在状态条的第一部分显示)和如何设置工具条的提示字符(利用一个小的ToolTip窗口显示)。

生成应用:利用AppWizard生成一个MFC工程,图例,并设置为单文档界面图例,最后选择工具条,状态条和ReBar支持,图例

修改菜单:利用资源编辑器删除多余的菜单并添加一个新的弹出菜单和三个子菜单,图例,分别是:

名称

ID

说明字符

Check

IDM_CHECK

SetCheck Demo\nSetCheck Demo

Disable

IDM_DISABLE

Disable Demo\nDisable Demo

ShowText on DialogBar

IDM_SHOW_TXT

ShowText on DialogBar Demo\nShowText on DialogBar

\n前的字符串将显示在状态条中作为命令解释,\n后的部分将作为具有相同ID的工具条按钮的提示显示在ToolTip窗口中。

修改 Dialog Bar:在Dialog Bar中添加一个输入框和按钮,按钮的ID为IDM_SHOW_TXT与一个菜单项具有相同的ID,这样可以利用映射菜单消息来处理按钮消息(当然使用不同ID值也可以利用ON_COMMAND来映射Dialog Bar上的按钮消息,但是ClassWizard没有提供为Dialog Bar上按钮进行映射的途径,只能手工添加消息映射代码)。图例

修改工具条:在工具条中添加两个按钮,ID值为IDM_CHECK和IDM_DISABLE和其中两个菜单项具有相同的ID值。图例

利用ClassWizard为三个菜单项添加消息映射和更新命令。图例

修改MainFrm.h文件

//添加一个成员变量来记录工具条上Check按钮的检查状态。 protected: BOOL m_fCheck; //手工添加状态条第二部分用于显示时间的更新命令,和用于禁止/允许输入框的更新命令 //{{AFX_MSG(CMainFrame) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnCheck(); afx_msg void OnUpdateCheck(CCmdUI* pCmdUI); afx_msg void OnDisable(); afx_msg void OnUpdateDisable(CCmdUI* pCmdUI); afx_msg void OnShowTxt(); afx_msg void OnUpdateShowTxt(CCmdUI* pCmdUI); //}}AFX_MSG //上面的部分为ClassWizard自动产生的代码 afx_msg void OnUpdateTime(CCmdUI* pCmdUI); //显示时间 afx_msg void OnUpdateInput(CCmdUI* pCmdUI); //禁止/允许输入框 

修改MainFrm.cpp文件

//修改状态条上各部分ID #define ID_TIME 0x705 //作为状态条上第二部分ID static UINT indicators[] = { ID_SEPARATOR, // status line indicator ID_SEPARATOR, //先设置为ID_SEPARATOR,在状态条创建后再进行修改 }; //修改消息映射 //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() ON_COMMAND(IDM_CHECK, OnCheck) ON_UPDATE_COMMAND_UI(IDM_CHECK, OnUpdateCheck) ON_COMMAND(IDM_DISABLE, OnDisable) ON_UPDATE_COMMAND_UI(IDM_DISABLE, OnUpdateDisable) ON_COMMAND(IDM_SHOW_TXT, OnShowTxt) ON_UPDATE_COMMAND_UI(IDM_SHOW_TXT, OnUpdateShowTxt) //}}AFX_MSG_MAP //以上部分为ClassWizard自动生成代码 ON_UPDATE_COMMAND_UI(ID_TIME, OnUpdateTime) ////显示时间 ON_UPDATE_COMMAND_UI(IDC_INPUT_TEST, OnUpdateInput) //禁止/允许输入框 //修改OnCreate函数,重新设置状态条第二部分ID值 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { .... // by wenyy 修改状态条上第二部分信息 m_wndStatusBar.SetPaneInfo(1,ID_TIME,SBPS_NORMAL,60);//set the width return 0; } //修改经过映射的消息处理函数代码 void CMainFrame::OnCheck() { //在Check按钮被按下时改变并保存状态 m_fCheck=!m_fCheck; } void CMainFrame::OnUpdateCheck(CCmdUI* pCmdUI) { //Check按钮是否设置为检查状态 pCmdUI->SetCheck(m_fCheck); } void CMainFrame::OnDisable() { //Disable按钮被按下 AfxMessageBox("you press disable test"); } void CMainFrame::OnUpdateDisable(CCmdUI* pCmdUI) { //根据Check状态决定自身禁止/允许状态 pCmdUI->Enable(m_fCheck); } void CMainFrame::OnShowTxt() { //得到Dialog Bar上输入框中文字并显示 CEdit* pE=(CEdit*)m_wndDlgBar.GetDlgItem(IDC_INPUT_TEST); CString szO; pE->GetWindowText(szO); AfxMessageBox(szO); } void CMainFrame::OnUpdateShowTxt(CCmdUI* pCmdUI) { //Dialog Bar上按钮根据Check状态决定自身禁止/允许状态 pCmdUI->Enable(m_fCheck); } void CMainFrame::OnUpdateInput(CCmdUI* pCmdUI) { //Dialog Bar上输入框根据Check状态决定自身禁止/允许状态 pCmdUI->Enable(m_fCheck); } void CMainFrame::OnUpdateTime(CCmdUI* pCmdUI) { //根据当前时间设置状态条上第二部分文字 CTime timeCur=CTime::GetCurrentTime(); char szOut[20]; sprintf( szOut, "%02d:%02d:%02d", timeCur.GetHour(), timeCur.GetMinute(),timeCur.GetSecond()); pCmdUI->SetText(szOut); } 

下载演示代码 17K

4.E General Window

从VC提供的MFC类派生图中我们可以看出窗口的派生关系,派生图,所有的窗口类都是由CWnd派生。所有CWnd的成员函数在其派生类中都可以使用。本节介绍一些常用的功能给大家。

改变窗口状态:
BOOL EnableWindow( BOOL bEnable = TRUE );可以设置窗口的禁止/允许状态。BOOL IsWindowEnabled( );可以查询窗口的禁止/允许状态。
BOOL ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 )/BOOL ModifyStyleEx( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );可以修改窗口的风格,而不需要调用SetWindowLong
BOOL IsWindowVisible( ) 可以检查窗口是否被显示。
BOOL ShowWindow( int nCmdShow );将改变窗口的显示状态,nCmdShow可取如下值:

  • SW_HIDE 隐藏窗口
  • SW_MINIMIZE SW_SHOWMAXIMIZED 最小化窗口
  • SW_RESTORE 恢复窗口
  • SW_SHOW 显示窗口
  • SW_SHOWMINIMIZED 最大化窗口

改变窗口位置:
void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );可以移动窗口。
void GetWindowRect( LPRECT lpRect ) ;可以得到窗口的矩形位置。
BOOL IsIconic( ) ;可以检测窗口是否已经缩为图标。
BOOL SetWindowPos( const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags );可以改变窗口的Z次序,此外还可以移动窗口位置。

使窗口失效,印发重绘:
void Invalidate( BOOL bErase = TRUE );使整个窗口失效,bErase将决定窗口是否产生重绘。
void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE )/void InvalidateRgn( CRgn* pRgn, BOOL bErase = TRUE );将使指定的矩形/多边形区域失效。

窗口查找:
static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );可以以窗口的类名和窗口名查找窗口。任一参数设置为NULL表对该参数代表的数据进行任意匹配。如FindWindow("MyWnd",NULL) 表明查找类名为MyWnd的所有窗口。
BOOL IsChild( const CWnd* pWnd ) 检测窗口是否为子窗口。
CWnd* GetParent( ) 得到父窗口指针。
CWnd* GetDlgItem( int nID ) 通过子窗口ID得到窗口指针。
int GetDlgCtrlID( ) 得到窗口ID值。
static CWnd* PASCAL WindowFromPoint( POINT point );将从屏幕上某点坐标得到包含该点的窗口指针。
static CWnd* PASCAL FromHandle( HWND hWnd );通过HWND构造一个CWnd*指针,但该指针在空闲时会被删除,所以不能保存供以后使用。

时钟:
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );可以创建一个时钟,如果lpfnTimer回调函数为NULL,窗口将会收到WM_TIMER消息,并可以在afx_msg void OnTimer( UINT nIDEvent );中安排处理代码
BOOL KillTimer( int nIDEvent );删除一个指定时钟。

可以利用重载来添加消息处理的虚函数:
afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct );窗口被创建时被调用
afx_msg void OnDestroy( );窗口被销毁时被调用
afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );需要得到窗口尺寸时被调用
afx_msg void OnSize( UINT nType, int cx, int cy );窗口改变大小后被调用
afx_msg void OnMove( int x, int y );窗口被移动后时被调用
afx_msg void OnPaint( );窗口需要重绘时时被调用,你可以填如绘图代码,对于视图类不需要重载OnPaint,所有绘图代码应该在OnDraw中进行
afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );接收到字符输入时被调用
afx_msg void OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );键盘上键被按下/放开时被调用
afx_msg void OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point );鼠标左/右键按下时被调用
afx_msg void OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point );鼠标左/右键放开时被调用
afx_msg void OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point );鼠标左/右键双击时被调用
afx_msg void OnMouseMove( UINT nFlags, CPoint point );鼠标在窗口上移动时被调用

4.F 关于WM_NOTIFY的使用方法

WM_NOTIF在WIN32中得到大量的应用,同时也是随着CommControl的出现 WM_NOTIFY成为了CommControl的基本消息。可以这样说CommControl的所有的新增特性都通过WM_NOTIFY来表达。同时 WM_NOTIFY也为CommControl的操作带来了一致性。

WM_NOTIFY消息中的参数如下:
idCtrl = (int) wParam;
pnmh = (LPNMHDR) lParam; 其中lParam为一个

typedef struct tagNMHDR
{
HWND hwndFrom;
UINT idFrom;
UINT code;
} NMHDR; 结构指针
从消息的参数我们已经可以分辩出消息的来源,但是这些信息还不足以分辩出消息的具体含义。所以我们需要更多的数据来得到更多的信息。MS的做法是对每种不同用途的通知消息都定义另一种结构来表示,同时这中结构里包含了struct tagNMHDR,所以你只要进行一下类型转换就可以得到数据指针。例如对于LVN_COLUMNCLICK消息(用于在ListCtrl的列表头有鼠标点击是进行通知),结构为;
typedef struct tagNMLISTVIEW{
NMHDR hdr;
int iItem;
int iSubItem;
UINT uNewState;
UINT uOldState;
UINT uChanged;
POINT ptAction;
LPARAM lParam;
} NMLISTVIEW, FAR *LPNMLISTVIEW;

在这个结构的最开始也就包含了struct tagNMHDR,所以在不损失数据和产生错误的情况下向处理消息的进程提供了更多的信息。

此外通过WM_NOTIFY我们可以一种完全一样的方式进行消息映射,如同在前几章中所见到的一样。
使用如下形式:ON_NOTIFY( wNotifyCode, id, memberFxn )。
处理函数也有统一的原型:afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result );
在MFC消息映射的内部将根据定义消息映射时所使用的wNotifyCode和WM_NOTIFY中参数中pnmh->code(pnmh = (LPNMHDR) lParam)进行匹配,然后调用相应的处理函数。

还有一点是利用WM_NOTIFY/ON_NOTIFY_REFLECT可以在窗口内部处理一些消息,从而建立可重用的控件。

暗牧输出及天赋问题

本文目录:

  第一页:总览-给各位RL的一点关于暗影牧师的小结

  第二页:法术、铭文以及BUFF

  第三页:天赋评价以及推荐天赋

  第四页:输出技巧

  第五页:装备选择原则以及宝石选择

  第六页:FAQ

  本文开始前的提示:这不是一篇“帮助我指出哪个装备需要去获得”的贴子,你可以根据这个贴子去判断哪个装备值得获得。请首先明确这点然后继续往下看。

  回帖中的问题在part IV的FAQ部分作了部分解答

  由于作者本人整个3.13 80%的时间是神圣形态,所以本文可能会在未来的1-2个月内继续修订。

  给出的tips仅供参考,本文仅作为暗牧入门基础指引

  目录

  part I:法术书

  -伤害类法术及铭文

  -资源类法术及铭文

  -暗影牧师能够提供的BUFF

  -暗影牧师需要团队给予的BUFF

  part II:天赋

  -暗影天赋

  -戒律天赋

  -神圣天赋

  -天赋模板&讨论

  part III:输出的技巧

  -输出思想

  -一些有帮助的提示

  -AOE的技巧

  -运动战的技巧

  part IV:物品属性价值

  -属性价值

  -物品价值

  -平衡点

  -FAQ

  总览:给各位RL的一点关于暗影牧师的小结

  暗影牧师的定位:

  -远程施法者

  -优秀的移动作战单位

  -最优秀的群体伤害输出职业

  -能够提供给全团补给BUFF

  暗影牧师的装备需求:

  -法术命中:部落暗影牧师需要289点命中等级来达到100%的命中上限,联盟暗影牧师需要263点命中等级来达到100%的命中上限

  对于暗影牧师来说,在WLK初期要达到命中上限并不容易,在命中未达到上限的前提下,1命中带来的收益是高于1法术能量的,它比爆击以及急速都要重要的多

  -法术能量:这是暗影牧师最重要的属性,1点法术能量将直观的提升暗影牧师1.2-1.6的DPS

  任何一个暗影牧师都应当以法术能量为第一装备选择属性,在过渡时期选择装备时甚至可以优先选择法术能量而不是命中

  -法术爆击:这是暗影牧师仅次于法术能量和法术命中的属性

  这个属性对于暗影牧师的价值来自下面2个天赋 暗影能量、暗影形态

  -法术急速:暗影牧师同样需要急速等级,它的重要性和爆击等级相当

  这个属性对于暗影牧师的价值可以参见下文 [ 暗牧急速理论,对吸血鬼之触/瘟疫的存在时间与急速等级的关系的全面分析 ]

  暗影牧师所能达到的伤害值:(以下数值为估算)

  -刚刚到达80级的任务蓝装:这个时期,暗影牧师的普通BOSS伤害应该在1500DPS,而全buff下的木桩战应当有2200的DPS,AOE场合应当由 1000×(N-1)的DPS

  -完成蓝装人成就,全身200级蓝装的暗影牧师:这个时期,暗影牧师的普通BOSS伤害应该在2100DPS,而全buff下的木桩战应当有2800的DPS,AOE场合应当有1200×(N-1)的DPS

  -全身大部分为200级以上紫装,未完成紫装人成就的牧师:这个时期,暗影牧师的普通BOSS伤害应该在2800DPS,而全buff下的木桩战应当有4000的DPS,AOE场合应当有1500×(N-1)的DPS

  -拥有圣火之炬,全身213级以上紫装且有部分226级紫装的3.09版本毕业暗影牧师:这个时期,暗影牧师的普通BOSS伤害应该在3500DPS,而全buff下的木桩战应当有5500的DPS,AOE场合应当有1800×(N-1)的DPS

  -奥达尔毕业:木桩战的全BUFF理论值是7000DPS,AOE场合应当可以维持AOE上限30000的全局AOE DPS



part I

  伤害类法术

  精神鞭笞:它是暗影牧师的主要输出手段,基础法伤加成为77%。由悲惨天赋,它拥有92%的法术能量加成。精神鞭笞的减速效果对于奥系法师有帮助。对于打断精神鞭笞释放吸血鬼之触等更加重要的DOT时,下面的quartz素材可以帮到你:[ [wlk]3.08两段鞭素材 ]

  心灵震爆:它是触发补给BUFF的必要技能,同时它的伤害在所有的暗牧技能中排名第三。它拥有8S的冷却时间,通过天赋强化心灵震爆可以将冷却时间降低到5.5S

  吸血鬼之触:它是暗影牧师最重要的伤害DOT,同时也是整个游戏中伤害最高的DOT,它是触发补给BUFF的必要技能。吸血鬼之触的法术能量加成是200%。

  噬灵瘟疫:这个疾病DOT拥有24S的冷却时间,同时它是暗牧常规输出DOT中仅次于吸血鬼之触的技能。在AOE场合,它可以通过死亡骑士的传染(铭文增加半径5码)传播到周围10码内的所有怪物身上,从而使暗牧获得惊人的AOE伤害。

  暗言术:痛:这是暗牧最具特色的DOT,也是牧师的职业特色技能之一。在天赋饱受折磨下,可以通过精神鞭笞刷新持续时间。刷新的时间不会重置痛的伤害间隔,仅仅增加持续时间,痛将每3S造成一次伤害。

  暗言术:灭:此技能的DPS略高于精神鞭笞,它将作为一个移动战/衔接技能大量的出现在奥达尔的战斗中。

  伤害类法术配套铭文:

  精神鞭笞铭文:能够使精神鞭笞的射程增加到30码,经天赋暗影延伸强化后为36码。

  暗言术:痛铭文:这个铭文的直观效果就是提升精神鞭笞10%的伤害,属于暗影牧师必备的铭文。在3.09版本中,它已经没有了任何BUG。

  心灵灼热铭文:这是3.1的新铭文,作用是增加5码的心灵灼热距离。在诸如芙蕾雅困难模式这样的战斗中这个铭文具有显著的效果:对着BOSS使用心灵灼热,你能A到大部分的ADD以及相当一部分的根须缠绕。

  当然在0守护yogg中我也尝试过这个铭文。

  消散铭文:减少45S的消散冷却时间。一般情况下你用不到它,某些特殊的场合也许你的CD会不够。比较典型的例子是阿加隆战斗,如果安排暗牧顶第一/第三次大爆炸,那么3分钟CD会比较紧张,装一个消散铭文就能够完美处理这个情况。

  某些时候你也许会觉得蓝不够(比方刚开荒yogg,近战无法很好的处理内场时会导致外场触手过多,于是你可能面临蓝的问题),那么装上他。

  暗影铭文:它同样是暗影牧师最重要的铭文之一,在raid状况下,暗影牧师一般拥有35%-40%的爆击,也就是说几乎可以全程保持这个BUFF,所以可以认为这个铭文提升了暗影牧师 精神/10 的法术能量。

  暗言术:灭铭文:相比较上面的3个铭文,这个铭文显得略微鸡肋。暗言术:灭的DPS并不比精神鞭笞高多少,而提升10%的爆击率在12秒的冷却时间下显得相当无力。不推荐将此铭文作为PVE暗牧铭文。

  资源类法术:

  消散:可恢复36%的法力值,一般raid状态下一个暗牧拥有20000的法力值,一次消散便可恢复7200的法力值。由于消散期间无法施法,在现阶段战斗中通常把消散作为最后的回蓝手段。除了回蓝,消散同样是一个自保技能,如果你追求极限DPS,甚至可以开了消散硬吃冰龙的原子弹,既回蓝又省去了跑路的时间。

  吸血鬼的拥抱:可以通过天赋强化吸血鬼的拥抱,将恢复的生命值提升至伤害量的25%,同时治疗队员5%×伤害值。这个技能是暗影牧师的特色技能之一,它赋予了暗影牧师无比强大的自保能力,在强行击杀3龙黑曜石2龙的末端时该天赋表现出众,能够缓解相当大的治疗压力,而对于类似冰龙等光环类伤害的BOSS,暗影牧师可凭借此天赋不借助外界的任何治疗完成DPS任务。作为一个暗影牧师,强化这个技能是有效且必要的。

  暗影恶魔:这是牧师的传统回蓝手段。它在持续的15S内可最多攻击10次,每击中一次恢复牧师4%的法力值。但往往由于BOSS移动,暗影恶魔实际的回蓝量只有最大值的80%左右。我们一般按照32%最大法力值来计算这个技能给予我们的蓝量。释放暗影恶魔务必注意时机,在预见BOSS将要释放近身AOE时当然不应当丢出暗影恶魔,你可以等BOSS释放完这些近身AOE技能后再放出它。暗影恶魔拥有相当高的躲闪和抗性,但尽管如此,它仍然十分脆弱易死。幸亏我们拥有暗影魔铭文,这个铭文能够一定程度上缓解暗影魔死亡带来的损失。

  奥术洪流:这是血精灵牧师的专有种族技能。6%的回蓝在20000法力值下就是1200,这是相当可观的回蓝量。

  对于普通战斗暗影魔的回复已经足够,对于多目标诸如yogg这样的战斗,在清理完全部目标等待下一波刷新的间隙可以使用影散回蓝。

  总而言之,暗影牧师的回蓝是不成问题的。

  资源类法术铭文:

  渐隐铭文:减少30%的渐隐消耗是比较实在的铭文作用。

  暗影魔铭文:这个铭文一定程度上缓解了暗影魔死亡带来的损失。

  坚韧铭文:韧是每个牧师能够带给团队的基础BUFF,此铭文的意义在于战斗中减员时给被战复起来的队员加韧。

  防护暗影铭文:此铭文可以使单体防护暗影的时间增加到20M,暗影防护祷言的持续时间增加到30M,在忘记了带蜡烛的场合还是有它的用处。

  漂浮铭文:漂浮铭文可谓牧师最重要的铭文。在3.08中,漂浮术可以对队友释放,用这个技能救一些经常跳不过塔迪乌斯台子的家伙是十分有效的,同时它可以让你在通过憎恶区绿水时心旷神怡。也许你可以试试给末日守卫加个漂浮术,没错,它飞了起来!

  暗影牧师能够提供的BUFF:

  1.真言术:韧:暗影牧师都会强化韧,在王者祝福下,这个技能能给团队提供额外的165×1.3×1.1=236点耐力,即2360点生命值上限

  2.防护暗影:130的暗抗,[ WLK的抗性机制 ]中的结论,在长时间的抵抗中,130的暗抗可以给予平均23.85%的暗影伤害减免。

  3.吸血鬼之触:由此技能触发的补给BUFF效果与惩戒骑士/生存猎人触发的补给BUFF效果一致。

  4.悲惨:该天赋和鹌鹑的强化精灵火一样能提供给团队3%的法术命中,暗牧可以永久性的保持这个BUFF,所以一般推荐由暗牧负责保证此BUFF。

  5.吸血鬼的拥抱:能够给予小队成员3%(强化后为5%)×暗影牧师伤害的回血量,在全员掉血的情况下,此技能能减轻团队治疗压力

  6.精神鞭笞:只要目标身上有精神鞭笞的减速debuff,即对奥术法师有加成作用。这是由[ Torment the Weak ]天赋得到的收益。注意,中文版数据库里的翻译仍然是诱捕,但在3.09,英文解释是snared or slowed targets.

  暗影牧师需要的BUFF:

  所有法系都需要的BUFF,以下BUFF可叠加

  5%的目标易爆:它由深冰法师的深冬之寒或者火法的强化灼烧来保持

  3%的目标易爆:它可以由元素萨满的天怒图腾得到,惩戒骑士的审判也能保持这一debuff。

  280法伤加成:元素萨满的天怒图腾提供。当然普通萨满能提供+140法伤的火舌图腾。

  5%的爆击BUFF:可以由元素萨满的元素誓约得到。或者鹌鹑的光环同样能给予这个BUFF

  3%的加速BUFF:可由鹌鹑的强化枭兽光环得到。

  5%的加速BUFF:可由萨满的空气之怒图腾得到。

  13%的目标法术易伤:深邪恶DK提供。深痛苦术士以及鹌鹑同样能够提供,但在AOE场合,深邪恶DK能将这个DEBUFF传播给每一个怪物。

  3%的伤害提升:可以由惩戒骑士提供。兽王猎人的凶猛灵感同样可提供,但不稳定。



part II:天赋信息

  暗影

  -强化精神分流:在raid中,暗影牧师一般拥有40%的对BOSS爆击,而暗影牧师在3.09版本毕业时将拥有raid状态下800的精神,提升80精神意味着提升8+8的法伤(8点来自扭曲信仰,8点来自暗影铭文),此天赋的实际意义回蓝价值高于DPS价值。不过由于黑暗目前处于第一层,所以这个天赋可以不点。

  -暗影亲和:在TBC必点的暗影亲和到了WLK反而成为了非必须天赋。由于坦克仇恨的大幅提升以及暗影形态能够减少30%仇恨,正常战斗中不需要点这3点,你几乎不可能OT。不过对于需要频繁AOE的场合,这个天赋具有它的实用性,你可以不点第一层的强化精神分流而选择这个天赋。

  -遮蔽之影:由于它现在减少2M的暗影恶魔CD,而影魔具有不错的DPS,所以这2点事非常值得去点上的。

  -强化吸血鬼的拥抱:这是一个优秀的天赋,在暗牧单刷黑龙等各种60级raidBOSS时,它几乎是必备的。而在80级的raid中,这个天赋能进一步增加暗牧的存活率。在追求极限的战斗中,压缩治疗成为提升副本速度的手段之一,此时,缓解治疗压力是十分值得重视的事情。而在开荒期,治疗蓝紧张的情况下,暗影牧师的自给自足将能极大的提高团队的开荒成功率。

  -强化暗影形态:在3.09 鞭笞正常化后这个天赋的价值变得更高了。在AOE频繁的场合它是必点的,暗牧在TBC时代由于没有防打断天赋吃了不少亏,WLK,这个天赋给予了暗牧和其他职业一样的防打断施法能力。

  -饱受折磨:这个天赋可以使精神鞭笞能够刷新暗言术:痛的持续时间,注意,它不会重置暗言术:痛的起始时间而仅仅刷新它的持续时间。

  -扭曲信仰:这个天赋与暗言术:痛铭文的效果是叠加的,也就说,目标身上有暗言术:痛时,精神鞭笞能够造成1.21倍伤害。

  戒律

  -双生戒律:此天赋除了作用于所有瞬发法术外,对精神鞭笞同样有伤害加成

  -强化心灵之火:这3点天赋可以使心灵之火的附加法伤从120变为174,也即提升了牧师54点法术能量,属于3系牧师必加的天赋。

  -冥想:冥想是暗牧主要的法力来源。作为一名PVE暗牧,没有理由不加满冥想。

  -心灵专注:由于心灵专注提供的25%爆击并不能影响DOT,所以这个天赋对于暗牧属于可有可无的天赋。

  神圣

  通常情况下你不应该去考虑这个系的天赋。

  天赋模板:

  1.13/0/58:该天赋中,点了强化吸血鬼的拥抱以及暗影亲和,放弃了心灵专注以及遮蔽之影。此天赋适合3.09版本装备基本毕业的牧师,在rush战中,暗影亲和有一定意义,同时,处于3.09终极状态下的牧师拥有850+的精神,蓝的问题几乎不存在,有关于回蓝的技能甚至只需要用到奥术洪流。

  2.13/0/58:放弃暗影亲和与强化吸血鬼的拥抱的主流点法。

  3.13暗牧的天赋选择是如此狭窄以至于没有多少搭配的余地。


part III:输出技巧

  输出思想

  在保证吸血鬼之触,噬灵瘟疫不断的情况下,尽可能的提高鞭笞的数目

  由于鞭笞可爆击,暗言术灭的意义已经不大,输出过程中我们更关注的是如何最大化吸血鬼之触,噬灵瘟疫的跳数。

  在3.09鞭笞系统改正常后,[ [wlk]3.08两段鞭素材 ]中提供的quartz素材,暗牧可以比3.03时更加容易的保持触与瘟疫

  鞭笞伤害和心灵震爆的差距还是比较显著的(3646:4579),因此打断鞭笞读心灵震爆仍然是有意义的

  一些提示

  1.任何时候都将125法伤的合剂当成常规BUFF,这是一个好习惯,永远记住法伤就是你的DPS。

  2.鞭笞和心灵烙印都是通道法术,这意味着除了施放时需要面对目标,之后你都可以鼠标右键转向选择下一个目标。

  对于yogg的战斗来说,这是最关键的技巧。

  3.心灵震爆不止能提升DPS,更是回蓝的必要条件。所以尽可能多的使用心灵震爆,它的优先级仅次于触和瘟疫。

  4.由于延迟的原因,读条法术结束后接一个瞬发的法术有助于提升DPS(读条法术后接个灭,瘟疫之类的)

  5.吸血鬼之触,暗言术灭,暗言术痛,噬灵瘟疫都可以背向目标施法,所以暗牧唯一需要注意朝向的法术只有心灵震爆(2个通道法术可以在引导后任意转向)

  6.裁缝是目前暗牧的首选专业,在3.2珠宝将降级为一个普通的专业。

  7.通常情况无尽狂野药水比加速药水好。在AOE达到上限时加速药水是最佳选择。

  8.在一些特殊的战斗中,尽量保证你的DOT在伤害提升的时候放出。比方,电男在保证身上叠加了10+层BUFF后再丢出瘟疫,我们宁可损失1-2跳的普通瘟疫伤害也不要损失8跳的220%伤害的瘟疫。瘟疫是个需要计算的技能,比如在蓝龙战中,当你站在一个伤害增加区域而此时瘟疫CD到了,DK又开始拉第二个球时,你可以等上这么6S,损失2跳的150%伤害,换来后面225%的8跳瘟疫伤害。

  9.对付血少的目标最多上一个痛然后使用心灵震爆+鞭笞输出(比方10W血的目标),如果目标血量过少那么连痛也不必上(比方8000血的目标...),这需要自己预估,一般来说10S左右死的怪痛还是有必要上的。    

  AOE的技巧

  1.DK不再能传染你的瘟疫

  2.通常情况选取血最多的怪物作为你的心灵灼热目标,它一般来说死的最慢,如果它死了,基本其他的怪也A的差不多了。

  但是也有特殊的例子,比方0守护yogg,AE的目标应当是血最少的那个。

  3.也许为了效率你们会不停的引怪,打的很奔放,这个时候可以在跑动的时候撒痛,停下来后立即对最新引到的怪使用心灵灼热。如果这个时候天赋里有暗影亲和,你一般不需要担心OT,要相信死亡凋零无比强大的仇恨,以及你们战士坦克的雷霆一击。

  4.加速属性是AOE的最佳属性,在到达AOE上限时(这个值应该是3W,具体待考证)更是如此。所以如果需要暗牧负责AOE,那么这个属性是值得重点关注的。

  5.暗牧是AOE职业。

  运动战的技巧

  1.消散时可以移动,近乎无敌。理解了这一点可以减少不少的移动时间,比方你可以硬吃一个原子弹。

  2.跑动过程中,你可以丢个灭。如果灭在CD,你可以补个吸血鬼之拥抱。如果你认为上面2个都没必要做,同时又不需要补瘟疫,那么给自己上个盾吧。这对于生存是很有效的,我们不能浪费任何的移动时间。

  3.在某些跑动中你最好不要让触断档,1个GCD的时间在跑动中是能省出来的,比方瘟疫2的2次喷发间隙是3S,这足够你读一个触,或者一个震爆。

  一般来讲,我们需要做到的就是跑动中只损失鞭笞的伤害。

  4.没事别用灭,它应该留给移动的时候。它不会提高你的木桩时间的DPS,但在跑动的时候灭如果在CD是件令人沮丧的事情。

  5.你最好能对自己的移动路线有个清晰的认识,比方憎恶2,你需要绕圈跑,那么可以在你需要补瘟疫,吸血鬼拥抱的时候移动,而灭可以CD到就用,同时提前移动。总之,千万别到BOSS超出你的射程后才想起要移动。

  6.在需要躲避某些技能的时候可以提前算好你躲避需要的时间,如果你需要躲避15S,那就把触补了再走。



part IV

  属性价值

  根据最近的simulationcraft计算

  [ http://code.google.com/p/simulationcraft/wiki/SampleOutputCaster ]

  目前暗牧各属性对DPS的提升分别为

  法术伤害:1.46

  法术命中:1.69

  爆击等级:0.88

  急速等级:0.79

  智力:0.26

  精神:0.28

  如果将法术伤害记为1PP

  那么

  智力:主要影响为166智力等于1%爆击,在王者祝福下,为1.1%爆击,所以1智力=0.178PP

  精神:主要由扭曲信仰和暗影铭文获得DPS提升,1精神=0.192PP

  爆击等级:1爆击等级=0.603PP

  急速等级:1急速等级=0.541PP

  命中等级:1命中等级=1.158PP

  法术能量:1法术能量=1PP

  由此可见,急速和爆击的价值相近,但都远低于法术能量

  物品价值

  物品价值可以通过简单的计算得到,上面的分值与[ http://bbs.ngacn.cc/read.php?tid=1955414&fpage=1 ]中的分值差的并不是太远

  所以[ http://bbs.ngacn.cc/read.php?tid=1955414&fpage=1 ]中的评分仍然适用

  [ http://www.lootrank.com/wr.asp?Cla=16&Art=8&Slot=1&mh=0.79&mcr=0.88&spd=1.46&mhit=1.69&Int=0.26&Spi=0.28&Ver=6]

  装备选择可以去lootrank查询

  在宝石选择上,可以遵循:

  红色:+19法伤

  黄色:+19法伤(奖励不重要)/ +9法伤+8命中

  蓝色:+19法伤(奖励不重要)/ +9法伤+8精神

  选择。

  平衡点

  事实上3.09阶段并无所谓的平衡点。急速等级由于鞭笞可打断的回归,不再具有明显的阶段性, [ 暗牧急速理论,对吸血鬼之触/瘟疫的存在时间与急速等级的关系的全面分析 ]一文中的结论不再适用(真可怜,写于CWLK前,结果CWLK还没开就宣告结论无效),不过此文还是展现了急速对暗牧的作用。

  因此现在可以更加大胆的选择装备,不用在乎急速过多的问题。

  至于急速和爆击的平衡点,这没有平衡点,从来就没有,从来是副本等级决定物品等级,物品等级决定法伤数值,法伤数值决定暗牧DPS

  法术能量仍然是暗牧的第一属性。



FAQ

  解答回复中的部分问题

  1.瘟疫传染问题

  DK已经无法传染我们的瘟疫了。

  2.PVP

  我几乎没正常的打过PVP,对于标题没加pve标签向各位道歉=.=

  PVP的信息希望其他人帮忙完成,当然不要在本帖内讨论了

  3.奥术洪流是不是那个沉默的种族天赋?

  是的,这是血精灵独有的。

  当然10个部落牧师有7个是血精灵。。。

  4.忽然发现一个问题求解,为什么在[ 神戒牧Raid白皮书3.0版 ]一文中,戒律63智力=1%暴击,神圣72智力=1%暴击,而暗影就是166智力=1%暴击?????

  我得说,你引用的这篇文章是3.0的,是70级的,而且考虑了王者祝福

  166智力=1爆击是不考虑王者祝福的80级基础属性标准

  5.我记得SP.com的测试结果是悲惨提供的15%加成是在加成系数上连乘而不是相加的

  这是3.08之前的结果,暴雪在3.08改动了这个加成,结果就是造成了悲惨的加成达到了45%。。。当然这个BUG在24小时内被hotfix了。

  但是目前的加成已经变为了加法而不是过去的乘法。

  6.帖子里提到珠宝可以无视蓝孔是什么意思?

  珠宝加工可以换取专有的龙眼石配方,龙眼石可以通过开80级的珠宝玻璃或者做每日珠宝任务得到,龙眼石可交易

  龙眼石配方里有珠宝专有的唯三的宝石,也就是你身上全部装备只能插3个,这些宝石的属性有+32伤害,+27智力,+27精神等等

  而同时,龙眼石切出来的宝石被视为三色宝石

  作为暗牧,我们需要用2个蓝孔来激活混沌之天火钻石,有了龙眼石,就可以在身上的蓝孔内放龙眼石

  蓝孔选择+9伤害+8精神,3.08加入的配方。。。(我火星了,抱歉,感谢40楼的朋友)

  那么换成+32伤害后,一个蓝孔就能多提供23伤害

  考虑到插槽奖励等因素,珠宝对暗牧的提升就高达 23×2+13=59点

  而其他专业技能都只有37点