65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2009年7月12日

CPOL

3分钟阅读

viewsIcon

42751

downloadIcon

1886

演示如何在 MFC 表格窗体中使用通用的 MDI 界面。

引言

通用的 MFC 界面主题既不新也不难。然而,不知何故,它需要大量的关注,首先,没有它,就不可能在编程中走得更远,其次,在实际应用程序中,需要一些调整。通常演示的例子都是抽象编程的,而且通常来说,它们与实际工作应用程序脱节。我们将使我们的演示“具体化”,以获得一个实际的应用。

我们将继续开发我们 之前的项目。我们将为具有继承自 CListCtrl 的类的管理对话框窗体添加一些标准的界面元素。这些元素中的每一个都不会引起任何特殊的困难;然而,存在一些细微之处。结果,我们将得到图 1 中所示的示例。

Tables.jpg

图 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 RodemeyerCMDITabs 类。可以在图 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 函数的调用被忽略。

© . All rights reserved.