Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-1

.pdf
Скачиваний:
108
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Updating Data Sources

When the Edit Order button is clicked, the read-only status of the controls for quantity and discount should be removed, and there should be a button to be clicked when the update should take place. To accommodate all this, you want the dialog box in the application to look as in Figure 20-7 after an Edit Order button click.

Figure 20-7

The edit controls for Quantity and Discount now allow data to be entered, the Edit Order button has a new label — Update — and there is a new button with the label Cancel that allows the operation to be aborted by the user, if necessary. In addition the toolbar buttons to move, the current record should be disabled when the Edit Order button is clicked. So, too, should be the menu items in the Record menu drop down. The program is now in “edit” mode.

You can add the Cancel button to the dialog box, but you don’t want the button displayed initially so you should set the Visible property for the button to False.

You will also need a handler for the Cancel button, so add one now to the COrderDetailsView class with the name OnCancel() in the same way as for the Edit Order button — you’ll be filling in the code later.

The process for the update operation is that the user enters the data in the enabled fields on the dialog box and clicks the Update button to complete the update. The dialog box then returns to its original read-only mode’ state with all the edit controls read-only. The user clicks the Cancel button instead of Update if he or she does not want to proceed with the update operation.

To achieve this mechanism, and to manage the update process effectively, you will need to do several things after the Edit Order button is clicked:

989

Chapter 20

Change the text on the Edit Order button to “Update” so it now becomes the button to complete the update operation.

Cause the Cancel button to appear on the dialog box — make it visible in other words.

Record in the class that ‘edit mode’ has been entered. This is necessary because you will use the same button for two purposes: flip-flopping the label between Edit Order and Update.

Enable keyboard entry for the edit controls that show fields you want to allow updating on.

See how you can put the code together that will do what you want.

Implementing Update Mode

Let’s start by providing for recording whether or not the application is in update mode. You can do that by adding an enum declaration to the COrderDetailsView class together with a variable of the enum type that reflect the current mode. Add the following two lines to the public section of the class:

enum

Mode {READ_ONLY, UPDATE};

//

Application

modes

Mode

m_Mode;

//

Records the

current mode

The application is initially in READ_ONLY mode so you can initialize m_Mode in the constructor accordingly:

COrderDetailsView::COrderDetailsView()

: CRecordView(COrderDetailsView::IDD)

,m_Mode(READ_ONLY)

{

m_pSet = NULL;

// TODO: add construction code here

}

You can do the switching of the button label and the program mode in the OnEditorder() handler that you added to the view class. In principle, a starting version of the function needs to implement capability something like this:

void COrderDetailsView::OnEditorder()

{

if(m_Mode == UPDATE)

{// When button was clicked we were in update

//Disable input to edit controls

//Change the Update button text to Edit Order

//Make the Cancel button invisible

//Enable Record menu items and toolbar buttons

//Complete the 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

//Change the Edit Order button text to Update

990

Updating Data Sources

//Make the Cancel button visible

//Disable Record menu items and toolbar buttons

//Start the update

m_Mode = UPDATE;

// Switch to update mode

}

}

The mode switching code is already there. At the moment all the function does is switch the m_Mode member between READ_ONLY and UPDATE to record the current mode. The rest of the functionality that you require is simply described in comments. Next, investigate how to implement each of the comment lines in turn.

Enabling and Disabling Edit Controls

To modify the properties of a control, you need to call a function of some kind that relates to the control. This implies that you must have access to an object that represents the control. It’s very easy to add a control variable to the view class; just right-click the control in Resource View and select Add Variable from the pop-up menu. Figure 20-8 shows the dialog box that displays for the edit control showing the discount value.

Figure 20-8

I have entered the variable name as m_DiscountCtrl. Because the variable relates to an edit control the variable is of type CEdit. Click Finish to add this variable to the View Class. Repeat this process for the edit control displaying the order quantity and give the variable the name m_QuantityCtrl.

With the two control variables added to the View Class you have access to the controls to update their styles, so you modify the OnEditorder() function as follows:

991

Chapter 20

void COrderDetailsView::OnEditorder()

{

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

//Make the Cancel button invisible

//Enable Record menu items and toolbar buttons

//Complete the 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

//Make the Cancel button visible

//Disable Record menu items and toolbar buttons

//Start the update

m_Mode = UPDATE;

// Switch to update mode

}

 

}

 

