概述
[前言:]拥有美丽的外观,软件就成功了一半。界面由控件、工具栏、菜单、窗体等元素组成,对他们进行美化就能得到一个美丽的界面。
让控件更醒目
在ComboBox中改变列表框的宽度
我们经常会使用到组合框,而组合框是是有2种功能的--下拉和列表。一般情况下,列表框的宽度和选择框是一样宽的,但是我们有些时候确实很需要把列表框的宽度变大,一便让我们能更好的看我们要选的东西。
为了能有这个功能,我写了下面的这个函数。首先得在你的对话框中添加一个的WM_CTLCOLOR的消息句柄,或者使用CComboBox的继承类,而且在其中添加下面的代码:
HBrush tvisualcombo::onctlcolor(CDC* pdc, CWND* pwnd, UINT nctlcolor) // todo: return a different brush if the default is not desired |
这样之后还没有完全好,你还得刷新一下列表框,那样才能随时根据列表框中的文本的长度,而改变列表框的宽度,要想这样的话,你还得这样,你必须扫描列表框中的条目,还得计算其中文本的长度(通过pdc),这样你如果再刷新列表框的话,才能一条目中比较长的来显示。
上面的方法是通过WM_CTLCOLOR消息来实现的,后来才知道在MFC的CComboBox类中有一个函数也可以实现同样的功能,就是:
CComboBox::SetDroppedWidth(int width); |
通过这个函数,你可以把宽度设成你自己喜欢的值,而它的实际的宽度是下面2个值中的最大值:
1.你所设置的值(就是通过上面的函数所设置的值)
2.列表框的值
如何获取一个对话控件的指针
有两种方法。其一,调用CWnd: : GetDlgItem,获取一个CWnd*指针调用成员函数。下例调用GetDlgItem,将返回值传给一个CSpinButtonCtrl*以便调用CSpinButtonCtrl : : SetPos 函数:
BOOL CSampleDialog : : OnInitDialog ( )
{
CDialog : : OnInitDialog ( ) ;
//Get pointer to spin button .
CSpinButtonCtrl * pSpin - ( CSpinButtonCtrl *) GetDlgItem (IDC_SPIN) ;
ASSERT _ VALID (pSpin) ;
//Set spin buttons default position .
pSpin —> SetPos (10) ;
return TRUE ;
}
其二, 可以使用ClassWizard将控件和成员变量联系起来。在ClassWizard中简单地选择Member Variables标签,然后选择Add Variable …按钮。如果在对话资源编辑器中,按下Ctrl键并双击控件即可转到Add Member Variable对话。
VC中如何改变对框中控件的颜色
在VC中,当我们大量的运用控件时,往往会为改变控件的颜色所烦恼。因为VC不象VB那样,可以方便地改变对话框及各个控件的颜色,要改变一个控件的颜色比较烦琐。本文所介绍的就是如何改变在一个对框上的控件的颜色。步骤如下:
① 先创建一个基于对话框的工程,命名为test,然后在对话框上加入一个ListBox控件。
② 在testDlg.h中加入一个成员变量:CBrush m_brush;
③ 在OnInitDialog()中,加入m_brush.CreateSolidBrush( RGB(0,0,0 );此处设置的RGB值可以改变ListBox的背景色。为了观看ListBox中字的颜色变化,我们给ListBox加入几个字:利用Class Wizard给ListBox加入一个Control类型的成员变量m_ctrlListBox,然后在OnInitDialog()加入如下所示的代码:
m_ctrlListBox.AddString("1号选手");
m_ctrlListBox.AddString("2号选手");
④ 点击Class Wizard,给testDlg加入WM_CTLCOLOR事件,单击Edit Code按钮,然后把改函数的内容替换为如下代码:
if(nCtlColor== CTLCOLOR_LISTBOX)
{
pDC- >SetBkMode(TRANSPARENT);
pDC- >SetTextColor(RGB(255,255,255));
//此处设置字体的颜色
return (HBRUSH)m_brush.GetSafeHandle();
}
else
return CDialog::OnCtlColor (pDC, pWnd, nCtlColor);
现在编译并运行改程序,可以看到列表框已经变成黑色而其中的字已经变为白色了!
工具栏和状态条设计
在VC++下实现高彩色工具条
引言
一些Windows系统自带程序如资源管理器、Internet Explorer等程序的工具条看上去和其他一些程序的工具条不太一样,在颜色上要漂亮许多。其实这些程序的工具条上的图标均为256色,而普通应用程序在工具栏上所显示图标的颜色通常只有16色,这就决定了后者在视觉上远没有前者美观。由于Windows随系统而带的程序也是由开发人员编写的应用程序,这就说明通过程序编码可以实现256色甚至更多色彩的图标在工具栏上的显示。为此笔者经过摸索,通过MFC编程在应用程序中实现了高彩色工具条。现将实现的主要方法介绍如下,以飨广大读者。
基本设计思路
在实现高彩色工具条之前,先研究一下普通16色的工具条的实现过程,并从中总结出改进方法。在VC的资源视图中工具条是一个资源名为IDR_MAINFRAME的Toolbar型资源,并可通过在编辑按钮上的图标来完成工具条上图标的绘制。虽然在资源视图中工具条上各按钮的图标是相互独立的,但在存储时并非像图标一样保存为ico格式文件而是以bmp位图格式保存在磁盘上的。该位图是一个由工具条上的按钮图标组成的长条型位图图像,中间没有任何缝隙,在程序运行和在资源视图对工具条进行编辑时该图像首先装载到一个图像列表中,然后工具栏根据索引依次从图像列表中将图像显示到工具条的各个按钮上。由于VC限制工具栏上的图标不能超出16色,因此不论是在资源视图直接编辑位图还是用复制粘贴等手段均无法获取超出256色的工具条(注:用复制粘贴的方法虽然在编辑视图中可以暂时显示出256色的图标,但在程序运行时仍会退化成16色)。
由于不能在资源视图中通过编辑Toolbar资源实现16色以上的图标,加之工具条在显示时有并不直接从Toolbar获取图标而是从图像列表中读取,因此可以通过其他一些图像处理软件做好类似于工具条的bmp图像(仅颜色比普通工具条bmp图像丰富,其余完全一样),并以位图的形式加入到程序资源。在使用时,先将其读取到图像列表,这样图像列表中用于显示到工具条上的图标的颜色就可以是256、24位、甚至32位色的了。由于工具条缺省时将直接加载资源名为IDR_MAINFRAME的Toolbar型资源作为图标的来源,因此还必须通过SetImageList()函数将含有高彩色工具条位图的图像列表指定为工具条的图标来源。
真彩工具条的实现
由于工具条的创建是在主框架类的OnCreate()函数中完成的,因此高彩色图像的装载和图像列表的替换工作必须也在此进行。在进行程序设计之前,需要做好各种准备工作,比如高彩色工具条位图的绘制、高彩色位图加入到资源等。绘制工具条位图时,必须控制好图像的尺寸,如需要有N个边长为 M的图标,那么需要绘制的位图尺寸为长=N*M;宽=M。真彩位图在加入到工程之后就不能再在VC的资源视图中进行编辑了。由于这个彩色位图仅起到美化界面的作用,因此具体对的事件响应等工作还要通过设置原有的Toolbar资源来完成。
准备工作就绪后,先要把工具条位图装载到图像列表,这样才能被工具条做获取。在作这一步时,必须用::LoadImage()函数去加载工具条位图,并通过宏MAKEINTRESOURCE()来指定具体要加载哪一个资源位图:
HBITMAP hbm = (HBITMAP)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_TOOLBAR), //加载IDB_TOOLBAR IMAGE_BITMAP, //按位图格式 0,0, // cx,cy LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS ); LoadImage返回一个位图句柄HBITMAP,但在MFC下使用CBitmaps会更加方便,可创建一个 CBitmap对象并用Attach()成员函数将它与位图句柄联系起来: CBitmap bm; bm.Attach(hbm); |
MFC加载工具栏位图时使用了一个内部函数AfxLoadSysColorBitmap()将缺省颜色设定为16色,因此为了显示16色以上的图像,必须在调用图像列表类CImageList的Create()函数创建图像列表时对图像清单做进一步的处理:
m_ilToolBar.Create(32,32,ILC_COLOR8, 4, 4); m_ilToolBar.Add(&bm,(CBitmap*)NULL); |
这里用ILC_COLOR8标明了创建的图像列表是256色的,在VC的commctrl.h中对其有定义,并且还提供有其他几种颜色位深度的预定义:
#define ILC_COLOR4 0x0004 //16色 #define ILC_COLOR8 0x0008 //256色 #define ILC_COLOR16 0x0010 //16位色 #define ILC_COLOR24 0x0018 //24位色 #define ILC_COLOR32 0x0020 //32位色 |
如果使用的工具条位图只有256色(对于多数程序这样已经足够),则显然没有必要再使用更高级别的位深度定义。最后一步,也是最关键的一步,必须通过SetImageList()函数指定工具条m_wndToolBar的图标来源不再是原来缺省的图像列表而是含有高彩色位图的图像列表m_ilToolBar:
m_wndToolBar.GetToolBarCtrl().SetImageList(&m_ilToolBar); |
到此为止就可以通过MFC在自己编写的程序中实现类似于IE等软件的漂亮的工具条了。下图就是笔者用上述方法得到的程序界面:
小结
本文通过对作为工具条图标来源的图像列表的替换,实现了在普通MFC应用程序中具备了以往只有Windows系统自带程序才具备的高彩色工具条。较好地美化了程序的界面。本文程序在Windows 98下,由Microsoft Visual C++ 6.0编译通过。
用VC制作非常酷的工具条
自微软推出Windows 95后,一大批全新的控件使我们的应用程序更加美观,使用也更加方便。其中一个显著的变化就是工具条不再是一个突出的3D小方框,而是变成了平面的状态,但 是只要把鼠标移动到上面,它就会自动地浮出来,大大方便了用户。
笔者经过一段时间摸索,终于找到了制作这种工具条的方法。原来Windows 95封装了许多常用的控件,大都被放在Comctrl32.dll中,其中Toolbar控件是用于制作工具条的。下面 简要介绍一下如何在VC5.0中添加一个Toolbar。
众所周知,所有的控件都是某一类型的窗口,所以制作Toolbar也要从制作窗口开始。由于MFC的Toolbar类并不支持新的功能,所以我们只好用SDK方法,通过API调用来完成整个过程 ,该过程与制作一个传统的工具条类似。
Toolbar是属于comctrl32.dll的扩展功能,所以要先调用InitCommonControlsEx()的函 数。该函数有一个重要的参数决定了对Toolbar的支持,它的主要作用是注册Toolbar窗口,以 便在以后的程序中制作这种窗口,而普通的工具条则要调用InitCommandControls()。需要注意的是这两个函数的写法。
INITCOMMONCONTROLSEX icex; DWORD dwStyle; icex.dwSize = sizeof(INITCOMMONCONTROLSEX); //注意下面这两个参数决定了是否注册Toolbar icex.dwICC=ICC_COOL_CLASSES|ICC_BAR_ CLASSES; InitCommonControlsEx(&icex); |
然后就可以调用CreateWindowEx这个函数来建立Toolbar窗口:
HWND hwndTB = CreateWindowEx( WS_EX_TOOLWINDOW, //扩展工具条风格 TOOLBARCLASSNAME, //Toolbar类名 NULL, WS_CHILD|WS_VISIBLE|TBSTYLE_FLAT, //窗口风格 0,0,0,0, //大小 AfxGetApp()->GetMainWnd(), //父窗口名 NULL, AfxGetInstanceHandle(), //实例 NULL); |
判断一下窗口句柄,如果不为空,就表示窗口建立成功。此时的Toolbar不过是一个空空的窗口,我们可以根据需要向里面添加按钮。向Toolbar中添加按钮是通过向它发送消息来 实现的,以下过程与制作传统的工具条基本一致。首先,建立一个ImageList控件,然后通过定义按钮的数据结构来确定每个按钮的类型。
// 建立一个Imagelist 控件, HWND himl; //MYICON_CX,MYICON_CY是每个按钮的大小 himl= ImageList_Create(MYICON_CX,MYICON_CY,ILC_COLOR4,0,4); //加入事先作好的工具条位图IDB_BITMAP2 ImageList_Add( himl, LoadBitmap(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDB_BITMAP2)),NULL); //通过消息把位图加入到Toolbar中 SendMessage(hwndTB, TB_SETIMAGELIST, 0, (LPARAM)himl); |
下面加入5个普通的按钮:
TBBUTTON tbArray[5]; //按钮的数据结构 for(i=0;i<5;i++){ tbArray[i].iBitmap = i; //第i个位图 tbArray[i].idCommand = IDM_BUTTONSTART+i; //命令ID tbArray[i].fsState = TBSTATE_ENABLED; tbArray[i].fsStyle = TBSTYLE_BUTTON; //按钮风格 tbArray[i].dwData = 0; tbArray[i].iString = i; //显示的字符串 } //设置按钮结构的大小 ::SendMessage(hwndTB,TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0); //添加按钮到Toolbar中 ::SendMessage(hwndTB,TB_ADDBUTTONS,(UINT)5, (LPARAM)tbArray); |
至此,一个很酷的工具条基本上制作完成,最后再调用函数ShowWindow()即可: ShowWindow(hwndTB, SW_SHOWMAXIMIZED);
当点击按钮时,Toolbar就把消息传送到父窗口中,由父窗口响应消息。Toolbar中按钮的ID包含在消息函数的wParam参数中,可以设置它来调度不同的模块。这时可以重载父窗口的O nCommand()函数,根据wParam参数判断点击了哪个按钮。假定父窗口是主窗口框架,代码如下:
BOOL CMainFrame::OnCommand(WPARAM wParam,LPARAM lParam) { switch(wParam){ case IDM_BUTTONSTART+0: AfxMessageBox(“你点中了第一个按钮!!", MB_ICONINFORMATION); break; case IDM_BUTTONSTART+1: AfxMessageBox(“你点中了第二个按钮!!",MB_ICONINFORMATION); break; case IDM_BUTTONSTART+2: AfxMessageBox(“你点中了第三个按钮!!", MB_ICONINFORMATION); break; } return CMainFrame::OnCommand(wParam,lParam); } |
Visual C++ 版本6中工具条的新特色
微软在www.microsoft.com/visualc已经推出Visual C++6.0预览版几个月了。正式版预计到今年年底发布。同时,预览版显示出版本6将包含大量的改进和提高,包括支持Internet控件,例如扁平工具条等。虽然改进的控件包与Internet无关,但它首先出现在Internet Explorer中,因此它就被取做这个名字了。事实上,官方发布的预览版的标题是“针对Internet Explorer 4.0的Visual C++ 5.0技术预览”。
在以前关于MFC工具条类的讨论专题中,我曾答应提供一个在版本6中工具条的外观演示。有一个很好的消息,那就是你现在用CToolBar所作的所有工作在新的版本中都是有效的,包括那些在以前的栏目中所描述的一些扩展功能。因此,你将很容易修改现存的程序以获得象Internet Explorer和Visual Studio中那样“酷”的界面。此外,并没有什么坏消息。
工具条的新特色
早在版本4中,CToolBar就已被MFC库完全实现了。一旦公用控件动态链接库(命名为comctl32.dll)变得无所不在了,CToolBar就成了如今已包含在操作系统中的工具条控件的代名词了。然而,CToolBar并没有揭示公用工具条控件的所有能力。如今,通过CreateEx()函数,它成功了。
公用控件动态链接库现在包含了至少三类风格:最初的、在Internet Explorer3.0中加入的以及在Internet Explorer 4.0中加入的。虽然这些版本理论上是向下兼容的,但某些专业人员曾写出一些不能在后来版本中正常运行的应用程序,这可能是这些程序采用了一些没有公开的功能,而这些功能并没有被包含在所有的版本中。
Visual C++程序员没有这样的经历,因为在Visual C++4.0或5.0中comctl32.dll并不是一个可以再分发的组件,它在安装Internet Explorer时被更新,因此MFC程序员无法依靠最新版本的某些功能来用于他们的程序。这就是CToolBar仅仅具有最初的DLL的有限功能的原因。CToolBar能够实现最新的特色意味着微软将在Visual C++6.0中包含最新的DLL并将其作为一个可以再分发的组件。
绝大多数新特色将由在调用CreateEx()和其它CToolBar成员函数时指定的新的风格标志来确定。下面是commctrl.h的一部分,它定义了TBSTYLE类标识符:
#define TBSTYLE_BUTTON 0x0000
#define TBSTYLE_SEP 0x0001
#define TBSTYLE_CHECK 0x0002
#define TBSTYLE_GROUP 0x0004
#define TBSTYLE_CHECKGROU TBSTYLE_GROUP | TBSTYLE_CHECK)
#if (_WIN32_IE $#@62;= 0x0300)
#define TBSTYLE_DROPDOWN 0x0008
#endif
#if (_WIN32_IE $#@62;= 0x0400)
#define TBSTYLE_AUTOSIZE 0x0010
#define TBSTYLE_NOPREFIX 0x0020
#endif
#define TBSTYLE_TOOLTIPS 0x0100
#define TBSTYLE_WRAPABLE 0x0200
#define TBSTYLE_ALTDRAG 0x0400
#if (_WIN32_IE $#@62;= 0x0300)
#define TBSTYLE_FLAT 0x0800
#define TBSTYLE_LIST 0x1000
#define TBSTYLE_CUSTOMERASE 0x2000
#endif
#if (_WIN32_IE $#@62;= 0x0400)
#define TBSTYLE_REGISTERDROP 0x4000
#define TBSTYLE_TRANSPARENT 0x8000
#define TBSTYLE_EX_DRAWDDARROWS 0x00000001
#endif
你会注意到其中的一些采用了条件编译,依赖于_WIN32_IE的值,它缺省指的是Internet Explorer 4.0(即取值为0x0400)。对于Internet Explorer 3.0(即取值为0x0300)以前的版本,大多数的TBSTYLE标识符指的是按钮或是一组按钮。Internet Explorer3.0引入了扁平钮、文本标签、下拉列表和自定义绘制。Internet Explorer 4.0增强了下拉列表和自定义绘制功能,并且增加了支持OLE拖动目标到一个工具条。
扁平钮和把手
在过去的18个月中我常常被问及该如何获得象Internet Explorer和Visual Studio中的工具条一样不使用浮雕按钮而是用扁平钮并且带有便于移动和定位的把手那样酷的界面。这些特色并不被MFC所支持,因此最简单获取的方法就是购买一个扩展库。而对于Visual C++ 6.0来说却无须多此一举,因为它使得CToolBar类实现了对扁平钮、把手和其它新的视觉效果的支持。
在预览版中,AppWizard并不会自动包括这些新特色,但它们却很容易被加入。表1显示了AppWizard创建的主框架窗口的OnCreate()函数,表2显示了需要做哪些修改以获得具有扁平钮和把手的工具条。图1显示了表1创建出的工具条,而图2显示出了表2实现的工具条。
表 1: CMainFrame::OnCreate as generated by AppWizard
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if(!m_wndToolBar.Create(this)||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}
if(!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)))
图1
表2: Adding flat buttons and the gripper
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if(CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if(!m_wndToolBar.CreateEx(this)||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar/n");
return -1; // fail to create
}
if(!m_wndStatusBar.Create(this)||!m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar/n");
return -1; // fail to create
}
// TODO: Remove this if you dont want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_GRIPPER | CBRS_BORDER_3D | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
// TODO: Delete these three lines if you dont want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
图2
为了作出扁平按钮我必须使用CreateEx()来代替Create()。这个新的函数在afxext.h中声明:
BOOL CreateEx
(
CWnd* pParentWnd, // parent window
DWORD dwCtrlStyle = TBSTYLE_FLAT, // extended style
DWORD dwStyle = // style
WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP,
CRect rcBorders = CRect(0,0,0,0), // border rectangle
UINT nID = AFX_IDW_TOOLBAR // identifier
);
因为扩展风格缺省指的就是TBSTYLE_FLAT,因此我要得到扁平按钮就只需要简单地将AppWizard形成的代码中的Create()改为CreateEx()即可。我将在后面实现其它的扩展风格。为了获得把手,我必须在调用SetBarStyle()函数时包含CBRS_GRIPPER标志,参看表2。这是CControlBar类的一个新风格,而CToolBar类是从它继承而来的。请注意到我也加入了CBRS_BORDER_3D标志,这是为了修正一个未知的绘制问题,该问题将会在工具条的边缘绘制一些多余的点。这也许意味着预览版确实有这个问题,因为一旦我将3D标志加入就立即解决了并且也似乎没有影响到别的什么。
上面所作的两个简单的改变是使得一个已存程序获得酷界面的最省力的方法。在一个程序具有了扁平钮和把手的同时,它也不会发生不应有的其它改变。(未完)
标题栏上添加按钮
自从Windows操作系统出现以后,在应用程序中进行人机交互的思想和手段便发生了根本性的改变,计算机的界面变得友好精彩。一个程序的好坏很大程度上决定于人机交互的方便程度。当前,大多数程序的标题栏都千篇一律,如何让自己的程序与众不同是每个程序员的梦想,但改变标题栏的内容的确有相当的难度。该篇文章向你介绍如何在标题栏上添加图标按钮,而且当鼠标经过和点击该图标时,鼠标将有不同的反应。请按照下面的步骤实现。
第一步:打开VC编程环境,生成一个新的基于单文档的工程temp,所有的选项都取默认值,下面,我们就在此工程的标题栏上生成三个按钮图标。
第二步:下载资源文件,共有三个文件:CaptionButton.cpp、CaptionButton.h和Thunk.h。将这三个文件添加到工程中(添加方法不必细说了吧)。
第三步:在Mainfrm.h中定义变量CCaptionButton cbExtra;,当然要包含头文件#include "CaptionButton.h"。
第四步:为工程加载位图资源,ID号分别为IDB_BITMAP1、IDB_BITMAP2、IDB_BITMAP3、IDB_BITMAP6、IDB_BITMAP7。这些位图将显示在标题栏上,至于用什么样的位图就看你的喜好了。
第五步:在Mainfrm.cpp的OnCreate函数中添加如下的代码:
//初识化,m_hWnd是我们要处理的窗口句柄 cbExtra.Init(m_hWnd); // // 设置标题栏上的原来的按钮(最大化、最小化和关闭)保留的数目 cbExtra.SetNumOfDefaultCaptions(3); // 设置位图的透明颜色 COLORREF crTransparent = RGB(255,0,255); cbExtra.SetTransparentColor(crTransparent); // 鼠标选中一个位图后该位图的样子 cbExtra.SetSelectionBitmap((HBITMAP)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP7), IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS|LR_DEFAULTCOLOR)); // 鼠标移动到一个位图后,该位图的变化 HBITMAP hMouseOverBitmap = (HBITMAP)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP2), IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS|LR_DEFAULTCOLOR); // 设置位图1 HBITMAP hCaptionAMBitmap = (HBITMAP)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP3), IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS|LR_DEFAULTCOLOR); HBITMAP hCaptionAMBitmapHilite = CCaptionButton::CombineBitmaps(hCaptionAMBitmap, hMouseOverBitmap, crTransparent); // 设置位图2 HBITMAP hCaption2Bitmap = (HBITMAP)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP6), IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS|LR_DEFAULTCOLOR); HBITMAP hCaption2BitmapHilite = CCaptionButton::CombineBitmaps(hCaption2Bitmap, hMouseOverBitmap,crTransparent); // 设置位图三 HBITMAP hCaption3Bitmap = (HBITMAP)LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1), IMAGE_BITMAP, 0, 0, LR_LOADMAP3DCOLORS|LR_DEFAULTCOLOR); HBITMAP hCaption3BitmapHilite = CCaptionButton::CombineBitmaps(hCaption3Bitmap, hMouseOverBitmap,crTransparent); // 利用上面的定义创建标题栏上的位图,宝库位图的ID号,鼠标经过时的 file://变换位图,鼠标选择时的变换位图和提示文字。 cbExtra.New(1,hCaptionAMBitmapHilite,hCaptionAMBitmap,"guan"); cbExtra.New(2,hCaption2BitmapHilite,hCaption2Bitmap,"xi"); cbExtra.New(3,hCaption3BitmapHilite,hCaption3Bitmap,"ming"); |
第六步:现在我们可以先停下来看看我们的成果如何。编译我们的工程,运行,我们可以发现,我们的标题栏上增加了三个按钮,鼠标在上面移动或点击的时候,位图会发生变化。只是我们还没有添加在位图上点击是的处理函数,不要急,接下来我们就添加相应的处理函数。
第七步:当我们在标题栏上点击图标时,将有一个消息WM_CBLBUTTONCLICKED产生,参数WPARAM是指点击图标的ID号。
在mainfrm.h中定义消息映射函数afx_msg LRESULT Hit(WPARAM wParam, LPARAM lParam);
在mainfrm.cpp中定义函数实现:
ON_MESSAGE(WM_CBLBUTTONCLICKED, Hit) LRESULT CMainFrame::Hit(WPARAM wParam, LPARAM lParam) { switch(wParam) {// begin wParam case 1: AfxMessageBox("第一个CAPtion"); break; case 2: AfxMessageBox("第二个Caption"); break; case 3: AfxMessageBox("第三个Caption"); break; } return 1; } |
这样,当我们单击图标时将弹出不同的提示对话框,这只是一个例子,至于实现什么样的功能随你的便了。
第八步:标题栏的动态改变。在程序的执行过程中如果你要改变标题栏的样子你同样可以实现,下面分别给出如何删除一个图标和更改一个图标的样子。
void CMainFrame::OnDelete() { cbExtra.Delete(1); } void CMainFrame::OnChange() { cbExtra.Replace(1, 1, hCaption4BitmapHilite, hCaption4Bitmap, "pNewToolTipText"); } |
好了,功能实现了,还算满意吧,希望对你有用。
通过例程分析状态条用法
状态条是一个包含信息的控制条,通常用于信息和状态提示,这里信息是有关菜单命令或工具命令的提示字符串以及其它指示/帮助信息,而状态是用来指示SCROLL LOCK 和NUM LOCK等一些键的状态。状态条通常框架窗口的底部。状态条的信息行能显示有关的程序状态或鼠标指向的工具按钮或菜单项的信息。状态条既不能接受用户输入,也不产生命令信息。
实际上,从编程人员的角度出发(至少是那些使用AppWizard创建应用程序的编程人员),状态条是如此普通,它们并不像工具条那样允许用户编辑的资源。在创建程序框架时,用户只需告诉AppWizard为应用程序包含一个状态条,可以说,此时,用户的工作就完成了。但是,我们如果巧妙使用工具条,我们会发现它可以帮我们实现很多功能。
按以下做法我们实现在状态条上显示滚动字符串,鼠标的坐标,动态时钟。
第一步:
运行AppWizard生成一个工程mystatus,接受所有的默认设置,除了下面两步:在step 1中选Single Document ;step 4 中去掉Docking Bar 前的对钩,然后点击 Advanced 按钮,选择Window Styles 中的 Maximized选项。点击Finish按钮,此时我们生成了一个工程。运行我们可以发现程序默认生成的状态条,接下来我们要对这个状态条进行修改。
第二步:
在这一步我们将实现把状态条移到菜单的下边。在MainFrm.cpp中我们可以看到状态条的定义部分
if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar/n"); return -1; // fail to create } 我们所要做的是将上面的定义改为 if(!m_wndStatusBar.Create(this, WS_CHILD|WS_VISIBLE|CBRS_TOP,AFX_IDW_STATUS_BAR) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar/n"); return -1; // fail to create } |
运行程序,这时我们可以发现,状态条已经移到了上边。
第三步:
在这一步,我们实现在状态条实现滚动文字。
< 1 > 在MainFrm.cpp中,我们可以发现如下的定义
static UINT indicators[] = { ID_SEPARATOR, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, }; 我们将其改为 static UINT indicators[] = { ID_SEPARATOR, ID_STATUS1, ID_STATUS2, ID_STATUS3, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, }; |
<2>在菜单VIEW中选择Resource Symbols , 在对话框中选择NEW, 在NAME中输入ID_STATUS1,VALUE中取默认值,同样方法定义ID_STATUS2,ID_STATUS3。
<3>定义字符串资源,在ResouceView中选择String Table,在其中为ID_STATUS1定义字符串资源为"me",同样方法定义ID_STATUS2,ID_STATUS3。
<4>在MainFrm.h中定义如下变量:
public:
CString str,str1;
<5>在MainFrm.cpp中修改OnCreate函数如下:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if(!m_wndStatusBar.Create (this,WS_CHILD|WS_VISIBLE|CBRS_TOP,AFX_IDW_STATUS_BAR) || !m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar/n"); return -1; // fail to create } m_wndStatusBar.SetPaneInfo(1,ID_STATUS1,SBPS_POPOUT,320); m_wndStatusBar.SetPaneInfo(2,ID_STATUS2,SBPS_POPOUT,100); m_wndStatusBar.SetPaneInfo(3,ID_STATUS3,SBPS_POPOUT,100); str1=str="你好,欢迎使用本程序,祝你合家幸福,事业有成!"; SetTimer(1,200,NULL); SetTimer(2,10,NULL); return 0; } |
<6>在类CmainFrame中重载函数OnTimer(),并添加如下代码:
void CMainFrame::OnTimer(UINT nIDEvent) { file:// TODO: Add your message handler code here and/or call default if(nIDEvent==1){ if(str.IsEmpty()) str=str1; str=str.Right(str.GetLength()-2); m_wndStatusBar.SetPaneText(1,str); } if(nIDEvent==2){ SYSTEMTIME t; ::GetLocalTime(&t); CString str2; str2.Format("%d:%d:%d:%d",t.wHour,t.wMinute,t.wSecond,t.wMilliseconds); m_wndStatusBar.SetPaneText(3,str2); } CFrameWnd::OnTimer(nIDEvent); } |
<7>将MainFrm.h中,定义m_wndStatusBar之前的 protected: 改为public:
<8>通过类向导在类CmystatusView中重载WM_MOUSEMOVE,并在实现函数中添加如下代码:
void CMystatusView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CString str3; str3.Format("X:%d,Y:%d",point.x,point.y); ((CMainFrame*)AfxGetMainWnd())->m_wndStatusBar.SetPaneText(2,str3); CView::OnMouseMove(nFlags, point); } |
好了,到这里我们所有的功能就都实现了,快编译运行一下吧!怎么样?还满意吗?
让标题栏文字居中
|添加以下模块:
PublicSubCenterC(frmAsForm)
DimSpcFAsIntegerHowmanyspacescanfit
DimclenAsIntegercaptionlength
DimoldcAsStringoldcaption
DimiAsIntegernotimportant
removeanyspacesattheendsofthecaption
veryeasyifyoureaditcarefully
oldc=frm.Caption
DoWhileLeft(oldc,1)=Space(1)
DoEvents
oldc=Right(oldc,Len(oldc)-1)
Loop
DoWhileRight(oldc,1)=Space(1)
DoEvents
oldc=Left(oldc,Len(oldc)-1)
Loop
clen=Len(oldc)
IfInStr(oldc,"!")$#@60;$#@62;0Then
IfInStr(oldc,"")$#@60;$#@62;0Then
clen=clen*1.5
Else
clen=clen*1.4
EndIf
Else
IfInStr(oldc,"")$#@60;$#@62;0Then
clen=clen*1.4
Else
clen=clen*1.3
EndIf
EndIf
seehowmanycharacterscanfit
SpcF=frm.Width/61.2244howmanyspacecanfit itthecaption
SpcF=SpcF-clenHowmanyspacescanfit-Howmuch spacethe
tiontakesup
Nowthetrickypart
IfSpcF$#@62;1Then
DoEventsspeeduptheprogram
frm.Caption=Space(Int(SpcF/2))+oldc
Elseiftheformistoosmallforspaces
frm.Caption=oldc
EndIf
EndSub
|在窗体中添加以下代码:
DimoldsizeAsLong
PrivateSubForm_Resize()
IfMe.Width=oldsizeThenifthewidthhasnt changed
ExitSubthendontmesswithit
Else
CenterCMe
oldsize=Me.Width
EndIf
EndSub
PrivateSubForm_Load()
CenterCMe
oldsize=Me.Width
EndSub
设计漂亮实用的菜单
谈在VC中动态改变菜单
大部分Windows应用程序都使用下拉式菜单实现自己特定的函数,它使编程更加方便,不需要在程序中增加多个按钮以完成这些操作。大多数情况下,我们的程序编译生成后,菜单就确定了,不能再修改。然而,在很多情况下,程序要根据用户的自己设置产生不同的菜单以适应不同用户的要求,这就需要我们动态的改变菜单。接下来我们就分析如何动态的生成不同的菜单。
第一步:
运行AppWizard生成一个工程mymenu,接受所有的默认设置,除了下面一步:在step 1中选Single Document ,点击Finish按钮,此时我们生成了一个工程。编译运行,我们可以发现程序默认生成的菜单,接下来我们要对这个菜单进行修改。
第二步:
添加一个菜单资源,按如下步骤:菜单中选择InsertàResouceàMenuàNew ,我们可以看到添加了一个ID号为IDR_MENU1的菜单资源,至于菜单中的各项你就随便添加了,为了我们下面的编程,请你添加几项,第一列要包含几个子菜单。
第三步:
添加一个对话框资源,按如下步骤:菜单中选择InsertàResouceàDialogàNew ,我们可以看到添加了一个ID号为IDD_DIALOG1的对话框资源,添加六个按钮,如下图:
ID号依次为ID_CLEAR, ID_GOONE ,ID_GOTWO, ID_ADD, ID_ADDITEM, ID_EDIT ,为对话框定义类名为CChangeMenu, 双击添加的六个按钮增加处理函数,用默认函数名。
第四步:
在IDR_MAINFRAME菜单中添加一项,ID号为ID_SET,名为"弹出设置对话框",为其添加处理函数,添加如下代码:
CChangeMenu dlg;
dlg.DoModal();
当然,别忘了在文件的开头添加#include "changemenu . h"
第五步:
找到添加的六个按钮的处理函数,依次添加如下的代码:
void CChangeMenu::OnClear()
{
AfxGetMainWnd()->SetMenu(NULL);
AfxGetMainWnd()->DrawMenuBar();
}
void CChangeMenu::OnGoone()
{
if(!menu1){
menu1.LoadMenu(IDR_MENU1);
AfxGetMainWnd()->SetMenu(&menu1);
AfxGetMainWnd()->DrawMenuBar();
menu1.Detach();
}
}
void CChangeMenu::OnGotwo()
{
if(!menu2){
menu2.LoadMenu(IDR_MAINFRAME);
AfxGetMainWnd()->SetMenu(&menu2);
AfxGetMainWnd()->DrawMenuBar();
menu2.Detach();
}
}
void CChangeMenu::OnAdd()
{
static int x=400;
AfxGetMainWnd()->GetMenu()->AppendMenu(MF_STRING,x,"mynew");
AfxGetMainWnd()->DrawMenuBar();
x++;
}
void CChangeMenu::OnAdditem()
{
static int y=500;
AfxGetMainWnd()->GetMenu()->GetSubMenu(0)->InsertMenu(1,MF_BYPOSITION,y,"mynewitem");
AfxGetMainWnd()->DrawMenuBar();
y++;
}
void CChangeMenu::OnEdit()
{
AfxGetMainWnd()->GetMenu()->ModifyMenu(0,MF_BYPOSITION,402,"dfd");
AfxGetMainWnd()->DrawMenuBar();
}
好了,到这里我们所有的功能就都实现了,快编译运行一下吧!怎么样?还满意吗
谈在VC中动态改变菜单
如何用VC++5在菜单中增加位图或图标
我们在使用Windows 95时,可以注意到在“开始”组中的菜单项前都有一个图标,而且在Word 97中的菜单项前也有一个图标。这些图标不但让我们清楚地了解到屏幕上的各种工具按钮与各个菜单项之间的联系,而且还增加了应用程序界面的美观。那么,请问如何用Visual C++ 5.0在应用程序菜单中增加图标?
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
PublicSubCenterC(frmAsForm)
DimSpcFAsIntegerHowmanyspacescanfit
DimclenAsIntegercaptionlength
DimoldcAsStringoldcaption
DimiAsIntegernotimportant
removeanyspacesattheendsofthecaption
veryeasyifyoureaditcarefully
oldc=frm.Caption
DoWhileLeft(oldc,1)=Space(1)
DoEvents
oldc=Right(oldc,Len(oldc)-1)
Loop
DoWhileRight(oldc,1)=Space(1)
DoEvents
oldc=Left(oldc,Len(oldc)-1)
Loop
clen=Len(oldc)
IfInStr(oldc,"!")$#@60;$#@62;0Then
IfInStr(oldc,"")$#@60;$#@62;0Then
clen=clen*1.5
Else
clen=clen*1.4
EndIf
Else
IfInStr(oldc,"")$#@60;$#@62;0Then
clen=clen*1.4
Else
clen=clen*1.3
EndIf
EndIf
seehowmanycharacterscanfit
SpcF=frm.Width/61.2244howmanyspacecanfit itthecaption
SpcF=SpcF-clenHowmanyspacescanfit-Howmuch spacethe
tiontakesup
Nowthetrickypart
IfSpcF$#@62;1Then
DoEventsspeeduptheprogram
frm.Caption=Space(Int(SpcF/2))+oldc
Elseiftheformistoosmallforspaces
frm.Caption=oldc
EndIf
EndSub
|在窗体中添加以下代码:
DimoldsizeAsLong
PrivateSubForm_Resize()
IfMe.Width=oldsizeThenifthewidthhasnt changed
ExitSubthendontmesswithit
Else
CenterCMe
oldsize=Me.Width
EndIf
EndSub
PrivateSubForm_Load()
CenterCMe
oldsize=Me.Width
EndSub
设计漂亮实用的菜单
谈在VC中动态改变菜单
大部分Windows应用程序都使用下拉式菜单实现自己特定的函数,它使编程更加方便,不需要在程序中增加多个按钮以完成这些操作。大多数情况下,我们的程序编译生成后,菜单就确定了,不能再修改。然而,在很多情况下,程序要根据用户的自己设置产生不同的菜单以适应不同用户的要求,这就需要我们动态的改变菜单。接下来我们就分析如何动态的生成不同的菜单。
第一步:
运行AppWizard生成一个工程mymenu,接受所有的默认设置,除了下面一步:在step 1中选Single Document ,点击Finish按钮,此时我们生成了一个工程。编译运行,我们可以发现程序默认生成的菜单,接下来我们要对这个菜单进行修改。
第二步:
添加一个菜单资源,按如下步骤:菜单中选择InsertàResouceàMenuàNew ,我们可以看到添加了一个ID号为IDR_MENU1的菜单资源,至于菜单中的各项你就随便添加了,为了我们下面的编程,请你添加几项,第一列要包含几个子菜单。
第三步:
添加一个对话框资源,按如下步骤:菜单中选择InsertàResouceàDialogàNew ,我们可以看到添加了一个ID号为IDD_DIALOG1的对话框资源,添加六个按钮,如下图:
ID号依次为ID_CLEAR, ID_GOONE ,ID_GOTWO, ID_ADD, ID_ADDITEM, ID_EDIT ,为对话框定义类名为CChangeMenu, 双击添加的六个按钮增加处理函数,用默认函数名。
第四步:
在IDR_MAINFRAME菜单中添加一项,ID号为ID_SET,名为"弹出设置对话框",为其添加处理函数,添加如下代码:
CChangeMenu dlg;
dlg.DoModal();
当然,别忘了在文件的开头添加#include "changemenu . h"
第五步:
找到添加的六个按钮的处理函数,依次添加如下的代码:
void CChangeMenu::OnClear()
{
AfxGetMainWnd()->SetMenu(NULL);
AfxGetMainWnd()->DrawMenuBar();
}
void CChangeMenu::OnGoone()
{
if(!menu1){
menu1.LoadMenu(IDR_MENU1);
AfxGetMainWnd()->SetMenu(&menu1);
AfxGetMainWnd()->DrawMenuBar();
menu1.Detach();
}
}
void CChangeMenu::OnGotwo()
{
if(!menu2){
menu2.LoadMenu(IDR_MAINFRAME);
AfxGetMainWnd()->SetMenu(&menu2);
AfxGetMainWnd()->DrawMenuBar();
menu2.Detach();
}
}
void CChangeMenu::OnAdd()
{
static int x=400;
AfxGetMainWnd()->GetMenu()->AppendMenu(MF_STRING,x,"mynew");
AfxGetMainWnd()->DrawMenuBar();
x++;
}
void CChangeMenu::OnAdditem()
{
static int y=500;
AfxGetMainWnd()->GetMenu()->GetSubMenu(0)->InsertMenu(1,MF_BYPOSITION,y,"mynewitem");
AfxGetMainWnd()->DrawMenuBar();
y++;
}
void CChangeMenu::OnEdit()
{
AfxGetMainWnd()->GetMenu()->ModifyMenu(0,MF_BYPOSITION,402,"dfd");
AfxGetMainWnd()->DrawMenuBar();
}
好了,到这里我们所有的功能就都实现了,快编译运行一下吧!怎么样?还满意吗
谈在VC中动态改变菜单
如何用VC++5在菜单中增加位图或图标
我们在使用Windows 95时,可以注意到在“开始”组中的菜单项前都有一个图标,而且在Word 97中的菜单项前也有一个图标。这些图标不但让我们清楚地了解到屏幕上的各种工具按钮与各个菜单项之间的联系,而且还增加了应用程序界面的美观。那么,请问如何用Visual C++ 5.0在应用程序菜单中增加图标?
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
PublicSubCenterC(frmAsForm)
DimSpcFAsIntegerHowmanyspacescanfit
DimclenAsIntegercaptionlength
DimoldcAsStringoldcaption
DimiAsIntegernotimportant
removeanyspacesattheendsofthecaption
veryeasyifyoureaditcarefully
oldc=frm.Caption
DoWhileLeft(oldc,1)=Space(1)
DoEvents
oldc=Right(oldc,Len(oldc)-1)
Loop
DoWhileRight(oldc,1)=Space(1)
DoEvents
oldc=Left(oldc,Len(oldc)-1)
Loop
clen=Len(oldc)
IfInStr(oldc,"!")$#@60;$#@62;0Then
IfInStr(oldc,"")$#@60;$#@62;0Then
clen=clen*1.5
Else
clen=clen*1.4
EndIf
Else
IfInStr(oldc,"")$#@60;$#@62;0Then
clen=clen*1.4
Else
clen=clen*1.3
EndIf
EndIf
seehowmanycharacterscanfit
SpcF=frm.Width/61.2244howmanyspacecanfit itthecaption
SpcF=SpcF-clenHowmanyspacescanfit-Howmuch spacethe
tiontakesup
Nowthetrickypart
IfSpcF$#@62;1Then
DoEventsspeeduptheprogram
frm.Caption=Space(Int(SpcF/2))+oldc
Elseiftheformistoosmallforspaces
frm.Caption=oldc
EndIf
EndSub
|在窗体中添加以下代码:
DimoldsizeAsLong
PrivateSubForm_Resize()
IfMe.Width=oldsizeThenifthewidthhasnt changed
ExitSubthendontmesswithit
Else
CenterCMe
oldsize=Me.Width
EndIf
EndSub
PrivateSubForm_Load()
CenterCMe
oldsize=Me.Width
EndSub
设计漂亮实用的菜单
谈在VC中动态改变菜单
大部分Windows应用程序都使用下拉式菜单实现自己特定的函数,它使编程更加方便,不需要在程序中增加多个按钮以完成这些操作。大多数情况下,我们的程序编译生成后,菜单就确定了,不能再修改。然而,在很多情况下,程序要根据用户的自己设置产生不同的菜单以适应不同用户的要求,这就需要我们动态的改变菜单。接下来我们就分析如何动态的生成不同的菜单。
第一步:
运行AppWizard生成一个工程mymenu,接受所有的默认设置,除了下面一步:在step 1中选Single Document ,点击Finish按钮,此时我们生成了一个工程。编译运行,我们可以发现程序默认生成的菜单,接下来我们要对这个菜单进行修改。
第二步:
添加一个菜单资源,按如下步骤:菜单中选择InsertàResouceàMenuàNew ,我们可以看到添加了一个ID号为IDR_MENU1的菜单资源,至于菜单中的各项你就随便添加了,为了我们下面的编程,请你添加几项,第一列要包含几个子菜单。
第三步:
添加一个对话框资源,按如下步骤:菜单中选择InsertàResouceàDialogàNew ,我们可以看到添加了一个ID号为IDD_DIALOG1的对话框资源,添加六个按钮,如下图:
ID号依次为ID_CLEAR, ID_GOONE ,ID_GOTWO, ID_ADD, ID_ADDITEM, ID_EDIT ,为对话框定义类名为CChangeMenu, 双击添加的六个按钮增加处理函数,用默认函数名。
第四步:
在IDR_MAINFRAME菜单中添加一项,ID号为ID_SET,名为"弹出设置对话框",为其添加处理函数,添加如下代码:
CChangeMenu dlg;
dlg.DoModal();
当然,别忘了在文件的开头添加#include "changemenu . h"
第五步:
找到添加的六个按钮的处理函数,依次添加如下的代码:
void CChangeMenu::OnClear()
{
AfxGetMainWnd()->SetMenu(NULL);
AfxGetMainWnd()->DrawMenuBar();
}
void CChangeMenu::OnGoone()
{
if(!menu1){
menu1.LoadMenu(IDR_MENU1);
AfxGetMainWnd()->SetMenu(&menu1);
AfxGetMainWnd()->DrawMenuBar();
menu1.Detach();
}
}
void CChangeMenu::OnGotwo()
{
if(!menu2){
menu2.LoadMenu(IDR_MAINFRAME);
AfxGetMainWnd()->SetMenu(&menu2);
AfxGetMainWnd()->DrawMenuBar();
menu2.Detach();
}
}
void CChangeMenu::OnAdd()
{
static int x=400;
AfxGetMainWnd()->GetMenu()->AppendMenu(MF_STRING,x,"mynew");
AfxGetMainWnd()->DrawMenuBar();
x++;
}
void CChangeMenu::OnAdditem()
{
static int y=500;
AfxGetMainWnd()->GetMenu()->GetSubMenu(0)->InsertMenu(1,MF_BYPOSITION,y,"mynewitem");
AfxGetMainWnd()->DrawMenuBar();
y++;
}
void CChangeMenu::OnEdit()
{
AfxGetMainWnd()->GetMenu()->ModifyMenu(0,MF_BYPOSITION,402,"dfd");
AfxGetMainWnd()->DrawMenuBar();
}
好了,到这里我们所有的功能就都实现了,快编译运行一下吧!怎么样?还满意吗
谈在VC中动态改变菜单
如何用VC++5在菜单中增加位图或图标
我们在使用Windows 95时,可以注意到在“开始”组中的菜单项前都有一个图标,而且在Word 97中的菜单项前也有一个图标。这些图标不但让我们清楚地了解到屏幕上的各种工具按钮与各个菜单项之间的联系,而且还增加了应用程序界面的美观。那么,请问如何用Visual C++ 5.0在应用程序菜单中增加图标?
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
MFC的CMenu类有一个成员函数SetMenuItemBitmaps,可以用于往菜单中增加图标。具体方法如下:
在应用程序的资源文件中添加想要增加的位图,并将其ID命名为IDB_OPEN1和IDB_OPEN2;---在应用程序的视图类中添加CBitmap类的对象,不妨取名为bm_open1和bm_open2。在视图类的构造函数中添加以下代码:
bm_open1.LoadBitmap(IDB_OPEN1);bm_open2.LoadBitmap(IDB_OPEN2);(函数LoadBitmap用于加载位图到CBitmap类的对象)在视图类的成员函数OnDraw()中添加以下代码:
CWnd*parent=GetParent();CMenu*pmenubar=parent-$#@62;GetMenu();
CMenu*pmenu=pmenubar-$#@62;GetSubMenu(2);
pmenu-$#@62;SetMenuItemBitmaps(1,MF_BYPOSITION,&bm_open1,&bm_open2);
前三行得到指向菜单的指针,第四行调用函数SetMenuItemBitmaps往菜单中增加图标,具体参数的含义可参见有关介绍MFC及其成员函数的书即可。
自绘菜单
在这里提供一个C++类(CCustomMenu),该类是CMenu的子类,并且拥有自绘能力。它可以向你提供以下的功能:
- 设置字体颜色。
- 设置高亮度颜色。
- 设置高亮度时的风格。
- 设置选中时和在普通状态下的菜单显示的图标。
- 设置显示图标大小。
在CCustomMenu中定义了结构MENUDATA,你必须根据你的需要填充该结构,并且在增加菜单时提供该结构的指针(调用AppendMenu,InsertMenu)。下面是一个例子:
1、定义CCustomMenu的实例,和MENUDATA结构变量。
CCustomMenu m_cCustomMenu; MENUDATA menuData [8]; // as many menu items are present , //You should be able to use //new and do the same
2、调用CreateMenu()设置有关参数。
m_customMenu.CreateMenu (); m_customMenu.SetIconSize (25,25); //This is to set the size of the Icon. // This should be used only once for any menu // in order to resize it, destroy and create the menu again with different size. m_customMenu.SetHighlightStyle (Normal); //Or TextOnly, if you want the // background color to remain the same // and the Text color to change to the Highlight color. // The following setXXXColor sets the menu colors. //If you dont want to change any, Dont call these member functions.
m_customMenu.SetTextColor (RGB (255,0,0)); m_customMenu.SetBackColor (RGB (255,255,255)); m_customMenu.SetHighlightColor (RGB (0,0,255)); 3、设置MENUDATA变量,并增加菜单项。 lstrcpy (menuData[0].menuText , "text1"); menuData[0].menuIconNormal= IDI_ICON1; m_customMenu.AppendMenu (MF_OWNERDRAW,3,(LPCTSTR)menuData);
3、在你的窗口中重载OnMeasureItem(...)函数。
void CMyView::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if ( lpMeasureItemStruct->CtlType == ODT_MENU &&
IsMenu((HMENU)lpMeasureItemStruct->i
ID) &&
(lpMeasureItemStruct->itemID == (UINT)m_hMenuSub) )
{
m_customMenu.MeasureItem (lpMeasureItemStruct);
}
else
// let MFCs self-drawing handle it
CView::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
下面的函数将帮助你设置菜单属性。
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
// HIGHLIGHTSTYLE : enum {Normal, TextOnly}
void SetHighlightTextColor (COLORREF);
下面是文件代码:
//*************************************************************************
// CustomMenu.h : header file
//
#if
!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
#define AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
class MENUDATA
{
public:
MENUDATA () { menuIconNormal = -1; menuIconSelected = -1;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
};
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
///
//
// CCustomMenu window
class CCustomMenu : public CMenu
{
// Construction
public:
CCustomMenu();
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CCustomMenu)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CCustomMenu();
virtual void DrawItem( LPDRAWITEMSTRUCT);
virtual void MeasureItem( LPMEASUREITEMSTRUCT );
void SetTextColor (COLORREF );
void SetBackColor (COLORREF);
void SetHighlightColor (COLORREF);
void SetIconSize (int, int);
void SetHighlightStyle (HIGHLIGHTSTYLE );
void SetHighlightTextColor (COLORREF);
// Generated message map functions
protected:
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
LOGFONT m_lf;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle;
//{{AFX_MSG(CCustomMenu)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
};
///
//
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.
#endif //!defined(AFX_CUSTOMMENU_H__FE5B01C3_1E02_11D1_B87A_0060979CDF6D__INCLUDED_)
//*************************************************************************
// CustomMenu.cpp : implementation file
//
#include "stdafx.h"
#include "CustomMenu.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///
//
// CCustomMenu
CCustomMenu::CCustomMenu()
{
m_clrText = GetSysColor (COLOR_MENUTEXT);
m_clrBack = GetSysColor (COLOR_MENU);
m_brBackground.CreateSolidBrush (m_clrBack);
m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
m_crText = m_clrText;
m_bLBtnDown = FALSE;
m_iconX = GetSystemMetrics ( SM_CXMENUCHECK);
m_iconY = GetSystemMetrics (SM_CYMENUCHECK );
m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);
m_brSelect.CreateSolidBrush (m_clrHilight);
m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);
ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
NONCLIENTMETRICS nm;
nm.cbSize = sizeof (NONCLIENTMETRICS);
//Get the system metrics for the Captionfromhere
VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0));
m_lf = nm.lfMenuFont;
m_iMenuHeight = nm.iMenuHeight;
m_fontMenu.CreateFontIndirect (&m_lf);
}
CCustomMenu::~CCustomMenu()
{
if ((HBRUSH) m_brBackground != NULL)
m_brBackground.DeleteObject ();
if ((HFONT)m_fontMenu !=NULL)
m_fontMenu.DeleteObject ();
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
}
///
//
// CCustomMenu message handlers
void CCustomMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS)
{
ASSERT(lpDIS != NULL);
CDC* pDC = CDC::FromHandle(lpDIS->hDC);
CRect rect;
HICON hIcon;
COLORREF crText = m_crText;
// draw the colored rectangle portion
rect.CopyRect(&lpDIS->rcItem);
// draw the up/down/focused/disabled state
UINT action = lpDIS->itemAction;
UINT state = lpDIS->itemState;
CString strText;
LOGFONT lf;
lf = m_lf;
CFont dispFont;
CFont *pFont;
//GetWindowText(strText);
if (lpDIS->itemData != NULL)
{
strText = (((MENUDATA*) (lpDIS->itemData))->menuText);
if ((((MENUDATA *)(lpDIS->itemData))->menuIconNormal) == -1)
hIcon = NULL;
else if (state & ODS_SELECTED)
{
if ((((MENUDATA *)(lpDIS->itemData))->menuIconSelected) != -1)
hIcon = AfxGetApp ()->LoadIcon ((
(MENUDATA *)(lpDIS->itemData))->menuIconSelected);
else
hIcon = AfxGetApp()->LoadIcon ((
(MENUDATA*)(lpDIS->itemData))->menuIconNormal);
}
else
hIcon = AfxGetApp()->LoadIcon (
((MENUDATA*)(lpDIS->itemData))->menuIconNormal);
TRACE1 ("Draw for %s/n", strText);
}
else
{
strText.Empty();
hIcon = NULL;
}
if ( (state & ODS_SELECTED) )
{
// draw the down edges
CPen *pOldPen = pDC->SelectObject (&m_penBack);
//You need only Text highlight and thats what you get
if (m_hilightStyle != Normal)
{
pDC->FillRect (rect,&m_brBackground);
}
else
{
pDC->FillRect (rect,&m_brSelect);
}
pDC->SelectObject (pOldPen);
pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));
lf.lfWeight = FW_BOLD;
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf);
crText = m_clrHilightText;
//While selected move the text a bit
TRACE0 ("SELECT,SELECTED/n");
}
else
{
CPen *pOldPen = pDC->SelectObject (&m_penBack);
pDC->FillRect (rect,&m_brBackground);
pDC->SelectObject (pOldPen);
// draw the up edges
pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
if ((HFONT)dispFont != NULL)
dispFont.DeleteObject ();
dispFont.CreateFontIndirect (&lf); //Normal
TRACE0 ("SELECT, NORMAL/n");
}
// draw the text if there is any
//We have to paint the text only if the image is nonexistant
if (hIcon != NULL)
{
if(DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,
(m_iconX)?m_iconX:32,(m_iconY)?m_iconY:32,0,NULL,DI_NORMAL))
TRACE0("Wrote the icon successfully/n");
else
TRACE0 ("SORRY.NOGO/n");
}
//This is needed always so that we can have the space for check marks
rect.left = rect.left +((m_iconX)?m_iconX:32);
if ( !strText.IsEmpty())
{
// pFont->GetLogFont (&lf);
int iOldMode = pDC->GetBkMode();
pDC->SetBkMode( TRANSPARENT);
pDC->SetTextColor( crText);
pFont = pDC->SelectObject (&dispFont);
TRACE1( "About To DrawText %s/n",strText);
pDC->DrawText (strText,rect,DT_LEFT|DT_SINGLELINE|DT_VCENTER);
TRACE0("Done/n");
pDC->SetBkMode( iOldMode );
pDC->SelectObject (pFont); //set it to the old font
}
dispFont.DeleteObject ();
}
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS )
{
CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();
CFont* pFont = pDC->SelectObject (&m_fontMenu);
int iconX = 0,iconY= 0;
TEXTMETRIC tm;
pDC->GetTextMetrics (&tm);
pDC->SelectObject (pFont);
AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);
if (m_iconX)
iconX = m_iconX;
if (m_iconY)
iconY = m_iconY;
lpMIS->itemWidth = iconX + tm.tmAveCharWidth
* lstrlen(((MENUDATA*)(lpMIS->itemData))->menuText) +10;
lpMIS->itemHeight = (iconY > (m_iMenuHeight+1))?iconY:m_iMenuHeight + 1;
}
void CCustomMenu::SetIconSize (int width, int height)
{
m_iconX = width;
m_iconY = height;
}
void CCustomMenu::SetTextColor (COLORREF clrText)
{
m_crText = clrText;
}
void CCustomMenu::SetBackColor (COLORREF clrBack)
{
m_clrBack = clrBack;
if ((HBRUSH)m_brBackground != NULL)
m_brBackground.DeleteObject ();
m_brBackground.CreateSolidBrush (clrBack);
}
void CCustomMenu::SetHighlightColor (COLORREF clrHilight)
{
m_clrHilight = clrHilight;
if ((HBRUSH)m_brSelect != NULL)
m_brSelect.DeleteObject ();
m_brSelect.CreateSolidBrush (clrHilight);
}
void CCustomMenu::SetHighlightTextColor (COLORREF clrHilightText)
{
m_clrHilightText = clrHilightText;
}
void CCustomMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
{
m_hilightStyle = hilightStyle;
}
//*************************************************************************
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
在系统菜单中加子菜单
系统菜单与其它菜单类似,你可以添加或删除项目,这需要使用CMenu 类的成员函数。下面的代码在你的系统菜单后面添加一个新菜单项:
CMenu *sysmenu;
sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
参见MFC 帮助文件中的CMenu 类
千奇百怪的窗体
VC++对话框的任意扩展
我们在信息输入的时候,可能有很大的信息量,而这些信息又不是必须的,这时我们就需要给信息输入人员一个选择的接口。例如一个人事部门的职工信息录入系统就有这样的问题,其中的姓名、性别、年龄、政治面目、职务、学历、部门和联系电话是必须输入的信息,而婚姻状况、毕业学校、籍贯和健康状况是可输可不输的信息且大多数情况下不需要录入,如何为信息录入人员提供一个方便的输入接口,下面我们就针对这个问题提供一个我认为比较好的方法。
第一步:在VC编程环境下建立一个基于对话框的工程,工程名为ExpandDlg,所有的选项都取默认值。
第二步:建立我们都对话框,其中必须要有这样两个控件,一个是PICTURE控件,一个为按钮,其ID值分别为IDC_DIVIDER和IDC_MORE。其它的控件可以任意布局,最终结果就是对话框被IDC_DIVIDER控件分成了两部分,其中下半部分可以根据你的爱好动态显示或不显示,对话框如下图:
第三步:生成按钮IDC_MORE的消息映射函数OnMore,在ExpandDlgDlg.h中定义两个函数如下:
public: void EnableVisibleChildren(); void ExpandDialog (int nResourceID, BOOL bExpand); |
第四步:在ExpandDlgDlg.cpp中定义函数的实现代码如下:
void CExpandDlgDlg::ExpandDialog (int nResourceID, BOOL bExpand) { // 对话框被nResourceID分成上下两部分,如果bExpand的值为TRUE // 对话框被完整显示,否则对话框显示上半部分。 static CRect rcLarge; static CRect rcSmall; CString sExpand; //开始时,对话框只显示上半部分 if (rcLarge.IsRectNull()) { CRect rcLandmark; CWnd* pWndLandmark = GetDlgItem (nResourceID); ASSERT(pWndLandmark); GetWindowRect (rcLarge); pWndLandmark->GetWindowRect (rcLandmark); rcSmall = rcLarge; rcSmall.bottom = rcLandmark.top; } if (bExpand) { //扩展对话框到最大尺寸 SetWindowPos(NULL, 0, 0, rcLarge.Width(), rcLarge.Height(), SWP_NOMOVE | SWP_NOZORDER); sExpand = "<< &Less"; EnableVisibleChildren(); } else { //只显示对话框的上半部分 SetWindowPos(NULL, 0, 0, rcSmall.Width(), rcSmall.Height(), SWP_NOMOVE | SWP_NOZORDER); sExpand = " &More >>"; EnableVisibleChildren(); } SetDlgItemText (IDC_MORE, sExpand); } void CExpandDlgDlg::EnableVisibleChildren() { //去掉没有显示的对话框的控件的功能和快捷键。 //得到第一个窗口 CWnd *pWndCtl = GetWindow (GW_CHILD); CRect rcTest; CRect rcControl; CRect rcShow; //得到对话框的完整矩形框 GetWindowRect(rcShow); while (pWndCtl != NULL) { //得到当前显示的对话框的矩形尺寸 pWndCtl->GetWindowRect (rcControl); if (rcTest.IntersectRect (rcShow, rcControl)) pWndCtl->EnableWindow(TRUE); else pWndCtl->EnableWindow(FALSE); //得到第二个矩形框 pWndCtl = pWndCtl->GetWindow (GW_HWNDNEXT); } } void CExpandDlgDlg::OnMore() { static BOOL bExpand = TRUE; ExpandDialog (IDC_DIVIDER, bExpand); bExpand = !bExpand; } |
按照上面的步骤生成我们的可执行文件后运行,点击对话框上的〔More〕我们可以发现对话框扩展,点击〔Less〕后,我们发现对话框收缩,希望可以给你带来方便。
使用VC创建不规则形状窗口
VC编程实现IE风格的界面
使用过IE浏览器的朋友都知道IE界面上的扁平工具条、地址栏,扁平工具栏上的按钮正常状态下为扁平态,按钮上的图像为灰色,当鼠标放在按钮上时,按钮突起(这种状态称为手柄),并且其上的图像变得鲜艳醒目,一些按钮上还有汉字说明或标有小黑三角的下拉按钮,单击时显示下拉菜单,这些技术是怎么实现的呢,本文针对这些问题介绍了如何利用VC编程来实现它们。
IE风格的实现主要在主框架类的CMainFrame::OnCreate()实现,它的主要思想如下:首先定义一个CReBar对象,用以作工具条、地址栏的容器,然后分别声明图像列表对象img用于存储工具栏上按钮的热点图像和正常状态下显示的图像。为了显示扁平工具栏,需要用CreateEx()函数创建CToolBar对象m_wndToolBar,用ModifyStyle()函数将工具栏的风格设为扁平类型,你不能用CToolBar::Create() 或 CToolBar:: SetBarStyle()设置这种新风格。CToolBar 类不支持TBSTYLE_FLAT。要解决这个问题,必须绕过CToolBar类,使用CWnd::ModifyStyle()。工具栏对象调用SetButtonInfo()设置按钮的风格为TBSTYLE_DROPDOWN,就可以将工具栏按钮设置为附带有下拉按钮。至于按钮带有中文提示,用工具栏的SetButtonText()就可以轻松实现了。下面是实现IE风格界面的部分代码和注释:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { CReBar m_wndReBar;//声明CReBar对象 CImageList img;//声明图像列表对象 CString str; if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndReBar.Create(this))//创建CReBar对象 { TRACE0("Failed to create rebar/n"); return -1; // fail to create } if (!m_wndToolBar.CreateEx(this))//创建工具条对象 { TRACE0("Failed to create toolbar/n"); return -1; // fail to create } // set up toolbar properties m_wndToolBar.GetToolBarCtrl().SetButtonWidth(50, 150); file://设置工具条上按钮的最大、最小尺寸 m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS); file://工具条可以带有下拉按钮 img.Create(IDB_HOTTOOLBAR, 22, 0, RGB(255, 0, 255)); file://向图像列表装载热点图像资源,IDB_HOTTOOLBAR为热点图像资源ID m_wndToolBar.GetToolBarCtrl().SetHotImageList(&img);//工具条装载热点图像 img.Detach(); img.Create(IDB_COLDTOOLBAR, 22, 0, RGB(255, 0, 255)); file://图象列表装载正常状态的图像资源,IDB_COLDTOOLBAR为图像资源ID m_wndToolBar.GetToolBarCtrl().SetImageList(&img);//将图像装入工具条 img.Detach(); m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT | TBSTYLE_TRANSPARENT); file://工具条为扁平风格 m_wndToolBar.SetButtons(NULL, 9);//工具条上有9个按钮 // set up each toolbar button file://以下分别对九个按钮分别设置风格和按钮汉语提示 m_wndToolBar.SetButtonInfo(0, ID_BUTTON0, TBSTYLE_BUTTON, 0); str.LoadString(IDS_ BUTTON0); m_wndToolBar.SetButtonText(0, str); m_wndToolBar.SetButtonInfo(1, ID_BUTTON1, TBSTYLE_BUTTON, 1); str.LoadString(IDS_ BUTTON1); m_wndToolBar.SetButtonText(1, str); m_wndToolBar.SetButtonInfo(2, ID_BUTTON2, TBSTYLE_BUTTON, 2); str.LoadString(IDS_ BUTTON2); m_wndToolBar.SetButtonText(2, str); m_wndToolBar.SetButtonInfo(3, ID_BUTTON3, TBSTYLE_BUTTON, 3); str.LoadString(IDS_ BUTTON3); m_wndToolBar.SetButtonText(3, str); m_wndToolBar.SetButtonInfo(4, ID_BUTTON4, TBSTYLE_BUTTON, 4); str.LoadString(IDS_ BUTTON4); m_wndToolBar.SetButtonText(4, str); m_wndToolBar.SetButtonInfo(5, ID_BUTTON5, TBSTYLE_BUTTON, 5); str.LoadString(IDS_ BUTTON5); m_wndToolBar.SetButtonText(5, str); m_wndToolBar.SetButtonInfo(6, ID_BUTTON6, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 6); str.LoadString(IDS_ BUTTON6); m_wndToolBar.SetButtonText(6, str); m_wndToolBar.SetButtonInfo(7, ID_BUTTON7, TBSTYLE_BUTTON, 7); str.LoadString(IDS_ BUTTON7); m_wndToolBar.SetButtonText(7, str); m_wndToolBar.SetButtonInfo(8,ID_BUTTON8, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 8); str.LoadString(IDS_ BUTTON8); m_wndToolBar.SetButtonText(8, str); file://重新调整按钮的尺寸 CRect rectToolBar; m_wndToolBar.GetItemRect(0, &rectToolBar);//得到工具条第一个按钮的尺寸 m_wndToolBar.SetSizes(rectToolBar.Size(), CSize(30,20)); file://第一个参数为按钮尺寸,第二个参数为图像尺寸 file://创建一个组合框作为地址栏 if (!m_wndAddress.Create(CBS_DROPDOWN | WS_CHILD, CRect(0, 0, 200, 120), this, AFX_IDW_TOOLBAR + 1)) { TRACE0("Failed to create combobox/n"); return -1; // fail to create } file://加入工具栏、地址栏 m_wndReBar.AddBar(&m_wndToolBar); str.LoadString(IDS_ADDRESS); m_wndReBar.AddBar(&m_wndAddress, str, NULL, RBBS_FIXEDBMP | RBBS_BREAK); file://定义REBARBANDINFO对象,对工具条和地址栏设置理想尺寸 REBARBANDINFO rbbi; rbbi.cbSize = sizeof(rbbi); rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_SIZE; rbbi.cxMinChild = rectToolBar.Width(); rbbi.cyMinChild = rectToolBar.Height(); rbbi.cx = rbbi.cxIdeal = rectToolBar.Width() * 9; m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);//设置工具栏尺寸 rbbi.cxMinChild = 0; CRect rectAddress; rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE; m_wndAddress.GetEditCtrl()->GetWindowRect(&rectAddress); rbbi.cyMinChild = rectAddress.Height() + 10; rbbi.cxIdeal = 200; m_wndReBar.GetReBarCtrl().SetBandInfo(2, &rbbi);//设置地址栏尺寸 m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED); if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar/n"); return -1; // fail to create } return 0; } |
以上代码在Windows2000和Visual C++环境下编译通过,程序运行正常,有兴趣的朋友可以动手亲自实验一下。
VC限制窗口大小又一法
一般说见到的方法,,都是截获WM_GETMAXMININFO消息。
俺有另一经验可实现之。
由于一般窗口大小的改变,都是用户拖动窗口边框而造成的。所以,我们可以截获主窗口消息WM_NCHITTEST在其响应函数中判断CWnd::OnNcHitTest()的返回值是否为HTRIGHT,HTLEFT,HTTOP,HTBOTTOM四个值之一,如果是,说明用户此时已点击了四个边框之一,此时我们应该返回HTCLIENT.那么,鼠标的形状就不会变成水平或垂直的双向箭头,用户就不可能依靠拖动边框来改变窗口大小了。
另外,还应补上一个小漏洞,就是还要把系统菜单中的SC_SIZE去掉。
主程序之前的版权窗口
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD lTime;
try
{
Application->Initialize();
AboutBox=new TAboutBox(AboutBox);
AboutBox->BorderStyle=bsNone;
AboutBox->OKButton->Visible=false;
AboutBox->Height=185;
AboutBox->Show();
AboutBox->Update();
lTime=GetTickCount();
Application->CreateForm(__classid(TMainForm), &MainForm);
while((GetTickCount()-lTime) / 1000 < 3);
AboutBox->Hide();
AboutBox->Free();
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
VISUAL C++6.0在MDI主框架窗口中添加位图
笔者在开发项目时想在MDI程序中添加彩色位图以美化界面,也实验了几种方法,但都有一些小问题,经多方查找资料,终于圆满的实现了这种功能,现把我的实现方法介绍给大家。
首先要清楚对于一个MDI应用程序的主框架窗口来说包含一个特殊的子窗口称为MDICLIENT窗口,应用程序的主框架类中有一个成员变量m_hWndMDIClient 指的就是MDICLIENT窗口。MDICLIENT窗口负责管理主框架窗口的客户区,对MDI客户窗口编程有一定的难度。原因是MDIFrameWnd的客户区完全被MDICLIENT窗口覆盖掉了。这样,MDI主窗口类MDIFrameWnd的背景色和光标都不起作用。同时,微软并不支持将MDICLIENT窗口作为子类,MDICLIENT窗口只能使用标准的背景色和光标。所以,对MDI客户窗口编程不能象对普通窗口那样简单地重载WM_PAINT的消息处理函数。我们可以在主框架窗口截获关于MDICLIENT窗口的重画消息,然后加入自己设计的代码。我用PreTranslateMessage(MSG* pMsg) 截获MDI客户窗口WM_PAINT消息,在这个函数中向主框架窗口发送WM_PAINT消息,在该消息的处理函数中实现彩色位图的显示。我的具体实现如下:1、向程序添加256色彩色位图资源,命名为IDB_BITMAP1;2、用ClassWizard向主框架类添加函数CMainFrame::PreTranslateMessage(MSG* pMsg);3、用ClassWizard向主框架类添加函数CMainFrame::OnPaint();现给出两个函数的实现:
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if(pMsg->hwnd==m_hWndMDIClient && pMsg->message==WM_PAINT) PostMessage(WM_PAINT); return CMDIFrameWnd::PreTranslateMessage(pMsg); } void CMainFrame::OnPaint() { CDC dc, memdc; dc.m_hDC=::GetDC(this->m_hWndMDIClient); CRect rect; CBitmap bitmap; BITMAP szbitmap; bitmap.LoadBitmap(IDB_BITMAP1); bitmap.GetObject(sizeof(BITMAP),&szbitmap); CSize size(szbitmap.bmWidth,szbitmap.bmHeight); memdc.CreateCompatibleDC(&dc); CBitmap *oldbitmap=memdc.SelectObject(&bitmap); GetClientRect(&rect); StretchBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(), memdc.m_hDC,0,0,size.cx,size.cy,SRCCOPY); memdc.SelectObject(oldbitmap); memdc.DeleteDC(); dc.DeleteDC(); CMDIFrameWnd::OnPaint(); } |
按上述步骤就可以实现在MDI程序中显示彩色位图了,我举的例子用的是256色位图,你也可以实现真彩色位图的显示,具体方法我就不多说了,有兴趣的朋友可以试一试。
华丽的界面
VC6.0实现逆向操作并防止界面闪烁
在系统编程中,使用VC是很好的开发工具,而对于一个成熟的系统,几乎都需要有回退与重做功能(即文档操作逆向化)以防止用户误操作或不合适的操作,从而提高系统的友好性和可操作性。在很多VC技术文章中均提到过这个问题,不过总存在着界面闪烁或不完全可逆.
本文提出一种对系统编程可实现完全可逆并防止闪屏的方法.
一、基本原理
要对文档进行回退重做功能,要做两方面的工作,一方面要保留删除的文档(在操作过程中,删除的文档资料一定能够保留),另一方面,系统必须能够记录进行文档操作的全过程及每个操作过程的参数。为了保留历史操作,所有数据非常占用内存空间,这就是一些系统只能进行有限次退步逆向操作的原因。本文提出的方法建立如下存储机制:建一个临时文件储存数据模拟堆栈,进行一次操作时将相关操作数据入栈.回退一次将相关数据弹出栈,重做一次又依据相关数据重新恢复原有数据.它的好处是在回退和重做时只入一次栈即申请一次内存。
堆栈的数据排放如图:
// Undo、Redo 数据排放示意图(m_UndoDataList)
// // ==== // |###| } // |###| } // |###| } ----->> Redo 数据 // |###| } // |###| } // |///| } // |///| } // |///| } // |///| } --->> Undo 数据(Undo数据弹出后将转换为Redo数据) // |///| } // |///| } // ===== // Undo数据栈 |
二、实现文档回退重做的引擎
建一文档逆向化堆栈引擎.主要代码为:
1.建立临时文件.(m_TempPath可以按照某种规则形成路径)
if(m_File.Open((LPCTSTR)m_TempPath, CFile::modeCreate|CFile::modeReadWrite|CFile::shareExclusive)) { m_File.SeekToBegin(); m_UndoCount = 0; file://当前可重做的步数 m_RedoCount = 0; file://当前可回退的步数 } |
2.保存回退数据模块.
// 保存一个Undo数据块(由用户提供) int CRedoUndoEngine::PushData( LPVOID pData, // 由用户提供的内存块首地址,其中含有用户定义的待保存的数据。 // (注:如果函数成功,此内存块将会被本函数释放,因此,该内存块必须是用::GlobalAlloc()函数分配的) DWORD size, // pData指向的内存块尺寸 DWORD param1, // 用户提供的对该内存块的说明参数,含义由用户定义 DWORD param2, // 用户提供的对该内存块的说明参数,含义由用户定义 int *pIndex // 如果成功,本函数将返回压入的Undo块在栈中的索引值。 如果不需要此返回值,可用NULL作为参数 ) { // 删除Redo数据 if (m_RedoCount) { while(m_RedoCount--) delete (LPISEEUNDOINFO)m_UndoDataList.RemoveTail(); m_RedoCount = 0; } // 填写Undo数据的索引信息(lpISeeUndoInfo为一个保存数据的结构体) lpISeeUndoInfo->m_index = m_UndoCount; // 索引 lpISeeUndoInfo->m_UserData1 = param1; // 用户定义的标识性数据1 lpISeeUndoInfo->m_UserData2 = param2; // 用户定义的标识性数据2 lpISeeUndoInfo->m_DataSize = size; // 用户的Undo数据块尺寸 lpISeeUndoInfo->m_FilePosition = _get_current_overwrite_pos(); // 加新的Undo数据到Undo栈的尾部 m_UndoDataList.AddTail((void*)lpISeeUndoInfo); // 将用户的Undo数据写入临时文件 m_File.Seek(lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Write((const void *)pData, size); 并使Undo块计数加1 m_UndoCount++; // 此时用户传过来的数据块已经无用,删除! ::GlobalFree(pData); return 1; } |
3.弹出重做数据模块.
// 弹出一个Redo数据块 int CIUndoEngine::RedoData( LPVOID *ppData, // 用于接收本函数返回的含有最近一个Redo数据的内存块首地址的指针 // (注:此内存块交由调用者释放,使用::GlobalFree()函数) DWORD *pSize, // ppData内存块的尺寸(in byte) ,如果不需要此数据可用NULL作为参数 DWORD *pParam1, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 DWORD *pParam2, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 int *pIndex // 返回本Redo块的索引,如果不需要此数据可用NULL作为参数 ) { if (!m_RedoCount) return 0; // 锁定待弹出的Redo索引信息块的地址 POSITION pos = m_UndoDataList.FindIndex(m_UndoCount); ASSERT(pos); LPISEEUNDOINFO lpISeeUndoInfo= (LPISEEUNDOINFO)m_UndoDataList.GetAt(pos); ASSERT(lpISeeUndoInfo); ASSERT(lpISeeUndoInfo->m_index == m_UndoCount); if (!(*ppData)) return -1; // 读出用户保存在临时文件中的Undo数据(也即Redo数据) m_File.Seek((LONG)lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Read(*ppData, lpISeeUndoInfo->m_DataSize); m_UndoCount++; // 可用Undo数据块个数加1 m_RedoCount--; // 可用Redo数据块个数减1 if (pSize) *pSize = lpISeeUndoInfo->m_DataSize; if (pParam1) *pParam1= lpISeeUndoInfo->m_UserData1; if (pParam2) *pParam2= lpISeeUndoInfo->m_UserData2; if (pIndex) *pIndex = m_RedoCount;// 注:此处的索引是Redo的索引,而不是Undo的 return 1; } |
由这个文档逆向化操作引擎,可以获得当前改动的文档的数据,并根据改动的数据更新视图,而不刷新没有更改数据的视图.从而防止了闪烁的产生.
三、简单开发实例
下面以我们开发服装CAD过程中加入的回退重做功能(文档逆向化)说明之。
1.定义回退类型
#define REUNDO_MOV 0x0001 file://衣片移动回退重做 #define REUNDO_SEL 0x0002 file://衣片选择回退重做 ………. |
2.保存某个操作之前和之后的数据(以衣片移动回退重做为例)
//----------申请内存----------------------// int nByte = 4*sizeof(DWORD); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte); LPVOID pData = (LPVOID) GlobalLock(hMem); file://-----保存衣片移动前后的位置读入内存------//用移动前后衣片的某个坐标点表示 memcpy((DWORD*)pData, &m_oldPoint, 2*sizeof(DWORD)); memcpy((DWORD*)pData+2,&point, 2*sizeof(DWORD)); file://--------数据入栈---------------------------------------// m_pReUndoEngine->PushData(pData,//衣片m_pReUndoEngine文档逆向化引擎对象指针 nByte,//保存数据衣片字节数 REUNDO_MOV,//回退类型 NULL,NULL); |
3.当回退操作事件触发时.
//弹出回退值 int nByte = m_pReUndoEngine->GetPopDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->PopData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelUndo(pData,index,&dc);break; case REUNDO_MOV: MovUndo(pData);break; ………… } void CMarkView::MovUndo(LPVOID pData) 函数功能 { CPoint pt1,pt2; memcpy(&pt1,(DWORD*)pData,8); memcpy(&pt2,(DWORD*)pData+2,8); …….由pt1 和pt2可以求出位移量,从而恢复原衣片的位置. } |
4.当重做操作事件触发时
//弹出回退值 int nByte = m_pReUndoEngine->GetRedoDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->RedoData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelRedo(pData,index,&dc,nByte);break; case REUNDO_MOV: MovRedo(pData); break; ………… } |
函数MovRedo(pData)与MovUndo(pData)类似就不多说了.
由3,4可以看出,在回退与重做过程中,只是保存和取出操作对象已变化的过程,使编程者很容易实现高效率刷新与充分节约存储空间.
小结
在系统编程中,文档的回退与重做几乎是必不可少的,本文提出了一种思路,即对文档的各种操作分解,并把每种操作下变化的对象的数据值保存于临时文件(栈)中,在回退与重做时根据变化量很容易恢复操作之前状态或重做, 避免了有些系统(保存全部文档数据)占用大量内存空间而只能有限次文档逆向化,并且全部刷新而闪烁,破坏了界面的友好性。
怎样在一个Pane中显示多种View?
在MS Windows 中,一个窗口可以分割成若干个子窗口,每一个子窗口称作一个窗片(pane),每个窗片可以独立控制,这给界面设计提供了很大的方便。
---- 利用VC 可以很方便地实现分割窗口。分割的方法有两种:动态和静态。动态分割时可以根据用户的需要分割成数目不同的窗片,但所有窗片的属性和父窗口都是一样的;而静态分割的窗片的数目在程序中指定,运行时是固定的,但每个窗片可以有各自不同类型的视(View),因此其使用范围更为广泛。本文所讨论的问题仅限于静态分割。
---- 窗片中视的类型大多是在主窗口的创建过程中指定的。这也就意味着,一个窗片虽然可以显示任意类型的视,但是这种类型一旦确定,在程序运行过程中就难以改变。
---- 一、我要的是这样的!
---- 但是我们有时确实需要改变一个窗片所显示的视的类型,也就是说,需要让一个窗片显示多种类型的视。例如一个窗口被分割成两部分,一边是命令窗口,另一边是工作窗口,根据命令窗口中发出的不同命令,需要变换不同的工作类型,这就需要工作窗口中能够显示多种类型的视窗,那么,如何做到这一点呢?
---- 二、你可以这样做!
---- 从图1 中可以看到,本程序共有三个视类,分别是:
---- ? 命令视类CCmdView:用来控制右边窗片中不同视的显示;
---- ? 选项按钮视类CRdiView:显示在右窗片中的选项视类;
---- ? 检查按钮视类CChkView:显示在右窗片中的检查视类。
---- 这三个视类都是CFormView 的子类。
---- 下面我们来看如何在右窗片内进行两类视间的切换。实际上,由视A 切换到视B 的原理很简单,那就是:
---- 1. 从窗片中删除视A;
---- 2. 往窗片中添加视B。
---- 步骤1 的实现非常简单,仅用一条语句即可:
---- m_wndSplitter.DeleteView(0, 1);
---- 但它是必不可少的,因为你不能让一个窗片同时包含两个视。我本来希望往一个窗片中添加新的视时,VC 会自动将原来的视删簦墒撬桓伞?br>
---- 我们来看如何实现步骤2,当一个窗片是空的时候,怎样往里面添加一个视呢?其实这样的功能在程序里我们已经用过了,看下面的语句:
BOOL CMainFrame::OnCreateClient
(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
……
if (!m_wndSplitter.CreateView(0, 0,
pContext->m_pNe ewClass,
size,
pContext))
……
}
---- 是的,用的就是CSplitterWnd::CreateView(),要注意的是它共有五个参数,其中前两个用来指定分割窗口的窗片,第三个用来指定视的类型,第四个指定视的大小。最后的一个我们暂时用不上,用空值NULL 就可以了。
---- 这样我们就可以编写视切换的代码了。因为视切换要操纵m_wndSplitter,而它是主窗口的成员,因此切换过程最好设计为主窗口的成员函数。但是切换命令是CCmdView 接受的,因而可以让CCmdView 接受到视更改消息后,将消息传给主窗口,由主窗口完成视更改。具体的代码是这样的:
---- 命令视类中的消息映射:
BEGIN_MESSAGE_MAP(CCmdView, CFormView)
……
ON_BN_CLICKED(IDC_CHECK, OnSwitchToCheckView)
ON_BN_CLICKED(IDC_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
命令视类中的消息响应:
void CCmdView::OnSwitchToCheckView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_CHECK);
}
void CCmdView::OnSwitchToRadioView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_RADIO);
}
主窗口中的消息映射:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
……
ON_COMMAND(ID_CHECK, OnSwitchToCheckView)
ON_COMMAND(ID_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
主窗口中的消息响应:
void CMainFrame::OnSwitchToCheckView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CChkView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
---- 好啦,运行一下这个程序,感觉是否不错?看来大功告成了,可是……
---- 三、还有一个问题
---- 在运行我们辛辛苦苦编出来的程序时,回头看看VC 的调试窗口,你会发现有很多行这样的话:
---- Create view without document.
---- 这是说我们创建了视,可是没有相应的文档。好在这只是警告信息,不是什么错误,如果你不需要相应的文档,就完全不用去管它。可是,VC 中一种很重要的结构就是文档- 视结构,利用这种结构,对数据操纵起来非常方便。如果需要建立与视相对应的文档,应该怎么办呢?
---- 这就涉及到VC 中文档- 视结构的知识,不过不用怕麻烦,与本文有关的就只有这么两点而已:
---- 1. 利用VC 创建的应用程序一般都会管理一些文档模板(Document Template),文档类和视类的对应关系就是在文档模板里描述的。
---- 2. 一个文档可以有多个视,创建视的时候,需要根据文档和视的对应关系,给出它所依附的文档。
---- 怎样实现上述第一点呢?
---- 首先建立相应的文档类:CRdiDoc 和CChkDoc。
---- 其次是定义相应的文档模板,这是应用类的成员变量。因为在别的类中要使用它们,我们将之定义为公共类型:
class CViewSwitcherApp : public CWinApp
{
……
public:
CSingleDocTemplate* m_pRdiDocTemplate;
CSingleDocTemplate* m_pChkDocTemplate;
……
}
然后创建这两个文档模板,并加入到模板列表中:
BOOL CViewSwitcherApp::InitInstance()
{
……
m_pRdiDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CRdiDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CRdiView));
AddDocTemplate(m_pRdiDocTemplate);
m_pChkDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CChkDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CChkView));
AddDocTemplate(m_pChkDocTemplate);
……
}
---- 至于第二点,是在创建视时完成的。还记得创建视的情况么?当时有一个叫做pCreateContext 的参数,我们将之置为空,这里就要用到它了。
---- pCreateContext 是一个指向被称作" 创建上下文"(CreateContext) 结构的指针,这个结构中保存一些与创建视相关的内容。在创建主窗口时,系统会构造这样一个结构,并将它作为参数传递到与创建视有关的函数中。但现在我们不创建主窗口,因此不得不自己构造这样一个结构。实际上,该结构中我们所要使用的字段只有三个:
---- 1. 新视所属的文档模板m_pNewDocTemplate;
---- 2. 新视的类型m_pNewViewClass;
---- 3. 新视所属的文档m_pCurrentDoc;
---- 其中仅有第三项需要新建,前两项都是已知的,只要指定即可。以切换到选项视为例,修改后的代码是:
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
CCreateContext createContext;
// 定义并初始化CreateContext
// 获取新视所属的文档模板
CSingleDocTemplate* pDocTemplate =
((CViewSwitcherApp*)AfxGetApp())-> m_pRdiDocTemplate;
// 创建新文档并初始化
CDocument* pDoc = pDocTemplate->CreateNewDocument();
pDoc->OnNewDocument();
// 设置CreateContext 相关字段
createContext.m_pNewViewClass = RUNTIME_CLASS(CChkView);
createContext.m_pCurrentDoc = pDoc;
createContext.m_pNewDocTemplate = pDocTemplate;
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
&createContext);
m_wndSplitter.RecalcLayout();
}
---- 四、最后的修改
---- 为了使这个程序更符合要求,我们还要做一些与更换视无关的修改。在这个程序中我们一共定义了三种类型的文档,程序启动时一般要新建一个文档开始工作,可是它不知道要选择哪一种,就弹出一个对话框来询问。而这是我们不希望看到的。修改的方法是不让VC 选择新文档类型,而我们指定创建哪一种类型的文档,即把CViewSwitcherApp::CViewSwitcherApp() 中的语句
---- if (!ProcessShellCommand(cmdInfo)) return FALSE;
---- 更改为
---- m_pDocTemplate->OpenDocumentFile(NULL)。
让基于对话框应用程序也有启动画面
用MFC的应用向导创建一个有主框架结构的应用程序,要使它具有启动画面是很简单的(下面会体验到),而要使一个基于对话框的应用程序也有启动画面则要费些事了,不过按以下笔者的方法则也是很容易的,我主要介绍方法,对画面仅采用默认情况,读者有兴趣可自己加工。
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
仔细查看了一下WIN32的API,发现其实创建任意形状的窗口其实也是很简单的,在VC中简单步骤如下:
当我们注册并创建了一个窗口类以后,我们在WM_CREATE消息中做如下处理:
(1)创建一个区域,使用CreatePolyonRgn,该函数创建一个多边形区域,(也可以使用其他方法如CreateRectRgn创建矩形区域),该函数返回一个HRGN的句柄;
(2)调用函数SetWindowRgn,即可设置窗口的形状。
补充说明的是,我们可以制作多个区域,然后用CombineRgn方法将多个区域合并为一个区域。这样我们就可以制作出更为丰富多采的窗口了。
VC编程实现IE风格的界面
使用过IE浏览器的朋友都知道IE界面上的扁平工具条、地址栏,扁平工具栏上的按钮正常状态下为扁平态,按钮上的图像为灰色,当鼠标放在按钮上时,按钮突起(这种状态称为手柄),并且其上的图像变得鲜艳醒目,一些按钮上还有汉字说明或标有小黑三角的下拉按钮,单击时显示下拉菜单,这些技术是怎么实现的呢,本文针对这些问题介绍了如何利用VC编程来实现它们。
IE风格的实现主要在主框架类的CMainFrame::OnCreate()实现,它的主要思想如下:首先定义一个CReBar对象,用以作工具条、地址栏的容器,然后分别声明图像列表对象img用于存储工具栏上按钮的热点图像和正常状态下显示的图像。为了显示扁平工具栏,需要用CreateEx()函数创建CToolBar对象m_wndToolBar,用ModifyStyle()函数将工具栏的风格设为扁平类型,你不能用CToolBar::Create() 或 CToolBar:: SetBarStyle()设置这种新风格。CToolBar 类不支持TBSTYLE_FLAT。要解决这个问题,必须绕过CToolBar类,使用CWnd::ModifyStyle()。工具栏对象调用SetButtonInfo()设置按钮的风格为TBSTYLE_DROPDOWN,就可以将工具栏按钮设置为附带有下拉按钮。至于按钮带有中文提示,用工具栏的SetButtonText()就可以轻松实现了。下面是实现IE风格界面的部分代码和注释:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { CReBar m_wndReBar;//声明CReBar对象 CImageList img;//声明图像列表对象 CString str; if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndReBar.Create(this))//创建CReBar对象 { TRACE0("Failed to create rebar/n"); return -1; // fail to create } if (!m_wndToolBar.CreateEx(this))//创建工具条对象 { TRACE0("Failed to create toolbar/n"); return -1; // fail to create } // set up toolbar properties m_wndToolBar.GetToolBarCtrl().SetButtonWidth(50, 150); file://设置工具条上按钮的最大、最小尺寸 m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS); file://工具条可以带有下拉按钮 img.Create(IDB_HOTTOOLBAR, 22, 0, RGB(255, 0, 255)); file://向图像列表装载热点图像资源,IDB_HOTTOOLBAR为热点图像资源ID m_wndToolBar.GetToolBarCtrl().SetHotImageList(&img);//工具条装载热点图像 img.Detach(); img.Create(IDB_COLDTOOLBAR, 22, 0, RGB(255, 0, 255)); file://图象列表装载正常状态的图像资源,IDB_COLDTOOLBAR为图像资源ID m_wndToolBar.GetToolBarCtrl().SetImageList(&img);//将图像装入工具条 img.Detach(); m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT | TBSTYLE_TRANSPARENT); file://工具条为扁平风格 m_wndToolBar.SetButtons(NULL, 9);//工具条上有9个按钮 // set up each toolbar button file://以下分别对九个按钮分别设置风格和按钮汉语提示 m_wndToolBar.SetButtonInfo(0, ID_BUTTON0, TBSTYLE_BUTTON, 0); str.LoadString(IDS_ BUTTON0); m_wndToolBar.SetButtonText(0, str); m_wndToolBar.SetButtonInfo(1, ID_BUTTON1, TBSTYLE_BUTTON, 1); str.LoadString(IDS_ BUTTON1); m_wndToolBar.SetButtonText(1, str); m_wndToolBar.SetButtonInfo(2, ID_BUTTON2, TBSTYLE_BUTTON, 2); str.LoadString(IDS_ BUTTON2); m_wndToolBar.SetButtonText(2, str); m_wndToolBar.SetButtonInfo(3, ID_BUTTON3, TBSTYLE_BUTTON, 3); str.LoadString(IDS_ BUTTON3); m_wndToolBar.SetButtonText(3, str); m_wndToolBar.SetButtonInfo(4, ID_BUTTON4, TBSTYLE_BUTTON, 4); str.LoadString(IDS_ BUTTON4); m_wndToolBar.SetButtonText(4, str); m_wndToolBar.SetButtonInfo(5, ID_BUTTON5, TBSTYLE_BUTTON, 5); str.LoadString(IDS_ BUTTON5); m_wndToolBar.SetButtonText(5, str); m_wndToolBar.SetButtonInfo(6, ID_BUTTON6, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 6); str.LoadString(IDS_ BUTTON6); m_wndToolBar.SetButtonText(6, str); m_wndToolBar.SetButtonInfo(7, ID_BUTTON7, TBSTYLE_BUTTON, 7); str.LoadString(IDS_ BUTTON7); m_wndToolBar.SetButtonText(7, str); m_wndToolBar.SetButtonInfo(8,ID_BUTTON8, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 8); str.LoadString(IDS_ BUTTON8); m_wndToolBar.SetButtonText(8, str); file://重新调整按钮的尺寸 CRect rectToolBar; m_wndToolBar.GetItemRect(0, &rectToolBar);//得到工具条第一个按钮的尺寸 m_wndToolBar.SetSizes(rectToolBar.Size(), CSize(30,20)); file://第一个参数为按钮尺寸,第二个参数为图像尺寸 file://创建一个组合框作为地址栏 if (!m_wndAddress.Create(CBS_DROPDOWN | WS_CHILD, CRect(0, 0, 200, 120), this, AFX_IDW_TOOLBAR + 1)) { TRACE0("Failed to create combobox/n"); return -1; // fail to create } file://加入工具栏、地址栏 m_wndReBar.AddBar(&m_wndToolBar); str.LoadString(IDS_ADDRESS); m_wndReBar.AddBar(&m_wndAddress, str, NULL, RBBS_FIXEDBMP | RBBS_BREAK); file://定义REBARBANDINFO对象,对工具条和地址栏设置理想尺寸 REBARBANDINFO rbbi; rbbi.cbSize = sizeof(rbbi); rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_SIZE; rbbi.cxMinChild = rectToolBar.Width(); rbbi.cyMinChild = rectToolBar.Height(); rbbi.cx = rbbi.cxIdeal = rectToolBar.Width() * 9; m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);//设置工具栏尺寸 rbbi.cxMinChild = 0; CRect rectAddress; rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE; m_wndAddress.GetEditCtrl()->GetWindowRect(&rectAddress); rbbi.cyMinChild = rectAddress.Height() + 10; rbbi.cxIdeal = 200; m_wndReBar.GetReBarCtrl().SetBandInfo(2, &rbbi);//设置地址栏尺寸 m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED); if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar/n"); return -1; // fail to create } return 0; } |
以上代码在Windows2000和Visual C++环境下编译通过,程序运行正常,有兴趣的朋友可以动手亲自实验一下。
VC限制窗口大小又一法
一般说见到的方法,,都是截获WM_GETMAXMININFO消息。
俺有另一经验可实现之。
由于一般窗口大小的改变,都是用户拖动窗口边框而造成的。所以,我们可以截获主窗口消息WM_NCHITTEST在其响应函数中判断CWnd::OnNcHitTest()的返回值是否为HTRIGHT,HTLEFT,HTTOP,HTBOTTOM四个值之一,如果是,说明用户此时已点击了四个边框之一,此时我们应该返回HTCLIENT.那么,鼠标的形状就不会变成水平或垂直的双向箭头,用户就不可能依靠拖动边框来改变窗口大小了。
另外,还应补上一个小漏洞,就是还要把系统菜单中的SC_SIZE去掉。
主程序之前的版权窗口
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD lTime;
try
{
Application->Initialize();
AboutBox=new TAboutBox(AboutBox);
AboutBox->BorderStyle=bsNone;
AboutBox->OKButton->Visible=false;
AboutBox->Height=185;
AboutBox->Show();
AboutBox->Update();
lTime=GetTickCount();
Application->CreateForm(__classid(TMainForm), &MainForm);
while((GetTickCount()-lTime) / 1000 < 3);
AboutBox->Hide();
AboutBox->Free();
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
VISUAL C++6.0在MDI主框架窗口中添加位图
笔者在开发项目时想在MDI程序中添加彩色位图以美化界面,也实验了几种方法,但都有一些小问题,经多方查找资料,终于圆满的实现了这种功能,现把我的实现方法介绍给大家。
首先要清楚对于一个MDI应用程序的主框架窗口来说包含一个特殊的子窗口称为MDICLIENT窗口,应用程序的主框架类中有一个成员变量m_hWndMDIClient 指的就是MDICLIENT窗口。MDICLIENT窗口负责管理主框架窗口的客户区,对MDI客户窗口编程有一定的难度。原因是MDIFrameWnd的客户区完全被MDICLIENT窗口覆盖掉了。这样,MDI主窗口类MDIFrameWnd的背景色和光标都不起作用。同时,微软并不支持将MDICLIENT窗口作为子类,MDICLIENT窗口只能使用标准的背景色和光标。所以,对MDI客户窗口编程不能象对普通窗口那样简单地重载WM_PAINT的消息处理函数。我们可以在主框架窗口截获关于MDICLIENT窗口的重画消息,然后加入自己设计的代码。我用PreTranslateMessage(MSG* pMsg) 截获MDI客户窗口WM_PAINT消息,在这个函数中向主框架窗口发送WM_PAINT消息,在该消息的处理函数中实现彩色位图的显示。我的具体实现如下:1、向程序添加256色彩色位图资源,命名为IDB_BITMAP1;2、用ClassWizard向主框架类添加函数CMainFrame::PreTranslateMessage(MSG* pMsg);3、用ClassWizard向主框架类添加函数CMainFrame::OnPaint();现给出两个函数的实现:
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if(pMsg->hwnd==m_hWndMDIClient && pMsg->message==WM_PAINT) PostMessage(WM_PAINT); return CMDIFrameWnd::PreTranslateMessage(pMsg); } void CMainFrame::OnPaint() { CDC dc, memdc; dc.m_hDC=::GetDC(this->m_hWndMDIClient); CRect rect; CBitmap bitmap; BITMAP szbitmap; bitmap.LoadBitmap(IDB_BITMAP1); bitmap.GetObject(sizeof(BITMAP),&szbitmap); CSize size(szbitmap.bmWidth,szbitmap.bmHeight); memdc.CreateCompatibleDC(&dc); CBitmap *oldbitmap=memdc.SelectObject(&bitmap); GetClientRect(&rect); StretchBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(), memdc.m_hDC,0,0,size.cx,size.cy,SRCCOPY); memdc.SelectObject(oldbitmap); memdc.DeleteDC(); dc.DeleteDC(); CMDIFrameWnd::OnPaint(); } |
按上述步骤就可以实现在MDI程序中显示彩色位图了,我举的例子用的是256色位图,你也可以实现真彩色位图的显示,具体方法我就不多说了,有兴趣的朋友可以试一试。
华丽的界面
VC6.0实现逆向操作并防止界面闪烁
在系统编程中,使用VC是很好的开发工具,而对于一个成熟的系统,几乎都需要有回退与重做功能(即文档操作逆向化)以防止用户误操作或不合适的操作,从而提高系统的友好性和可操作性。在很多VC技术文章中均提到过这个问题,不过总存在着界面闪烁或不完全可逆.
本文提出一种对系统编程可实现完全可逆并防止闪屏的方法.
一、基本原理
要对文档进行回退重做功能,要做两方面的工作,一方面要保留删除的文档(在操作过程中,删除的文档资料一定能够保留),另一方面,系统必须能够记录进行文档操作的全过程及每个操作过程的参数。为了保留历史操作,所有数据非常占用内存空间,这就是一些系统只能进行有限次退步逆向操作的原因。本文提出的方法建立如下存储机制:建一个临时文件储存数据模拟堆栈,进行一次操作时将相关操作数据入栈.回退一次将相关数据弹出栈,重做一次又依据相关数据重新恢复原有数据.它的好处是在回退和重做时只入一次栈即申请一次内存。
堆栈的数据排放如图:
// Undo、Redo 数据排放示意图(m_UndoDataList)
// // ==== // |###| } // |###| } // |###| } ----->> Redo 数据 // |###| } // |###| } // |///| } // |///| } // |///| } // |///| } --->> Undo 数据(Undo数据弹出后将转换为Redo数据) // |///| } // |///| } // ===== // Undo数据栈 |
二、实现文档回退重做的引擎
建一文档逆向化堆栈引擎.主要代码为:
1.建立临时文件.(m_TempPath可以按照某种规则形成路径)
if(m_File.Open((LPCTSTR)m_TempPath, CFile::modeCreate|CFile::modeReadWrite|CFile::shareExclusive)) { m_File.SeekToBegin(); m_UndoCount = 0; file://当前可重做的步数 m_RedoCount = 0; file://当前可回退的步数 } |
2.保存回退数据模块.
// 保存一个Undo数据块(由用户提供) int CRedoUndoEngine::PushData( LPVOID pData, // 由用户提供的内存块首地址,其中含有用户定义的待保存的数据。 // (注:如果函数成功,此内存块将会被本函数释放,因此,该内存块必须是用::GlobalAlloc()函数分配的) DWORD size, // pData指向的内存块尺寸 DWORD param1, // 用户提供的对该内存块的说明参数,含义由用户定义 DWORD param2, // 用户提供的对该内存块的说明参数,含义由用户定义 int *pIndex // 如果成功,本函数将返回压入的Undo块在栈中的索引值。 如果不需要此返回值,可用NULL作为参数 ) { // 删除Redo数据 if (m_RedoCount) { while(m_RedoCount--) delete (LPISEEUNDOINFO)m_UndoDataList.RemoveTail(); m_RedoCount = 0; } // 填写Undo数据的索引信息(lpISeeUndoInfo为一个保存数据的结构体) lpISeeUndoInfo->m_index = m_UndoCount; // 索引 lpISeeUndoInfo->m_UserData1 = param1; // 用户定义的标识性数据1 lpISeeUndoInfo->m_UserData2 = param2; // 用户定义的标识性数据2 lpISeeUndoInfo->m_DataSize = size; // 用户的Undo数据块尺寸 lpISeeUndoInfo->m_FilePosition = _get_current_overwrite_pos(); // 加新的Undo数据到Undo栈的尾部 m_UndoDataList.AddTail((void*)lpISeeUndoInfo); // 将用户的Undo数据写入临时文件 m_File.Seek(lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Write((const void *)pData, size); 并使Undo块计数加1 m_UndoCount++; // 此时用户传过来的数据块已经无用,删除! ::GlobalFree(pData); return 1; } |
3.弹出重做数据模块.
// 弹出一个Redo数据块 int CIUndoEngine::RedoData( LPVOID *ppData, // 用于接收本函数返回的含有最近一个Redo数据的内存块首地址的指针 // (注:此内存块交由调用者释放,使用::GlobalFree()函数) DWORD *pSize, // ppData内存块的尺寸(in byte) ,如果不需要此数据可用NULL作为参数 DWORD *pParam1, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 DWORD *pParam2, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 int *pIndex // 返回本Redo块的索引,如果不需要此数据可用NULL作为参数 ) { if (!m_RedoCount) return 0; // 锁定待弹出的Redo索引信息块的地址 POSITION pos = m_UndoDataList.FindIndex(m_UndoCount); ASSERT(pos); LPISEEUNDOINFO lpISeeUndoInfo= (LPISEEUNDOINFO)m_UndoDataList.GetAt(pos); ASSERT(lpISeeUndoInfo); ASSERT(lpISeeUndoInfo->m_index == m_UndoCount); if (!(*ppData)) return -1; // 读出用户保存在临时文件中的Undo数据(也即Redo数据) m_File.Seek((LONG)lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Read(*ppData, lpISeeUndoInfo->m_DataSize); m_UndoCount++; // 可用Undo数据块个数加1 m_RedoCount--; // 可用Redo数据块个数减1 if (pSize) *pSize = lpISeeUndoInfo->m_DataSize; if (pParam1) *pParam1= lpISeeUndoInfo->m_UserData1; if (pParam2) *pParam2= lpISeeUndoInfo->m_UserData2; if (pIndex) *pIndex = m_RedoCount;// 注:此处的索引是Redo的索引,而不是Undo的 return 1; } |
由这个文档逆向化操作引擎,可以获得当前改动的文档的数据,并根据改动的数据更新视图,而不刷新没有更改数据的视图.从而防止了闪烁的产生.
三、简单开发实例
下面以我们开发服装CAD过程中加入的回退重做功能(文档逆向化)说明之。
1.定义回退类型
#define REUNDO_MOV 0x0001 file://衣片移动回退重做 #define REUNDO_SEL 0x0002 file://衣片选择回退重做 ………. |
2.保存某个操作之前和之后的数据(以衣片移动回退重做为例)
//----------申请内存----------------------// int nByte = 4*sizeof(DWORD); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte); LPVOID pData = (LPVOID) GlobalLock(hMem); file://-----保存衣片移动前后的位置读入内存------//用移动前后衣片的某个坐标点表示 memcpy((DWORD*)pData, &m_oldPoint, 2*sizeof(DWORD)); memcpy((DWORD*)pData+2,&point, 2*sizeof(DWORD)); file://--------数据入栈---------------------------------------// m_pReUndoEngine->PushData(pData,//衣片m_pReUndoEngine文档逆向化引擎对象指针 nByte,//保存数据衣片字节数 REUNDO_MOV,//回退类型 NULL,NULL); |
3.当回退操作事件触发时.
//弹出回退值 int nByte = m_pReUndoEngine->GetPopDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->PopData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelUndo(pData,index,&dc);break; case REUNDO_MOV: MovUndo(pData);break; ………… } void CMarkView::MovUndo(LPVOID pData) 函数功能 { CPoint pt1,pt2; memcpy(&pt1,(DWORD*)pData,8); memcpy(&pt2,(DWORD*)pData+2,8); …….由pt1 和pt2可以求出位移量,从而恢复原衣片的位置. } |
4.当重做操作事件触发时
//弹出回退值 int nByte = m_pReUndoEngine->GetRedoDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->RedoData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelRedo(pData,index,&dc,nByte);break; case REUNDO_MOV: MovRedo(pData); break; ………… } |
函数MovRedo(pData)与MovUndo(pData)类似就不多说了.
由3,4可以看出,在回退与重做过程中,只是保存和取出操作对象已变化的过程,使编程者很容易实现高效率刷新与充分节约存储空间.
小结
在系统编程中,文档的回退与重做几乎是必不可少的,本文提出了一种思路,即对文档的各种操作分解,并把每种操作下变化的对象的数据值保存于临时文件(栈)中,在回退与重做时根据变化量很容易恢复操作之前状态或重做, 避免了有些系统(保存全部文档数据)占用大量内存空间而只能有限次文档逆向化,并且全部刷新而闪烁,破坏了界面的友好性。
怎样在一个Pane中显示多种View?
在MS Windows 中,一个窗口可以分割成若干个子窗口,每一个子窗口称作一个窗片(pane),每个窗片可以独立控制,这给界面设计提供了很大的方便。
---- 利用VC 可以很方便地实现分割窗口。分割的方法有两种:动态和静态。动态分割时可以根据用户的需要分割成数目不同的窗片,但所有窗片的属性和父窗口都是一样的;而静态分割的窗片的数目在程序中指定,运行时是固定的,但每个窗片可以有各自不同类型的视(View),因此其使用范围更为广泛。本文所讨论的问题仅限于静态分割。
---- 窗片中视的类型大多是在主窗口的创建过程中指定的。这也就意味着,一个窗片虽然可以显示任意类型的视,但是这种类型一旦确定,在程序运行过程中就难以改变。
---- 一、我要的是这样的!
---- 但是我们有时确实需要改变一个窗片所显示的视的类型,也就是说,需要让一个窗片显示多种类型的视。例如一个窗口被分割成两部分,一边是命令窗口,另一边是工作窗口,根据命令窗口中发出的不同命令,需要变换不同的工作类型,这就需要工作窗口中能够显示多种类型的视窗,那么,如何做到这一点呢?
---- 二、你可以这样做!
---- 从图1 中可以看到,本程序共有三个视类,分别是:
---- ? 命令视类CCmdView:用来控制右边窗片中不同视的显示;
---- ? 选项按钮视类CRdiView:显示在右窗片中的选项视类;
---- ? 检查按钮视类CChkView:显示在右窗片中的检查视类。
---- 这三个视类都是CFormView 的子类。
---- 下面我们来看如何在右窗片内进行两类视间的切换。实际上,由视A 切换到视B 的原理很简单,那就是:
---- 1. 从窗片中删除视A;
---- 2. 往窗片中添加视B。
---- 步骤1 的实现非常简单,仅用一条语句即可:
---- m_wndSplitter.DeleteView(0, 1);
---- 但它是必不可少的,因为你不能让一个窗片同时包含两个视。我本来希望往一个窗片中添加新的视时,VC 会自动将原来的视删簦墒撬桓伞?br>
---- 我们来看如何实现步骤2,当一个窗片是空的时候,怎样往里面添加一个视呢?其实这样的功能在程序里我们已经用过了,看下面的语句:
BOOL CMainFrame::OnCreateClient
(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
……
if (!m_wndSplitter.CreateView(0, 0,
pContext->m_pNe ewClass,
size,
pContext))
……
}
---- 是的,用的就是CSplitterWnd::CreateView(),要注意的是它共有五个参数,其中前两个用来指定分割窗口的窗片,第三个用来指定视的类型,第四个指定视的大小。最后的一个我们暂时用不上,用空值NULL 就可以了。
---- 这样我们就可以编写视切换的代码了。因为视切换要操纵m_wndSplitter,而它是主窗口的成员,因此切换过程最好设计为主窗口的成员函数。但是切换命令是CCmdView 接受的,因而可以让CCmdView 接受到视更改消息后,将消息传给主窗口,由主窗口完成视更改。具体的代码是这样的:
---- 命令视类中的消息映射:
BEGIN_MESSAGE_MAP(CCmdView, CFormView)
……
ON_BN_CLICKED(IDC_CHECK, OnSwitchToCheckView)
ON_BN_CLICKED(IDC_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
命令视类中的消息响应:
void CCmdView::OnSwitchToCheckView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_CHECK);
}
void CCmdView::OnSwitchToRadioView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_RADIO);
}
主窗口中的消息映射:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
……
ON_COMMAND(ID_CHECK, OnSwitchToCheckView)
ON_COMMAND(ID_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
主窗口中的消息响应:
void CMainFrame::OnSwitchToCheckView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CChkView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
---- 好啦,运行一下这个程序,感觉是否不错?看来大功告成了,可是……
---- 三、还有一个问题
---- 在运行我们辛辛苦苦编出来的程序时,回头看看VC 的调试窗口,你会发现有很多行这样的话:
---- Create view without document.
---- 这是说我们创建了视,可是没有相应的文档。好在这只是警告信息,不是什么错误,如果你不需要相应的文档,就完全不用去管它。可是,VC 中一种很重要的结构就是文档- 视结构,利用这种结构,对数据操纵起来非常方便。如果需要建立与视相对应的文档,应该怎么办呢?
---- 这就涉及到VC 中文档- 视结构的知识,不过不用怕麻烦,与本文有关的就只有这么两点而已:
---- 1. 利用VC 创建的应用程序一般都会管理一些文档模板(Document Template),文档类和视类的对应关系就是在文档模板里描述的。
---- 2. 一个文档可以有多个视,创建视的时候,需要根据文档和视的对应关系,给出它所依附的文档。
---- 怎样实现上述第一点呢?
---- 首先建立相应的文档类:CRdiDoc 和CChkDoc。
---- 其次是定义相应的文档模板,这是应用类的成员变量。因为在别的类中要使用它们,我们将之定义为公共类型:
class CViewSwitcherApp : public CWinApp
{
……
public:
CSingleDocTemplate* m_pRdiDocTemplate;
CSingleDocTemplate* m_pChkDocTemplate;
……
}
然后创建这两个文档模板,并加入到模板列表中:
BOOL CViewSwitcherApp::InitInstance()
{
……
m_pRdiDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CRdiDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CRdiView));
AddDocTemplate(m_pRdiDocTemplate);
m_pChkDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CChkDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CChkView));
AddDocTemplate(m_pChkDocTemplate);
……
}
---- 至于第二点,是在创建视时完成的。还记得创建视的情况么?当时有一个叫做pCreateContext 的参数,我们将之置为空,这里就要用到它了。
---- pCreateContext 是一个指向被称作" 创建上下文"(CreateContext) 结构的指针,这个结构中保存一些与创建视相关的内容。在创建主窗口时,系统会构造这样一个结构,并将它作为参数传递到与创建视有关的函数中。但现在我们不创建主窗口,因此不得不自己构造这样一个结构。实际上,该结构中我们所要使用的字段只有三个:
---- 1. 新视所属的文档模板m_pNewDocTemplate;
---- 2. 新视的类型m_pNewViewClass;
---- 3. 新视所属的文档m_pCurrentDoc;
---- 其中仅有第三项需要新建,前两项都是已知的,只要指定即可。以切换到选项视为例,修改后的代码是:
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
CCreateContext createContext;
// 定义并初始化CreateContext
// 获取新视所属的文档模板
CSingleDocTemplate* pDocTemplate =
((CViewSwitcherApp*)AfxGetApp())-> m_pRdiDocTemplate;
// 创建新文档并初始化
CDocument* pDoc = pDocTemplate->CreateNewDocument();
pDoc->OnNewDocument();
// 设置CreateContext 相关字段
createContext.m_pNewViewClass = RUNTIME_CLASS(CChkView);
createContext.m_pCurrentDoc = pDoc;
createContext.m_pNewDocTemplate = pDocTemplate;
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
&createContext);
m_wndSplitter.RecalcLayout();
}
---- 四、最后的修改
---- 为了使这个程序更符合要求,我们还要做一些与更换视无关的修改。在这个程序中我们一共定义了三种类型的文档,程序启动时一般要新建一个文档开始工作,可是它不知道要选择哪一种,就弹出一个对话框来询问。而这是我们不希望看到的。修改的方法是不让VC 选择新文档类型,而我们指定创建哪一种类型的文档,即把CViewSwitcherApp::CViewSwitcherApp() 中的语句
---- if (!ProcessShellCommand(cmdInfo)) return FALSE;
---- 更改为
---- m_pDocTemplate->OpenDocumentFile(NULL)。
让基于对话框应用程序也有启动画面
用MFC的应用向导创建一个有主框架结构的应用程序,要使它具有启动画面是很简单的(下面会体验到),而要使一个基于对话框的应用程序也有启动画面则要费些事了,不过按以下笔者的方法则也是很容易的,我主要介绍方法,对画面仅采用默认情况,读者有兴趣可自己加工。
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
俺有另一经验可实现之。
由于一般窗口大小的改变,都是用户拖动窗口边框而造成的。所以,我们可以截获主窗口消息WM_NCHITTEST在其响应函数中判断CWnd::OnNcHitTest()的返回值是否为HTRIGHT,HTLEFT,HTTOP,HTBOTTOM四个值之一,如果是,说明用户此时已点击了四个边框之一,此时我们应该返回HTCLIENT.那么,鼠标的形状就不会变成水平或垂直的双向箭头,用户就不可能依靠拖动边框来改变窗口大小了。
另外,还应补上一个小漏洞,就是还要把系统菜单中的SC_SIZE去掉。
主程序之前的版权窗口
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD lTime;
try
{
Application->Initialize();
AboutBox=new TAboutBox(AboutBox);
AboutBox->BorderStyle=bsNone;
AboutBox->OKButton->Visible=false;
AboutBox->Height=185;
AboutBox->Show();
AboutBox->Update();
lTime=GetTickCount();
Application->CreateForm(__classid(TMainForm), &MainForm);
while((GetTickCount()-lTime) / 1000 < 3);
AboutBox->Hide();
AboutBox->Free();
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
VISUAL C++6.0在MDI主框架窗口中添加位图
笔者在开发项目时想在MDI程序中添加彩色位图以美化界面,也实验了几种方法,但都有一些小问题,经多方查找资料,终于圆满的实现了这种功能,现把我的实现方法介绍给大家。
首先要清楚对于一个MDI应用程序的主框架窗口来说包含一个特殊的子窗口称为MDICLIENT窗口,应用程序的主框架类中有一个成员变量m_hWndMDIClient 指的就是MDICLIENT窗口。MDICLIENT窗口负责管理主框架窗口的客户区,对MDI客户窗口编程有一定的难度。原因是MDIFrameWnd的客户区完全被MDICLIENT窗口覆盖掉了。这样,MDI主窗口类MDIFrameWnd的背景色和光标都不起作用。同时,微软并不支持将MDICLIENT窗口作为子类,MDICLIENT窗口只能使用标准的背景色和光标。所以,对MDI客户窗口编程不能象对普通窗口那样简单地重载WM_PAINT的消息处理函数。我们可以在主框架窗口截获关于MDICLIENT窗口的重画消息,然后加入自己设计的代码。我用PreTranslateMessage(MSG* pMsg) 截获MDI客户窗口WM_PAINT消息,在这个函数中向主框架窗口发送WM_PAINT消息,在该消息的处理函数中实现彩色位图的显示。我的具体实现如下:1、向程序添加256色彩色位图资源,命名为IDB_BITMAP1;2、用ClassWizard向主框架类添加函数CMainFrame::PreTranslateMessage(MSG* pMsg);3、用ClassWizard向主框架类添加函数CMainFrame::OnPaint();现给出两个函数的实现:
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if(pMsg->hwnd==m_hWndMDIClient && pMsg->message==WM_PAINT) PostMessage(WM_PAINT); return CMDIFrameWnd::PreTranslateMessage(pMsg); } void CMainFrame::OnPaint() { CDC dc, memdc; dc.m_hDC=::GetDC(this->m_hWndMDIClient); CRect rect; CBitmap bitmap; BITMAP szbitmap; bitmap.LoadBitmap(IDB_BITMAP1); bitmap.GetObject(sizeof(BITMAP),&szbitmap); CSize size(szbitmap.bmWidth,szbitmap.bmHeight); memdc.CreateCompatibleDC(&dc); CBitmap *oldbitmap=memdc.SelectObject(&bitmap); GetClientRect(&rect); StretchBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(), memdc.m_hDC,0,0,size.cx,size.cy,SRCCOPY); memdc.SelectObject(oldbitmap); memdc.DeleteDC(); dc.DeleteDC(); CMDIFrameWnd::OnPaint(); } |
按上述步骤就可以实现在MDI程序中显示彩色位图了,我举的例子用的是256色位图,你也可以实现真彩色位图的显示,具体方法我就不多说了,有兴趣的朋友可以试一试。
华丽的界面
VC6.0实现逆向操作并防止界面闪烁
在系统编程中,使用VC是很好的开发工具,而对于一个成熟的系统,几乎都需要有回退与重做功能(即文档操作逆向化)以防止用户误操作或不合适的操作,从而提高系统的友好性和可操作性。在很多VC技术文章中均提到过这个问题,不过总存在着界面闪烁或不完全可逆.
本文提出一种对系统编程可实现完全可逆并防止闪屏的方法.
一、基本原理
要对文档进行回退重做功能,要做两方面的工作,一方面要保留删除的文档(在操作过程中,删除的文档资料一定能够保留),另一方面,系统必须能够记录进行文档操作的全过程及每个操作过程的参数。为了保留历史操作,所有数据非常占用内存空间,这就是一些系统只能进行有限次退步逆向操作的原因。本文提出的方法建立如下存储机制:建一个临时文件储存数据模拟堆栈,进行一次操作时将相关操作数据入栈.回退一次将相关数据弹出栈,重做一次又依据相关数据重新恢复原有数据.它的好处是在回退和重做时只入一次栈即申请一次内存。
堆栈的数据排放如图:
// Undo、Redo 数据排放示意图(m_UndoDataList)
// // ==== // |###| } // |###| } // |###| } ----->> Redo 数据 // |###| } // |###| } // |///| } // |///| } // |///| } // |///| } --->> Undo 数据(Undo数据弹出后将转换为Redo数据) // |///| } // |///| } // ===== // Undo数据栈 |
二、实现文档回退重做的引擎
建一文档逆向化堆栈引擎.主要代码为:
1.建立临时文件.(m_TempPath可以按照某种规则形成路径)
if(m_File.Open((LPCTSTR)m_TempPath, CFile::modeCreate|CFile::modeReadWrite|CFile::shareExclusive)) { m_File.SeekToBegin(); m_UndoCount = 0; file://当前可重做的步数 m_RedoCount = 0; file://当前可回退的步数 } |
2.保存回退数据模块.
// 保存一个Undo数据块(由用户提供) int CRedoUndoEngine::PushData( LPVOID pData, // 由用户提供的内存块首地址,其中含有用户定义的待保存的数据。 // (注:如果函数成功,此内存块将会被本函数释放,因此,该内存块必须是用::GlobalAlloc()函数分配的) DWORD size, // pData指向的内存块尺寸 DWORD param1, // 用户提供的对该内存块的说明参数,含义由用户定义 DWORD param2, // 用户提供的对该内存块的说明参数,含义由用户定义 int *pIndex // 如果成功,本函数将返回压入的Undo块在栈中的索引值。 如果不需要此返回值,可用NULL作为参数 ) { // 删除Redo数据 if (m_RedoCount) { while(m_RedoCount--) delete (LPISEEUNDOINFO)m_UndoDataList.RemoveTail(); m_RedoCount = 0; } // 填写Undo数据的索引信息(lpISeeUndoInfo为一个保存数据的结构体) lpISeeUndoInfo->m_index = m_UndoCount; // 索引 lpISeeUndoInfo->m_UserData1 = param1; // 用户定义的标识性数据1 lpISeeUndoInfo->m_UserData2 = param2; // 用户定义的标识性数据2 lpISeeUndoInfo->m_DataSize = size; // 用户的Undo数据块尺寸 lpISeeUndoInfo->m_FilePosition = _get_current_overwrite_pos(); // 加新的Undo数据到Undo栈的尾部 m_UndoDataList.AddTail((void*)lpISeeUndoInfo); // 将用户的Undo数据写入临时文件 m_File.Seek(lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Write((const void *)pData, size); 并使Undo块计数加1 m_UndoCount++; // 此时用户传过来的数据块已经无用,删除! ::GlobalFree(pData); return 1; } |
3.弹出重做数据模块.
// 弹出一个Redo数据块 int CIUndoEngine::RedoData( LPVOID *ppData, // 用于接收本函数返回的含有最近一个Redo数据的内存块首地址的指针 // (注:此内存块交由调用者释放,使用::GlobalFree()函数) DWORD *pSize, // ppData内存块的尺寸(in byte) ,如果不需要此数据可用NULL作为参数 DWORD *pParam1, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 DWORD *pParam2, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 int *pIndex // 返回本Redo块的索引,如果不需要此数据可用NULL作为参数 ) { if (!m_RedoCount) return 0; // 锁定待弹出的Redo索引信息块的地址 POSITION pos = m_UndoDataList.FindIndex(m_UndoCount); ASSERT(pos); LPISEEUNDOINFO lpISeeUndoInfo= (LPISEEUNDOINFO)m_UndoDataList.GetAt(pos); ASSERT(lpISeeUndoInfo); ASSERT(lpISeeUndoInfo->m_index == m_UndoCount); if (!(*ppData)) return -1; // 读出用户保存在临时文件中的Undo数据(也即Redo数据) m_File.Seek((LONG)lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Read(*ppData, lpISeeUndoInfo->m_DataSize); m_UndoCount++; // 可用Undo数据块个数加1 m_RedoCount--; // 可用Redo数据块个数减1 if (pSize) *pSize = lpISeeUndoInfo->m_DataSize; if (pParam1) *pParam1= lpISeeUndoInfo->m_UserData1; if (pParam2) *pParam2= lpISeeUndoInfo->m_UserData2; if (pIndex) *pIndex = m_RedoCount;// 注:此处的索引是Redo的索引,而不是Undo的 return 1; } |
由这个文档逆向化操作引擎,可以获得当前改动的文档的数据,并根据改动的数据更新视图,而不刷新没有更改数据的视图.从而防止了闪烁的产生.
三、简单开发实例
下面以我们开发服装CAD过程中加入的回退重做功能(文档逆向化)说明之。
1.定义回退类型
#define REUNDO_MOV 0x0001 file://衣片移动回退重做 #define REUNDO_SEL 0x0002 file://衣片选择回退重做 ………. |
2.保存某个操作之前和之后的数据(以衣片移动回退重做为例)
//----------申请内存----------------------// int nByte = 4*sizeof(DWORD); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte); LPVOID pData = (LPVOID) GlobalLock(hMem); file://-----保存衣片移动前后的位置读入内存------//用移动前后衣片的某个坐标点表示 memcpy((DWORD*)pData, &m_oldPoint, 2*sizeof(DWORD)); memcpy((DWORD*)pData+2,&point, 2*sizeof(DWORD)); file://--------数据入栈---------------------------------------// m_pReUndoEngine->PushData(pData,//衣片m_pReUndoEngine文档逆向化引擎对象指针 nByte,//保存数据衣片字节数 REUNDO_MOV,//回退类型 NULL,NULL); |
3.当回退操作事件触发时.
//弹出回退值 int nByte = m_pReUndoEngine->GetPopDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->PopData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelUndo(pData,index,&dc);break; case REUNDO_MOV: MovUndo(pData);break; ………… } void CMarkView::MovUndo(LPVOID pData) 函数功能 { CPoint pt1,pt2; memcpy(&pt1,(DWORD*)pData,8); memcpy(&pt2,(DWORD*)pData+2,8); …….由pt1 和pt2可以求出位移量,从而恢复原衣片的位置. } |
4.当重做操作事件触发时
//弹出回退值 int nByte = m_pReUndoEngine->GetRedoDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->RedoData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelRedo(pData,index,&dc,nByte);break; case REUNDO_MOV: MovRedo(pData); break; ………… } |
函数MovRedo(pData)与MovUndo(pData)类似就不多说了.
由3,4可以看出,在回退与重做过程中,只是保存和取出操作对象已变化的过程,使编程者很容易实现高效率刷新与充分节约存储空间.
小结
在系统编程中,文档的回退与重做几乎是必不可少的,本文提出了一种思路,即对文档的各种操作分解,并把每种操作下变化的对象的数据值保存于临时文件(栈)中,在回退与重做时根据变化量很容易恢复操作之前状态或重做, 避免了有些系统(保存全部文档数据)占用大量内存空间而只能有限次文档逆向化,并且全部刷新而闪烁,破坏了界面的友好性。
怎样在一个Pane中显示多种View?
在MS Windows 中,一个窗口可以分割成若干个子窗口,每一个子窗口称作一个窗片(pane),每个窗片可以独立控制,这给界面设计提供了很大的方便。
---- 利用VC 可以很方便地实现分割窗口。分割的方法有两种:动态和静态。动态分割时可以根据用户的需要分割成数目不同的窗片,但所有窗片的属性和父窗口都是一样的;而静态分割的窗片的数目在程序中指定,运行时是固定的,但每个窗片可以有各自不同类型的视(View),因此其使用范围更为广泛。本文所讨论的问题仅限于静态分割。
---- 窗片中视的类型大多是在主窗口的创建过程中指定的。这也就意味着,一个窗片虽然可以显示任意类型的视,但是这种类型一旦确定,在程序运行过程中就难以改变。
---- 一、我要的是这样的!
---- 但是我们有时确实需要改变一个窗片所显示的视的类型,也就是说,需要让一个窗片显示多种类型的视。例如一个窗口被分割成两部分,一边是命令窗口,另一边是工作窗口,根据命令窗口中发出的不同命令,需要变换不同的工作类型,这就需要工作窗口中能够显示多种类型的视窗,那么,如何做到这一点呢?
---- 二、你可以这样做!
---- 从图1 中可以看到,本程序共有三个视类,分别是:
---- ? 命令视类CCmdView:用来控制右边窗片中不同视的显示;
---- ? 选项按钮视类CRdiView:显示在右窗片中的选项视类;
---- ? 检查按钮视类CChkView:显示在右窗片中的检查视类。
---- 这三个视类都是CFormView 的子类。
---- 下面我们来看如何在右窗片内进行两类视间的切换。实际上,由视A 切换到视B 的原理很简单,那就是:
---- 1. 从窗片中删除视A;
---- 2. 往窗片中添加视B。
---- 步骤1 的实现非常简单,仅用一条语句即可:
---- m_wndSplitter.DeleteView(0, 1);
---- 但它是必不可少的,因为你不能让一个窗片同时包含两个视。我本来希望往一个窗片中添加新的视时,VC 会自动将原来的视删簦墒撬桓伞?br>
---- 我们来看如何实现步骤2,当一个窗片是空的时候,怎样往里面添加一个视呢?其实这样的功能在程序里我们已经用过了,看下面的语句:
BOOL CMainFrame::OnCreateClient
(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
……
if (!m_wndSplitter.CreateView(0, 0,
pContext->m_pNe ewClass,
size,
pContext))
……
}
---- 是的,用的就是CSplitterWnd::CreateView(),要注意的是它共有五个参数,其中前两个用来指定分割窗口的窗片,第三个用来指定视的类型,第四个指定视的大小。最后的一个我们暂时用不上,用空值NULL 就可以了。
---- 这样我们就可以编写视切换的代码了。因为视切换要操纵m_wndSplitter,而它是主窗口的成员,因此切换过程最好设计为主窗口的成员函数。但是切换命令是CCmdView 接受的,因而可以让CCmdView 接受到视更改消息后,将消息传给主窗口,由主窗口完成视更改。具体的代码是这样的:
---- 命令视类中的消息映射:
BEGIN_MESSAGE_MAP(CCmdView, CFormView)
……
ON_BN_CLICKED(IDC_CHECK, OnSwitchToCheckView)
ON_BN_CLICKED(IDC_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
命令视类中的消息响应:
void CCmdView::OnSwitchToCheckView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_CHECK);
}
void CCmdView::OnSwitchToRadioView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_RADIO);
}
主窗口中的消息映射:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
……
ON_COMMAND(ID_CHECK, OnSwitchToCheckView)
ON_COMMAND(ID_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
主窗口中的消息响应:
void CMainFrame::OnSwitchToCheckView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CChkView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
---- 好啦,运行一下这个程序,感觉是否不错?看来大功告成了,可是……
---- 三、还有一个问题
---- 在运行我们辛辛苦苦编出来的程序时,回头看看VC 的调试窗口,你会发现有很多行这样的话:
---- Create view without document.
---- 这是说我们创建了视,可是没有相应的文档。好在这只是警告信息,不是什么错误,如果你不需要相应的文档,就完全不用去管它。可是,VC 中一种很重要的结构就是文档- 视结构,利用这种结构,对数据操纵起来非常方便。如果需要建立与视相对应的文档,应该怎么办呢?
---- 这就涉及到VC 中文档- 视结构的知识,不过不用怕麻烦,与本文有关的就只有这么两点而已:
---- 1. 利用VC 创建的应用程序一般都会管理一些文档模板(Document Template),文档类和视类的对应关系就是在文档模板里描述的。
---- 2. 一个文档可以有多个视,创建视的时候,需要根据文档和视的对应关系,给出它所依附的文档。
---- 怎样实现上述第一点呢?
---- 首先建立相应的文档类:CRdiDoc 和CChkDoc。
---- 其次是定义相应的文档模板,这是应用类的成员变量。因为在别的类中要使用它们,我们将之定义为公共类型:
class CViewSwitcherApp : public CWinApp
{
……
public:
CSingleDocTemplate* m_pRdiDocTemplate;
CSingleDocTemplate* m_pChkDocTemplate;
……
}
然后创建这两个文档模板,并加入到模板列表中:
BOOL CViewSwitcherApp::InitInstance()
{
……
m_pRdiDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CRdiDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CRdiView));
AddDocTemplate(m_pRdiDocTemplate);
m_pChkDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CChkDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CChkView));
AddDocTemplate(m_pChkDocTemplate);
……
}
---- 至于第二点,是在创建视时完成的。还记得创建视的情况么?当时有一个叫做pCreateContext 的参数,我们将之置为空,这里就要用到它了。
---- pCreateContext 是一个指向被称作" 创建上下文"(CreateContext) 结构的指针,这个结构中保存一些与创建视相关的内容。在创建主窗口时,系统会构造这样一个结构,并将它作为参数传递到与创建视有关的函数中。但现在我们不创建主窗口,因此不得不自己构造这样一个结构。实际上,该结构中我们所要使用的字段只有三个:
---- 1. 新视所属的文档模板m_pNewDocTemplate;
---- 2. 新视的类型m_pNewViewClass;
---- 3. 新视所属的文档m_pCurrentDoc;
---- 其中仅有第三项需要新建,前两项都是已知的,只要指定即可。以切换到选项视为例,修改后的代码是:
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
CCreateContext createContext;
// 定义并初始化CreateContext
// 获取新视所属的文档模板
CSingleDocTemplate* pDocTemplate =
((CViewSwitcherApp*)AfxGetApp())-> m_pRdiDocTemplate;
// 创建新文档并初始化
CDocument* pDoc = pDocTemplate->CreateNewDocument();
pDoc->OnNewDocument();
// 设置CreateContext 相关字段
createContext.m_pNewViewClass = RUNTIME_CLASS(CChkView);
createContext.m_pCurrentDoc = pDoc;
createContext.m_pNewDocTemplate = pDocTemplate;
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
&createContext);
m_wndSplitter.RecalcLayout();
}
---- 四、最后的修改
---- 为了使这个程序更符合要求,我们还要做一些与更换视无关的修改。在这个程序中我们一共定义了三种类型的文档,程序启动时一般要新建一个文档开始工作,可是它不知道要选择哪一种,就弹出一个对话框来询问。而这是我们不希望看到的。修改的方法是不让VC 选择新文档类型,而我们指定创建哪一种类型的文档,即把CViewSwitcherApp::CViewSwitcherApp() 中的语句
---- if (!ProcessShellCommand(cmdInfo)) return FALSE;
---- 更改为
---- m_pDocTemplate->OpenDocumentFile(NULL)。
让基于对话框应用程序也有启动画面
用MFC的应用向导创建一个有主框架结构的应用程序,要使它具有启动画面是很简单的(下面会体验到),而要使一个基于对话框的应用程序也有启动画面则要费些事了,不过按以下笔者的方法则也是很容易的,我主要介绍方法,对画面仅采用默认情况,读者有兴趣可自己加工。
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
仔细查看了一下WIN32的API,发现其实创建任意形状的窗口其实也是很简单的,在VC中简单步骤如下:
当我们注册并创建了一个窗口类以后,我们在WM_CREATE消息中做如下处理:
(1)创建一个区域,使用CreatePolyonRgn,该函数创建一个多边形区域,(也可以使用其他方法如CreateRectRgn创建矩形区域),该函数返回一个HRGN的句柄;
(2)调用函数SetWindowRgn,即可设置窗口的形状。
补充说明的是,我们可以制作多个区域,然后用CombineRgn方法将多个区域合并为一个区域。这样我们就可以制作出更为丰富多采的窗口了。
VC编程实现IE风格的界面
使用过IE浏览器的朋友都知道IE界面上的扁平工具条、地址栏,扁平工具栏上的按钮正常状态下为扁平态,按钮上的图像为灰色,当鼠标放在按钮上时,按钮突起(这种状态称为手柄),并且其上的图像变得鲜艳醒目,一些按钮上还有汉字说明或标有小黑三角的下拉按钮,单击时显示下拉菜单,这些技术是怎么实现的呢,本文针对这些问题介绍了如何利用VC编程来实现它们。
IE风格的实现主要在主框架类的CMainFrame::OnCreate()实现,它的主要思想如下:首先定义一个CReBar对象,用以作工具条、地址栏的容器,然后分别声明图像列表对象img用于存储工具栏上按钮的热点图像和正常状态下显示的图像。为了显示扁平工具栏,需要用CreateEx()函数创建CToolBar对象m_wndToolBar,用ModifyStyle()函数将工具栏的风格设为扁平类型,你不能用CToolBar::Create() 或 CToolBar:: SetBarStyle()设置这种新风格。CToolBar 类不支持TBSTYLE_FLAT。要解决这个问题,必须绕过CToolBar类,使用CWnd::ModifyStyle()。工具栏对象调用SetButtonInfo()设置按钮的风格为TBSTYLE_DROPDOWN,就可以将工具栏按钮设置为附带有下拉按钮。至于按钮带有中文提示,用工具栏的SetButtonText()就可以轻松实现了。下面是实现IE风格界面的部分代码和注释:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { CReBar m_wndReBar;//声明CReBar对象 CImageList img;//声明图像列表对象 CString str; if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndReBar.Create(this))//创建CReBar对象 { TRACE0("Failed to create rebar/n"); return -1; // fail to create } if (!m_wndToolBar.CreateEx(this))//创建工具条对象 { TRACE0("Failed to create toolbar/n"); return -1; // fail to create } // set up toolbar properties m_wndToolBar.GetToolBarCtrl().SetButtonWidth(50, 150); file://设置工具条上按钮的最大、最小尺寸 m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS); file://工具条可以带有下拉按钮 img.Create(IDB_HOTTOOLBAR, 22, 0, RGB(255, 0, 255)); file://向图像列表装载热点图像资源,IDB_HOTTOOLBAR为热点图像资源ID m_wndToolBar.GetToolBarCtrl().SetHotImageList(&img);//工具条装载热点图像 img.Detach(); img.Create(IDB_COLDTOOLBAR, 22, 0, RGB(255, 0, 255)); file://图象列表装载正常状态的图像资源,IDB_COLDTOOLBAR为图像资源ID m_wndToolBar.GetToolBarCtrl().SetImageList(&img);//将图像装入工具条 img.Detach(); m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT | TBSTYLE_TRANSPARENT); file://工具条为扁平风格 m_wndToolBar.SetButtons(NULL, 9);//工具条上有9个按钮 // set up each toolbar button file://以下分别对九个按钮分别设置风格和按钮汉语提示 m_wndToolBar.SetButtonInfo(0, ID_BUTTON0, TBSTYLE_BUTTON, 0); str.LoadString(IDS_ BUTTON0); m_wndToolBar.SetButtonText(0, str); m_wndToolBar.SetButtonInfo(1, ID_BUTTON1, TBSTYLE_BUTTON, 1); str.LoadString(IDS_ BUTTON1); m_wndToolBar.SetButtonText(1, str); m_wndToolBar.SetButtonInfo(2, ID_BUTTON2, TBSTYLE_BUTTON, 2); str.LoadString(IDS_ BUTTON2); m_wndToolBar.SetButtonText(2, str); m_wndToolBar.SetButtonInfo(3, ID_BUTTON3, TBSTYLE_BUTTON, 3); str.LoadString(IDS_ BUTTON3); m_wndToolBar.SetButtonText(3, str); m_wndToolBar.SetButtonInfo(4, ID_BUTTON4, TBSTYLE_BUTTON, 4); str.LoadString(IDS_ BUTTON4); m_wndToolBar.SetButtonText(4, str); m_wndToolBar.SetButtonInfo(5, ID_BUTTON5, TBSTYLE_BUTTON, 5); str.LoadString(IDS_ BUTTON5); m_wndToolBar.SetButtonText(5, str); m_wndToolBar.SetButtonInfo(6, ID_BUTTON6, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 6); str.LoadString(IDS_ BUTTON6); m_wndToolBar.SetButtonText(6, str); m_wndToolBar.SetButtonInfo(7, ID_BUTTON7, TBSTYLE_BUTTON, 7); str.LoadString(IDS_ BUTTON7); m_wndToolBar.SetButtonText(7, str); m_wndToolBar.SetButtonInfo(8,ID_BUTTON8, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN, 8); str.LoadString(IDS_ BUTTON8); m_wndToolBar.SetButtonText(8, str); file://重新调整按钮的尺寸 CRect rectToolBar; m_wndToolBar.GetItemRect(0, &rectToolBar);//得到工具条第一个按钮的尺寸 m_wndToolBar.SetSizes(rectToolBar.Size(), CSize(30,20)); file://第一个参数为按钮尺寸,第二个参数为图像尺寸 file://创建一个组合框作为地址栏 if (!m_wndAddress.Create(CBS_DROPDOWN | WS_CHILD, CRect(0, 0, 200, 120), this, AFX_IDW_TOOLBAR + 1)) { TRACE0("Failed to create combobox/n"); return -1; // fail to create } file://加入工具栏、地址栏 m_wndReBar.AddBar(&m_wndToolBar); str.LoadString(IDS_ADDRESS); m_wndReBar.AddBar(&m_wndAddress, str, NULL, RBBS_FIXEDBMP | RBBS_BREAK); file://定义REBARBANDINFO对象,对工具条和地址栏设置理想尺寸 REBARBANDINFO rbbi; rbbi.cbSize = sizeof(rbbi); rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE | RBBIM_SIZE; rbbi.cxMinChild = rectToolBar.Width(); rbbi.cyMinChild = rectToolBar.Height(); rbbi.cx = rbbi.cxIdeal = rectToolBar.Width() * 9; m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);//设置工具栏尺寸 rbbi.cxMinChild = 0; CRect rectAddress; rbbi.fMask = RBBIM_CHILDSIZE | RBBIM_IDEALSIZE; m_wndAddress.GetEditCtrl()->GetWindowRect(&rectAddress); rbbi.cyMinChild = rectAddress.Height() + 10; rbbi.cxIdeal = 200; m_wndReBar.GetReBarCtrl().SetBandInfo(2, &rbbi);//设置地址栏尺寸 m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_FIXED); if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar/n"); return -1; // fail to create } return 0; } |
以上代码在Windows2000和Visual C++环境下编译通过,程序运行正常,有兴趣的朋友可以动手亲自实验一下。
VC限制窗口大小又一法
一般说见到的方法,,都是截获WM_GETMAXMININFO消息。
俺有另一经验可实现之。
由于一般窗口大小的改变,都是用户拖动窗口边框而造成的。所以,我们可以截获主窗口消息WM_NCHITTEST在其响应函数中判断CWnd::OnNcHitTest()的返回值是否为HTRIGHT,HTLEFT,HTTOP,HTBOTTOM四个值之一,如果是,说明用户此时已点击了四个边框之一,此时我们应该返回HTCLIENT.那么,鼠标的形状就不会变成水平或垂直的双向箭头,用户就不可能依靠拖动边框来改变窗口大小了。
另外,还应补上一个小漏洞,就是还要把系统菜单中的SC_SIZE去掉。
主程序之前的版权窗口
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
DWORD lTime;
try
{
Application->Initialize();
AboutBox=new TAboutBox(AboutBox);
AboutBox->BorderStyle=bsNone;
AboutBox->OKButton->Visible=false;
AboutBox->Height=185;
AboutBox->Show();
AboutBox->Update();
lTime=GetTickCount();
Application->CreateForm(__classid(TMainForm), &MainForm);
while((GetTickCount()-lTime) / 1000 < 3);
AboutBox->Hide();
AboutBox->Free();
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
VISUAL C++6.0在MDI主框架窗口中添加位图
笔者在开发项目时想在MDI程序中添加彩色位图以美化界面,也实验了几种方法,但都有一些小问题,经多方查找资料,终于圆满的实现了这种功能,现把我的实现方法介绍给大家。
首先要清楚对于一个MDI应用程序的主框架窗口来说包含一个特殊的子窗口称为MDICLIENT窗口,应用程序的主框架类中有一个成员变量m_hWndMDIClient 指的就是MDICLIENT窗口。MDICLIENT窗口负责管理主框架窗口的客户区,对MDI客户窗口编程有一定的难度。原因是MDIFrameWnd的客户区完全被MDICLIENT窗口覆盖掉了。这样,MDI主窗口类MDIFrameWnd的背景色和光标都不起作用。同时,微软并不支持将MDICLIENT窗口作为子类,MDICLIENT窗口只能使用标准的背景色和光标。所以,对MDI客户窗口编程不能象对普通窗口那样简单地重载WM_PAINT的消息处理函数。我们可以在主框架窗口截获关于MDICLIENT窗口的重画消息,然后加入自己设计的代码。我用PreTranslateMessage(MSG* pMsg) 截获MDI客户窗口WM_PAINT消息,在这个函数中向主框架窗口发送WM_PAINT消息,在该消息的处理函数中实现彩色位图的显示。我的具体实现如下:1、向程序添加256色彩色位图资源,命名为IDB_BITMAP1;2、用ClassWizard向主框架类添加函数CMainFrame::PreTranslateMessage(MSG* pMsg);3、用ClassWizard向主框架类添加函数CMainFrame::OnPaint();现给出两个函数的实现:
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg) { // TODO: Add your specialized code here and/or call the base class if(pMsg->hwnd==m_hWndMDIClient && pMsg->message==WM_PAINT) PostMessage(WM_PAINT); return CMDIFrameWnd::PreTranslateMessage(pMsg); } void CMainFrame::OnPaint() { CDC dc, memdc; dc.m_hDC=::GetDC(this->m_hWndMDIClient); CRect rect; CBitmap bitmap; BITMAP szbitmap; bitmap.LoadBitmap(IDB_BITMAP1); bitmap.GetObject(sizeof(BITMAP),&szbitmap); CSize size(szbitmap.bmWidth,szbitmap.bmHeight); memdc.CreateCompatibleDC(&dc); CBitmap *oldbitmap=memdc.SelectObject(&bitmap); GetClientRect(&rect); StretchBlt(dc.m_hDC,0,0,rect.Width(),rect.Height(), memdc.m_hDC,0,0,size.cx,size.cy,SRCCOPY); memdc.SelectObject(oldbitmap); memdc.DeleteDC(); dc.DeleteDC(); CMDIFrameWnd::OnPaint(); } |
按上述步骤就可以实现在MDI程序中显示彩色位图了,我举的例子用的是256色位图,你也可以实现真彩色位图的显示,具体方法我就不多说了,有兴趣的朋友可以试一试。
华丽的界面
VC6.0实现逆向操作并防止界面闪烁
在系统编程中,使用VC是很好的开发工具,而对于一个成熟的系统,几乎都需要有回退与重做功能(即文档操作逆向化)以防止用户误操作或不合适的操作,从而提高系统的友好性和可操作性。在很多VC技术文章中均提到过这个问题,不过总存在着界面闪烁或不完全可逆.
本文提出一种对系统编程可实现完全可逆并防止闪屏的方法.
一、基本原理
要对文档进行回退重做功能,要做两方面的工作,一方面要保留删除的文档(在操作过程中,删除的文档资料一定能够保留),另一方面,系统必须能够记录进行文档操作的全过程及每个操作过程的参数。为了保留历史操作,所有数据非常占用内存空间,这就是一些系统只能进行有限次退步逆向操作的原因。本文提出的方法建立如下存储机制:建一个临时文件储存数据模拟堆栈,进行一次操作时将相关操作数据入栈.回退一次将相关数据弹出栈,重做一次又依据相关数据重新恢复原有数据.它的好处是在回退和重做时只入一次栈即申请一次内存。
堆栈的数据排放如图:
// Undo、Redo 数据排放示意图(m_UndoDataList)
// // ==== // |###| } // |###| } // |###| } ----->> Redo 数据 // |###| } // |###| } // |///| } // |///| } // |///| } // |///| } --->> Undo 数据(Undo数据弹出后将转换为Redo数据) // |///| } // |///| } // ===== // Undo数据栈 |
二、实现文档回退重做的引擎
建一文档逆向化堆栈引擎.主要代码为:
1.建立临时文件.(m_TempPath可以按照某种规则形成路径)
if(m_File.Open((LPCTSTR)m_TempPath, CFile::modeCreate|CFile::modeReadWrite|CFile::shareExclusive)) { m_File.SeekToBegin(); m_UndoCount = 0; file://当前可重做的步数 m_RedoCount = 0; file://当前可回退的步数 } |
2.保存回退数据模块.
// 保存一个Undo数据块(由用户提供) int CRedoUndoEngine::PushData( LPVOID pData, // 由用户提供的内存块首地址,其中含有用户定义的待保存的数据。 // (注:如果函数成功,此内存块将会被本函数释放,因此,该内存块必须是用::GlobalAlloc()函数分配的) DWORD size, // pData指向的内存块尺寸 DWORD param1, // 用户提供的对该内存块的说明参数,含义由用户定义 DWORD param2, // 用户提供的对该内存块的说明参数,含义由用户定义 int *pIndex // 如果成功,本函数将返回压入的Undo块在栈中的索引值。 如果不需要此返回值,可用NULL作为参数 ) { // 删除Redo数据 if (m_RedoCount) { while(m_RedoCount--) delete (LPISEEUNDOINFO)m_UndoDataList.RemoveTail(); m_RedoCount = 0; } // 填写Undo数据的索引信息(lpISeeUndoInfo为一个保存数据的结构体) lpISeeUndoInfo->m_index = m_UndoCount; // 索引 lpISeeUndoInfo->m_UserData1 = param1; // 用户定义的标识性数据1 lpISeeUndoInfo->m_UserData2 = param2; // 用户定义的标识性数据2 lpISeeUndoInfo->m_DataSize = size; // 用户的Undo数据块尺寸 lpISeeUndoInfo->m_FilePosition = _get_current_overwrite_pos(); // 加新的Undo数据到Undo栈的尾部 m_UndoDataList.AddTail((void*)lpISeeUndoInfo); // 将用户的Undo数据写入临时文件 m_File.Seek(lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Write((const void *)pData, size); 并使Undo块计数加1 m_UndoCount++; // 此时用户传过来的数据块已经无用,删除! ::GlobalFree(pData); return 1; } |
3.弹出重做数据模块.
// 弹出一个Redo数据块 int CIUndoEngine::RedoData( LPVOID *ppData, // 用于接收本函数返回的含有最近一个Redo数据的内存块首地址的指针 // (注:此内存块交由调用者释放,使用::GlobalFree()函数) DWORD *pSize, // ppData内存块的尺寸(in byte) ,如果不需要此数据可用NULL作为参数 DWORD *pParam1, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 DWORD *pParam2, // 返回用户对该Redo块的附加信息,如果不需要此数据可用NULL作为参数 int *pIndex // 返回本Redo块的索引,如果不需要此数据可用NULL作为参数 ) { if (!m_RedoCount) return 0; // 锁定待弹出的Redo索引信息块的地址 POSITION pos = m_UndoDataList.FindIndex(m_UndoCount); ASSERT(pos); LPISEEUNDOINFO lpISeeUndoInfo= (LPISEEUNDOINFO)m_UndoDataList.GetAt(pos); ASSERT(lpISeeUndoInfo); ASSERT(lpISeeUndoInfo->m_index == m_UndoCount); if (!(*ppData)) return -1; // 读出用户保存在临时文件中的Undo数据(也即Redo数据) m_File.Seek((LONG)lpISeeUndoInfo->m_FilePosition, CFile::begin); m_File.Read(*ppData, lpISeeUndoInfo->m_DataSize); m_UndoCount++; // 可用Undo数据块个数加1 m_RedoCount--; // 可用Redo数据块个数减1 if (pSize) *pSize = lpISeeUndoInfo->m_DataSize; if (pParam1) *pParam1= lpISeeUndoInfo->m_UserData1; if (pParam2) *pParam2= lpISeeUndoInfo->m_UserData2; if (pIndex) *pIndex = m_RedoCount;// 注:此处的索引是Redo的索引,而不是Undo的 return 1; } |
由这个文档逆向化操作引擎,可以获得当前改动的文档的数据,并根据改动的数据更新视图,而不刷新没有更改数据的视图.从而防止了闪烁的产生.
三、简单开发实例
下面以我们开发服装CAD过程中加入的回退重做功能(文档逆向化)说明之。
1.定义回退类型
#define REUNDO_MOV 0x0001 file://衣片移动回退重做 #define REUNDO_SEL 0x0002 file://衣片选择回退重做 ………. |
2.保存某个操作之前和之后的数据(以衣片移动回退重做为例)
//----------申请内存----------------------// int nByte = 4*sizeof(DWORD); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte); LPVOID pData = (LPVOID) GlobalLock(hMem); file://-----保存衣片移动前后的位置读入内存------//用移动前后衣片的某个坐标点表示 memcpy((DWORD*)pData, &m_oldPoint, 2*sizeof(DWORD)); memcpy((DWORD*)pData+2,&point, 2*sizeof(DWORD)); file://--------数据入栈---------------------------------------// m_pReUndoEngine->PushData(pData,//衣片m_pReUndoEngine文档逆向化引擎对象指针 nByte,//保存数据衣片字节数 REUNDO_MOV,//回退类型 NULL,NULL); |
3.当回退操作事件触发时.
//弹出回退值 int nByte = m_pReUndoEngine->GetPopDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->PopData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelUndo(pData,index,&dc);break; case REUNDO_MOV: MovUndo(pData);break; ………… } void CMarkView::MovUndo(LPVOID pData) 函数功能 { CPoint pt1,pt2; memcpy(&pt1,(DWORD*)pData,8); memcpy(&pt2,(DWORD*)pData+2,8); …….由pt1 和pt2可以求出位移量,从而恢复原衣片的位置. } |
4.当重做操作事件触发时
//弹出回退值 int nByte = m_pReUndoEngine->GetRedoDataSize(); HGLOBAL hMem = GlobalAlloc(GMEM_FIXED,nByte);//申请内存 LPVOID pData = (LPVOID) GlobalLock(hMem); DWORD undo_type;DWORD index; m_pReUndoEngine->RedoData(&pData,NULL,&undo_type,&index); switch(undo_type){//回退类型 case REUNDO_SEL: SelRedo(pData,index,&dc,nByte);break; case REUNDO_MOV: MovRedo(pData); break; ………… } |
函数MovRedo(pData)与MovUndo(pData)类似就不多说了.
由3,4可以看出,在回退与重做过程中,只是保存和取出操作对象已变化的过程,使编程者很容易实现高效率刷新与充分节约存储空间.
小结
在系统编程中,文档的回退与重做几乎是必不可少的,本文提出了一种思路,即对文档的各种操作分解,并把每种操作下变化的对象的数据值保存于临时文件(栈)中,在回退与重做时根据变化量很容易恢复操作之前状态或重做, 避免了有些系统(保存全部文档数据)占用大量内存空间而只能有限次文档逆向化,并且全部刷新而闪烁,破坏了界面的友好性。
怎样在一个Pane中显示多种View?
在MS Windows 中,一个窗口可以分割成若干个子窗口,每一个子窗口称作一个窗片(pane),每个窗片可以独立控制,这给界面设计提供了很大的方便。
---- 利用VC 可以很方便地实现分割窗口。分割的方法有两种:动态和静态。动态分割时可以根据用户的需要分割成数目不同的窗片,但所有窗片的属性和父窗口都是一样的;而静态分割的窗片的数目在程序中指定,运行时是固定的,但每个窗片可以有各自不同类型的视(View),因此其使用范围更为广泛。本文所讨论的问题仅限于静态分割。
---- 窗片中视的类型大多是在主窗口的创建过程中指定的。这也就意味着,一个窗片虽然可以显示任意类型的视,但是这种类型一旦确定,在程序运行过程中就难以改变。
---- 一、我要的是这样的!
---- 但是我们有时确实需要改变一个窗片所显示的视的类型,也就是说,需要让一个窗片显示多种类型的视。例如一个窗口被分割成两部分,一边是命令窗口,另一边是工作窗口,根据命令窗口中发出的不同命令,需要变换不同的工作类型,这就需要工作窗口中能够显示多种类型的视窗,那么,如何做到这一点呢?
---- 二、你可以这样做!
---- 从图1 中可以看到,本程序共有三个视类,分别是:
---- ? 命令视类CCmdView:用来控制右边窗片中不同视的显示;
---- ? 选项按钮视类CRdiView:显示在右窗片中的选项视类;
---- ? 检查按钮视类CChkView:显示在右窗片中的检查视类。
---- 这三个视类都是CFormView 的子类。
---- 下面我们来看如何在右窗片内进行两类视间的切换。实际上,由视A 切换到视B 的原理很简单,那就是:
---- 1. 从窗片中删除视A;
---- 2. 往窗片中添加视B。
---- 步骤1 的实现非常简单,仅用一条语句即可:
---- m_wndSplitter.DeleteView(0, 1);
---- 但它是必不可少的,因为你不能让一个窗片同时包含两个视。我本来希望往一个窗片中添加新的视时,VC 会自动将原来的视删簦墒撬桓伞?br>
---- 我们来看如何实现步骤2,当一个窗片是空的时候,怎样往里面添加一个视呢?其实这样的功能在程序里我们已经用过了,看下面的语句:
BOOL CMainFrame::OnCreateClient
(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
……
if (!m_wndSplitter.CreateView(0, 0,
pContext->m_pNe ewClass,
size,
pContext))
……
}
---- 是的,用的就是CSplitterWnd::CreateView(),要注意的是它共有五个参数,其中前两个用来指定分割窗口的窗片,第三个用来指定视的类型,第四个指定视的大小。最后的一个我们暂时用不上,用空值NULL 就可以了。
---- 这样我们就可以编写视切换的代码了。因为视切换要操纵m_wndSplitter,而它是主窗口的成员,因此切换过程最好设计为主窗口的成员函数。但是切换命令是CCmdView 接受的,因而可以让CCmdView 接受到视更改消息后,将消息传给主窗口,由主窗口完成视更改。具体的代码是这样的:
---- 命令视类中的消息映射:
BEGIN_MESSAGE_MAP(CCmdView, CFormView)
……
ON_BN_CLICKED(IDC_CHECK, OnSwitchToCheckView)
ON_BN_CLICKED(IDC_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
命令视类中的消息响应:
void CCmdView::OnSwitchToCheckView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_CHECK);
}
void CCmdView::OnSwitchToRadioView()
{
AfxGetApp()->m_pMainWnd->
SendMessage(WM_COMMAND, ID_RADIO);
}
主窗口中的消息映射:
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
……
ON_COMMAND(ID_CHECK, OnSwitchToCheckView)
ON_COMMAND(ID_RADIO, OnSwitchToRadioView)
……
END_MESSAGE_MAP()
主窗口中的消息响应:
void CMainFrame::OnSwitchToCheckView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CChkView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
NULL);
m_wndSplitter.RecalcLayout();
}
---- 好啦,运行一下这个程序,感觉是否不错?看来大功告成了,可是……
---- 三、还有一个问题
---- 在运行我们辛辛苦苦编出来的程序时,回头看看VC 的调试窗口,你会发现有很多行这样的话:
---- Create view without document.
---- 这是说我们创建了视,可是没有相应的文档。好在这只是警告信息,不是什么错误,如果你不需要相应的文档,就完全不用去管它。可是,VC 中一种很重要的结构就是文档- 视结构,利用这种结构,对数据操纵起来非常方便。如果需要建立与视相对应的文档,应该怎么办呢?
---- 这就涉及到VC 中文档- 视结构的知识,不过不用怕麻烦,与本文有关的就只有这么两点而已:
---- 1. 利用VC 创建的应用程序一般都会管理一些文档模板(Document Template),文档类和视类的对应关系就是在文档模板里描述的。
---- 2. 一个文档可以有多个视,创建视的时候,需要根据文档和视的对应关系,给出它所依附的文档。
---- 怎样实现上述第一点呢?
---- 首先建立相应的文档类:CRdiDoc 和CChkDoc。
---- 其次是定义相应的文档模板,这是应用类的成员变量。因为在别的类中要使用它们,我们将之定义为公共类型:
class CViewSwitcherApp : public CWinApp
{
……
public:
CSingleDocTemplate* m_pRdiDocTemplate;
CSingleDocTemplate* m_pChkDocTemplate;
……
}
然后创建这两个文档模板,并加入到模板列表中:
BOOL CViewSwitcherApp::InitInstance()
{
……
m_pRdiDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CRdiDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CRdiView));
AddDocTemplate(m_pRdiDocTemplate);
m_pChkDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CChkDoc),
RUNTIME_CLASS(CMainFrame),
RUNTIME_CLASS(CChkView));
AddDocTemplate(m_pChkDocTemplate);
……
}
---- 至于第二点,是在创建视时完成的。还记得创建视的情况么?当时有一个叫做pCreateContext 的参数,我们将之置为空,这里就要用到它了。
---- pCreateContext 是一个指向被称作" 创建上下文"(CreateContext) 结构的指针,这个结构中保存一些与创建视相关的内容。在创建主窗口时,系统会构造这样一个结构,并将它作为参数传递到与创建视有关的函数中。但现在我们不创建主窗口,因此不得不自己构造这样一个结构。实际上,该结构中我们所要使用的字段只有三个:
---- 1. 新视所属的文档模板m_pNewDocTemplate;
---- 2. 新视的类型m_pNewViewClass;
---- 3. 新视所属的文档m_pCurrentDoc;
---- 其中仅有第三项需要新建,前两项都是已知的,只要指定即可。以切换到选项视为例,修改后的代码是:
void CMainFrame::OnSwitchToRadioView()
{
m_wndSplitter.DeleteView(0, 1);
CCreateContext createContext;
// 定义并初始化CreateContext
// 获取新视所属的文档模板
CSingleDocTemplate* pDocTemplate =
((CViewSwitcherApp*)AfxGetApp())-> m_pRdiDocTemplate;
// 创建新文档并初始化
CDocument* pDoc = pDocTemplate->CreateNewDocument();
pDoc->OnNewDocument();
// 设置CreateContext 相关字段
createContext.m_pNewViewClass = RUNTIME_CLASS(CChkView);
createContext.m_pCurrentDoc = pDoc;
createContext.m_pNewDocTemplate = pDocTemplate;
m_wndSplitter.CreateView(0, 1,
RUNTIME_CLASS(CRdiView),
CSize(0, 0),
&createContext);
m_wndSplitter.RecalcLayout();
}
---- 四、最后的修改
---- 为了使这个程序更符合要求,我们还要做一些与更换视无关的修改。在这个程序中我们一共定义了三种类型的文档,程序启动时一般要新建一个文档开始工作,可是它不知道要选择哪一种,就弹出一个对话框来询问。而这是我们不希望看到的。修改的方法是不让VC 选择新文档类型,而我们指定创建哪一种类型的文档,即把CViewSwitcherApp::CViewSwitcherApp() 中的语句
---- if (!ProcessShellCommand(cmdInfo)) return FALSE;
---- 更改为
---- m_pDocTemplate->OpenDocumentFile(NULL)。
让基于对话框应用程序也有启动画面
用MFC的应用向导创建一个有主框架结构的应用程序,要使它具有启动画面是很简单的(下面会体验到),而要使一个基于对话框的应用程序也有启动画面则要费些事了,不过按以下笔者的方法则也是很容易的,我主要介绍方法,对画面仅采用默认情况,读者有兴趣可自己加工。
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
一、给一文档/视图应用程序做启动画面
(一) 建立一单文档/视图应用程序Hs
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Hs作为项目名并单击OK。在第一步中选中Single Document单选按钮,其它接受所有默认选项。
(二) 添加启动画面
当AppWizard完成,而且Visual C++打开项目的时候,从Project菜单中选择Add To Project,并单击位于次级菜单上的Comonents and Controls…,选择Splash screen组件,如图1(略)所示,单击Insert。接受所有的默认设置。
以上几步就建立起了一个有主框架结构的应用程序,并使它具有了启动画面。这是我们要做的准备工作已经完成。
二、给基于对话框应用程序做启动画面
(一)建立基于对话框的应用程序Spla
从File菜单选择New对话,在Projects选项卡中选择AppWizard(exe)图标。键入Spla 作为项目名并单击OK。在第一步中选中Dialog Based单选按钮,其它接受所有默认选项。
(二)做启动画面
这里做启动画面如果仍采用前述用Gallery来插入是不行的,因为基于对话框的应用程序没有主框架。不过我们可以把上面建立起的启动画面文件移植过来,然后,对程序进行少许编程修改就行。请按照下面的步骤来做:
1、将Splash.cpp和Splash.h两个文件从Hs工程中拷贝到你的工程中。添加如下代码到CSplaApp的InitInstance()函数中。
#include "Splash.h"//头文件请放在开始位置 BOOL CSplaApp::InitInstance() { CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); CSplashWnd::EnableSplashScreen (cmdInfo.m_bShowSplash); ... }
2、接下来,使用ClassWizard来添加OnCreate函数到你的对话框类中,并且添加如下代码: #include "Splash.h"//头文件请放在开始位置
int CSplaDlg::OnCreate (LPCREATESTRUCT lpCreateStruct) { … CSplashWnd::ShowSplashScreen(this); … }
3、将Splash16.bmp文件从Hs工程中拷贝到你的工程中 蚩猈orkspace的Resouce项,将Splash16.bmp插入。打开Properties将IDB_BITMAP1改为IDB_SPLASH,这个ID值只要和程序中一致起来就行,现在这样改最简便。
现在可以编译运行程序了,程序运行时出现如图2(略)的启动画面。这是默认的画面,你可以打开图形编辑器自己加工。如果你要改变启动画面的停留时间,就修改SetTime中的第二个参数,这里是750毫秒。
int CSplashWnd::OnCreate (LPCREATESTRUCT lpCreateStruct) { … // Set a timer to destroy the splash screen. SetTimer(1, 750, NULL); return 0; }
最后
以上就是隐形毛衣为你收集整理的Visual C++界面编程的全部内容,希望文章能够帮你解决Visual C++界面编程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复