Menu, tabs, toolbars, scrollbars, hotkeys, frames resizing, and the ON_COMMAND_RANGE handler for classes derived from CListCtrl on CFormView dialogs





5.00/5 (4 votes)
A demonstration of using a general-purpose MDI interface in MFC table forms.
Introduction
The theme of general-purpose MFC interface is neither new nor difficult. However, somehow it demands a lot of attention, first because without it, it is impossible to move farther in programming, and second, in real applications, some adaptations are required. The usually demonstrated examples are programmed abstractly, and in general, as a rule, they are torn off from working applications. We will do our demonstrations as «concrete» to get a practical application.
We continue to develop our previous project. We will add some standard elements of interface for a management dialog form with classes inheriting from CListCtrl
. Each of these elements does not present any special difficulties; however there are some nuances. As a result, we will get the example indicated in Fig. 1.
1. Scrollbars
When forms are small, horizontal and vertical scrollbars appear on them. It is a default property, and if we do not need it, we remove them forcedly. For this purpose, we use the following code:
/////////////////////////////////////////////////////////////////////////////
// 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
However, the vertical scrollbar is another problem. It is represented only when there are a lot of records in the list. If not, the vertical scrollbar is absent. Therefore, we will forcedly show the vertical scrollbar always, with this code:
/////////////////////////////////////////////////////////////////////////////
// OnCreate
/////////////////////////////////////////////////////////////////////////////
int CMainView::OnCreate(LPCREATESTRUCT pCS){
. . .
//*** Shows the vertical scroll bar always
pTable->ShowScrollBar(SB_VERT);
. . .
} // OnCreate
and this analogical code in the handler:
/////////////////////////////////////////////////////////////////////////////
// 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 message handlers for main and child frames
If we change the size of a child frame, table in it might become inaccessible because the scrollbars might not be visible. The same situation can happen when we change the main, parent frame sizes. Child windows can also have invisible scrollbars. This situation can be observed in a Russian accountant program. On minimizing the main window, child windows can become inaccessible for management using scrollbars. To do away with such situations, we process the ON_WM_SIZE
messages for the parent and child frames.
In the child frame, use the following code:
/////////////////////////////////////////////////////////////////////////////
// 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
And in the parent frame, we need to process not just one child window, but all of them. This is done using the following code:
/////////////////////////////////////////////////////////////////////////////
// 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. Tabs on the main frame
For organizing tabs on the main frame, we take advantage of the CMDITabs
classes from Christian Rodemeyer. It is possible to see their use in our program in Fig. 1.
4. Menu
The construction of user submenu presents no problems. The only problem could be the automatic creation of elements of the Window submenu which correspond to the opened forms of the application. We do not write handlers for these menu commands and have no access to them. Their implementation does not update tabs via the m_MainTabs.Update
function. To intercept these system handlers in some way, we plug the ON_WM_CHILDACTIVATE
messages in our map. Here is the handler:
/////////////////////////////////////////////////////////////////////////////
// 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();
}
As a result, we get the necessary update of tabs on the main frame.
5. Toolbars (are docked side by side)
At the creation of our toolbar, it is by default positioned in a « new line » to the previously created toolbar. To dock a « new » control to an «old» one, side by side, it is possible to take advantage of the third parameter in this function of the main frame:
void DockControlBar(CControlBar *pBar, UINT nDockBarID = 0, LPCRECT lpRect = NULL);
However, this method does not work if we will not take the advice of from Kirk Stowell who suggests calling the RecalcLayout
function before the DockControlBar
function. We have thic ode finally:
/////////////////////////////////////////////////////////////////////////////
// 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. Hotkeys
For calling our tables, the following keys are used:
- F10 - First table;
- F11 - Second table;
- F12 - Third table.
7. Common processing of table form commands
To process our tables using one handler, we will add the following macro in the message map:
ON_COMMAND_RANGE(ID_TABLE_NULL, ID_TABLE_MAX, OnTable)
where the OnTable
function is:
/////////////////////////////////////////////////////////////////////////////
// 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
To eliminate the CWinApp::OnFileNew
function, it is necessary to remove the code which creates a new child frame without a concomitant document. Thus, the AddDocTemplate
function cab be eliminated from the CMainApp::InitInstance
function. The commented code is left for interested programmers. Note that the call to the CMainView::SetScaleToFitSize
function is ignored.