The SetReadOnly() member of the CEdit class has a parameter of type BOOL that has the default value TRUE. Thus call the function with no argument implies the argument value is the default and the control read-only property is set to true. Passing a FALSE value to the function when you call it sets the readonly property to false. Incidentally, you could reduce the code in the function by removing the calls to SetReadOnly() in the if-else statement and adding two statements after the if statement:

m_QuantityCtrl.SetReadOnly(m_Mode == UPDATE);

m_DiscountCtrl.SetReadOnly(m_Mode == UPDATE);

The argument expression, m_Mode == UPDATE, is TRUE when m_Mode has the value UPDATE. This is FALSE otherwise, so these two calls to the SetReadOnly() function do the work of the original four. The downside is that it would not be quite as clear what was happening in the code.

Just to remind you(many of the MFC functions have parameters of type BOOL that can have values TRUE and FALSE because they were written before the availability of the bool type in C++. You can always use values of type bool as arguments for BOOL parameters if you want, but I prefer to stick to the TRUE and FALSE arguments values with type BOOL and only use true and false for variables of type bool.

Changing the Button Label

You can get at the object corresponding to the Edit Order button by adding a control data member, m_EditOrderCtrl, to the View Class in exactly the same way as you did for the edit controls. The variable is of type CButton, which is the MFC class that defines a button. You can use the variable to set the

992

Updating Data Sources

button label in the OnEditorder() member by calling the SetWindowText() member that is inherited in the CButton class from CWnd:

void COrderDetailsView::OnEditorder()

{

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

//Enable Record menu items and toolbar buttons

//Complete the 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

//Disable Record menu items and toolbar buttons

//Start the update

m_Mode = UPDATE;

// Switch to update mode

}

}

Each call of the SetWindowText() function sets the text displayed on the button to the string you supply as the argument to the function. The parameter type is LPCTSTR for which you can use a CString argument or a string constant of type _T.

Controlling the Visibility of the Cancel Button

To make the Cancel button visible or invisible, you need a control variable corresponding to that button available, so add a control variable with the name m_CancelEditCtrl just as you did for the Edit Order button. Because the CButton class is derived from CWnd, you can call the inherited ShowWindow() member of the CButton object to set the button as visible or invisible, as follows:

void COrderDetailsView::OnEditorder()

{

if(m_Mode == UPDATE)

{ // When button was clicked we were in update // Disable input to edit controls m_QuantityCtrl.SetReadOnly();

993

Chapter 20

m_DiscountCtrl.SetReadOnly();

// Change the Edit Order button text to Update m_EditOrderCtrl.SetWindowText(_T(“Edit Order”));

//Make the Cancel button invisible m_CancelEditCtrl.ShowWindow(SW_HIDE);

//Enable Record menu items and toolbar buttons

//Complete the 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);

//Disable Record menu items and toolbar buttons

//Start the update

m_Mode = UPDATE;

// Switch to update mode

}

}

The ShowWindow() function that the CButton class inherits from CWnd requires an argument of type int that must be one of range of fixed values( see the documentation for the full set). You use the argument value SW_HIDE to make the button disappear if m_Mode has the value UPDATE, and SW_SHOW when the application is entering edit mode to make the button visible and activate it.

Disabling the Record Menu

You want to disable the menu items in the Record menu when the m_Mode member of the view has the value UPDATE. You won’t do this in the OnEditorder() handler, though, because there is an easier and better way as you will now see, so you can remove the two comment lines in the if statement in the OnEditorder() handler that relate to this.

You can manage the state of the menu items and toolbar buttons by adding update handlers for them in the view class that are specifically for this purpose. Display the menu resource for the application in Resource View. This is the menu resource in the DBSimpleUpdate.rc file with the ID IDR_MAINFRAME. Add a handler for the UPDATE_COMMAND_UI message for each of the menu items in the Record menu — starting with the First Record menu item. Extend the Record menu by clicking it and then right-click the First Record menu item in Resource View and select Add Event Handler from the pop-up menu. Figure 20-9 shows the Event Handler wizard dialog box that is displayed as a result.

994

Updating Data Sources

Figure 20-9

As you can see, I have selected the message type to be UPDATE_COMMAND_UI and the class where the handler is to be added as COrderDetailsView. The description at the bottom of the dialog box indicates the prime purpose of the UPDATE_COMMAND_UI handler — exactly what you need in this instance. Click the Add and Edit button in the dialog box to add the handler and repeat the process for the three other items in the Record menu.

