初学界面设计,把用到的技巧记录下来。。。
1、 怎样在程序开始的时候让它最大化?自定义窗口大小与标题?
在App类里的C…App::InitInstance()中把m_pMainWnd->ShowWindow(SW_SHOW)改成m_pMainWnd->ShowWindow(SW_MAXIMIZE);
在MainFrame的PreCreateWindow中修改为:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
cs.cx=1024;//窗口宽度
cs.cy=768;//窗口高度,这两处自己添加
cs.style &= ~FWS_ADDTOTITLE;
cs.lpszName = "TPSDemo";//这两句改变窗口标题
return TRUE;
}
2、使用CSplitterWnd类分割窗体,拆分窗体
第一步:打开Visual C++ 6.0,文件—>新建,选择MFC AppWizard(exe),输入工程名test,点确定,然后选择单文档点下一步,第二,第三,第四,第五都点下一步,第六步的时候让我选择每个类的基类,我们把CTestView类的基类改为CFormView,点完成。
第二步:新建一个类,类的类型选择Generic Class,填写名称为CMySplitter,基类我们填CSplitterWnd,点击确定。这个时候我们有可能会遇到一个问题(之所以我说有可能是因为这种情况有时不会发生),那就是当我们点击确定之后有时会弹出一个对话框,说找不到CSplitterWnd.h文件,这个时候我们点确定即可。因为,MFC的Classwizard不支持继承CSplitter类。但是,我们这里选择的是Generic Class,而不是MFC,所以可以继承,忽略掉那个提醒即可,不明白原因不要紧,只要记得如果弹出对话框,只要点确定即可。
第三步:在CMainFrame类的头文件最顶部加上#include "MySplitter.h",再在CMainFrame类中声明两个public的CMySplitter型的成员变量,如下:
public:
CMySplitter wndSplitter_horizontal;//水平分隔条
CMySplitter wndSplitter_erect;//竖直分隔条
第四步:打开资源视图,新建一个对话框,填写ID为IDD_MONITORTOP_FORM,新建一个类,类的类型选择MFC Class,填写名称为CMonitorTopView,基类我们填CFormView,对话框ID我们填写IDD_MONITORTOP_FORM,这样我们就新建了一个以CFormView为基类的视图类CMonitorTopView。类似的,我们再新建另外一个对话框,ID为IDD_MONITORRIGHT_FORM,以此再建立一个以CFormView为基类的视图类CMonitorRightView。
第五步:重载CMainFrame类的OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)方法。加入代码,具体如下:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
// TODO: Add your specialized code here and/or call the base class
//创建一个静态分栏窗口,分为2行1列
if(wndSplitter_horizontal.CreateStatic(this,2,1)==NULL)
return FALSE;
//将CMonitorTopView视图连接到0行0列窗格上
wndSplitter_horizontal.CreateView(0,0,RUNTIME_CLASS(CMonitorTopView),CSize(50,50),pContext);
//将第1行0列再分开1行2列
if(wndSplitter_erect.CreateStatic(&wndSplitter_horizontal,1,2,WS_CHILD|WS_VISIBLE, wndSplitter_horizontal.IdFromRowCol(1, 0))==NULL)
return FALSE;
//将CMonitorView类连接到第二个分栏对象的0行0列
wndSplitter_erect.CreateView(0,0,RUNTIME_CLASS(CMonitorView),CSize(220,220),pContext);
//将CMonitorRightView类连接到第二个分栏对象的0行1列
wndSplitter_erect.CreateView(0,1,RUNTIME_CLASS(CMonitorRightView),CSize(220,220),pContext);
return TRUE;
//return CFrameWnd::OnCreateClient(lpcs, pContext);
}
第六步:其实,截止到第五步完,我们运行程序就可以出现我图例所实现的样子了,但是现在我们来补充一下改变分隔条的特征,好了,现在你应该知道为什么我们要自己定义一个类,继承自CSplitterWnd了吧,因为我们要修改它的特征,例如改变分隔条的宽度,改变分隔条的颜色,固定分隔条(不让别人拖动)等等。
1、改变分隔条的宽度:在CMySplitter类的构造函数中加入代码,具体如下:
CMySplitter::CMySplitter()
{
m_cxSplitter = 10; //must >=4 ,拖动时横向拖动条的宽度
m_cySplitter = 25; //must >=4 ,拖动时竖向拖动条的宽度
m_cxBorderShare = 0; //按下鼠标时横向拖动条的偏移量
m_cyBorderShare = 0; //按下鼠标时竖向拖动条的偏移量
m_cxSplitterGap= 5; //静止时横向拖动条的宽度
m_cySplitterGap= 5; //静止时横向拖动条的宽度
}
2、固定分隔条(不让别人拖动):锁定切分条的最简单的方法莫过于不让CSplitterWnd类来处理WM_LBUTTONDOWN,WM_MOUSEMOVE,WM_SETCURSOR消息,而是将这些消息交给CWnd窗口进行处理,从而屏蔽掉这些消息。现在我们打开CMySplitter类的头文件,我们手动的添加一些东西(为什么要手动呢?因为,我们选择的是Generic Class),好了,我们来看一下都加入了哪些代码:
class CMySplitter : public CSplitterWnd
{
public:
CMySplitter();
virtual ~CMySplitter();
//以固定分隔条加入的代码
DECLARE_DYNCREATE(CMySplitter)
protected:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
//以上是固定分隔条加入的代码
};
我们仔细的看一下在DECLARE_DYNCREATE(CMySplitter)和DECLARE_MESSAGE_MAP()之间是消息处理的声明,告诉计算机,我们重载了这三个消息处理函数。现在我们在打开CMySplitter类的.cpp文件,在构造函数CMySplitter::CMySplitter()上面加入如下代码:
IMPLEMENT_DYNCREATE(CMySplitter, CSplitterWnd)
BEGIN_MESSAGE_MAP(CMySplitter, CSplitterWnd)
ON_WM_LBUTTONDOWN()
ON_WM_SETCURSOR()
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
在程序结尾添加下面的代码:
void CMySplitter::OnLButtonDown(UINT nFlags, CPoint point)
{
// 直接返回,不处理
return;
}
BOOL CMySplitter::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
// 当光标进入分割窗口时,不允许改变样子,不处理
return FALSE;
}
void CMySplitter::OnMouseMove(UINT nFlags, CPoint point)
{
//将CSplitter类的处理改为由CWnd处理
//CSplitterWnd::OnMouseMove(nFlags, point);
CWnd::OnMouseMove(nFlags, point);
}
3、给对话框加背景图片
(可用WM_PAINT)把下面这段代码加进OnPaint()
CPaintDC dc(this);
CBitmap bitmap;
//这个IDB_BITMAP1要自己添加
bitmap.LoadBitmap(IDB_BITMAP1);
CBrush brush;
brush.CreatePatternBrush(&bitmap);
CBrush* pOldBrush = dc.SelectObject(&brush);
//这些参数可以调整图片添加位置和大小
dc.Rectangle(0,0,200,200);
dc.SelectObject(pOldBrush);
4、改变对话框背景颜色,设置CFormView背景色
在需要改变的View中增加变量 CBrush m_Brush;
在构造函数中创建颜色画刷 m_Brush.CreateSolidBrush(RGB(255,0,0)); //在此创建为红色画刷,(255,255,255)为白色
给View添加WM_CTLCOLOR消息
注释掉return hdr;
返回自己的画刷 return m_Brush;
此时FormView的背景被更改
5、添加启动画面
利用组件库中的Splash Screen组件实现
(1)用Photoshop等制作启动画面图像,保存为bmp格式。
(2)用Appwizard建一个基于单文档的工程Splash。
(3)在资源中插入位图资源
打开VC++的资源编辑器,用鼠标右键单击Resources文件夹,选择Import命令,插入所制作的位图。如果位图超过256色,VC会弹出一个对话框,提示位图已经插入但不能在位图编辑器中显示,确定即可。将位图ID改为IDB_SPLASH。
(4)添加Splash Screen控件
①选择菜单“project”/“Add To Project”/“Conponents and Controls”打开对话框,在列表框中双击“Visual C++ Conponents”选项,选择“Splash Screen”控件,然后单击“Insert”。
②确认或修改类名和位图资源ID,单击OK确认。
③编译、连接,漂亮的启动画面就显示出来了。
(5)如果需要改变启动画面的停留时间,就修改SetTimer()函数的第二个参数,默认是750 毫秒。该函数所在位置:
int CSplashWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...
// Set a timer to destroy the splash screen.
SetTimer(1, 750, NULL); //修改第二个参数以调整画面停留时间
return 0;
}
6、自定义向导的建立
1、生成一个MFC APPWIZARD 新工程,命名为CustomWizard,在Step1 中选择基于Dialog Based样式。在自动生成的Dialog 资源中加入一个按钮IDC_BENGINWIZ 用来启动向导。新建一个对话框 资源,命名为IDC_WIZARD,用来显示自定义向导界面,如图
依次创建向导的每页 的对话框资源,命名为IDD_STEP1,IDD_STEP2,IDD_STEP3,
(图4)
2. 生成所需要的类
为了方便叙述,表1将所用的类进行了归纳
(表1)
类名 | 基类 | 说明 |
CWizard | CDialog | 向导的框架 |
CStep1 | CDialog | 向导的第一步 |
CStep2 | CDialog | 向导的第二步 |
CStep3 | CDialog | 向导的第三步 |
CCustomWizardDlg | CDialog | 启动向导 |
3. 在CWizard添加要使用的数据结构
为了方便描述,表2列出了使用到的成员变量
(表2)
成员变量 | 类型 | 说明 |
rectPage | CRect | 每页显示的范围 |
nPageCount | UINT | 页的总数 |
nCurrentPage | UINT | 正在显示的页 |
nPageLink | PAGELINK* | 用来链接所有的页 |
typedef struct PAGELINK{ | nNum为页的编号 |
4. CWizard所使用到的函数 添加一个新页到Wizard框架,入口参数为要添加的对话框指针和ID
void CWizard::AddPage(CDialog* pDialog, UINT nID)
{
struct PAGELINK* pTemp = pPageLink;
//插入新生成的结点
struct PAGELINK* pNewPage = new PAGELINK;
pNewPage->pDialog = pDialog;
pNewPage->pDialog->Create(nID,this); // 以无模式创建窗口
ASSERT(::IsWindow(pNewPage->pDialog->m_hWnd));
// 检查每页的样式
DWORD dwStyle = pNewPage->pDialog->GetStyle();
ASSERT((dwStyle & WS_CHILD) != 0); // 子窗口
ASSERT((dwStyle & WS_BORDER) == 0); // 无边界
// 显示
pNewPage->pDialog->ShowWindow(SW_HIDE); //先隐藏,需要时再显示
pNewPage->pDialog->MoveWindow(rectPage);
//移动对话框到制定位置,rectPage已经初始化了
pNewPage->Next=NULL;
pNewPage->nNum=++nPageCount; //计数器加1
if (pTemp) //插入到链表
{ //如果不是空链表
while (pTemp->Next) pTemp=pTemp->Next; // 移动链表末尾
pTemp->Next=pNewPage;
}
else // 空链表
pPageLink=pNewPage; //若是第一个节点
}
显示的页,入口参数为要显示的某特定页的编码
void CWizard::ShowPage(UINT nPos)
{
struct PAGELINK* pTemp=pPageLink;
while(pTemp)
{
if(pTemp->nNum==nPos)
{
pTemp->pDialog->ShowWindow(SW_SHOW);
}
else
//不显示
pTemp->pDialog->ShowWindow(SW_HIDE);
pTemp=pTemp->Next;
}
if (nPos>=nPageCount) //最后一页
{
nCurrentPage=nPageCount;
SetWizButton(2);
return;
}
if (nPos<=1) //首页
{
nCurrentPage=1;
SetWizButton(0);
return;
}
//如果是中间步
SetWizButton(1);
}
为了与显示统一,需要相应的设置按钮
void CWizard::SetWizButton(UINT uFlag)
{
GetDlgItem(IDC_CANCEL)->EnableWindow(TRUE);
GetDlgItem(IDC_PREV)->EnableWindow(TRUE);
GetDlgItem(IDC_NEXT)->EnableWindow(TRUE);
GetDlgItem(IDC_FINISH)->EnableWindow(TRUE);
switch(uFlag)
{
case 0: //第一步
GetDlgItem(IDC_PREV)->EnableWindow(FALSE);
break;
case 1: //中间步
break;
case 2: //最后一步
GetDlgItem(IDC_NEXT)->EnableWindow(FALSE);
break;
}
}
点击“上一步”、“下一步”、“完成”、“取消”代码
void CWizard::OnPrev()
{
// TODO: Add your control notification handler code here
ShowPage(--nCurrentPage);
}
void CWizard::OnNext()
{
// TODO: Add your control notification handler code here
ShowPage(++nCurrentPage);
}
void CWizard::OnFinish()
{
// TODO: Add your control notification handler code here
AfxMessageBox("采用默认值完成向导");
CDialog::OnOK();
}
void CWizard::OnCancel()
{
// TODO: Add your control notification handler code here
if (AfxMessageBox(IDS_QUIT,MB_OKCANCEL|MB_ICONQUESTION)==IDCANCEL)
return;
CDialog::OnCancel();
}
5. 辅助代码,如初始化等
BOOL CWizard::OnInitDialog()
{
CDialog::OnInitDialog();
//获得每页显示的范围
CRect Rect1;
GetWindowRect(&Rect1); // 获得主窗口的位置
int nCaption = ::GetSystemMetrics(SM_CYCAPTION); // 系统Title高度
int nXEdge = ::GetSystemMetrics(SM_CXEDGE);
int nYEdge = ::GetSystemMetrics(SM_CYEDGE);
CRect Rect2;
GetDlgItem(IDC_POS)->GetWindowRect(&Rect2); // 获得框架的位置
Rect1.top=Rect1.top+nCaption+nYEdge; // 相对坐标
Rect1.left=Rect1.left+2*nXEdge;
//计算机位置
rectPage.top=Rect2.top-Rect1.top;
rectPage.left=Rect2.left-Rect1.left;
rectPage.bottom=Rect2.bottom-Rect1.top;
rectPage.right=Rect2.right-Rect1.left;
//页示的添加要显
CStep1* pStep1 = new CStep1;
CStep2* pStep2 = new CStep2;
CStep3* pStep3 = new CStep3;
AddPage(pStep1, IDD_STEP1);
AddPage(pStep2, IDD_STEP2);
AddPage(pStep3, IDD_STEP3);
//显示第一页
ShowPage(1);
return TRUE;// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
因为是无模式窗体,所以要自己销毁窗体
void CWizard::OnDestroy()
{
CDialog::OnDestroy();
// TODO: Add your message handler code here
//每页依次消除
struct PAGELINK* pTemp=pPageLink;
while(pTemp)
{
struct PAGELINK* pNextTemp = pTemp->Next;
pTemp->pDialog->DestroyWindow();
delete pTemp->pDialog;
delete pTemp;
pTemp = pNextTemp;
}
}
6. 启动向导需要在IDC_BEGINWIZ 按钮的Click事件中加入下列代码:
CWizard MyWiz; //显示向导
MyWiz.DoModal();
7、类XP系统按钮的实现
CXPButton类的使用:往对话框中添加一个按钮控件,假设它的ID值为IDC_BUTTON1。进入类向导(Class Wizard)的Member Variables属性页,为IDC_BUTTON1添加一个变量m_btnNormal。确定退出后再进行编译,就可以看到重新定义过XP风格按钮了。
如果你是直接把CXPButton的源文件引入自己的工程中的,那么在上图的Variable type中是看不到CXPButton选项的。但是可以通过以下方法加入:
1. 首先保存工程后退出。
2. 在工程的目录下找到一个后缀名为.clw的文件,将其删除。但是为了以防万一还是建议你实现备份一下。
3. 重新打开工程,进入类向导,此时会看到一下一个弹出对话框,我们选择“是(Yes)”。
4. 再选择“Add All”,这样我们就可以在类向导中使用CXPButton的变量类型了。
8、使用dll
采用隐式链接法,将*.lib文件放置在工程目录下,*.dll文件放置在输出目录下,在projectàSettingsàLink中的Object/library modules:下添加所要用到的*.lib名称,如多个用逗号隔开。
9、error C2660: 'new' : function does not take 3 parameters改错
错误来自于函数的声明与具体调用不匹配,当代码是复制得来时常出现这样的错误,可将*.cpp和*.h中关于此函数的声明同时改为源代码中的声明。
10、error C2248: 'CMainFrame::CMainFrame' : cannot access protected member declared in class 'CMainFrame'改错
public,private,protected修饰访问符的作用,错误来自于protected、private成员不能在该类域外直接引用,对象不能访问,而public 可以访问,将出错的对象改为public即可
11、error C2065: 'IDD_XXXX_DIALOG' : undeclared identifier”的问题
编译vc项目时提示类似“error C2065: 'IDD_XXXX_DIALOG' : undeclared identifier”的问题,可能有两种原因:
第一种是确实不存在该对话框的ID,这种情况下就要把对应的对话框的ID改为正确的;
第二种是存在该对话框,但是找不到,这种情况下可以在类的头文件中加入“#include "resource.h" ”语句解决;
12、退出SDI
一个基于CFormView的SDI程序,在View上加一个按钮Button1,并在OnButton1中加入下面三句话的任何一句,均可正常退出,没有任何问题:
1、AfxGetApp()-> m_pMainWnd-> PostMessage(WM_CLOSE); //SendMessage
2、PostQuitMessage(1);
3、ExitProcess(1);
第二种方法当多线程时可能不灵,其他两种方法绝对没问题——除非程序有什么特殊处理。
这里我选择了第三种,可以实现,如果是对话框可用CDialog::OnCancel();来退出,OnOK();也是一种退出的选择,二者分别可查看MSDN
13、错误LNK2001
<File>.obj : error LNK2001: unresolved external symbol "public: void __thiscall <Class1>::<Function1>(<Type>)"
This a generic form of a LNK2001 error where <File>.obj can be any object file in your project and <Class1>::<Function1>(<Type>) can be any function in any class. Substitute the specific <File>, <Class>, <Function>, and <Type> in your message into the instructions below to diagnose and correct the problem.
An LNK2001 error means that the link editor is looking for a compiled function and can't find it. The call to the "missing function" is somewhere in <File>.cpp. Unfortunately, double-clicking on the error message won't take you to the point in <File.cpp> where the function is called but you can search for it with Find or Find In Files. The function the link editor can't find is a member of <Class>, its name is <Function1>, and its return type is <Type>.
There are two common reasons for a LNK2001 error:
1. The call in <File>.cpp doesn't match the function prototype in <Class>.h and/or the implementation in <Class>.cpp. The mismatch may be in the function name, return type, or number and/or type of parameters. Correction strategies include:
o Check that the function name is spelled the same (case sensitive) in all three files (File.cpp, Class.h, and Class.cpp).
o Check that the function is actually declared and defined within <Class> - perhaps you defined it as a member of a different class or perhaps you tried to call the function (in <File>.cpp) using an object or object pointer of a different class.
o Check that the number and type of parameters in the function implementation (in <Class>.cpp) matches the number and type of parameters declared in the function declaration in <Class>.h.
o Check that the number and type of parameters in the function call (in <File>.cpp) matches the number and type of parameters declared in the function header in <Class>.cpp.
2. The function was never declared or was declared but never defined. To see if either is the case go to the ClassView window of the Workspace view. Click the + next to <Class> and find <Function> in the list of member functions.
o If <Function> is NOT in the list then it was never declared or defined - add a declaration to the class declaraion in <Class>.h and implement the function in <Class>.cpp.
14、用DECLARE_DYNCREATE(CMyClass) ,编译生成dll时连接出错
解决:需要在相应的实现文件中添加IMPLEMENT_DYNCREATE(CMyClass,基类)
15、待续。。。