菜单、标签页、工具栏、滚动条、热键、框架重调大小,以及 CFormView 对话框中派生自 CListCtrl 的类的 ON_COMMAND_RANGE 处理程序





5.00/5 (4投票s)
演示如何在 MFC 表格窗体中使用通用的 MDI 界面。
引言
通用的 MFC 界面主题既不新也不难。然而,不知何故,它需要大量的关注,首先,没有它,就不可能在编程中走得更远,其次,在实际应用程序中,需要一些调整。通常演示的例子都是抽象编程的,而且通常来说,它们与实际工作应用程序脱节。我们将使我们的演示“具体化”,以获得一个实际的应用。
我们将继续开发我们 之前的项目。我们将为具有继承自 CListCtrl
的类的管理对话框窗体添加一些标准的界面元素。这些元素中的每一个都不会引起任何特殊的困难;然而,存在一些细微之处。结果,我们将得到图 1 中所示的示例。
1. 滚动条
当窗体较小时,窗体上会出现水平和垂直滚动条。这是默认属性,如果我们不需要它,就强制删除它们。为此,我们使用以下代码
/////////////////////////////////////////////////////////////////////////////
// OnInitialUpdate
/////////////////////////////////////////////////////////////////////////////
void CMainView::OnInitialUpdate() {
. . .
//*** Don’t set the Size to {0} else the debug version of the program
// will be wrong!
SIZE Size = {1, 1};
//*** Turns off scroll bars of the form
SetScaleToFitSize(Size);
} // OnInitialUpdate
然而,垂直滚动条是另一个问题。它仅在列表中有很多记录时才会出现。如果没有,则垂直滚动条不存在。因此,我们将强制始终显示垂直滚动条,使用此代码
/////////////////////////////////////////////////////////////////////////////
// OnCreate
/////////////////////////////////////////////////////////////////////////////
int CMainView::OnCreate(LPCREATESTRUCT pCS){
. . .
//*** Shows the vertical scroll bar always
pTable->ShowScrollBar(SB_VERT);
. . .
} // OnCreate
以及处理程序中的这个类似代码
/////////////////////////////////////////////////////////////////////////////
// OnSize
/////////////////////////////////////////////////////////////////////////////
void CChildFrame::OnSize(UINT nType, int cx, int Ñу){
. . .
//*** Shows the vertical scroll bar always
m_pTable->ShowScrollBar(SB_VERT);
} // OnSize
2. 主框架和子框架的 ON_WM_SIZE 消息处理程序
如果改变子框架的大小,其中的表格可能会变得不可访问,因为滚动条可能不可见。当改变主框架(父框架)大小时,也可能发生同样的情况。子窗口也可能有不可见的滚动条。这种情况可以在俄罗斯会计程序中观察到。最小化主窗口时,子窗口可能变得无法通过滚动条进行管理。为了避免这种情况,我们处理父框架和子框架的 ON_WM_SIZE
消息。
在子框架中,使用以下代码
/////////////////////////////////////////////////////////////////////////////
// OnSize
/////////////////////////////////////////////////////////////////////////////
void CChildFrame::OnSize(UINT nType, int cx, int cy) {
CMDIChildWnd::OnSize(nType, cx, cy);
//*** Common offset for child frames
int m_nChildFrmOffset = m_pMainApp->m_nChildFrmOffset;
//*** Common width of main frame for border
UINT m_nMainFrmBorders = m_pMainApp->m_nMainFrmBorders;
//*** Current table pointer
m_pTable = m_pMainApp->m_apTable[m_eTable];
if(!m_pTable) {
_M("CChildFrame: Empty a CListCtrlEx object!");
return;
}
//*** Offset & Border size
cx = cx - m_nMainFrmBorders + m_nChildFrmOffset;
cy = cy - m_nMainFrmBorders + m_nChildFrmOffset;
//*** Uses suitable sizes
RECT Rect = {m_nChildFrmOffset, m_nChildFrmOffset, cx, cy};
//*** Changes a window sizes
m_pTable->MoveWindow(&Rect);
//*** Shows the vertical scroll bar always
m_pTable->ShowScrollBar(SB_VERT);
} // OnSize
而在父框架中,我们不仅需要处理一个子窗口,还需要处理所有子窗口。这可以通过以下代码完成
/////////////////////////////////////////////////////////////////////////////
// OnSize
/////////////////////////////////////////////////////////////////////////////
void CMainFrame::OnSize(UINT nType, int cx, int cy) {
CMDIFrameWnd::OnSize(nType, cx, cy);
//*** True sizes of the main frame client
int nMainWidth = cx - m_pMainApp->m_nMainFrmBorders;
int nMainHeight = cy - m_pMainApp->m_nClientCtrlsHeight;
//*** Current child frame rectangle
RECT ChildRect = {0}; // For safe change a child rectangle
RECT *pChildRect = NULL;
//*** Table Id
ETABLE eTable = m_pMainApp->m_eTable;
if(!eTable) {
//_M("CMainFrame: No objects is in the application!");
return; // Simply exit
}
//*** Current child frame pointer
CChildFrame **apChildFrame = m_pMainApp->m_apFrame;
CChildFrame *pChildFrame = NULL;
//*** The meta table structure
META_TABLE *aMetaTable = m_pMainApp->m_aMetaTable;
//*** Sizes of child frames
int nChildLeft = 0;
int nChildTop = 0;
int nChildWidth = 0;
int nChildHeight = 0;
//*** Look at the child frame array
for(UINT i = e_NULL; i < e_MAX; i++) {
pChildFrame = apChildFrame[i];
if(!pChildFrame)
continue;
pChildRect = aMetaTable[i].pFrmRect;
if(!pChildRect) {
_M("CChildFrame: Empty a child rectangle!");
return;
}
//*** Sizes of the child frame
nChildLeft = pChildRect->left;
nChildTop = pChildRect->top;
nChildWidth = pChildRect->right - pChildRect->left;
nChildHeight = pChildRect->bottom - pChildRect->top;
//*** Changes sizes and locality of the child frame
if(nChildWidth > nMainWidth) {
ChildRect.left = 0;
ChildRect.right = nMainWidth;
} else if(nChildLeft > nMainWidth - nChildWidth) {
ChildRect.left = nMainWidth - nChildWidth;
ChildRect.right = nMainWidth;
} else {
ChildRect.left = nChildLeft;
ChildRect.right = nChildLeft + nChildWidth;
}
if(nChildHeight > nMainHeight) {
ChildRect.top = 0;
ChildRect.bottom = nMainHeight;
} else if(nChildTop > nMainHeight - nChildHeight) {
ChildRect.top = nMainHeight - nChildHeight;
ChildRect.bottom = nMainHeight;
} else {
ChildRect.top = nChildTop;
ChildRect.bottom = nChildTop + nChildHeight;
}
pChildFrame->MoveWindow(&ChildRect);
}
pChildFrame = m_pMainApp->m_apFrame[eTable];
if(pChildFrame) {
//*** Activates current child frame
pChildFrame->ActivateFrame();
//*** Updates tabs
m_MainTabs.Update();
}
} // OnSize
3. 主框架上的标签页
为了在主框架上组织标签页,我们利用了 Christian Rodemeyer 的 CMDITabs
类。可以在图 1 中看到它们在我们的程序中的使用。
4. 菜单
用户子菜单的构造没有问题。唯一的问题可能是自动创建“窗口”子菜单中的条目,这些条目对应于应用程序打开的窗体。我们不编写这些菜单命令的处理程序,也无法访问它们。它们的实现不会通过 m_MainTabs.Update
函数更新标签页。为了某种方式拦截这些系统处理程序,我们将 ON_WM_CHILDACTIVATE
消息插入到我们的映射中。这是处理程序
/////////////////////////////////////////////////////////////////////////////
// OnChildActivate
/////////////////////////////////////////////////////////////////////////////
void CChildFrame::OnChildActivate() {
CMDIChildWnd::OnChildActivate();
//*** Saves current table Id in the main application
m_pMainApp->m_eTable = m_eTable;
//*** Updates tabs
m_pMainFrame->m_MainTabs.Update();
}
结果,我们实现了主框架上标签页的必要更新。
5. 工具栏(并排停靠)
在创建我们的工具栏时,它默认会定位在先前创建的工具栏的“新行”上。要将“新”控件“旧”控件旁边停靠,可以利用主框架此函数的第三个参数
void DockControlBar(CControlBar *pBar, UINT nDockBarID = 0, LPCRECT lpRect = NULL);
然而,如果我们不采纳 Kirk Stowell 的建议,即在调用 DockControlBar
函数之前调用 RecalcLayout
函数,则此方法将不起作用。最终我们得到此代码
/////////////////////////////////////////////////////////////////////////////
// OnCreate
/////////////////////////////////////////////////////////////////////////////
int CMainFrame::OnCreate(LPCREATESTRUCT pCS){
. . .
//*** To enable dock able control bars in the frame window
EnableDocking(CBRS_ALIGN_ANY);
//*** To enable the control bars to be docked
m_ToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_ToolBar2.EnableDocking(CBRS_ALIGN_ANY);
//*** Causes the control bar to be docked to the frame window
DockControlBar(&m_ToolBar);
//*** Called when the control bars are toggled on or off or when the frame
// window is resized
RecalcLayout();
RECT Rect = {0};
//*** Copies the dimensions of the bounding rectangle of the toolbar to
// the structure pointed to by &Rect
m_ToolBar.GetWindowRect(&Rect);
//*** Docking the toolbars side by side
Rect.left++;
DockControlBar(&m_ToolBar2, AFX_IDW_DOCKBAR_TOP, &Rect);
. . .
} // OnCreate
6. 热键
用于调用我们的表格,使用以下按键
- F10 - 第一个表格;
- F11 - 第二个表格;
- F12 - 第三个表格。
7. 表格窗体命令的通用处理
为了使用一个处理程序来处理我们的表格,我们将在消息映射中添加以下宏
ON_COMMAND_RANGE(ID_TABLE_NULL, ID_TABLE_MAX, OnTable)
其中 OnTable
函数是
/////////////////////////////////////////////////////////////////////////////
// OnTable
/////////////////////////////////////////////////////////////////////////////
void CMainApp::OnTable(UINT nTable) {
//*** Calculates m_eTable
m_eTable = (ETABLE) (nTable - ID_TABLE_NULL);
if(m_eTable <= e_NULL || m_eTable >= e_MAX) {
_M("No data is for this table!");
return;
}
//*** Current child frame pointer
CChildFrame *pChildFrame = m_apFrame[m_eTable];
if(pChildFrame) {
//*** Activates current child frame
pChildFrame->ActivateFrame();
//*** Checks the pointer before to call m_MainTabs.Update()
if(!m_pMainFrame) {
_M("CMainApp: Empty a CMainFrame object!");
return;
}
//*** Updates tabs
m_pMainFrame->m_MainTabs.Update();
return;
}
//*** Creates a new child frame with a document
//*
CWinApp::OnFileNew();
//*
//*** Creates a new child frame with a document
//*** Creates a new child frame without a document
//*** Doesn't call CMainView::OnInitialUpdate() and ignores a call of
//* CMainView::SetScaleToFitSize function
//*
/*if(!m_pDocTemplate) {
_M("CMainApp: Empty a CMultiDocTemplate object!");
return;
}
pChildFrame = reinterpret_cast<CChildFrame *>(
m_pDocTemplate->CreateNewFrame(NULL, NULL)
);
//*** Current meta table static structure
META_TABLE MetaTable = m_aMetaTable[m_eTable];
//*** Sets a title of child frame
pChildFrame->SetTitle(MetaTable.szTblName);
//*** Activates current child frame
pChildFrame->ActivateFrame();*/
//*
//*** Creates a new child frame without a document
//*** Checks the pointer before to call m_MainTabs.Update()
if(!m_pMainFrame) {
_M("CMainApp: Empty a CMainFrame object!");
return;
}
//*** Updates tabs
m_pMainFrame->m_MainTabs.Update();
} // OnTable
为了消除 CWinApp::OnFileNew
函数,需要删除创建新子框架而不带关联文档的代码。因此,可以从 CMainApp::InitInstance
函数中删除 AddDocTemplate
函数。为了方便有兴趣的程序员,注释掉的代码被保留。请注意,对 CWinView::SetScaleToFitSize
函数的调用被忽略。