C++ Buider开发多媒体软件常用技术
软件世界
目前多媒体软件的发展正如火如荼,让人目不暇接。对于编程爱好者来说,最大的乐趣莫过于自己动手编写想要的软件了。笔者最近编了一个媒体播放器,可播放*.mp3、*.mid、*.avi、*.mpg等格式的媒体文件,支持随机/循环播放,并可载入及保存播放清单(*.pls)。
程序用C++ Builder 5编写,主要采用了MediaPlayer、ListBox等控件。程序主体部分不是很复杂,关键是控制好各播放按钮的动作。由于看到如Winamp(我用的是2.64版)之类的播放器界面很漂亮,于是笔者也绞尽脑汁设计界面,最后总算如愿以偿(当然还比不上Winamp, 但个人感觉还是非常漂亮)。以下是笔者总结的一些编程心得,与大家共同探讨。
一、主窗体界面设计
1.常用窗口边框形式
这很简单,一般多媒体软件多采用无边框模式的窗体,这只需将窗体的BorderStyle属性设为bsNone即可。至于全屏幕,则只需将同时将窗体最大化(BorderStyle必须为bsNone),即将WindowState属性设置为wsMaximized。属性的设置可在编程时在对象属性窗口(Object Inspector)中直接设置,也可在运行时由程序设置。
2.窗体背景的实现
多媒体软件的一大特征是窗体的多姿多彩,丰富的窗体背景是其中最常见的形式,其实实现窗体背景的技术一点都不复杂,请参看以下代码:
a.在主窗体头文件中窗体类的private部分加上:
Graphics::TBitmap *bmp;//背景图像
b.在窗体的FormCreate事件处理器中加上:
void __fastcall TFormPlay::FormCreate(TObject *Sender)
{ //此处窗体名为FormPlay, 以下同
//其他代码
bmp=new Graphics::TBitmap();
try{
bmp->LoadFromFile(facefile);
//载入图像,facefile为文件路径,亦可预先放入Image控件(使用时用Image1->Picture->Bitma获取位图)或资源文件中(.res,用LoadImage载入)
}
catch(...){
bmp=NULL;
}
}
c.在窗体的FormPaint事件处理器中加上:
void __fastcall TFormPlay::FormPaint(TObject *Sender)
{
//其他代码
if(bmp)
{
int x=0;
int y=0;
int width=bmp->Width;
int height=bmp->Height;
do{ //使用循环防止图像过小
do{
Canvas->Draw(x,y,bmp);
y+=height;
}while(y
y=0;
}while(x
}
d.更换背景的实现(简单的换肤(Skin))
你只需在实现更换背景的方法中加上以下代码:
try{
bmp->LoadFromFile(facefile);//facefile为位图文件路径
}
catch(...){
bmp=NULL;
}
FormPaint(Sender);//绘制窗体
Repaint(); //重画窗体(刷新)
二、带背景的主菜单
窗体的背景变漂亮了,这时你如果仍用MainMenu创建主菜单,你会发现主菜单部分仍是原样。怎样让主菜单也带上背景呢?
1.使用CoolBar实现带背景的主菜单
这种方法大概是最直接的了,甚至都不用编写代码。具体步骤是:首先,将原来的主菜单的Visible属性设为false(不要说你还未设计主菜单);接下来从C++Builder主界面的Win32标签中选择CoolBar控件添加到窗体中,并设置好位置,选择CoolBar的Bitmap属性,载入一幅位图;然后,选中CoolBar控件,再在其中添加ToolBar控件(对,ToolBar添加到CoolBar中),将ToolBar的Flat、Transparent及ShowCaptions属性均设为true,添加需要个数的ToolButton,将每个ToolButton的Grouped属性设为true,接着依次将各ToolButton的MenuItem属性设置好相应的菜单项(从下拉列表框中选择,具体操作试一下就知道了);最后,别忘了将CoolBar的AutoSize属性设为true。至于其他属性,可根据需要一一设置。
2.使用ToolBar的CustomDraw或AdvancedCustomDraw事件
上面的方法虽然简单直接,但CoolBar看起来总不那么自然,于是就有了第二种方法:首先按照上面添加ToolBar的方法给主窗体添加一个ToolBar控件(CoolBar当然不需要了),按上述步骤设置好各属性后,再确认ToolBar的Align属性为alTop;接着,在ToolBar的CustomDraw事件处理器中添加如下代码:
void __fastcall TFormPlay::ToolBar1CustomDraw(TToolBar *Sender,const TRect &ARect, bool &DefaultDraw)
{ //此处为ToolBar1
//其他代码
int x= -ToolBar1->Left; //此句及下句是为了使背景看起来更自然
int y= -ToolBar1->Top;
int width=bmp->Width; //bmp为位图对象,见以上代码
int height=bmp->Height;
do{ //使用循环是防止图像过小
do{
ToolBar1->Canvas->Draw(x,y,bmp);
y+=height;
}while(y
x+=width;
y=0;
}while(x
}
说明:设置ToolButton的菜单项时,除了上述方法,还可建立一系列的弹出式菜单(PopupMenu),而后将ToolButton的DropdownMenu属性设为相应的菜单。
三、设计系统按钮
当你按照上述方法设计好了窗体及菜单,并且窗体的BorderStyle属性为bsNone,那么你大概会想到窗体的最小化及关闭,但BorderStyle为bsNone的窗体是没有边框的,当然也没有最小化及关闭等按钮。这时,你必须自己设计这些按钮。
1.使用SpeedButton控件
这是最简单的实现方法,只要将其Flat属性设为true,再实现其OnClick事件处理器即可(最小化事件可用Application->Minimize()来实现,还可添加其他代码)。
方法虽然简单,但使用SpeedButton实现最小化按钮时也有个小小的不足:当窗体有背景时,点击SpeedButton制作的最小化按钮,窗体最小化时将会在按钮处重现窗体原来的颜色,看起来总不那么舒服!
2.使用Image控件
要解决方法一中的不足,则可以用Image控件来实现最小化按钮(其他按钮亦可)。这种方法其实也很简单,只要准备一幅适当大小的位图(表示最小化,可用“画图”设计),设计时载入Image控件中,调整好尺寸,然后实现其OnClick事件处理器就可以了。别忘了,将Image控件的Transparent属性设为true,这样可避免挡住窗体背景。
四、窗体BorderStyle为bsNone时,移动窗体的实现方法
系统按钮实现了,接下来该实现窗体的移动了。
1.直接利用窗体的MouseDown、MouseMove、MouseUp事件
a.在主窗体头文件中窗体类的private部分加上:
bool move; //移动窗体
int old_x,old_y;//移动窗体时用
b.分别在窗体的FormMouseDown、FormMouseMove、FormMouseUp事件处理器中加上:
void __fastcall TFormPlay::FormMouseDown(TObject *Sender, TMouseButton Button,TShiftState Shift, int X, int Y)
{ //其他代码
if(Button==mbLeft) //此处可加上如Y<20之类的限制
{ TPoint pos;
if(GetCursorPos(&pos)) //获取鼠标位置
{ old_x=pos.x;
old_y=pos.y;
move=true; }
}}
//
void__fastcall TFormPlay::FormMouseMove(TObject *Sender, TShiftState Shift,
int X, int Y)
{ //其他代码
if(move)
{ TPoint pos;
if(GetCursorPos(&pos)) //获取鼠标位置
{ Left+=pos.x-old_x;
Top+=pos.y-old_y;
old_x=pos.x;
old_y=pos.y;}
}}
//
void__fastcall TFormPlay::FormMouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{ //其他代码
move=false;}
2.利用其他附加控件如Label的MouseDown、MouseMove、MouseUp事件
此种方法与利用窗体的Mouse事件类似,只需在该控件的Mouse事件中加上以代码即可,且有一个好处:控件的大小位置设好了,可响应移动窗体事件的区域也就确定了。
五、自定义光标的载入及使用
别具一格的光标往往为程序增色不少(看看“画图”中的光标,既形象又直观),这就需要往程序中载入光标资源。
1.在主窗体头文件中窗体类的private部分加上:
HCURSOR ArrowCur,OldArrow; //光标句柄
2.程序部分:
先在FormCreate事件处理器加上:
OldArrow=LoadCursor(NULL,IDC_ARROW);//先载入系统原来的光标
OldArrow=CopyCursor(OldArrow); //保存原有光标,此处仍用OldArrow是因为系统原有光标无须清除
a.运行时从*.cur文件直接载入
可利用API函数LoadCursorFromFile()载入,示例如下:
在FormCreate事件处理器加上:
ArrowCur=LoadCursorFormFile(curfile.c_str()) ;//curfile为光标文件名,AnsiString型
b.设计时利用*.res文件载入
首先向工程中加入含有光标资源的*.res文件(可用CBuilder5\Bin\brcc32.exe编译RC文件或直接利用C++Builder5附带的Image Editor(在Tools菜单中有命令项)制作),然后使用在FormCreate事件处理器中以下语句:
ArrowCur=LoadCursor(HInstance,"ARROW");//ARROW为光标资源名
c.光标的加载使用
(1)临时改变光标
在需要改变光标的窗口或控件的MouseDown及MouseMove事件中加入以下代码:
::SetCursor(ArrowCur);//ArrowCut==NULL则SetCursor()返回false
(2)改变窗体默认光标
这需要Timer控件的帮助(若窗体中无Timer控件,则添加一个)。在在定时器的OnTimer事件处理器添加以下代码:
TPoint pos; if(GetCursorPos(&pos)&&pos.x>Left&&pos.y>Top&&pos.x<=Left+Width&&pos.y<=Top
+Height)
SetSystemCursor(ArrowCur,32512); //32512为IDC_ARROW之ID
else
SetSystemCursor(OldArrow,32512);
d.光标资源的释放
用上述方法载入光标后,在退出程序时要记住释放光标所占的系统资源。API函数DestroyCursor()可用来释放光标资源,只需在主窗口的FormCloseQuery事件处理器中加上:
SetSystemCursor(OldArrow,32512);//恢复系统原来的默认光标
DestroyCursor(ArrowCur);//ArrowCur见上面的代码
六、字幕的实现
较流行的播放器都实现了字幕功能,主要用来滚动显示当前正播放的音乐文件名等信息,给人一种挺不错的动感。下面是笔者找到的几种实现方法,供大家参考:
1.用Label、Image等控件的Canvas属性之CopyRect方法
这里笔者以一个Label及一个Image控件为例来实现字幕(你当然可用两个Image或者其他Visible为false时还可在运行时使用Canvas属性的控件如PaintBox来实现):
a.向主窗体添加一个Label及一个Image控件,分别取名为LabelShow及ImageShow;设置好LabelShow大小(此处设为长240、高16)和位置,将其Font属性设为“宋体9号”,Color设为clFuchsia(此处设置字体是为了赋给ImageShow→Canvas→Font),AutoSize设为false,作为字幕目标区;接下来设置好ImageShow的高度(16),并确保其AutoSize、Visible属性值为false,作为显示字幕源图像;再添加一个Timer定时器控件,用于控制字幕的显示(其Interval属性值可设得小一些,亦可在程序中调整);
b.在主窗体头文件中窗体类的private部分加上:
AnsiString ShowStr;//字幕之字符串
int MaxLen,NowPos; //字幕之字符串长及当前显示字符位置
c.在主窗体的FormCreate事件处理器中加上:
ImageShow->Canvas->Font->Assign(LabelShow->Font);//设定字体
d.在改变显示文本的方法中添加以下代码:
ShowStr=...;//改变显示文本
MaxLen=ShowStr.Length();
if(MaxLen<40)//此处40为LabelShow可显示字符数
{int len=41-MaxLen;
char *space=new char[len];
len--;
MaxLen+=len;
int i=0;
while(i++
*space=0;
ShowStr+=AnsiString(space);
delete space;}
MaxLen++;//增加一个字符宽的空白,对长串来说美观一些
ImageShow->Width=MaxLen*6;//改变Image长度,6为宋体9号字一个半角字符宽
ImageShow->Canvas->Brush->Color=clGreen;
ImageShow->Canvas->FillRect(TRect(0,0,ImageShow->Width,LabelShow->Height)) ;
ImageShow->Canvas->TextOutA(6,2,ShowStr)
e.在定时器的OnTimer事件处理器中添加以下代码:
void __fastcall TFormPlay::TimerTimer(TObject*Sender)
{ //此处为Timer
//其他代码
NowPos++;//前进一个半角字符
NowPos%=MaxLen;
if(MaxLen-NowPos>=40)//40为LabelShow可显示字符数
{ LabelShow->Canvas->CopyRect(TRect(0,0,ImageShow->Width,LabelShow->Height),
ImageShow->Canvas,TRect(NowPos*6,0,NowPos*6+ImageShow->Width,LabelShow->Heigh )) ;
}
else
{ int len=MaxLen-NowPos;
LabelShow->Canvas->CopyRect(TRect(0,0,len*6,LabelShow->Height),
ImageShow->Canvas,TRect(NowPos*6,0,MaxLen*6,LabelShow->Height));
LabelShow->Canvas->CopyRect(TRect(len*6,0,ImageShow->Width,LabelShow->Height),
ImageShow->Canvas,TRect(0,0,(40-len)*6,LabelShow->Height));
}}
2.用Label、Image等控件的Canvas属性之TextOut(A)方法
(1)按方法1中添加Label、Timer控件(只需一个Label或Image等控件),并设置好各属性,需要注意的是LabelShow之Alignment及Layout属性必须分别设为taCenter和tlCenter;
(2)在主窗体头文件中窗体类的private部分加上:
bool isascii;//是ASCII字符?
int infolen; //滚动显示信息标准长
int showlen; //滚动显示信息长
int lastpos; //上一次滚动显示位置
AnsiString showinfo;//滚动显示信息
AnsiString showtmp; //滚动显示信息缓冲
(3)在主窗体的FormCreate事件处理器中加上:
infolen=...;//比LabelShow可显示的字符多2个字符
isascii=true;
showtmp=...;//初始信息,比LabelShow可显示的字符多2个字符,必须如此!
showinfo=showtmp;
LabelShow->Caption=showtmp;
(4)在改变显示文本的方法中添加以下代码:
showinfo=...;//改变显示文本
if(showinfo.Length()
char *space=new char[len+1];
for(int i=0;i
*space=0;
showinfo+=AnsiString(space);
delete space; }
showlen=showinfo.Length();
(5)在定时器的OnTimer事件处理器添加以下代码:
void __fastcall TFormPlay::TimerTimer(TObject *Sender)
{ //此处为Timer
//其他代码
lastpos%=showlen;
char *pp=&showinfo[++lastpos];//前进一个半角字符
char *qq=&showtmp[1];
int i=lastpos;
int k=1;
while(i++<=showlen&&k++<=infolen)*qq++=*pp++;
if(k<=infolen)//此如果语句与上面的循环更新滚动显示信息缓冲
{ pp=&showinfo[1];
while(k++<=infolen)*qq++=*pp++;}
if(isascii)
isascii=int(showinfo[lastpos])>0;
else
{ isascii=int(showinfo[lastpos])>0;
if(!isascii)//解决汉字的显示(防止乱码)
{ showtmp[1]=' ';//对可能引起乱码的半个汉字用空格代替
isascii=true;}}
// LabelShow->Caption=showtmp; *No.0
LabelShow->Canvas->Brush->Color=clGreen; //*No.1
LabelShow->Canvas->FillRect(TRect(0,0,LabelShow->Width,LabelShow->Height)) ;//*No.2
LabelShow->Canvas->TextOutA(-6,2,showtmp);//*No.3
//之所以为-6(一个半角字符宽),是为了避免显示引起乱码的字符(已改为空格)
}
3.直接用Label等控件的Caption属性(不断更新Caption之内容)
只需将上面代码中后面标注为*No.0的语句代替*No.1-*No.3即可。不过,由于此方法因为AnsiString之动态内存申请,使其频繁进行内存申请及释放,虽然对程序性能可能影响不大,但还是建议使用方法2。
编后:至此,我们对用C++ Builder进行多媒体开发的常用技术介绍就告一段落了,愿大家也能做出更多的类似Winamp、超级解霸那样的软件。