孙鑫:第九讲 界面修改,工具栏,状态栏,启动栏

 

1:如何修改单文档应用程序的窗口标题,查阅MSDC文章:

Changing the styles of a window created by MFC.
  要在CMainFrame的PrecreatWindow()中加入如下代码:
  cs.style&=~FWS_ADDTOTITLE;
  cs.lpszName="This is a test!";
  可以先不要上一句试一试!
另一种方法是 :
  cs.style=WS_OVERLAPPEDWINDOW;
再进行修改,也可以不修改,那么是去掉默认文档标题,而只显示原程序标题!


  另一类方法是在窗口创建后再修改,因为在OnCreate中,开始的这些代码:
 if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
  return -1;
 
 if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
  | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
  !m_wndToolBar.LoadToolBar (IDR_MAINFRAME))
 {
  TRACE0("Failed to create toolbarn");
  return -1;      // fail to create
 }

 if (!m_wndStatusBar.Create(this) ||
  ! m_wndStatusBar.SetIndicators(indicators,
    sizeof (indicators)/sizeof(UINT)))
 {
  TRACE0("Failed to create status barn");
  return -1;      // fail to create
 }
 // TODO: Delete these three lines if you don't want the toolbar to
 //  be dockable
 m_wndToolBar.EnableDocking (CBRS_ALIGN_ANY);
 EnableDocking (CBRS_ALIGN_ANY);
 DockControlBar(&m_wndToolBar);

完成了窗口创建,工具栏,状态栏的创建等工作,可以在后面利用一个系统全局函数SetWindowLong()函数进行修改:

加入代码为:SetWindowLong(m_hWnd,GWL_STYLE,WS_OVERLAPPEDWINDOW);
与此相对,还有一个GetWindowLong()函数可供使用!如下面代码去掉了窗口上的最大化按钮:
SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_STYLE) & ~MAXIMIZEBOX);
当然SetWindowLon()还可以做别的修改.与SetWindowLong()相类似的另一个系统全局函数为 SetClassLong();


2:如何完成一个动画图标
其实就是准备好几个图标,在定时器消息 响应中更改图标即可完成.
第一步是准备好几个(如三个)图标.
第二步是在CMainFrame类中做三个图标类的相关对象的成员变量,或者是一个大小为3的HICON数组.
第三步是在CMainFrame类的OnCreate()函数中LoadIcon()进行对三个图标的加载.其中用到的实例句柄的获取有三种方法:
一:用全局函数AfxGetInstanceHandle()获取,
二:先在CMainFrame类中用extern声明一下全局对象theApp,然后使用theApp.hInstance;
三:使用全局函数AfxGetApp()获取全局对象theApp对象的指针,然后用AfxGetApp()->hInstance;
第二个参数是一个字符指针,可我们只有图标的资源ID,所以要进行必要的转换:用MAKEINTRESOURCE宏!
第四步是设置定时器,也在OnCreate()函数中定义:SetTimer(1,1000,NULL);
第五步是在CMainFrame中添加WM_TIMER消息响应,在其中加入代码:
 static int index=0;
 SetClassLong(m_hWnd,GCL_HICON,(LONG)m_hIcons[index]);
 index=++index%3;


3:在工具栏上新加一个按钮,要让它与前一个按钮之间有一个分隔符,只需要将它轻轻向一旁拖动一点点再放开即可,而要删除工具栏上的一个按钮,你只是选中它再按DEL键是完不成的,它只是将按钮上的图案删除,所以删除一个按钮要将它拖动到工具栏之外,再松手!


4:如何创建一个工具栏
在MSDN的关于CToolBar的讲解页有详细说明!
一:插入工具栏资源,
二:在CMainFrame中加入一个CToolBar类对象的成员变量,
三:在CMainFrame的OnCreate()中加入:
 if (!m_MyToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
  | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
  ! m_MyToolBar.LoadToolBar(IDR_MYTOOLBAR))
 {
  TRACE0("Failed to create toolbarn");
  return -1;      // fail to create
 }

 m_MyToolBar.EnableDocking (CBRS_ALIGN_ANY);
 DockControlBar(&m_MyToolBar);
各个函数调用及参数传递查看MSDN!


5:如何让一个工具栏隐藏或显示:
 if(m_MyToolBar.IsWindowVisible())
 {
  m_MyToolBar.ShowWindow(SW_HIDE);
 }
 else
 {
  m_MyToolBar.ShowWindow(SW_SHOW);
 }
但这样做的结果是工具栏虽说隐藏了, 但是工具条还在,所以还要在后面加上一句:
        ReCalcLayout();
这样做还是有问题,如果工具栏没有停靠在边上而是一个单独的小窗口,那么只做上面的工作只使得工具栏上的按钮不见了,而那个小窗口还在,所以,还要调用一个函数:
 DockControlBar (&m_MyToolBar);
