Click here to Skip to main content
15,880,608 members
Articles / Desktop Programming / MFC

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

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
12 Jul 2009CPOL3 min read 41.6K   1.9K   25  
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.

Tables.jpg

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:

C++
/////////////////////////////////////////////////////////////////////////////
// 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:

C++
/////////////////////////////////////////////////////////////////////////////
// OnCreate
/////////////////////////////////////////////////////////////////////////////
int CMainView::OnCreate(LPCREATESTRUCT pCS){
  . . .

  //*** Shows the vertical scroll bar always
  pTable->ShowScrollBar(SB_VERT);

  . . .
}  // OnCreate

and this analogical code in the handler:

C++
/////////////////////////////////////////////////////////////////////////////
// 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:

C++
/////////////////////////////////////////////////////////////////////////////
// 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:

C++
/////////////////////////////////////////////////////////////////////////////
// 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:

C++
/////////////////////////////////////////////////////////////////////////////
// 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:

C++
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:

C++
/////////////////////////////////////////////////////////////////////////////
// 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:

C++
ON_COMMAND_RANGE(ID_TABLE_NULL, ID_TABLE_MAX, OnTable)

where the OnTable function is:

C++
/////////////////////////////////////////////////////////////////////////////
// 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --