FAQ1: 如何为工具栏添加CHEVRON
FAQ2: 如何实现工具栏按钮的拖拽
最近一直很忙,没空把新的知识沉淀、更好的消化。今天就把有关工具栏编程的知识汇总一下。那些教科书上有的东西就不写了,呵呵!
如何为工具栏添加CHEVRON
先说一下如何为工具栏添加CHEVRON,就是当工具栏按钮的长度超过了窗口能显示的长度时,在工具栏的右侧自动添加一小按钮,点击后可以把后面未显示的工具栏按钮以菜单的形式显示出来。点击此下载相关的代码
实际上,这是个重组栏(Rebar)提供的功能,首先需为Rebar添加RBBS_USECHEVRON风格。请看下面的代码:
REBARBANDINFO rbi;
memset( &rbi , 0 , sizeof(rbi) ) ;
rbi.cbSize = sizeof( REBARBANDINFO ) ;
rbi.fMask = RBBIM_STYLE ;
m_Rebar.GetBandInfo( index , &rbi ) ;
rbi.fStyle |= ( RBBS_USECHEVRON | RBBS_BREAK ) ; // RBBS_BREAK风格不是必需的!
m_Rebar.SetBandInfo( index , &rbi ) ;
或者可以通过另一种方式来添加RBBS_USECHEVRON风格:
m_wndReBar.AddBar(&m_wndToolBar, NULL, NULL, RBBS_USECHEVRON ) ;
然后为ReBar指定正常显示的宽度,即当ReBar的实际长度超过这个宽度就自动出现CHEVRON。
REBARBANDINFO rbi;
memset( &rbi , 0 , sizeof(rbi) ) ;
rbi.cbSize = sizeof( REBARBANDINFO ) ;
rbi.fMask = RBBIM_IDEALSIZE ;
rbi.cxIdeal = GetToolbarWidth() ;
m_Rebar.SetBandInfo( index , &rbi ) ;
看看GetToolbarWidth()的代码:
int nWidth = 0 ;
int n = GetToolBarCtrl().GetButtonCount() ;
for( int i = 0 ; i < n ; i ++ )
{
CRect rtItem ;
GetItemRect( i , &rtItem ) ;
nWidth += rtItem.Width() ;
}
return nWidth ;
显然,这是个CToolbar派生类的成员函数!通过它来得到工具栏所有按钮的宽度的总和,这里包括了隐藏按钮的宽度,如果你用到了隐藏按钮,可能最终的效果和你期望的有一点出入。你还可以用CToolbarCtrl::GetMaxSize这个函数得到工具栏的宽度(此函数不计算隐藏按钮)。我不用GetMaxSize是有原因的,因为我做的工具栏长度是动态改变的,它不断的添加或删除某个按钮。我发现在这种情况下,GetMaxSize并不能得到准确的数据!使得在该出现CHEVRON时并不出现,或者在完全有足够长度显示按钮时也显示CHEVRON!
当然,你可以把两个SetBandInfo调用集中在一起,这样效率会更好。但我所做的工具栏的长度是在不断变化的,每次改变时都要重设cxIdeal,所以我把设置cxIdeal提取出来独立成一个函数。
还有一个相关的风格:TBSTYLE_EX_HIDECLIPPEDBUTTONS。此风格是工具栏(不是ReBar)的扩展风格哦,通过CToolBarCtrl::SetExtendedStyle设置。设置了该风格后,工具栏上无法完全显示的按钮就会隐藏,并且加到CHEVRON的菜单中。
最后声明一点,文中的代码与提供的例子并不是同一个代码!这个例子是我从网络上搜索来的。
如何实现工具栏按钮的拖拽
再写一个关于工具栏按钮的拖拽。
有两个工具栏通知消息:TBN_BEGINDRAG和TBN_ENDDRAG。关于这两个通知消息书上介绍的很少,网上也不多见!下面的代码介绍了如何处理该通知消息来实现工具栏按钮的拖拽:
TBN_BEGINDRAG通过消息的处理:
void CMyToolbar::OnTbnBeginDrag(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTOOLBAR pNMTB = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
m_DragedID = pNMTB->iItem ; // m_DragedID 保存了被拖的按钮ID
m_bDragButton = TRUE ; // m_bDragButton 是BOOL变量,是个拖拽标志
*pResult = 0;
}
注意:这里不用调用SetCapture哦!应该是默认的消息处理已经帮我们调用了SetCapture。
TBN_ENDDRAG通知消息的处理:
void CMyToolbar::OnTbnEndDrag(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMTOOLBAR pNMTB = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
if( m_bDragButton )
{
//
// 放处理
//
CPoint point ;
::GetCursorPos( &point ) ;
ScreenToClient( &point ) ;
// 先检查鼠标是否在工具栏窗体内
CRect rc;
GetClientRect( &rc ) ;
if( rc.PtInRect( point ) )
{
//
// 检查鼠标在哪个按钮之上,是否在搜索按钮上
//
int index = GetToolBarCtrl().HitTest( &point ) ;
UINT id = GetItemID( index ) ;
if( id >= 0 )
{
// 拖放对象不是同一个
if( id != m_DragedID )
{
// 调换位置
// 把ID=m_DragedID的按钮放在ID=id的前面(后面也可以,看你的需求)
…
}
}
}
m_DragedID = 0 ;
m_bDragButton = FALSE ;
SetCursor( LoadCursor( NULL,IDC_ARROW ) ) ;
InvalidateRect(NULL);
}
*pResult = 0;
}
最后是WM_MOUSEMOVE消息的处理,将拖的过程反馈给用户:
void CMyToolbar::OnMouseMove(UINT nFlags, CPoint point)
{
if ( m_bDragButton )
{
//
// 先检查鼠标是否在工具栏窗体内
//
CRect rc;
GetClientRect( &rc ) ;
if( rc.PtInRect( point ) )
{
//
// 检查鼠标在哪个按钮之上
//
int index = GetToolBarCtrl().HitTest( &point ) ;
UINT id = GetItemID( index ) ;
if( id >= 0 )
{
//
// 拖放对象不是同一个
//
if( id != m_DragedID )
{
//
// 在放目标按钮的前面画一条2象素的竖线,表示要将某个按钮拖到该按钮的前面!
// 这里要怎么画,你可以凭自已的想象力,目的就是要让用户知道被拖的按钮即将被放在目标按钮的旁
// 边,可以是前面或后面。可以画竖线,也可以画其它的。
//
CRect rtItem ;
GetItemRect( index , &rtItem ) ;
rtItem.right = rtItem.left + 2 ;
CClientDC dc (this);
dc.FillSolidRect( &rtItem , RGB(0,0,0) );
}
SetCursor( LoadCursor( ::AfxGetInstanceHandle() , (LPCTSTR)IDC_CURSOR_DROP_BUTTON ) ) ;
}
else
{
SetCursor( LoadCursor( NULL,IDC_NO ) ) ;
}
}
//
// 不能放的区域
//
else
{
SetCursor( LoadCursor( NULL,IDC_NO ) ) ;
}
}
CToolBar::OnMouseMove(nFlags, point);
}
上面对三个消息的处理并不复杂,如果你曾经或常常为TREE或LIST控件写拖拽的代码,相信你很容易就可以理解。