The argument that is passed to the UPDATE_COMMAND_UI handler function is of type CCmdUI and the CCmdUI class has a member function, Enable(), that you can call to enable or disable the item. An argument value of TRUE enables the item, and a value of FALSE disables it. The default value for the parameter is TRUE so you can call the function with no argument value to enable the item. You want to disable the menu items and toolbar buttons when m_Mode has the value UPDATE, but the circumstances when you want to disable them are a little more complicated because of the behavior of the menu items ands toolbar buttons before you started messing with them.

The program as generated by default already disables the menu items and toolbar buttons corresponding to the IDs ID_RECORD_FIRST and ID_RECORD_PREV when the current record is the first in the recordset. Similarly, when the current record is the last in the recordset, the ID_RECORD_NEXT and ID_RECORD_LAST items are disabled. You should maintain this behavior when m_Mode is READ_ONLY. The key to doing this is to use inherited functions in the View Class that test whether the current record is the first or the last. You only need add one line of code to each of these handlers to do what you want. It’s exactly the same line of code for OnUpdateRecordFirst() and OnUpdateRecordPrev(). For example:

995

Chapter 20

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());

}

The IsOnFirstRecord() function returns TRUE if the view is on the first record in the recordset, and FALSE otherwise. This disableS the items (the menu item and the corresponding toolbar button) if either m_Mode has the value UPDATE or the value returned by IsOnFirstRecord() member of COrderDetailsView is TRUE. The items is enabled if m_Mode has the value READ_ONLY and the value returned by the IsOnFirstRecord() function is FALSE. This handler affects both the menu item and the toolbar button because they both have the same ID, ID_RECORD_FIRST.

The handlers corresponding to ID_RECORD_NEXT and ID_RECORD_LAST also require the same line of code:

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());

}

This works in the same way as the previous handler.

Expediting the Update

The last piece you need is to actually carry out the update when the Update button is clicked. To update a record, the user first clicks the Edit Order button, so at this point you must call the Edit() member of the recordset object to start the process of modifying the recordset. When the Update button is clicked, you need to call the Update() member of the recordset object to get the new data written to the record in the database. You can implement it using the m_pSet member of the View Class like this:

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

996

Updating Data Sources

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.”));

}

As I discussed at the beginning of this chapter, the Edit() and Update() functions can throw an exception if an error occurs, so you put the calls within a try block along with the rest of the code. Clearly, if you cannot update the recordset, there is no purpose to any of the processing in the OnEditorder() function. If an exception is thrown, you call its ReportError() function to display an error message. The catch block exception parameter is a pointer to CException, so the catch block is executed for exceptions objects of type CException, or any class derived from CException. You need this to accommodate the CMemoryException that can be thrown by Edit(), as well as the CDBException that can be thrown by both Edit() and Update(). Note the use of the pointer as the catch block parameter. Recall that this is because these are MFC exceptions thrown using the THROW macro, not C++ exceptions thrown using the keyword throw. If they were the latter, you would use a reference as the catch block parameter type.

You also verify that the recordset is updateable by calling its CanUpdate() member. If this returns FALSE, you display an error message in a message box.

Implementing the Cancel Operation

The Cancel button should abort the update operation. All that is necessary to do this is to call the CancelUpdate() member of the COrderDetailsSet object. Of course, you have a little housekeeping to do, but this is exactly the same as if the Update() button was pressed, except that you don’t call Edit(). Here’s the code for the OnCancel() handler:

997

Chapter 20

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

}

 

The CancelUpdate() function ends the update operation and restores the recordset object’s fields to what they were before Edit() was called. Because the Cancel button can only be clicked in edit mode, you can update the buttons and other controls in the same way as in the OnEditorder() handler. That’s everything you need. You are ready for a trial run.

Try It Out

Controlled Updating

Assuming you have no typos in your code, when you compile and run the program, it should work as planned. You can only enter data in the Quantity and Discount edit controls after you have clicked the Edit button. Figure 20-10 shows an example of the update operation window.

Figure 20-10

The buttons for moving to a new record are now disabled, as are the menu items for the Record menu. To complete the update after you have entered the new data, you click the Update button. This causes the new data to be written to the database, and the application returns to the normal state — all edit controls disabled and the buttons and menu restored to their original status.

998