经过上面这句,小窗口也如愿消失了,但问题还有一点,就是当用户将工具栏放置为一个小窗口时,再点击菜单,要让这个工具栏显示出来,当然我们应该将工具栏仍按用户先前的小窗口样式显示出来比较好,可是这次工具栏又自动停靠在客户区顶部了?这个功能如何实现呢?孙老师只是提示可以查MSDN中CToolBar的成员函数解决这个问题,并没细讲,所以我看了MSDN,发现有两个函数:CToolBar::IsFloating()利用这个函数可以判断一个工具栏是否处于浮动状态,另一个是CFrameWnd::FloatControlBar()这个函数可以让一个控制栏处于浮动状态,然后我在CMainFrame中加入了一个BOOL型的成员变量,在每次判断工具栏是否可见时用来记录工具栏是否处于浮动状态,然后在重新生成工具栏时根据它的置决定是否将工具栏设为浮动状态,但是第二个函数好像不太好使,所以我又换用了SetWindowPos()成员函数,可是也不能将它放置为一个独立的小窗口.
 显示和隐藏工具栏的第二种方法:
用一个函数:CFrameWnd::ShowControlBar(),因为这个函数的固有特性,上面是if...else...判断就可以简化为一句代码:
 ShowControlBar(&m_MyToolBar,!m_MyToolBar.IsWindowVisible(),FALSE);
并且我惊讶的发现,用这个函数时,上面提到的浮动工具栏让它在恢复的时候仍回复为浮动的问题自动解决了!哈哈,好.


6:状态栏相关编程
因为MFC自动生成的系统已经包含了一个状态栏,所以我们暂时仅限于已有状态栏的修改,而不是另外生成一个状态栏.
状态栏最左边的那一长条,就是经常显示一些提示字符串的那部分叫做提示行,而右侧那三个小窗口是用来指示 CapsLock,ScrollLock,NumLock开关的状态,称为状态指示器.
状态栏跟工具栏一样,也是在CMainFrame类中定义并在OnCreate()中创建的.
下面的代码在状态指示器的最左边放置了两个小窗口,并在第一个小窗口中放置了一个时钟:
同样的CMainFrame的OnCreate() 中,
 CTime tm=CTime::GetCurrentTime();
 CString strTime=tm.Format("%H:%M:%S");

 CClientDC dc(this);
 CSize sz=dc.GetTextExtent(strTime);
 m_wndStatusBar.SetPaneInfo (1,IDS_TIMER,SBPS_NORMAL,sz.cx);//调整窗口大小
 m_wndStatusBar.SetPaneText(1,strTime);
 SetTimer(2,1000,NULL);
当然要先有准备工作,在字符串资源中添加两个字符串,ID分别为:IDS_TIMER,IDS_PROGRESS,交将之添加到MainFrm.cpp的
static UINT indicators[] =
{
 ID_SEPARATOR,            // status line indicator
 IDS_TIMER,
 IDS_PROGRESS,
 ID_INDICATOR_CAP S,
 ID_INDICATOR_NUM,
 ID_INDICATOR_SCRL,
};
这个数组中,并且上面SetPaneInfo()中的第一个参数就是相应ID在indicators[]数组中的索引值,显然最后三个ID是大小写,数字锁及ScroolLock的标识.
做完上面这些工作,这个时钟还不能变化,只是显示了一个定值,我们还要在定时器的响应函数中再刷新该值:
 if (2==nIDEvent)
 {
  CTime tm=CTime::GetCurrentTime();
  CString strTime=tm.Format("%H:%M:%S");
  m_wndStatusBar.SetPaneText (1,strTime);
 }
这样就完成了一个在状态栏中显示的,可以动态变化的时钟!


7:如何创建一个进度条并将之放置在状态栏中的某个位置?
与MFC中其它标准资源一样,进度条也有一个专门的类与之相对应:CProgressCtrl类.
先在CMainFrame中放置一个成员变量:CProgressCtrl m_ProgBar;
然后在OnCreate()中加 入:
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,CRect (300,100,500,120),this,88888);
就可以在窗口中显示一个进度条.
至于要将之放置于状态栏上的某个窗格之中,就要先得到窗格所在的矩形区域,然后在上面是第二个参数中进行指定,故将上面一句改为如下:
 CRect rect;
 m_wndStatusBar.GetItemRect (2,&rect);
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,this,88888);
可是这样并未实现设置,所以我们在上面设置断点,发现到达m_ProgBar.Create()时矩形区域的值并不正常,原来在OnCreate()未结束时,状态栏的设置无法完成,无法获得矩形区域位置,所以,就要想办法在OnCreate()结束时做上面的工作,可以做一个自定义消息,在OnCreate()末尾发送一个该消息,并做一个消息响应函数,然后将上面的代码放置在其中即可.
要自定义消息,首先要在CMainFrame的头文件首部 做:
 #define UM_PROGRESS WM_USER+1 //注意,没有;
WM_USER是一个系统定义宏,详细情况查MSDN.
然后在CMainFrame的头文件的消息响应中加入消息响应函数的声明:
 afx_msg void OnProgress();
再加入消息映射:
 ON_MESSAGE(UM_PROGRESS,OnProgress)
