我们在WINDOWS下用C++进行界面编程,常用的界面库有两个,MFC、WTL。MFC也是我们最常用的一个,通常为了满足功能和界面美观的需要,大家在用时都会对其中一些控件进行再次的封装,这样的封装也有很多朋友把它放在了网上共享,以便大家可以直接拿过来用,省力省时。因为常用,所以MFC库支持下的控件显的很多,大家只要想得到的到网上基本上就能找到。有时我们为了写一些特殊的东东不能使用MFC,比如用ATL写一个控件,这时可能我们就会用到WTL。
WTL由于用的人比较少,而且也没有系统的书籍进行讲解,刚开始使用时比较不爽。在网上更是难以找到已进行封装的控件。我在学习时走了不少的弯路,但好在最终还是略懂一二了,所以现在就以播放器控制窗口为例讲解一下实现方法,同时把我写的几个控件类也贡献一下。
在此讲例子可以从http://download.csdn.net/source/1428561下载。
在工程目录下WTL文件夹下是WTL头文件,为了让大家能在进行时不会缺少文件我就把其拷到了此处。
在UIBmpBtn.h文件中有我已经封装的控件类。有以下几个模板类:
CBitmapButtonImpl 这个类在WTL中也能找到,我仅将其复制到此
CBitmapBtnImpl 这个类和上面的类差不多,我仅对其作了一点扩展,它能支持5种状态。比上面的更好用一些。下面是五种状态的说明
_nImageNormal 平时没有操作时的状态
_nImagePushed, 当点鼠标按下时的状态
_nImageFocusOrHover, 当鼠标放在按钮上或经过时的状态
_nImageDisabled, 当按钮被禁用时的状态
_nImageClicked, 当按钮被点击之后放开鼠标后的状态,在下面的录像和静音按钮都有这一种状态。
CBmpBtnFive 这个类是对五种状态的进一步继承,加入手弄光标
CBmpBtn 这个类是对CBitmapButtonImpl的进一步继承,加入手弄光标
CVideoSliderCtrl 这是视频进度条类
CAudioSliderCtrl 音频控制SLIDER
我们的这个例子就只有一个对话框,主对话框就是的我们要实现的播放器控制窗口。所以就来讲一下这个窗口的实现。
首先在stdafx.h中我们已经包含了我们要用的头WTL头文件和STL中的的头文件。这里不再详细说明。
先看一下我们要在控制窗口中实现的控件变量在MainDlg.h中如下
CBmpBtn m_cBtnPlay; // 开始按钮
CBmpBtn m_cBtnPause; // 暂停按钮
CBmpBtn m_cBtnStop; // 停止按钮
CBmpBtn m_cUpRate; // 加速按钮
CBmpBtn m_cDownRate; // 减速按钮
CBmpBtnFive m_cBtnRec; // 录像按钮
CBmpBtnFive m_cSilence; // 静音按钮
CVideoSliderCtrl m_cSliderVideo; // 视频滑动条
CAudioSliderCtrl m_cSliderAudio; // 音频滑动条
这些控件变量都是手工添加的。但是在我们要先在窗口资源中加入对应的控件,但是不需添加控件变量。因为我们将会用上面声明的变量进行子类化关联。加入按钮的情况如下图:
在构造中我们看到如下:
CMainDlg()
: m_cSliderAudio(IDB_BMP_AUDIO_BK, IDB_BMP_SLIDER_THUM)
, m_cSliderVideo(IDB_BMP_VIDEO_BK, IDB_BMP_VIDEO_CHAL, IDB_BMP_VIDEO_THUM, IDB_BMP_VIDEO_LEFT, IDB_BMP_VIDEO_RIGHT)
{
}
Slider构造函数要求我们必须传入控件内部要用的位图,所以我们一定要以初始化列表的形式进行初始。对于CVideoSliderCtrl它的背景图切分方法可能比较烦索,但这是为了满足在拉伸时不会变形的要求。如果你要实现你自己的样式可以按照例子中方法去切分替换即可。
我们再来看LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)的实现。这里面主要就是控件的初始。
头两个是SLIDER的实现
m_cSliderVideo.SubclassWindow(this->GetDlgItem(IDC_SLIDER_VIDEO));//与界面资源进行子类化关联
m_cSliderVideo.SetParent(this->m_hWnd);
m_cSliderVideo.SetPageSize(0);
m_cSliderVideo.SetIdentiID(this->m_hWnd, IDC_SLIDER_VIDEO);//这里是设置你要接收控件消息的窗口,和控件的分辨的ID(在本例中没有什么用,后面我们讲Slider分段时就会用到)
m_cSliderVideo.ShowWindow(SW_SHOW);
m_cSliderAudio.SubclassWindow(this->GetDlgItem(IDC_SLIDER_AUDIO));
m_cSliderAudio.SetParent(this->m_hWnd);
m_cSliderAudio.SetPageSize(0);
m_cSliderAudio.SetIdentiID(this->m_hWnd, IDC_SLIDER_AUDIO);
m_cSliderAudio.ShowWindow(SW_SHOW);
m_cSliderAudio.SetRange( 0, 100 ); //设置音量的范围
m_cSliderAudio.SetPos( 50 ); //设置音量的默认值
之后就是各个按钮的实现,我只看一个播放(4个状态)和一个录像(5个状态)按钮的初始:
播放按钮4种状态
DWORD dwStyle = BMPBTN_AUTOSIZE | BMPBTN_HOVER;//让按钮支持自动大小和HOVER状态
CBitmap theBitTemp;
theBitTemp.LoadBitmap(IDB_PLAY); //加载按钮的图像,
m_cBtnPlay.SetBitmapButtonExtendedStyle(dwStyle);//设置类型
m_cBtnPlay.m_ImageList.Create(30, 18, ILC_COLOR8|ILC_MASK, 4, 1);//把位图按指定大小个数进行切分
m_cBtnPlay.m_ImageList.Add(theBitTemp.m_hBitmap, RGB(0, 255, 0));//添加图像列表
// 下面是设置4种状态对应列表中的位图set normal, pressed, hover, disabled images
m_cBtnPlay.SetImages(0, 1, 2, 3);
m_cBtnPlay.SetToolTipText(_T("播放"));//设置HOVER状态时显示的文本
m_cBtnPlay.SubclassWindow(this->GetDlgItem(IDC_BTN_PLAY));//与窗口资源进行子类化关联
theBitTemp.DeleteObject();//释放位图资源
录像按钮5种状态,和4种状态按钮的不同在于多一个nImageClicked状态,如下设置
theBitTemp.LoadBitmap(IDB_BMP_RECORD);
m_cBtnRec.SetBitmapButtonExtendedStyle(dwStyle);
m_cBtnRec.m_ImageList.Create(30, 18, ILC_COLOR8|ILC_MASK, 4, 1);
m_cBtnRec.m_ImageList.Add(theBitTemp.m_hBitmap, RGB(0, 255, 0));
// 下面是设置4种状态对应列表中的位图set normal, pressed, hover, disabled Clicked images
m_cBtnRec.SetImages(0, 1, 2, 3, 2);
m_cBtnRec.SetToolTipText(_T("录像"));
m_cBtnRec.SubclassWindow(this->GetDlgItem(IDC_BTN_RECORD));
m_cBtnRec.EnableWindow(false);
theBitTemp.DeleteObject();
由上面可以看出它们的初始也很简单,搞不明白就复制几个就可以了,HOHO
我再来看MainDlg.CPP中的实现部分;
整个控制窗口若没有背景那么可能是一种很不美观的事,所以我们要给其加一个背景,那就在
LRESULT CMainDlg::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)中画一个背景,这个是很Easy的事情一看就Know了。
我们能看到在窗口资源中各个控件的位置是随意放置,这是因为我们就是摆放了也没有什么用,因为当窗口拉伸时就可能就会变了,所以我们必须在OnSize()里面进行布局设置,只要窗口有变化就会根据我们的要求自动摆放,这里也不再多说了。
接下来是对两个Slider的点击和拖动的消息响应
LRESULT CMainDlg::OnMsgSliderPos(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
switch( wParam )//这个参数就是指明了是来自于哪一个SLIDER
{
case IDC_SLIDER_VIDEO: //视频SLIDER的操作
{
//this->OnVideoSliderPos(lParam);
//lParam指明了当前点击或拖动到的位置,
break;
}
case IDC_SLIDER_AUDIO://音频SLIDER的操作
{
//this->OnAudioSliderPos(lParam);
break;
}
default: /*-- do nothing --*/ break;
}
return S_OK;
}
这个消息是手式添加的,是一个自定义消息,你只需在消息映射中加入下面的一行就可以了
MESSAGE_HANDLER(WM_SLIDER_POS_MSG, OnMsgSliderPos)
这样你的SLIDER就可以使用了,是不是很Easy呀!
为了更好的演示,我们再加上OnTimer(),以及开始和停止按钮的响应函数:
LRESULT CMainDlg::OnTimer(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
hash_map<__int64,__int64> MapArea;
if( m_iEndPos == 101 )
return S_OK;
//在此可以画多个区域,当在网络播放器上用时要显示下载的进度,这里就可根据下载的情况进行下//载的区域填充。如果是播放器进行了SEEK操作,那么就可能会出现多个不连续的数据块,此时可能//根据已有下载数据进行分区域填充,以示当前下载的情况
m_cSliderVideo.SetPos(m_iEndPos);
//MapArea[ 区域开始 ] = 区域结束位置; 100是指在MapArea中的最大值,这样内部会用这个值去计算所画的位置。也就是说我们外面可以用任何一个范围值,而不必和我们给SLIDER设的范围一致。
MapArea[ 0 ] = m_iEndPos++;
m_cSliderVideo.DrawChannelBarBack( MapArea, 100 );//
return S_OK;
}
LRESULT CMainDlg::OnBnClickedBtnPlay(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
//MessageBox("开始播放了!");
SetTimer(1,1000,NULL);
m_cBtnPlay.EnableWindow(FALSE);
m_cBtnPause.EnableWindow();
m_cBtnStop.EnableWindow();
m_cBtnRec.EnableWindow();
return S_OK;
}
LRESULT CMainDlg::OnBnClickedBtnStop(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
KillTimer(1);
m_cBtnPlay.EnableWindow(TRUE);
m_cBtnPause.EnableWindow(FALSE);
m_cBtnStop.EnableWindow(FALSE);
m_cBtnRec.EnableWindow(FALSE);
m_cSliderVideo.Reset();
m_iEndPos = 0;
return S_OK;
}
这一些好明白吧,很好懂,至此我们可以编译了。就能看到运行结果了,你点一下播放按钮看看有什么现象,点一下录像按钮看看,再点一点停止看看,是不是有点像个播放器的控制窗口了。哈哈
我在此写这点东东,只是为了给初学者一点启发。老手就见笑了。后面我们会再讲一下ATL控件的编写,最终我们会做出一个播放器的控件来。这是要一步步来的了。