21.3.1 实现更新模式(3)
4. 禁用Record菜单
当视图类中的m_Mode成员的值是UPDATE时,我们希望禁用Record菜单中的菜单项。但我们不打算在OnEditorder()处理程序中做这件事,因为有一种更简单、更好的方法-- 马上就将看到这种方法,因此可以将OnEditorder()处理程序的if语句中与此相关的两行注释删除。
可以在视图类中为菜单项和工具栏按钮添加专用的更新处理程序,管理它们的状态。在Resource View中双击菜单的ID,显示出该应用程序的菜单资源。该菜单资源在DBSimpleUpdate.rc文件中,其ID为IDR_MAINFRAME。为Record菜单中每个菜单项的UPDATE_COMMAND_UI消息添加一个处理程序-- 从First Record菜单项开始。单击Record菜单将其展开,然后在Resource View中右击First Record菜单项,并从弹出菜单中选择Add Event Handler。图21-9是此时显示出的Event Handler Wizard对话框。
可以看出,此处选择的消息类型是UPDATE_COMMAND_UI,从类列表中选择要添加的处理程序的类COrderDetailsView。该对话框底部的说明指出了UPDATE_COMMAND_UI处理程序的首要目的-- 那正是我们在本示例中所需要的。单击对话框中的Add and Edit按钮,添加该处理程序;然后为Record菜单中的其他3个菜单项重复上述过程。
|
| (点击查看大图)图 21-9 |
传递给UPDATE_COMMAND_UI处理程序的实参属于CCmdUI类型,而CCmdUI类有个成员函数Enable();我们调用该函数就可以启用或禁用菜单项。实参值为TRUE将启用相应的菜单项,而FALSE将禁用菜单项。形参的默认值是TRUE,因此我们不带实参值调用该函数将启用菜单项。当m_Mode的值是UPDATE时,希望禁用上述菜单项和工具栏按钮。但先别急着动手,希望禁用菜单项和工具栏按钮时的情况由于它们的行为而稍微有些复杂。
在当前记录是记录集中的第一条时,默认生成的程序已经禁用了对应于ID_RECORD_ FIRST和ID_RECORD_PREV这两个ID的菜单项和工具栏按钮。同样,在当前记录是记录集中的最后一条时,对应ID_RECORD_NEXT和ID_RECORD_LAST的菜单项被禁用。当m_Mode是READ_ONLY时,我们应该保留这样的行为。做到这一点的关键是使用视图类中继承的、测试当前记录是不是第一条或最后一条的函数。我们只需要给每个处理程序添加一行代码,就可以做到我们想做的事情。OnUpdateRecordFirst()和OnUpdateRecordPrev()这两个处理程序中要添加的代码完全相同。例如:
- void COrderDetailsView::OnUpdateRecordFirst(CCmdUI* pCmdUI)
- {
- // Disable item if m_Mode is UPDATE
- // Enable item if m_Mode is READ_ONLY and it's not the 1st record
- pCmdUI->Enable((m_Mode == READ_ONLY) && !IsOnFirstRecord());
- }
如果视图在记录集中的第一条记录上,则IsOnFirstRecord()函数返回TRUE;否则返回FALSE。如果m_Mode的值是UPDATE,或者COrderDetailsView的IsOnFirstRecord()成员返回的值是TRUE,则禁用相应的菜单项(以及对应的工具栏按钮)。如果m_Mode的值是READ_ONLY,并且IsOnFirstRecord()函数返回的值是FALSE,则启用相应的菜单项。该处理程序既影响菜单项,也影响工具栏按钮,因为两者的ID相同,都是ID_RECORD_FIRST。
对应ID_RECORD_NEXT和ID_RECORD_LAST的处理程序也需要相同的一行代码:
- void COrderDetailsView::OnUpdateRecordLast(CCmdUI* pCmdUI)
- {
- // Disable item if m_Mode is UPDATE
- // Enable item if m_Mode is READ_ONLY and it's not the 1st record
- pCmdUI->Enable((m_Mode == READ_ONLY) && !IsOnLastRecord());
- }
该处理程序的工作方式与上一个处理程序相同。
5. 执行更新
最后所需要的是当Update按钮被单击时,实际执行更新。为了更新某条记录,用户首先要单击Edit Order按钮,因此此刻必须调用记录集对象的Edit()成员,从而开始修改记录集的过程。Update按钮被单击之后,我们需要调用记录集对象的Update()成员,将新数据写出到数据库的记录中。如下所示,可以使用视图类的m_pSet成员实现前述操作:
- void COrderDetailsView::OnEditorder()
- {
- if(m_pSet->CanUpdate())
- {
- try
- {
- if(m_Mode == UPDATE)
- { // When button was clicked we were in update
- // Disable input to edit controls
- m_QuantityCtrl.SetReadOnly();
- m_DiscountCtrl.SetReadOnly();
-
- // Change the Update button text to Edit Order
- m_EditOrderCtrl.SetWindowText(_T("Edit Order"));
-
- // Make the Cancel button invisible
- m_CancelEditCtrl.ShowWindow(SW_HIDE);
-
- // Complete the update
- m_pSet->Update();
- m_Mode = READ_ONLY; // Change to read-only mode
- }
- else
- { // When button was clicked we were in read-only mode
-
- // Enable input to edit controls
- m_QuantityCtrl.SetReadOnly(FALSE);
- m_DiscountCtrl.SetReadOnly(FALSE);
-
- // Change the Edit Order button text to Update
- m_EditOrderCtrl.SetWindowText(_T("Update"));
-
- // Make the Cancel button visible
- m_CancelEditCtrl.ShowWindow(SW_SHOW);
-
- // Start the update
- m_pSet->Edit();
-
- m_Mode = UPDATE; // Switch to update mode
- }
- }
- catch(CException* pEx)
- {
- pEx->ReportError(); // Display the error message
- }
- }
- else
- AfxMessageBox(_T("Recordset is not updatable."));
- }
正如本章一开始所讨论的那样,Edit()和Update()函数都可以在错误发生时抛出异常,因此把对这两个函数的调用以及其余代码都放在一个try代码块内。如果不能更新记录集,则OnEditorder()函数中的任何处理无疑都将没有意义。如果某个异常被抛出,就调用该异常的ReportError()函数来显示出错消息。catch代码块的异常形参是指向CException类型的指针,因此只要异常对象属于CException类型或任何从CException派生的类型,该catch代码块就将执行。这是我们所需要的,因为既要处理可能由Edit()抛出的CMemoryException,也要处理Edit()和Update()都可能抛出的CDBException。注意,catch代码块的形参是指针。因为这些异常是使用THROW宏抛出的MFC异常,而不是使用throw关键字抛出的C++(www.cppentry.com)异常。如果是后者,就应当使用引用作为catch代码块的形参类型。
还调用了记录集的CanUpdate()成员来验证该记录集是否可以更新。如果该函数返回FALSE,我们就在消息框中显示一条出错消息。
6. 实现取消操作
Cancel按钮应该取消更新操作。为此所需的全部代码就是调用COrderDetailsSet对象的CancelUpdate()成员。当然,还有一些整理工作要做,但除了不调用Edit()函数以外,这些工作与Update按钮被按下时完全相同。下面是OnCancel()处理程序的代码:
- void COrderDetailsView::OnCancel()
- {
- m_pSet->CancelUpdate(); // Cancel the update operation
- m_EditOrderCtrl.SetWindowText(_T("Edit"));// Switch button text
- m_CancelEditCtrl.ShowWindow(SW_HIDE);// Hide the Cancel button
- m_QuantityCtrl.SetReadOnly(TRUE); // Set state of quantity edit control
- m_DiscountCtrl.SetReadOnly(TRUE); // Set state of discount edit control
- m_Mode = READ_ONLY; // Switch the mode
- }
CancelUpdate()函数终止更新操作,并将记录集对象的字段恢复为调用Edit()函数之前的数值。因为Cancel按钮只能在编辑模式中被单击,所以更新按钮和其他控件的方式与OnEditorder()处理程序中相同。这就是所需要的一切,现在该程序已经可以试运行了。
试一试:受控的更新
假设代码中没有打字错误,那么当编译并运行该程序之后,它应该以预期的方式工作。在单击Edit按钮之后,只能在Quantity和Discount编辑控件中输入数据。图21-10是更新操作窗口的示例。
移动到新记录的按钮以及Record菜单中的菜单项现在都不能使用。为了在输入新数据之后完成更新,要单击Update按钮。该动作使新数据写出到数据库中,并使该应用程序返回到正常状态-- 即所有编辑控件都不能使用,按钮和菜单恢复到原来的状态。
|
| (点击查看大图)图 21-10 |