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.
Fig. 1. Modified lists on the changed forms with the elements of the user interface.
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:
void CMainView::OnInitialUpdate() {
. . .
SIZE Size = {1, 1};
SetScaleToFitSize(Size);
}
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:
int CMainView::OnCreate(LPCREATESTRUCT pCS){
. . .
pTable->ShowScrollBar(SB_VERT);
. . .
}
and this analogical code in the handler:
void CChildFrame::OnSize(UINT nType, int cx, int Ñу){
. . .
m_pTable->ShowScrollBar(SB_VERT);
}
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:
void CChildFrame::OnSize(UINT nType, int cx, int cy) {
CMDIChildWnd::OnSize(nType, cx, cy);
int m_nChildFrmOffset = m_pMainApp->m_nChildFrmOffset;
UINT m_nMainFrmBorders = m_pMainApp->m_nMainFrmBorders;
m_pTable = m_pMainApp->m_apTable[m_eTable];
if(!m_pTable) {
_M("CChildFrame: Empty a CListCtrlEx object!");
return;
}
cx = cx - m_nMainFrmBorders + m_nChildFrmOffset;
cy = cy - m_nMainFrmBorders + m_nChildFrmOffset;
RECT Rect = {m_nChildFrmOffset, m_nChildFrmOffset, cx, cy};
m_pTable->MoveWindow(&Rect);
m_pTable->ShowScrollBar(SB_VERT);
}
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:
void CMainFrame::OnSize(UINT nType, int cx, int cy) {
CMDIFrameWnd::OnSize(nType, cx, cy);
int nMainWidth = cx - m_pMainApp->m_nMainFrmBorders;
int nMainHeight = cy - m_pMainApp->m_nClientCtrlsHeight;
RECT ChildRect = {0}; RECT *pChildRect = NULL;
ETABLE eTable = m_pMainApp->m_eTable;
if(!eTable) {
return; }
CChildFrame **apChildFrame = m_pMainApp->m_apFrame;
CChildFrame *pChildFrame = NULL;
META_TABLE *aMetaTable = m_pMainApp->m_aMetaTable;
int nChildLeft = 0;
int nChildTop = 0;
int nChildWidth = 0;
int nChildHeight = 0;
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;
}
nChildLeft = pChildRect->left;
nChildTop = pChildRect->top;
nChildWidth = pChildRect->right - pChildRect->left;
nChildHeight = pChildRect->bottom - pChildRect->top;
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) {
pChildFrame->ActivateFrame();
m_MainTabs.Update();
}
}
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:
void CChildFrame::OnChildActivate() {
CMDIChildWnd::OnChildActivate();
m_pMainApp->m_eTable = m_eTable;
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:
int CMainFrame::OnCreate(LPCREATESTRUCT pCS){
. . .
EnableDocking(CBRS_ALIGN_ANY);
m_ToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_ToolBar2.EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_ToolBar);
RecalcLayout();
RECT Rect = {0};
m_ToolBar.GetWindowRect(&Rect);
Rect.left++;
DockControlBar(&m_ToolBar2, AFX_IDW_DOCKBAR_TOP, &Rect);
. . .
}
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:
void CMainApp::OnTable(UINT nTable) {
m_eTable = (ETABLE) (nTable - ID_TABLE_NULL);
if(m_eTable <= e_NULL || m_eTable >= e_MAX) {
_M("No data is for this table!");
return;
}
CChildFrame *pChildFrame = m_apFrame[m_eTable];
if(pChildFrame) {
pChildFrame->ActivateFrame();
if(!m_pMainFrame) {
_M("CMainApp: Empty a CMainFrame object!");
return;
}
m_pMainFrame->m_MainTabs.Update();
return;
}
CWinApp::OnFileNew();
if(!m_pMainFrame) {
_M("CMainApp: Empty a CMainFrame object!");
return;
}
m_pMainFrame->m_MainTabs.Update();
}
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.