MFC项目实战(1)文件管理器--界面设计篇
1.创建项目
文件管理器是一个基于对话框的应用程序。首先新建一个“MFC应用程序”类型的项目,然后输入新建项目的名称“FileMng”,并指定该项目保存的位置,单击“确定”按钮,如图所示。
在弹出的“应用程序类型”向导对话框中,选择“应用程序类型”为“基于对话框”,并取消对“使用Unicode库”复选框的选择,如图所示。
点击Next按钮,在弹出的“用户界面接口”向导对话框中,选择Minimize box 和 Maximize box 复选框,并取消对“About box”复选框的选择,如图所示。
接下来点击Finish完成FileMng项目的创建。
2.设计程序主界面
在向对话框资源上添加控件前,先将对话框上自动创建的ok 和cancel按钮删除掉,然后右键对话框属性设置Font(Size)属性为“宋体(9)”。
(1)首先需要向项目导入一些图标和位图。其中图标用来显示在树形控件及列表控件中,而位图主要用来创建位图按钮。在资源视图中项目名的根节点上右键,在弹出的快捷键菜单中选择“添加”->“资源”菜单命令,在弹出的“添加资源”对话框中单击“导入”按钮并导入相应的位图和图标,如图所示。
(2)在对话框资源上添加一个静态图片控件和两个组框,在属性窗口中设置静态图片控件的Type属性为“Frame”,Color属性设置为“Gray”,设置控件ID号为“IDC_STATIC_BAN”以及修改组框的Caption属性分别为“目录结构(&D)”和“文件列表(&F)”,同时设置它们的控件ID号分别为“IDC_STATIC_MULU”和“IDC_STATIC_FILE”并适当调整它们的大小和位置,如图所示。
记得把那个紫色的虚框往上拉一点,留出添加状态栏的宽度。
同时添三个静态图片控件,在属性窗口中设置它们的Type属性为Bitmap,指定它们的控件ID号分别为“IDC_STATIC_LEFT”、“IDC_STATIC_MID”和“IDC_STATIC_RIGHTT”并分别指定Image属性为IDB_BITMAP1、IDB_BITMAP3和IDB_BITMAP2。添加位图后的对话框资源如图所示。
(3)添加状态栏,首先资源视图中添加一个String table 并在里面添加两个字符串,ID分别为IDS_INDICATOR_MESSAGE 和IDS_INDICATOR_TIME,把它们的Caption分别设置为Msg 和 星期%d 上午 00:00:00
接着声明int型index1,index2用来保存IDS_INDICATOR_MESSAGE面板和IDS_INDICATOR_TIME面板的索引,另外声明两个int变量nWidth1,nWidth2用来保存IDS_INDICATOR_MESSAGE面板和IDS_INDICATOR_TIME面板的宽度。
接着再声明UINT型变量nID和nStyle分别用来接收面板ID和面板风格,还要声明一个CString 变量str 用来接收Msg的内容,然后在FileMng.h文件中声明一个CStatusBar的变量m_wndStatusBar ;在FileMng.h文件中代码如下:
1: public:
2: CStatusBar m_wndStatusBar;
3: int index1, //保存IDS_INDICATOR_MESSAGE,IDS_INDICATOR_TIME索引
4: index2,
5: nWidth1, //保存IDS_INDICATOR_MESSAGE,IDS_INDICATOR_TIME宽度
6: nWidth2;
7: UINT nID, //面板ID
8: nStyle; // 面板Style
9: CString str; // 接收Msg内容
然后在FileMng.cpp文件开头添加如下数组:
1: static UINT indicators[] =
2: {
3: IDS_INDICATOR_MESSAGE,
4: IDS_INDICATOR_TIME
5: };
然后在FileMng.cpp文件中的OnInitDialog()函数添加状态栏创建代码:
1: BOOL CFileMngDlg::OnInitDialog()
2: {
3: CDialogEx::OnInitDialog();
4:
5: // Set the icon for this dialog. The framework does this automatically
6: // when the application's main window is not a dialog
7: SetIcon(m_hIcon, TRUE); // Set big icon
8: SetIcon(m_hIcon, FALSE); // Set small icon
9:
10: // TODO: Add extra initialization here
11: ModifyStyle(WS_THICKFRAME,0);
12: if(!m_wndStatusBar.Create(this)||
13: !m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT)))
14: {
15: TRACE0("Can't create status bar");
16: return false;
17: }
18: ModifyStyle(0,WS_THICKFRAME);
19:
20: //获取IDS_INDICATOR_MESSAGE,IDS_INDICATOR_TIME的索引和对应的宽度
21: index1 = this->m_wndStatusBar.CommandToIndex(IDS_INDICATOR_MESSAGE);
22: index2 = this->m_wndStatusBar.CommandToIndex(IDS_INDICATOR_TIME);
23: m_wndStatusBar.GetPaneInfo(index2,nID,nStyle,nWidth2);
24: //设置IDS_INDICATOR_MESSAGE面板的宽度
25: CRect rect;
26: GetClientRect(rect);
27: this->m_wndStatusBar.SetPaneInfo(index1,nID,nStyle,rect.Width()-nWidth2-15);
28: //设置IDS_INDICATOR_TIME面板的宽度
29: this->m_wndStatusBar.SetPaneInfo(index2,nID,nStyle,rect.Width());
30: //重新摆放控件,因为增加状态栏之后,控件相对位置发生变化,重新摆放才能显示出来
31: RepositionBars(AFX_IDW_CONTROLBAR_FIRST,AFX_IDW_CONTROLBAR_LAST,0);
32: //使用 UNICODE 设置字幕文本 可以做成一个函数动态的改变 滚动字幕的文本
33: str=_T(" 欢迎使用本软件...........");
34: //设置文本 index=0处的 即设置IDS_INDICATOR_MESSAGE面板的文本内容
35: m_wndStatusBar.SetPaneText(0,str);
36:
37: //设置定时器
38: SetTimer(1,500,NULL);
39:
40: return TRUE; // return TRUE unless you set the focus to a control
41: }
上面代码中的第11行和18行的ModifyStyle()代码分别是关闭和开启对话框边框可调属性,因为CStatusBar 在创建状态栏时如果它的父窗口也就是对话框的属性是边框可调的话,那么它创建的状态栏就会自动带有一个右下角的可调三角按钮,比如单独注释掉第11和18号,效果如下:
这个时候你看到鼠标可以放在那个三角按钮对状态栏包括大小和长度的可调,这是我们不希望看到,但是我们又想要FileMng边框是可调的,而CStatusBar的父窗口如果具有可调属性,那么它就默认创建可调的三角按钮,那我们怎么实现父窗口可调的情况下,CStatusBar不创建可调的三角按钮呢? 答案就是恢复注释掉到第11和18行代码,就是在创建CStatusBar时,我们暂时关闭FileMng窗口的可调属性,让CStatusBar误以为它的父窗口没有可调边框属性,等CStatusBar创建完了之后,我们在打开FileMng的可调边框属性,这样就通过欺骗CStatusBar,而达到我们的目的。
F5运行之后,当窗口最大化时你会发现状态栏并没有跟着发生大小和位置的改变,那怎么让它不管在窗口大小变化的时候,一直是处于窗体的底部,并且大小也跟着窗体的大小而变化呢?其实当窗体发生大小改变时它都会发送一个WM_SIZE的消息,所以要使得状态栏跟着窗体大小发生变化就可以在WM_SIZE中来做了,首先给FileMng类添加一个WM_ONSIZE消息,然后在OnSize()函数中添加如下代码:
1: void CFileMngDlg::OnSize(UINT nType, int cx, int cy)
2: {
3: CDialogEx::OnSize(nType, cx, cy);
4:
5: CRect rectDlg,
6: rectBar;
7: GetClientRect(rectDlg);
8: // 当程序刚开始创建时,它会先执行到OnSize()这里再去执行OnInitDialog()函数,所以开始的时候要检测m_wndStatusBar是否已经存在
9: //此外到窗体最小化时,此时rectDlg.width()的宽度就是0, 那么此时就不能进行状态栏的movewindow()工作,因为窗体都没有了, 还move个p啊
10: if(m_wndStatusBar&&(0 != rectDlg.Width()))
11: {
12: CStatusBar *m_pwndStatusBar = (CStatusBar*)&m_wndStatusBar; //获取指向StatusBar句柄的指针
13: m_pwndStatusBar->GetClientRect(&rectBar);//获取当前StatusBar的矩形大小
14: m_pwndStatusBar->SetPaneInfo(index1,nID,nStyle,rectDlg.Width()-nWidth2-15); //重新设置IDS_INDICATOR_MESSAGE面板的宽度
15: m_pwndStatusBar->MoveWindow(0,cy-rectBar.Height(),rectDlg.Width(),rectBar.Height()); //移动StatusBar 的位置 并重新设置它的大小
16: }
17: }
注意到第14行代码重新设置IDS_INDICATOR_MESSAGE面板的宽度,如果注释掉它,则最大化时IDS_INDICATOR_MESSAGE和IDS_INDICATOR_TIME面板平分状态栏,而我们希望IDS_INDICATOR_TIME一直是在右下角,并且希望它的长度大小不变。
好但现在为止,状态栏上时间和提示Msg都还没有变化,时间的获取可以通过CTime类来完成,由此我们自定义一个GetTimeInfo()函数,并返回一个CString字符串,在FileMng.h添加函数声明:
1: public:
2: CString GetTimeInfo();
在FileMng.cpp文件中实现如下:
1: CString CFileMngDlg::GetTimeInfo()
2: {
3: CTime time = CTime::GetCurrentTime();
4: CString s = time.Format("%H:%M:%S");
5: CString strweek;
6: switch( time.GetDayOfWeek())
7: {
8: case 1: strweek=_T("星期天"); break;
9: case 2: strweek=_T("星期一"); break;
10: case 3: strweek=_T("星期二"); break;
11: case 4: strweek=_T("星期三"); break;
12: case 5: strweek=_T("星期四"); break;
13: case 6: strweek=_T("星期五"); break;
14: case 7: strweek=_T("星期六"); break;
15: default: TRACE(_T("Error")); break;
16: }
17: //time.GetHour() = 0 表示凌晨12;00 到12:59
18: //time.GetHour() = 4 表示凌晨4:00 到 4:59
19: CString strtime;
20: if((time.GetHour()>=0)&&(time.GetHour()<=4))
21: {
22: strtime = _T("凌晨");
23: }
24: else if((time.GetHour()>4)&&(time.GetHour()<=8))
25: {
26: strtime = _T("早上");
27: }
28: else if((time.GetHour()>8)&&(time.GetHour()<=11))
29: {
30: strtime = _T("上午");
31: }
32: else if((time.GetHour()>11)&&(time.GetHour()<=13))
33: {
34: strtime = _T("中午");
35: }
36: else if((time.GetHour()>13)&&(time.GetHour()<=17))
37: {
38: strtime = _T("下午");
39: }
40: else
41: {
42: strtime = _T("晚上");
43: }
44:
45: CString clock = strweek+" "+strtime+" "+ s;
46: return clock;
47: }
现在得到了系统时间,那么怎么在状态栏上动态显示时间呢? 这里就需要添加一个WM_TIMER消息来响应了, 首先在FileMng类中添加一个WM_TIMER消息,然后在OnTimer()消息添加相应的操作代码,具体如下:
1: void CFileMngDlg::OnTimer(UINT_PTR nIDEvent)
2: {
3: static int index = 0; // 初始化字符串的长度
4: if(index < 0)
5: index = str.GetLength(); // 获取字符串的长度
6: m_wndStatusBar.SetPaneText(0,str.Right(index));//从右向左读取字符串
7: index -= 2; // 使得index <0 , 循环加载字符串,实现字符滚动显示效果
8:
9: //设置IDS_INDICATOR_TIMER的时间
10: CString clock = GetTimeInfo();
11: m_wndStatusBar.SetPaneText(1,clock);
12:
13: CDialogEx::OnTimer(nIDEvent);
14: }
F5 运行可以看到Msg的字符滚动显示, 右下角动态显示时间,但是这里有个问题就是程序刚启动的时候右下角并没有立即显示时间而是过了一会,那这怎么解决呢?在OnInitDialog的设置IDS_INIDCATOR_MESSAGE内容语句下面添加一个设置IDS_INDICATOR_TIEM 的语句,这样初始化时就会立即显示系统时间。
1: // ……
2: //设置IDS_INDICATOR_TIME的内容
3: CString clock = GetTimeInfo();
4: m_wndStatusBar.SetPaneText(1,clock);
5: //设置定时器
6: SetTimer(1,500,NULL);
这里有些人就要问了,这个SetTimer(1,500,NULL)中的1,500,NULL都是指的啥?这个1就是这个定时器的标识符,有时候要定义多个定时器就需要这个参数来标识是哪个定时器,500是指每0.5秒触发一次定时器,执行一次OnTimer函数,这个值的设置就需要注意了,如果你设置一个比较大的值比如1000,5000之类的可能你就会看到那个时间有点闪,此时就要设置一个比较小的值比如500,为什么会这样呢? 这个跟每个机子的配置有关,机子好的可以设置大一点没关系,机子差的就最后设置小一点,免得闪烁。最后一个参数表示你要调用的是哪个函数,比如你要调用一个自定义的函调函数这里就需要写上你自定义的回调函数名称,如果写NULL,则表示调用WM_TIMER生成的OnTimer()函数执行。
关于SetTimer函数的用法,请参考:SetTimer函数的用法
(4)接着设计主界面,使用控件工具箱向对话框资源中加入树形控件、列表控件、按钮、复选框、单选框等控件,并按照下面的表格设置相应的属性值,适当调整控件的大小,最终如图所示。
类型 |
ID标识符 |
属 性 |
静态文本 |
IDC_STATIC_ADDR |
Caption:地址(&A): |
组合框 |
IDC_DIRPATH |
Type:DropDown |
按钮 |
IDC_STEPADDR |
Caption:转到(&G)Default Button:true |
树形控件 |
IDC_DIRTREE |
Always Show Selection:true Has Button: true Has Line : true |
列表控件 |
IDC_FILELIST |
Always Show Selection : true View : report |
复选框 |
IDC_HIDEFILE |
Caption : 隐藏文件(&H) |
复选框 |
IDC_SYSFILE |
Caption : 系统文件(&S) |
组框 |
IDC_STATIC_DEL |
Caption : 删除选项 |
单选按钮 |
IDC_DELOPT |
Caption : 到回收站(&R) Group : true |
单选按钮 |
IDC_RADIO2 |
Caption : 切底删除(&V) |
按钮 |
IDC_OPENTO |
Caption :打开 Owner Draw : true |
按钮 |
IDC_RENAME |
Caption :重命名 Owner Draw : true |
按钮 |
IDC_COPYTO |
Caption :复制到 Owner Draw : true |
按钮 |
IDC_MOVETO |
Caption :移动到 Owner Draw : true |
按钮 |
IDC_DELETE |
Caption :删除 Owner Draw : true |
静态文本 |
IDC_STATIC_MSG |
Caption : 信息(&M) |
静态文本 |
IDC_MSGINFO |
Caption : 提示信息 |
按照上面的步骤搭建好了之后,界面如图:
哇, 这个界面真是丑爆了! 但是设计的时候不是挺好的看的吗? 你看下图不是挺好看的吗?
为什么呢? 这是因为新建项目的时候没有选择 Unicode 字符那个复选框,那还有什么方法可以挽救吗? 答案是有的, 把下面的XML代码保存为XPStyle.manifest文件,然后把它拷贝到res目录下
1: <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2:
3: <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
4:
5: <assemblyIdentity
6:
7: version="1.0.0.0"
8:
9: processorArchitecture="X86"
10:
11: name="XP style manifest"
12:
13: type="Win32"
14:
15: />
16:
17: <dependency>
18:
19: <dependentAssembly>
20:
21: <assemblyIdentity
22:
23: type="Win32"
24:
25: name="Microsoft.Windows.Common-Controls"
26:
27: version="6.0.0.0"
28:
29: processorArchitecture="X86"
30:
31: publicKeyToken="6595b64144ccf1df"
32:
33: language="*"
34:
35: />
36:
37: </dependentAssembly>
38:
39: </dependency>
40:
41: </assembly>
在资源视图中通过添加资源的方式把这个XPStyle.manifest文件添加进来,Resource Type 填入:IDR_XP_STYLE 然后把它的ID改为1 编译运行就可以解决界面风格问题了。
现在还有一个问题就是当最大化窗体时, 那个什么树形控件,什么控件的都不会随着放大和移动位置,那怎么办呢? 也许你会想到之前状态栏在OnSize进行了大小和位置的调整设置,那这里还可以这样用吗? 回答是当然可以的, 但是你想一下这么多控件要移动,而且要计算每个控件的大小,更恼火的是哪些控件要保持大小位置不变,哪些只变大小,或只变位置,或只变长度不变宽度等等都要计算,做起来这么琐碎,还要采取这种方法吗? 可以但是难免会出错啊, 那怎么办呢? 到网上找了一个别人写好控件随窗体变化大小位置的类,这里我们直接用就可以了。
首先把ResizeControl.cpp/.h文件拷贝到项目目录中,然后通过添加存在项把它添加到项目中,然后在FileMngDlg.h中包含ResizeControl.h头文件 并声明一个变量m_Reszie. 然后在OnInitDialog()中添加 m_Resize.SetOwner(this); 启用ResizeControl类,接下来通过m_Resize的SetResize()函数来设置各个控件的跟随效果,具体代码如下:
1:
2: ////////////////////////////////////////////////////////控件随窗体大小发生移动或发生大小变化//////////////
3:
4: m_Resize.SetOwner(this);
5: //左上角固定,右上角向右移动,大小发生变化,向右延伸
6: m_Resize.SetResize(IDC_STATIC_BAN,PK_TOP_LEFT,PK_TOP_RIGHT);
7: //随窗口左移动,不会改变大小
8: m_Resize.SetResize(IDC_STATIC_LEFT,PK_TOP_LEFT,PK_TOP_LEFT);
9: //左上角固定,右上角向右移动,大小发生变化,向右延伸
10: m_Resize.SetResize(IDC_STATIC_MID,PK_TOP_LEFT,PK_TOP_RIGHT);
11: //随窗口右移动,不会改变大小
12: m_Resize.SetResize(IDC_STATIC_RIGHT,PK_TOP_RIGHT,PK_TOP_RIGHT);
13: //随窗口左移动,不会改变大小
14: m_Resize.SetResize(IDC_STATIC_ADDR,PK_TOP_LEFT,PK_TOP_LEFT);
15: //左上角固定,右上角向右移动,大小发生变化,向右延伸
16: m_Resize.SetResize(IDC_DIRPATH,PK_TOP_LEFT,PK_TOP_RIGHT);
17: //随窗口右移动,不会改变大小
18: m_Resize.SetResize(IDC_STEPADDR,PK_TOP_RIGHT,PK_TOP_RIGHT);
19: //左上角固定,左下角随窗口变化
20: m_Resize.SetResize(IDC_STATIC_MULU,PK_TOP_LEFT,PK_BOTTOM_LEFT);
21: //左上角固定,右下角随窗口变化
22: m_Resize.SetResize(IDC_STATIC_FILE,PK_TOP_LEFT,PK_BOTTOM_RIGHT);
23:
24: //左上角固定,左下角随窗口变化
25: m_Resize.SetResize(IDC_DIRTREE,PK_TOP_LEFT,PK_BOTTOM_LEFT);
26: //左上角固定,右下角随窗口变化
27: m_Resize.SetResize(IDC_FILELIST,PK_TOP_LEFT,PK_BOTTOM_RIGHT);
28: //只随窗口上下移动,也不会改变大小
29: m_Resize.SetResize(IDC_HIDEFILE,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
30: //只随窗口上下移动,也不会改变大小
31: m_Resize.SetResize(IDC_SYSFILE,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
32:
33: //只随窗口上下移动,也不会改变大小
34: m_Resize.SetResize(IDC_STATIC_DEL,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
35:
36: //只随窗口上下移动,也不会改变大小
37: m_Resize.SetResize(IDC_DELOPT,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
38:
39: //只随窗口上下移动,也不会改变大小
40: m_Resize.SetResize(IDC_RADIO2,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
41:
42: //只随窗口上下移动,也不会改变大小
43: m_Resize.SetResize(IDC_OPENTO,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
44:
45: //只随窗口上下移动,也不会改变大小
46: m_Resize.SetResize(IDC_RENAME,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
47:
48: //只随窗口上下移动,也不会改变大小
49: m_Resize.SetResize(IDC_COPYTO,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
50:
51: //只随窗口上下移动,也不会改变大小
52: m_Resize.SetResize(IDC_MOVETO,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
53:
54: //只随窗口上下移动,也不会改变大小
55: m_Resize.SetResize(IDC_DELETE,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
56:
57: //只随窗口上下移动,也不会改变大小
58: m_Resize.SetResize(IDC_STATIC_MSG,PK_BOTTOM_LEFT,PK_BOTTOM_LEFT);
59:
60: //只随窗口上下移动,大小发生变化,右边向右延伸
61: m_Resize.SetResize(IDC_MSGINFO,PK_BOTTOM_LEFT,PK_BOTTOM_RIGHT);
62:
63:
64: ////////////////////////////////////////////////////////控件随窗体大小发生移动或发生大小变化//////////////
然后在OnSize()消息函数中先把 //CDialogEx::OnSize(nType, cx, cy); 这行给注释掉,然后加入m_Resize.OnSize(cx,cy); 然后F5 效果出来了。 至此界面设计亦完结了。