MFC编写界面的好处是可以做到深入控制,又在某种程度上摆脱了API函数的麻烦,并且符合面向对象的程序方法,便于我们扩大程序规模。
这个例子是用户的维护和监控。首先用OLE DB读出用户数据库。操作人员可以通过程序的一个界面维护用户信息,还可以通过一个用户监控界面和一些另外的信息知道用户目前在做什么。
用户信息界面是个ListView,左键点击某个item出现一个显示这个用户选择信息的品种,而用右键点击则显示一个可以登记用户信息的对话框。它的中心区域是可以滚动的,用来显示用户可选择的产品种类。而一旦用户选择了某类信息,则下面的两个dtpicker控件也被激活。用来输入用户订阅的这类稿件起始日期。而对话框最下面的起始日期的确认,会改变滚动区域内所有被激活的起始日期。而翻转按钮可以翻转所有可滚动区域里的控件的状态。最后,有户的所有信息存入另一张表中。
用户监控界面是一个分割窗口,1*3的格式,由两个ListView和一个EditView组成,显示用户的动作,并可以进行控制。
这些界面的特殊之处在于:
1.一般而言,程序应当是SDI的,这样符合以文档而不是以功能为中心的思想。但MFC为一个文档之提供了一个frame和一个view。但有时我们想让一个文档对应几个view,象电视频道一样的可以选择,比如本例。
2.有时我们又想在对话框里显示许多控件,比如说几十个到100多个。但又不想分页显示,因为它们是一个整体。这时,我们便希望对话框的某一区域有滚动功能,就象本例登记用户信息的对话框一样,白色区域中一共有38*3=114个控件。
3.我们还想然让控件符合我们的要求,比如有ToolTip,改变前景背景,响应一些特殊事件。。。比如本例对话框里的按钮所示。但标准控件不提供这些。
这些都要自己想办法。深一步的了解MFC,找到些技巧。如下:
1.对于一个文档要添加几个一般的窗口,可以这样做:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,
CCreateContext* pContext)
{
//。。。
pContext->m_pNewViewClass=RUNTIME_CLASS(CNewView);
m_pNewView=(CNewView*)CreateView(pContext,AFX_IDW_PANE_FIRST+1);
//。。。
}
需要显示时,就使用ShowWindow()。并处理好OnSize。
但对于要添加一些分割窗口,还想让每个窗口对应不同的资源,以上方法就不行了。用SDI还是可以实现的。大体上是:将资源,文档,框架,视的RUNTIME_CLASS存入CDocTemplate,再用AddDocTemplate方法存入App对象中。注意,要为每个View生成一个CDocTemplate对象,还要记住哪个视是属于哪个CSplitterWnd的,以及它们在CSplitterWnd里的位置。先加载不同的资源,再在CFrameWnd::OnCreateClient()中,用CSplitterWnd和CFrameWnd的CreateView方法创建View,其中的pContext参数保存了CDocTemplate对象的信息。还要用到其未公开的m_pViewClass变量,(详情请见MSDN C/C++ Q&A,以及Dynamic Runtime Objects: Building Applications Your Users Can Modify at Runtime),并保存对View的指针,作为将来显示之用。。。当然就要复杂许多。
一种简单的方法是用MDI,其原理是所有的资源,文档,视都可以看成是与子框架相关的。将它们存入一个CMultiDocTemplate,再用AddDocTemplate()放入App。这里有两个问题:⑴这样一来,有几个界面就有几个文档。这时,我们需要将用户信息在App里存取,并以指针的形式传给各个文档,以保证内容的一致。⑵MFC并不知道你究竟想显示哪个文档模版,一执行CWinApp::OnFileNew()时会产生一个对话框,让你选择。解决办法是创建自己的CWinApp::OnFileNew()函数,在里面直接显示界面。
2.虽然windows似乎没有提供可以存放控件的控件。有一个理论上可行的办法:在一个区域的外围放上Scroll Bar。然后根据用户对这个Scroll Bar的点击,重画那个区域的控件,由于它们的位置发生的改变,使这个区域看起来在“滚动”。不用说,这个方法很复杂。
另一个办法:把一个ScrollView嵌入我们希望放置控件的区域,再将控件放在这个View里。只要你设置好这个View的大小,它就可以提供正确的滚动功能。这是对我们要到程序运行时才知道要显示什么控件以及那个区域的大小而言。在本例中这些要显示的种类是不断调整的,是从数据库的一个表中读出的。只有将种类信息从表中读出时,我们才知道这次可供用户选择的信息是哪几种。所以只能用ScrollView。如果我们在程序设计时就对要显示控件的种类和数量知道清楚,当然就是确定了可滚动区域的大小,则可以嵌入FormView。
可以用FormView的场合要简单些,因为我们在程序设计时就知道了一切。现在我们就举一个需要用ScrollView的例子,也就是控件的种类和布局知道,但数量不知道,需要在程序运行时确定。
这个界面的要点如下:
⑴那个ScrollView没有Frame,也没有Document。MFC有时会有一个警告,当你用鼠标点击时在Debug版本还会有一个断言出错。如下:
int CView::OnMouseActivate(CWnd* pDesktopWnd, UINT nHitTest, UINT message)
{
//。。。
CFrameWnd* pParentFrame = GetParentFrame();
if (pParentFrame != NULL)
{
// eat it if this will cause activation
ASSERT(pParentFrame == pDesktopWnd || pDesktopWnd->IsChild(pParentFrame));
//。。。
}
pDesktopWnd指向这个ScrollView嵌入的对话框,所以这个断言肯定会出错。解决办法只有象MFC所说的:eat it。也就是自己响应WM_MOUSEACTIVATE消息,去掉这个断言。
⑵还要相应的某些类的成员函数从protected改成public,才可以被其他对象调用。
⑶本例中控件的值和状态是根据某个数组确定的,控件的个数=数组元素个数*3。所以这些控件只能用CWnd::Create()方法生成。这会产生一个问题:控件之间如何通信?一个办法是Dialog和ScrollView各设置一个TIMER和一些指示用的变量,当指示变量发生改变时,那么使其下的控件发生相应改变。显然,这样有些复杂,且不合规范。另一个办法是用ON_CONTROL_RANGE宏,这个相对符合规范,但还是有些复杂,因为所有的消息处理都在Dialog和ScrollView中进行。如果控件种类一多,会相当复杂。我采用的方法是:新控件是从继承一个标准控件得来,新控件自己处理各种消息,并对其父窗口发送一个用户自定义消息来通知其他控件。这样每个控件处理自己的消息,大大简化了程序,很好的实现了封装。符合面向对象的要求,应当可以满足我们的要求。本例子使用的按钮控件是从Cstatic类派生得来的。
3.用派生类来实现自己的控件是一个广泛应用的方法。比如本例的按钮控件的前景背景和WM_LBUTTONUP事件可以按自己的需要进行处理,还加上了一个ToolTip,用户还可以根据自己的需要,进行别的设计。本例中点击左键产生的对话框上的按钮也有ToolTip。它是通过响应Dialog的ON_NOTIFY_EX_RANGE和ON_NOTIFY_EX事件来设置和显示ToolTip的。我认为这种方法稍显逊色,因为这需要知道控件的ID才能确定要显示的内容,稍显复杂。而且暴露细节,也不符合封装的要求。
下面是相应程序:
1.多视的实现办法
// Monitor.cpp : Defines the class behaviors for the application.
//。。。
BOOL CMonitorApp::InitInstance()
{
//。。。
//生成模版,并保存
m_pMonitorDocTemplate = new CMultiDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMonitorDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CLeftView));
AddDocTemplate(m_pMonitorDocTemplate);
m_pListDocTemplate = new CMultiDocTemplate(
IDR_MAINFRAME, //可以选择不同的资源
RUNTIME_CLASS(CMonitorDoc),
RUNTIME_CLASS(CUserListWnd), // custom MDI child frame
RUNTIME_CLASS(CUserListView));
AddDocTemplate(m_pListDocTemplate);
//。。。
}
void CMonitorApp::OnFileNew()
{
// TODO: Add your command handler code here
//生成界面,希望一开始显示的界面应当最后生成
m_pMonitorDoc=(CMonitorDoc*)m_pMonitorDocTemplate->OpenDocumentFile(NULL);
m_pListDoc=(CMonitorDoc*)m_pListDocTemplate->OpenDocumentFile(NULL);
return;
}
//那里面指向不同的文档对象的指针是要保留的,因为我们要根据它找到View,并根据
//View找到子框架。然后通过操纵子框架来显示不同的界面。如下:
void CMonitorApp::OnSwitchMonitor()
{
// TODO: Add your command handler code here
//隐藏列表窗口
CWnd *pP,*pV;
POSITION ps;
ps=m_pListDoc->GetFirstViewPosition();
pV=(CWnd*)m_pListDoc->GetNextView(ps);
pP=pV->GetParent();
pP->ShowWindow(SW_HIDE);
//显示监控窗口
ps=m_pMonitorDoc->GetFirstViewPosition();
pV=(CWnd*)m_pMonitorDoc->GetNextView(ps);
pP=pV->GetParent();
pP=pP->GetParent();
pP->BringWindowToTop();
}
//。。。
2滚动区域的实现办法
// InfoEdit.cpp : implementation file,from CSrollView
void CInfoEdit::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal;
// TODO: calculate the total size of this view
//根据自己的规则确定区域的大小
//。。。
SetScrollSizes(MM_TEXT, sizeTotal);
for(int i=0;i<(iTypeNumber/3)+1;i++)
{
//生成并显示控件
CNewEditButton *pNewButton = new CNewEditButton;
pNewButton->Create( (LPCTSTR)strOneType,WS_VISIBLE|WS_CHILD|WS_TABSTOP
|SS_NOTIFY,myRect,this,IDC_EDIT_BUTTONES+p);
//。。。
}
}
//去掉那些会出错的断言
int CInfoEdit::OnMouseActivate(CWnd* pDesktopWnd,UINT nHitTest,UINT message)
{
int nResult = CWnd::OnMouseActivate(pDesktopWnd, nHitTest, message);
SetActiveWindow();
return nResult;
}
//相应用户自定义消息,并传送别的消息给别的控件
BOOL CInfoEdit::OnInvertControl(WPARAM NoUse,LPARAM ForFuture)
{
两个参数是必须的,否则release版本会出错
for(int i=0;i<iTypeNumber;i++)
{
CNewEditButton *p=(CNewEditButton*)TypeButtonArray.GetAt(i);
::SendNotifyMessage(p->m_hWnd,WM_LBUTTONUP,0,0);
}
return TRUE;
}
// UserEdit.cpp : implementation file,from CDialog
//生成并显示滚动区域
BOOL CUserEdit::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
//。。。
m_pInfoEdit=new CInfoEdit;
m_pInfoEdit->SetData(&UserValueList,TypeList);
m_pInfoEdit->Create(NULL,NULL,WS_CHILD|WS_VISIBLE|WS_BORDER,CRect(13,100,752,405)
,this,IDD_INFOEDIT,NULL);
m_pInfoEdit->OnInitialUpdate();
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
3.请看派生的两个按钮
CNewEditButton,CTypeButton。并看CuserChoice,观察ToolTip实现方式的不同。
编程工具:VC++6。0,运行环境PWIN98。
|