再实现消息响应函数:
void CMainFrame::OnProgress()
{
 CRect rect;
 m_wndStatusBar.GetItemRect(2,&rect);
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,123);//注意,这里父窗口就不能设置为this了, 而要是状态栏!
 m_ProgBar.SetPos(50);
}
当然,不能忘了在OnCreate()最 后发送消息:
 PostMessage(UM_PROGRESS);

这样就可以了!
但是问题还是有的,你会发现当你拉动窗口改变其大小时,状态栏的位置发生了变化,不再覆盖在先关状态栏的那个小窗口上了,怎么办呢?你会想到在窗口大小改变时,系统会接受到一个WM_PAINT消息,只要在那个消息的响应函数中实时获取小窗口的矩形区域,再改变进度条的位置,不就可以了吧,没错,这样的确可以,又因为WM_PAINT消息当窗口显示时,也就是说OnCreate()之后就会马上收到,所以我们也不用像上面那样麻烦的自定义什么消息啦,直接在CMainFrame中加入WM_PAINT的消息响应函数,并在其中加入:
 CRect rect;
 m_wndStatusBar.GetItemRect(2,&rect);
 m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,123);
 m_ProgBar.SetPos(50);
即可了,但是问题又来了,当窗口大小一改变时,程序发生了一个致使错误,原来我们不能在每一次响应时都创建进度条,进度条对象只有一个,哪能多次使用呢?所以,改为如下代码:
 CRect rect;
 m_wndStatusBar.GetItemRect(2,&rect);
 if(!m_ProgBar.m_hWnd)
 {
  m_ProgBar.Create(WS_CHILD | WS_VISIBLE,rect,&m_wndStatusBar,88888);
 }
 else
 {
  m_ProgBar.MoveWindow(&rect);
 }
 m_ProgBar.SetPos(50);
这下,无论怎么拖,也不会发生异常现象了!
那么,如何让进度条动起来呢?相关的成员函数为
SetStep(),以及StepIt();


8:下面的代码将鼠标当前的位置座标显示在状态栏的提示行中:

在View类中响应WM_MOUSEMOVE消息,在其中加入代码如下:

 CString str;
 str.Format("x=%d,y=% d",point.x,point.y);
 ((CMainFrame*)GetParent())->m_wndStatusBar.SetWindowText(str);

这段代码要正常运行的前提是首先CMainFrame在View文件中不可见,所以要先包含一个MainFrm.h头文件,然后,因为m_wndStatusBar在CMainFrame中是一个protected变量,所以不能在View类中访问,所以我们要手动把它的访问权限改为public.然后就OK了!
其实还有一种更方便的方法:调用CFrameWnd::SetMessageText()
所以上面的代码就 可以改为:
  CString str;
  str.Format("x=%d,y=% d",point.x,point.y);
 ((CMainFrame*)GetParent())->SetMessageText (str);
这样子,就不用取得状态栏指针了,也就不用去修改其访问权限了!
关于这个工作,还有第三种方法,也不用去修改状态栏的访问权限,这是应用了另一个函数:CHtmlView::GetStatusBar(),上面的代码就可以是:
 CString str;
 str.Format("x=%d,y=%d",point.x,point.y);
 ((CMainFrame*)GetParent())->GetMessageBar()->SetWindowText(str);
下面还有第四种方式:这是应用了又一个函数:CWnd::GetDescendantWindow(),这个函数可以根据窗口ID来从调用它的窗口出发找到与该ID相同的一个子孙窗口的指针:
 CString str;
 str.Format("x=%d,y=%d",point.x,point.y);
 GetParent()->GetDescendantWindow(AFX_IDW_STATUS_BAR)->SetWindowText(str);
至于上面那个ID号,是从CStatusBar::Create()中看到它!关于GetDescendantWindow()的调用,要注意其第二个参数在MSDN中关于临时窗口及持久窗口的讲解!


9:关于程序的启动画面的制作
这个跟往程序中加入右键PopUp菜单一样,可以使用VC的组件库中的组件.找到一个叫SplashScreen的东西,加入它到工程中,别的什么也别动,先编译运行一下,你会发现程序已经有一个启动画面了,不过是很简陋的.
这个组件在我们的程序中加入了一副默认位图,并加入了一个叫CSplashWnd的类,并且在CMainFrame的OnCreate()函数中加入了一句:CSplashWnd::ShowSplashScreen(this),进行了相应的启动工作.
在CSplashWnd的成员函数OnCreate()中有一句SetTimer(1, 750, NULL);其消息响应函数中进行了启动画面的隐藏,所以,修改其中的时间值可以修改启动画面显示的时间长度!
另外,可以发现窗口的显示与启动画面的显示是同时进行的,如何才能让画面消失时窗口才显示出来呢?

至于启动画面的改变,可以自己插入位图,然后将它的ID修改为IDB_SPLASH 然后RebuildAll,也可以在其源文件中LoadBitmap()中将ID改为自己想要加载的位图的ID!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章