1. Trang chủ
  2. » Công Nghệ Thông Tin

Ivor Horton’s Beginning Visual C++ 2005 phần 9 pptx

122 313 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 122
Dung lượng 2,1 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Figure 19-14 shows how data from the database ultimately gets to be displayed by the view.Figure 19-14 The transfer of data between the data members in the CProductSetobject that corresp

Trang 1

your recordset object accesses a record If the data in the current record is modified by another user, this

is not apparent in your recordset object unless you move to another record and then return to the nal record A dynaset recordset uses an index to the database tables involved to generate the contents ofeach record dynamically Because you have no other users accessing the Northwinddatabase, you canchoose the Snapshotoption for your example

origi-After Snapshot has been chosen, you can click the Generated Classesoption to display the classes inyour application The dialog box is shown in Figure 19-13

Figure 19-13

Here you can change the class names and the corresponding file names assigned by the wizard to something more suitable, if you want In addition to the changes shown for the CDBSampleViewandCProductViewclasses and the corresponding changes to the names of the hand cppfiles for the class,you could also change the CDBSampleSetclass name to CProductSet, and the associated hand cppfilenames to be consistent with the class name After that is done, click Finishand generate the project

Understanding the Program Structure

The basic structure of the program is as you have seen before, with an Application Class CDBSampleApp,

a Frame Window Class CMainFrame, a Document Class CDBSampleDoc, and a view class CProductView

A document template object is responsible for creating and relating the frame window, document, andview objects This is done in a standard manner in the InitInstance()member of the applicationobject The document class is standard, except that the MFC Application wizard has added a data mem-ber, m_DBSampleSet,which is an object of the CProductSetclass type As a consequence, a recordsetobject is automatically created when the document object is created in the InitInstance()functionmember of the application object The significant departures from a non-database program arise in thedetail of the CRecordsetclass, and in the CRecordViewclass, so take a look at those

936

Trang 2

Understanding Recordsets

You can look at the definition of the CProductSetclass that the Application wizard has generated meal and see how each piece works I’ll show the bits under discussion as shaded in the code fragments

piece-Recordset Creation

The first segment of the class definition that is of interest is:

class CProductSet : public CRecordset{

public:

CProductSet(CDatabase* pDatabase = NULL);

DECLARE_DYNAMIC(CProductSet)// Plus more of the class definition

// Overrides// Wizard generated virtual function overridespublic:

virtual CString GetDefaultConnect(); // Default connection stringvirtual CString GetDefaultSQL(); // default SQL for Recordsetvirtual void DoFieldExchange(CFieldExchange* pFX);// RFX support// Plus some more standard stuff

};

The class has CRecordsetas a base class and provides the functionality for retrieving data from thedatabase The constructor for the class accepts a pointer to a CDatabaseobject that is set to NULLas adefault The parameter to the constructor allows a CProductSetobject to be created for a CDatabaseobject that already exists, which allows an existing connection to a database to be reused Opening a connection to a database is a lengthy business, so it’s advantageous to re-use a database connectionwhen you can

If no pointer is passed to the constructor, as is the case for the m_DBSampleSetmember of the documentclass CDBSampleDoc, the framework automatically creates a CDatabaseobject for you and calls theGetDefaultConnect()function member of CProductSetto define the connection The Applicationwizard provides the following implementation of this function:

CString CProductSet::GetDefaultConnect(){

Trang 3

The GetDefaultConnect()function is a pure virtual function in the base class, CRecordset, and somust always be implemented in a derived recordset class The value returned from the function is a sin-gle string between double quotes but I have shown it spread over several lines to make the contents ofthe string more apparent The implementation provided by Application wizard returns the text stringshown to the framework This identifies the database with its name and path plus values for the otherparameters you can see and enables the framework to create a CDatabaseobject that establishes thedatabase connection automatically The meaning of the arguments in the connection string is as follows:

Argument Description

DBQ The database qualifier, which in this case is the path to the Access database

file

DriverID The ID of the ODBC driver for the database

MaxBufferSize The maximum size of the buffer to be used for data transfer

PageTimeout The length of time in seconds to wait for a connection to the database It is

important to set this value to an adequate value to avoid connection failureswhen accessing a remote database

UID The user ID for accessing the database

In practice, it’s usually necessary to supply a password as well as a user ID before access to a database

is permitted, and it’s unwise to expose the password in the code in plain text form For this reason theApplication wizard has inserted the following line preceding the definition of the GetDefaultConnect()function:

#error Security Issue: The connection string may contain a password

Compilation fails with this directive in the code, so you must comment it out or delete it to compile theprogram successfully

You can make the framework pop up a dialog box for the user to select the database name from the list ofregistered database sources by writing the return statement in the GetDefaultConnect()function as:return _T(“ODBC;”);

You will also be prompted for a user ID and password when this is required for access to the database

Querying the Database

The CProductSetclass includes a data member for each field in the Productstable The Applicationwizard obtains the field names from the database and uses these to name the corresponding data mem-bers of the class They appear in the block of code following the Field/Param Datacomment in theCProductSetclass definition:

938

Trang 4

class CProductSet : public CRecordset{

public:

CProductSet(CDatabase* pDatabase = NULL);

DECLARE_DYNAMIC(CProductSet)// Field/Param Data

// The string types below (if present) reflect the actual data type of the// database field - CStringA for ANSI datatypes and CStringW for Unicode// datatypes This is to prevent the ODBC driver from performing // potentially unnecessary conversions

// If you wish, you may change these members to// CString types and the ODBC driver will perform all necessary // conversions

// (Note: You must use an ODBC driver version that is version 3.5 or // greater to support both Unicode and these conversions)

long m_ProductID; // Number automatically assigned to new product

CStringW m_ProductName;

long m_SupplierID; // Same entry as in Suppliers table

long m_CategoryID; // Same entry as in Categories table

CStringW m_QuantityPerUnit; // (e.g., 24-count case, 1-liter bottle)

double m_UnitPrice;

int m_UnitsInStock;

int m_UnitsOnOrder;

int m_ReorderLevel; // Minimum units to maintain in stock

BOOL m_Discontinued; // Yes means item is no longer available

// Overrides// Wizard generated virtual function overridespublic:

virtual CString GetDefaultConnect(); // Default connection string

virtual CString GetDefaultSQL(); // default SQL for Recordsetvirtual void DoFieldExchange(CFieldExchange* pFX); // RFX support// Implementation

#ifdef _DEBUGvirtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif};

The type of each data member is set to correspond with the field type for the corresponding field in theProductstable You may not want all these fields in practice, but you shouldn’t delete them willy-nilly

in the class definition As you will see shortly, they are referenced in other places, so you must ensurethat all references to a field are deleted, too A further caveat is that you must not delete primary keys Ifyou do, the recordset won’t work, so you need to be sure which fields are primary keys before choppingout what you don’t want

Note that two of the fields have CStringWas the type You haven’t seen this before, but the CStringWclass type just encapsulates a Unicode string rather than an ASCII string It is more convenient when

Trang 5

you are accessing the fields to use type CString, so change the type of the m_ProductNameandm_QuantityPerUnitmembers to CString This allows the strings to be handled as ASCII strings in the example Clearly, if you are writing internationalized database applications, you would need tomaintain any CStringWfields as such because they may contain characters that are not within the ASCII character set.

The SQL operation that applies to the recordset to populate these data members is specified in theGetDefaultSQL()function The implementation that the Application wizard has supplied for this is:CString CProductSet::GetDefaultSQL()

The GetDefaultSQL()function is called by the MFC framework when it constructs the SQL statement

to be applied for the recordset The framework slots the string returned by this function into a skeletonSQL statement with the form:

SELECT * FROM < String returned by GetDefaultSQL() >;

This looks simplistic, and indeed it is, but you can add WHEREand ORDER BYclauses to the operation, asyou’ll see later

Data Transfer between the Database and the Recordset

The transfer of data from the database to the recordset, and vice versa, is accomplished by the

DoFieldExchange()member of the CProductSetclass The implementation of this function is:void CProductSet::DoFieldExchange(CFieldExchange* pFX)

{

pFX->SetFieldType(CFieldExchange::outputColumn);

// Macros such as RFX_Text() and RFX_Int() are dependent on the

// type of the member variable, not the type of the field in the database

// ODBC will try to automatically convert the column value to the requested

// type

RFX_Long(pFX, _T(“[ProductID]”), m_ProductID);

RFX_Text(pFX, _T(“[ProductName]”), m_ProductName);

RFX_Long(pFX, _T(“[SupplierID]”), m_SupplierID);

RFX_Long(pFX, _T(“[CategoryID]”), m_CategoryID);

RFX_Text(pFX, _T(“[QuantityPerUnit]”), m_QuantityPerUnit);

RFX_Double(pFX, _T(“[UnitPrice]”), m_UnitPrice);

RFX_Int(pFX, _T(“[UnitsInStock]”), m_UnitsInStock);

RFX_Int(pFX, _T(“[UnitsOnOrder]”), m_UnitsOnOrder);

RFX_Int(pFX, _T(“[ReorderLevel]”), m_ReorderLevel);

RFX_Bool(pFX, _T(“[Discontinued]”), m_Discontinued);

}

940

Trang 6

This function is called automatically by the MFC framework to store data in and retrieve data from thedatabase It works in a similar fashion to the DoDataExchange()function you have seen with dialogcontrols in that the pFXparameter determines whether the operation is a read or a write Each time it’scalled, it moves a single record to or from the recordset object.

The first function called is SetFieldType(), which sets a mode for the RFX_()function calls that low In this case, the mode is specified as outputColumn, which indicates that data is to be exchangedbetween the database field and the corresponding argument specified in each of the following RFX_()function calls

fol-There is a whole range of RFX_()functions for various types of database field The function call for aparticular field corresponds with the data type applicable to that field The first argument to an RFX_()function call is the pFXobject that determines the direction of data movement The second argument isthe table field name and the third is the data member that is to store that field for the current record

Understanding the Record View

The purpose of the view class is to display information from the recordset object in the application dow, so you need to understand how this works The bits of the CProductViewclass definition that are

win-of primary interest are shown shaded:

class CProductView : public CRecordView{

protected: // create from serialization onlyCProductView();

DECLARE_DYNCREATE(CProductView)public:

enum{ IDD = IDD_DBSAMPLE_FORM };

CProductSet* m_pSet;

// Attributespublic:

CDBSampleDoc* GetDocument();

// Operationspublic:

// Overridespublic:

virtual CRecordset* OnGetRecordset();

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV supportvirtual void OnInitialUpdate(); // called first time after constructvirtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// Implementationpublic:

Trang 7

virtual ~CProductView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

dis-Note that the constructor is protected This is because objects of this class are expected to be createdfrom serialization, which is a default assumption for record view classes When you add further recordview classes to the application, you’ll need to change the default access for their constructors to publicbecause you’ll be creating the views yourself

In the first publicblock in the class, the enumeration adds the ID IDD_DBSAMPLE_FORMas a memberthe class This is the ID for a blank dialog that the Application Wizard has included in the program.You’ll add controls to this dialog to display the database fields from the Productstable that you wantdisplayed The dialog ID is passed to the base class, CRecordView, in the initialization list of the con-structor for the view class:

Trang 8

Figure 19-14 shows how data from the database ultimately gets to be displayed by the view.

Figure 19-14

The transfer of data between the data members in the CProductSetobject that correspond to fields inthe Products table and the controls in the dialog box associated with the CProductViewobject is man-aged by the DoDataExchange()member of CProductView The code in this function to do this is not inplace yet because you first need to add the controls to the dialog that are going to display the data andthen link the controls to the recordset data members You will do that next

Creating the View Dialog

The first step is to place the controls on the dialog box, so go to Resource View, expand the list of dialogresources, and double-click Idd_Dbsample_Form You can delete the static text object with the TODOmessage from the dialog If you right-click the dialog box, you can choose to view its properties, asshown in Figure 19-15

If you scroll down through the properties you’ll see that the Styleproperty has been set to Childbecause the dialog box is going to be a child window and will fill the client area The Borderpropertyhas been set to Nonebecause if the dialog box is to fill the client area, it won’t need a border

You’ll add a static text control to identify each field from the recordset that you want to display, plus anedit control to display it

RecordSet Object

View Object

Trang 10

You can add the text to each static control by just typing it as soon as the control has been placed on thedialog As you see, I have entered the text for each static control so that it corresponds to the field name

in the database It’s a good idea to make sure that all the edit controls have sensible and different IDs, soright-click each of them in turn to display and modify their properties Figure 19-17 shows the propertiesfor the control corresponding to Product ID

Figure 19-17

It’s helpful to use the field name as part of the control ID as this indicates what the control display.Figure 19-17 shows the ID for the first edit control in the title bar of the properties window after I havemodified it You can change the IDs for the other edit controls similarly Because you are not intending toupdate the database in this example, you should make sure that the data displayed by each edit box can-not be modified from the keyboard You can do that by setting the Read Onlyproperty for of each of theedit controls as True The background to the edit boxes will then have a different color to signal thatthey cannot be altered, as shown in Figure 19-18

You can add other fields to the dialog box, if you want The one that is most important for the rest of ourexample is the Product ID, so you must include that Save the dialog and then move on to the last step:linking the controls to the variables in the recordset class

Trang 11

Figure 19-18

Linking the Controls to the Recordset

As you saw earlier in Figure 19-14, getting the data from the recordset displayed by the appropriate trol is the job of the DoDataExchange()function in the CProductViewclass The m_pSetmember pro-vides a means of accessing the members of the CProductSetobject that contains the fields retrievedfrom the database, so linking the controls to the data members of CProductSetis easy MFC defines arange of DDX_Fieldfunctions at global scope that are specifically for exchanging data between a viewand a recordset In particular, the DDX_FieldText()function has overloaded versions that transfers avariety of types of data between a recordset field and an edit box in a CRecordViewobject The typesthat can be exchanged by the DDX_FieldText()function are:

float double CString COleDateTime COleCurrency

When you call the DDX_FieldText()function you must supply four arguments:

❑ ACDataExchangeobject that determines the direction of data transfer — whether the data is to

be transferred to or from the recordset You just supply the pointer that is passed as the ment to the DoDataExchange()function

argu-❑ The ID of the control that is the source or destination of the data

❑ A reference to a field data member in the CRecordsetobject that is the source or destination ofthe data

❑ A pointer to the CRecordsetobject with which data is to be exchanged

So to implement the transfer of data between the recordset and the control for the Product ID field, insertthe following call of the DDX_FieldText()function in the body of the DoDataExchange()function:DDX_FieldText(pDX, IDC_PRODUCTID, m_pSet->m_ProductID, m_pSet);

The first argument is the pDXargument that is passed to the DoDataExchange()function The secondargument is the ID for the first edit control in the dialog box for the view, the third argument uses them_pSetmember of the CProductViewclass to access the m_ProductIDmember of the recordset object,and the last argument is the pointer to the recordset object

946

Trang 12

You can therefore fill out the code for the DoDataExchange()function in the CProductViewclass like this:void CProductView::DoDataExchange(CDataExchange* pDX)

{CRecordView::DoDataExchange(pDX);

DDX_FieldText(pDX, IDC_PRODUCTID, m_pSet->m_ProductID, m_pSet);

DDX_FieldText(pDX, IDC_PRODUCTNAME, m_pSet->m_ProductName, m_pSet);

DDX_FieldText(pDX, IDC_UNITPRICE, m_pSet->m_UnitPrice, m_pSet);

DDX_FieldText(pDX, IDC_UNITSINSTOCK, m_pSet->m_UnitsInStock, m_pSet);

DDX_FieldText(pDX, IDC_CATEGORYID, m_pSet->m_CategoryID, m_pSet);

DDX_FieldText(pDX, IDC_UNITSONORDER, m_pSet->m_UnitsOnOrder, m_pSet);

CProductSet

UnitsinStock

ProductName

ProuctID

m_ProductNamem_ProductID

m_UnitslnStock

Trang 13

The recordset class and the record view class cooperate to enable data to be transferred between thedatabase and the controls in the dialog box The CProductSetclass handles transfers between thedatabase and its data members and CProductViewdeals with transfers between the data members ofCProductSetand the controls in the dialog.

Exercising the Example

Believe it or not you can now run the example Just build it in the normal way and then execute it Theapplication should display a window similar to the one shown in Figure 19-20

Sor ting a Recordset

As you saw earlier, the data is retrieved from the database by the recordset, using an SQLSELECTment that is generated by the framework using the GetDefaultSQL()member You can add an ORDER

state-BYclause to the statement generated by setting a value in the m_strSortmember of CProductSet,which is inherited from CRecordSet This causes the output table from the query to be sorted, based onthe string stored in m_strSort You need to set only the m_strSortmember to a string that contains thefield name or names that you want to sort on; the framework provides the ORDER BYkeywords Whereyou have multiple names, you separate them by commas But where should you add the code to do this?

948

Trang 14

The transfer of data between the database and the recordset occurs when the Open()member of therecordset object is called In your program, the Open()function member of the recordset object is called

by the OnInitialUpdate()member of the base class to the view class, CRecordView You can, therefore,put the code for setting the sort specification in the OnInitialUpdate()member of the CProductViewclass, as follows:

void CProductView::OnInitialUpdate(){

m_pSet = &GetDocument()->m_productSet;

m_pSet->m_strSort = “[CategoryID],[ProductID]”; // Set the sort fieldsCRecordView::OnInitialUpdate();

}

You just set m_strSortin the recordset to a string containing the name of the category ID field followed

by the name of the product IDfield Square brackets are useful, even when there are no blanks in aname, because they differentiate strings containing these names from other strings, so you can immedi-ately pick out the field names They are, of course, optional if there are no blanks in the field name

Modifying the Window Caption

There is one other thing you could add to this function at this point The caption for the window would

be better if it showed the name of the table being displayed You can arrange for this to happen byadding code to set the title in the document object:

void CProductView::OnInitialUpdate(){

m_pSet = &GetDocument()->m_productSet;

m_pSet->m_strSort = “[CategoryID],[ProductID]”; // Set the sort fieldsCRecordView::OnInitialUpdate();

// Set the document title to the table name

if (m_pSet->IsOpen()) // Verify the recordset is open{

CString strTitle = _T(“Table Name”); // Set basic title stringCString strTable = m_pSet->GetTableName();

if(!strTable.IsEmpty()) // Verify we have a table namestrTitle += _T(“: “) + strTable; // and add to basic titleGetDocument()->SetTitle(strTitle); // Set the document title}

}

After checking that the recordset is indeed open, you initialize a local CStringobject with a basic titlestring You then get the name of the table from the recordset object by calling its GetTableName()mem-ber In general, you should check that you do get a string returned from the GetTableName()function.Various conditions can arise that can prevent a table name from being set — for instance, there may bemore than one table involved in the recordset After appending a colon followed by the table name youhave retrieved to the basic title in strTitle, you set the result as the document title by calling the docu-ment’s SetTitle()member

Trang 15

If you rebuild the application and run it again, it works as before but with a new window caption asFigure 19-21 shows The product IDs are in ascending sequence within each category ID, with the cate-gory IDs in sequence, too.

Figure 19-21

Using a Second Recordset Object

Now that you can view all the products in the database, a reasonable extension of the program would be

to add the ability to view all the orders for any particular product To do this, you’ll add another set class to handle order information from the database and a complementary view class to display some

record-of the fields from the recordset You’ll also add a button to the Products dialog to enable you to switch tothe Orders dialog when you want to view the orders for the current product This enables you to operatewith the arrangement shown in Figure 19-22

The Products dialog box is the starting position where you can step backwards and forwards through allthe available products Clicking the Show Orders button switches you to the dialog where you can viewall the orders for the current product You can return to the Products dialog box by clicking the ShowProducts button

Adding a Recordset Class

You can start by adding the recordset class for the orders; right-click DBSamplein Class View and select Add > Classfrom the pop-up Select MFC from the set of Visual C++ categories and MFC ODBCConsumer as the template When you click the Addbutton in the Add Class dialog box that is displayed,you’ll see the MFC ODBC Consumer Wizard dialog box shown in Figure 19-23

950

Trang 16

You’ll select two tables to associate with the new recordset class that you’re going to create, so hold theCtrlkey down and select the Orders and Order Details table names You can then click the OKbutton tocomplete the selection process This returns you to the MFC ODBC Consumer dialog boxwhere you’llsee the class name and file names have been entered You can change the class name to COrderSetandthe file names in a corresponding way, as shown in Figure 19-25.

Clicking on the Finishbutton completes the process and causes the COrderSetclass to generate

EditProduct ID

EditUnit Price

EditProduct Name

EditCategory ID

Pressing the Show Orders buttonwill open the Orders dialogfor the current product

Pressing the Show Productsbutton will return tothe Products dialog

Units In Stock

Sample edUnits On Order

This dialog will allow you

to step through theproducts available

This dialog will allow you

to step through all theorders for a given product

EditOrder ID

EditQuantity

EditProduct ID

EditCustomer ID

Show Products

Trang 17

Figure 19-23

Figure 19-24

952

Trang 18

Figure 19-25

As you saw in the CProductSetclass that was created as part of the initial project, the implementation

of the GetDefaultConnect()function for the COrderSetclass is preceded by an #errordirective thatprevents compilation from succeeding, so comment that out

A data member has been created in the COrderSetclass for every field in each of the tables When youselect two or more tables for a given recordset, it is always possible, indeed likely, that there are fieldnames duplicated; the OrderIDfield appears in both tables, for example To ensure the names corre-sponding to field data members are always differentiated, the field names are prefixed with the tablename in each case If you don’t want to keep all these fields, you can delete or comment out any of them,but as I said earlier, you must take care not to delete any variables that are primary keys When you delete

a data member for a table field, you must also delete the initialization for it in the class constructor andthe RFX_()call for it in the DoFieldExchange()member function You must also change the initialvalue for the m_nFieldsmember in the COrderSetconstructor so that it reflects the number of fields left

in the class The data members that you need to keep for this example are as follows: m_OrdersOrderID,m_OrderDetailsOrderID, m_OrderDetailsProductID, m_OrderDetailsQuantity,and

m_OrdersCustomerID If you keep just these you should change the value assigned to m_nFieldsto 5.Change the members of type CStringWto type CString

To hook the new recordset to the document, you need to add a data member to the definition of theCDBSampleDocclass, so right-click the class name in Class View and select Add Member Variable fromthe pop-up Specify the type as COrderSetand the variable name as m_OrderSet You can leave it as apublicmember of the class Click OK to finish adding the data member to the document The compilerhas to understand that COrderSetis a class before it begins compiling the CBSampleDocclass If youtake a look at the contents of the DBSampleDoc.hheader file, you will see that an #includestatementhas already been added for you to the top of DBSampleDoc.h:

Trang 19

Adding a View Class for the Recordset

At this point you might expect to be adding a class derived from CRecordViewusing the Add > Classmenu item in the Class View context menu to display the data from the COrderSetobject This used

to be possible in earlier versions of Visual C++, but unfortunately the Visual C++ 2005 product does not provide for this The dialog box for adding a new class does not allow the possibility of selectingCRecordViewas a base class at all, so you must always create classes that have CRecordViewas a basemanually

You need to create another dialog resource before you create the view class so you have the ID for theresource that you can use in the definition of the view class

Creating the Dialog Resource

Switch to Resource View, right-click the Dialog folder, and select Insert Dialog from the context menu.You can delete both of the default buttons from the dialog Now change the ID and styles for the dialog,

so right-click it and display its properties by selecting Properties from the pop-up Change the ID erty to IDD_ORDERS_FORM You also need to change the Styleproperty to Childand the Borderprop-erty to None

prop-You’re now ready to populate the dialog box with controls for the fields that you want to display fromthe Orders and Order Details tables If you switch to Class View and select the COrderSetclass name,you’ll be able to see the names of the variables concerned while you are working on the dialog Add con-trols to the dialog box as shown in Figure 19-26

Figure 19-26

Here, there are four edit controls for the OrderID, CustomerID, ProductID, and Quantityfields fromthe tables associated with the COrderSetclass, together with static controls to identify them You canadd controls to display a few more fields if you want, as long as you haven’t deleted the class members.Don’t forget to modify the IDs for the edit controls so that they are representative of the purpose of thecontrol You can use the table field names prefixed by the table name to match the data member names.Finally, you need to make the edit controls read-only by setting the Read Onlyproperty to Truefor eachcontrol Alternatively, you can set them all to read-only in one go by selecting each of them with the Ctrlkey held down and then setting the Read Onlyproperty to True

954

Trang 20

The button control labeled Show Productsis used to return to the Products table view, so modify the

ID for this button to IDC_PRODUCTS When you have arranged everything to your liking, save the dialogresource

Creating the Record View Class

Create the OrderView.hfile that holds the COrderViewclass definition To do this just right-clickDBSamplein Solution Explorer and select Add >| New Itemfrom the list Choose the template to create a hfile and enter the file name as OrderView After you have created the file, you can add thecode for the class definition as:

#pragma onceclass COrderSet; // Declare the class nameclass CDBSampleDoc; // Declare the class name// COrderView form view

class COrderView : public CRecordView{

DECLARE_DYNCREATE(COrderView)protected:

return reinterpret_cast<CDBSampleDoc*>(m_pDocument);

}COrderSet* GetRecordset();

virtual CRecordset* OnGetRecordset();

COrderView(); // constructor now public

#ifdef _DEBUGvirtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif};

This code is based on the CProductViewthat was generated The DECLARE_DYNCREATEmacro enablesobjects of this class type to be created by the MFC framework at runtime In general MFC document,view, and frame classes should define this macro You will add the complementary IMPLEMENT_

DYNCREATEmacro to the cppfile a little later I have omitted the debug version of the GetDocument()because the CProductViewclass contains a version of the function that validates the document object

Trang 21

The inline version in the COrderViewclass definition just assumes the cast to CDBSampleDoc*will be

OK I have included declarations for AssertValid()and Dump()that is compiled only when debugmode is in effect, so definition has to be included in the cppfile for the class The enumeration definesthe ID for the dialog and you will use this in the definition of the constructor The m_pSetmember willhold the address of the recordset object that supplies the data displayed by this view

The implementation of the COrderViewclass goes in the OrderView.cppfile, so create that file withinthe project using the procedure you followed for the hfile You can add the initial #includedirectivefor the classes you will need to reference:

Trang 22

void COrderView::DoDataExchange(CDataExchange* pDX){

There are two functions to be defined that are involved in retrieving the recordset You’ll call theGetRecordset()function to obtain a pointer to the COrderSetobject encapsulating the recordset Youcan implement this as follows:

COrderSet* COrderView::GetRecordset(){

pro-The OnGetRecordset()function is a pure virtual function in the base class, so you must define it here.You can implement it as

CRecordset* COrderView::OnGetRecordset(){

Customizing the Recordset

As it stands, the SQLSELECToperation for a COrderSetobject produces a table that will contain allcombinations of records from the two tables involved This could be a lot of records, so you must add

Trang 23

the equivalent of a WHEREclause to the query to restrict the records selected to those that make sense Butthere is another problem, too: when you switch from the Productstable display, you don’t want to look

at just any old orders You want to see precisely those orders for the product ID we were looking at, whichamounts to selecting only those orders that have the same product ID as that contained in the currentCProductSetrecord This is also effected through a WHEREclause In the MFC context, the WHEREclausefor a SQLSELECToperation for a recordset is called a filter.

Adding a Filter to the Recordset

You add a filter to the query by assigning a string to the m_strFiltermember of the recordset object.This member is inherited from the base class, CRecordSet As with the ORDER BYclause, which youadded by assigning a value to the m_strSortmember of the recordset, the place to implement this is inthe OnInitialUpdate()member of the record view class, just before the base class function is called.You want to set two conditions in the filter One is to restrict the records generated in the recordset tothose where the OrderIDfield in the Orderstable is equal to the field with the same name in the OrderDetailstable You can write this condition as:

[Orders].[OrderID] = [Order Details].[OrderID]

The other condition you want to apply is that, for the records meeting the first condition, you want onlythose with a ProductIDfield that is equal to the ProductIDfield in the current record in the recordsetobject displaying the Productstable This means that you need to have the ProductIDfield from theCOrderSetobject compared to a variable value The variable in this operation is called a parameter, and

the condition in the filter is written in a special way:

ProductID = ?

The question mark represents a parameter value for the filter, and the selected records are those wherethe ProductIDfield equals the parameter value The value that is to replace the question mark is set inthe DoFieldExchange()member of the recordset You’ll implement this in a moment, but first you’llcomplete the specification of the filter

You can define the string for the filter variable that incorporates both the conditions that you need withthe statement:

// Set the filter as Product ID field with equal Order IDs

m_pSet->m_strFilter =

“[ProductID] = ? AND [Orders].[OrderID] = [Order Details].[OrderID]”;

You’ll incorporate this into the OnInitialUpdate()member of the COrderViewclass, but before that,you’ll finish setting the parameter for the filter

Defining the Filter Parameter

Add a data member to the COrderSetclass to store the current value of the ProductIDfield from the CProductSetobject This member also actS as the parameter to substitute for the ?in the filter forthe COrderSetobject So, right-click the COrderSetclass name in Class View and select Add > AddVariablefrom the pop-up The variable type needs to be the same as that of the m_ProductIDmember

of the CProductSetclass, which is type long, and you can specify the name as m_ProductIDparam

958

Trang 24

You can also leave it as a publicmember You need to initialize this data member in the constructor andalso set the parameter count The application framework requires the count of the number of parameters

in your recordset to be set to reflect the number of parameters you are using; otherwise, it won’t workcorrectly Add the shaded code shown below to the COrderSetconstructor definition:

COrderSet::COrderSet(CDatabase* pdb) : CRecordset(pdb){

}

All of the unshaded code was supplied by Class wizard to initialize the data members corresponding tothe fields in the recordset and to specify the type as snapshot You should delete the initialization forthe other fields in the recordset The new code initializes the parameter to zero and sets the count of thenumber of parameters to 1 The m_nParamsvariable is inherited from the base class, CRecordSet.Because there is a parameter count, you can deduce that you can have more than one parameter in thefilter for a recordset

At this point you can also remove or comment out the members from the COrderSet class that storefields from the recordset that you won’t need Remove or comment out the fields that are not requiredfrom the class definition, just leaving the following:

long m_OrderDetailsOrderID; // Same as Order ID in Orders table

long m_OrderDetailsProductID; // Same as Product ID in Products table

int m_OrderDetailsQuantity;

long m_OrdersOrderID; // Unique order number

CString m_OrdersCustomerID; // Same entry as in Customers table

long m_ProductIDparam;

To identify the m_ProductIDparamvariable in the class as a parameter to be substituted in the filter forthe COrderSetobject, you must also add some code to the DoFieldExchange()member of the class:void COrderSet::DoFieldExchange(CFieldExchange* pFX)

{pFX->SetFieldType(CFieldExchange::outputColumn);

RFX_Long(pFX, _T(“[Order Details].[OrderID]”), m_OrderDetailsOrderID);

RFX_Long(pFX, _T(“[Order Details].[ProductID]”),

m_OrderDetailsProductID);

RFX_Int(pFX, _T(“[Order Details].[Quantity]”), m_OrderDetailsQuantity);

RFX_Long(pFX, _T(“[Orders].[OrderID]”), m_OrdersOrderID);

RFX_Text(pFX, _T(“[Orders].[CustomerID]”), m_OrdersCustomerID);

// Set the field type as parameterpFX->SetFieldType(CFieldExchange::param);

RFX_Long(pFX,_T(“ProductIDParam”), m_ProductIDparam);

}

Trang 25

The Class wizard provided code to transfer data between the database and the field variables it hasadded to the class There will be one RFX_()function call for each data member of the recordset Youcan delete those that are not required in this application, leaving just those shown in the preceding code.The first new line of code calls the SetFieldType()member of the pFXobject to set the mode for thefollowing RFX_()call to param The effect of this is to cause the third argument in any succeedingRFX_()calls to be interpreted as a parameter that is to replace a ?in the filter for the recordset If youhave more than one parameter, the parameters substitute for the question marks in the m_strFilterstring in sequence from left to right, so it’s important to ensure that the RFX_()calls are in the rightorder when there are several With the mode set to param, the second argument in the RFX_()call isignored, so you could put NULLhere or some other string if you want.

Initializing the Record View

You can now implement the override for the OnInitialUpdate()function in the COrderViewclass.This function is called by the MFC framework before the view is initially displayed, so you can put code

in this function to do any one-time initialization that you need In this case you will specify the filter forthe recordset Here’s the definition of the function to do this:

void COrderView::OnInitialUpdate()

{

BeginWaitCursor();

CDBSampleDoc* pDoc = static_cast<CDBSampleDoc*>(GetDocument());

m_pSet = &pDoc->m_OrderSet; // Get a pointer to the recordset// Use the DB that is open for products recordset

imple-The BeginWaitCursor()call at the start of the OnInitialUpdate()function displays the hourglass sor while this function is executing The reason for this is that this function can take an appreciable time toexecute, especially when multiple tables are involved The processing of the query and the transfer of data

cur-to the recordset all takes place in here The cursor is returned cur-to normal by the EndWaitCursor()call atthe end of the function

960

Trang 26

The first thing that the code does is to set the m_pDatabasemember of the COrderSetobject to thesame as that for the CProductSetobject If you don’t do this, the framework re-opens the databasewhen the orders recordset is opened Because the database has already been opened for the productsrecordset, this wastes a lot of time.

Next, you set the value for the m_ProductIDparamparameter variable to the current value stored in the m_ProductIDmember of the products recordset This value replaces the question mark in the filterwhen the orders recordset is opened, so select the records you want; then set the filter for the ordersrecordset to the string you saw earlier

Accessing Multiple Table Views

Because you have implemented the program with the single document interface, the application has onedocument and one view The availability of just one view might appear to be a problem, but it isn’t inpractice You can arrange for the frame window object in the application to create an instance of theCOrderViewclass and switch the current window to that when the orders recordset is to be displayed.You’ll need to keep track of what the current window is, which you can do by assigning a unique ID toeach of the record view windows in the application At the moment there are two views: the productsview and the orders view To define IDs for these, create a new file called OurConstants.hand add thefollowing code to it:

// Definitions for our constants

#pragma once// Arbitrary constants to identify record viewsconst unsigned int PRODUCT_VIEW = 1;

const unsigned int ORDER_VIEW = 2;

You can now use one of these constants to identify each view and to record the ID of the current view inthe frame window object To record the current view ID, add a publicdata member to the CMainFrameclass of type unsigned intand give it the name m_CurrentViewID After you have done that, you caninitialize it in the constructor for CMainFrameby adding code as follows:

CMainFrame::CMainFrame() : m_CurrentViewID(PRODUCT_VIEW){

Trang 27

Right-click CMainFrameand select Add > Add Functionfrom the pop-up to add a new public member

to the class You can enter the return type as voidand the function name as SelectView The parametername can be ViewID,and the type is unsigned int You can implement the function as follows:void CMainFrame::SelectView(unsigned int ViewID)

{

CView* pOldActiveView = GetActiveView(); // Get current view

// Get pointer to new view if it exists

// if it doesn’t the pointer will be null

CView* pNewActiveView = static_cast<CView*>(GetDlgItem(ViewID));

// If this is 1st time around for the new view,

// the new view won’t exist, so we must create it

if (pNewActiveView == NULL)

{

switch(ViewID){

case ORDER_VIEW: // Create an Order viewpNewActiveView = new COrderView;

context.m_pCurrentDoc = pOldActiveView->GetDocument();

pNewActiveView->Create(NULL, NULL, 0L, CFrameWnd::rectDefault,

this, ViewID, &context);

pNewActiveView->OnInitialUpdate();

}

SetActiveView(pNewActiveView); // Activate the new view

pOldActiveView->ShowWindow(SW_HIDE); // Hide the old view

pNewActiveView->ShowWindow(SW_SHOW); // Show the new view

pOldActiveView->SetDlgCtrlID(m_CurrentViewID); // Set the old view ID

pNewActiveView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

m_CurrentViewID = ViewID; // Save the new view ID

RecalcLayout();

}

The operation of the function falls into three distinct parts:

1. Getting pointers to the current view and the new view

2. Creating the new view if it doesn’t exist

3. Swapping to the new view in place of the current view

The address of the current active view is supplied by the GetActiveView()member of the CMainFrameobject To get a pointer to the new view, you call the GetDlgItem()member of the frame window

962

Trang 28

object If a view with the ID specified in the argument to the function exists, it returns the address of theview; otherwise, it returns NULL,and you need to create the new view.

After creating a view object, you define a CCreateContextobject, context ACCreateContextobject

is necessary only when you are creating a window for a view that is to be connected to a document ACCreateContextobject contains data members that can tie together a document, a frame window, and

a view, and for MDI applications, a document template as well When you switch between views, youwill create a new window for the new view to be displayed Each time you create a new view window,you use the CCreateContextobject to establish a connection between the view and your documentobject You need to store a pointer to the document object only in the m_pCurrentDocmember on context In general, you may need to store additional data in the CCreateContextobject before youcreate the view; it depends on the circumstances and what kind of window you are creating

In the call to the Create()member of the view object that creates the window for the new view, youpass the contextobject as an argument This establishes a proper relationship with your document andvalidates the document pointer The argument thisin the call to Create()specifies the current frame

as the parent window, and the ViewIDargument specifies the ID of the window This ID enables theaddress of the window to be obtained with a subsequent call to the GetDlgItem()member of the par-ent window

To make the new view the active view, you call the SetActiveView()member of CMainFrame The newview then replaces the current active view To remove the old view window, you call the ShowWindow()member of the view with the argument SW_HIDEusing the pointer to the old view To display the newview window, you call the same function with the argument SW_SHOWusing the pointer to the new view SetActiveView(pNewActiveView); // Activate the new view

pOldActiveView->ShowWindow(SW_HIDE); // Hide the old viewpNewActiveView->ShowWindow(SW_SHOW); // Show the new viewpOldActiveView->SetDlgCtrlID(m_CurrentViewID); // Set the old view IDpNewActiveView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

m_CurrentViewID = ViewID; // Save the new view ID

You restore the ID of the old active view to the ID value that you have defined for it in them_CurrentViewIDmember of the CMainFrameclass that you added earlier You also set the ID of thenew view to AFX_IDW_PANE_FIRSTto identify it as the first window for the application This is neces-sary because the application has only one view, so the first view is the only view Lastly, you save the

ID for the new window in the m_CurrentViewIDmember, so it’s available the next time the currentview is replaced The call to RecalculateLayout()causes the view to be redrawn when the new view

is selected

You must add a #includedirective for the OrderView.hfile to beginning of the MainFrm.cppfile sothat the COrderViewclass definition is available here After you save MainFrm.cpp, you can move on toadding a button control to the Products dialog to link to the Orders dialog You’ll then be able to addhandlers for this button and its partner on the Orders dialog to call the SelectView()member ofCMainFrame

Enabling the Switching Operation

To implement the view switching mechanism, go back to Resource View and open the IDD_DBSAMPLE_FORMdialog You need to add a button control to the dialog box, as shown in Figure 19-27

Trang 29

You must add the following #includedirectives to the beginning of the ProductView.cppfile:

#include “OurConstants.h”

#include “MainFrm.h”

The next task is to add the handler for the button that you previously placed on the IDD_ORDERS_FORMdialog From the Editor window showing this dialog box, use the same process to add the OnProducts()handler to the COrderViewclass and add a single line of code to its implementation:

964

Trang 30

Handling View Activation

When you switch to a view that already exists, you need to ensure that the recordset is refreshed and thatthe dialog is re-initialized so that the correct information is displayed When an existing view is activated

or deactivated, the framework calls the OnActivateView()member of the class, so this is a good place totake care of refreshing the recordset and the dialog box You can override this function in each of the viewclasses You can do this by selecting the Overridesbutton in the Propertieswindow for a view classand selecting OnActivateViewfrom the list Make sure you add the override to both view classes

You can add the following code to complete the implementation of the OnActivateView()functionoverride for the COrderViewclass:

void COrderView::OnActivateView(BOOL bActivate,

CView* pActivateView, CView* pDeactiveView){

if(bActivate){

// Get a pointer to the documentCDBSampleDoc* pDoc = GetDocument();

// Get a pointer to the frame windowCMainFrame* pMFrame = static_cast<CMainFrame*>(GetParentFrame());

// If the last view was the product view, we must re-query// the recordset with the product ID from the product recordsetif(pMFrame->m_CurrentViewID==PRODUCT_VIEW)

{if(!m_pSet->IsOpen()) // Make sure the recordset is openreturn;

// Set current product ID as parameterm_pSet->m_ProductIDparam = pDoc->m_DBSampleSet.m_ProductID;

m_pSet->Requery(); // Get data from the DB// If we are past the EOF there are no recordsif(m_pSet->IsEOF())

AfxMessageBox(L”No orders for the current product ID”);

}// Set the window captionCString strTitle = _T(“Table Name: “);

CString strTable = m_pSet->GetTableName();

if(!strTable.IsEmpty())strTitle += strTable;

elsestrTitle += _T(“Orders - Multiple Tables”);

Trang 31

argu-present because the previous view is always the Product View, but when you add another view to theapplication, this will not always be true so you might as well put the code in now.

To re-query the database, you set the parameter member of COrderSet, m_ProductIDparam, to the rent value of the m_ProductIDmember of the product recordset This causes the orders for the currentproduct to be selected You don’t need to set the m_strFiltermember of the recordset here becausethat was set in the OnInitialUpdate()function when the CRecordViewobject was first created TheIsEOF()function member of the COrderSetobject is inherited from CRecordSetand returns TRUEifthe recordset is empty when it is re-queried

cur-Ad the code for OnActivateView()function in the CProductViewclass as follows:

void CProductView::OnActivateView(BOOL bActivate,

CView* pActivateView, CView* pDeactiveView){

if(bActivate){

// Update the window captionCString strTitle = _T(“Table Name”);

CString strTable = m_pSet->GetTableName();

strTitle += _T(“: “) + strTable;

GetDocument()->SetTitle(strTitle);

}CRecordView::OnActivateView(bActivate, pActivateView, pDeactiveView);

Viewing Orders for a Product

You are now ready to try to build the executable module for the new version of the example When yourun the example, you should be able to see the orders for any product just by clicking the Orders button

on the products dialog box A typical view of an order is shown in Figure 19-28

Figure 19-28

966

Trang 32

Clicking the Show Productsbutton returns you to the products dialog box, so you can browse furtherthrough the products In this dialog, you can use the toolbar buttons to browse all the orders for the cur-rent product The Customer IDis a bit cryptic You could add one more view to display the details ofthe customer’s name and address It won’t be too difficult, because you have built already the mecha-nism to switch between views.

V iewing Customer DetailsThe basic mechanism that you’ll add will work through another button control on the order dialog thatswitches to a new dialog for customer data As well as controls to display customer data, you’ll add two buttons to the customer dialog box: one to return to the Order View, and the other to return to theProduct View You’ll need another view ID corresponding to the customer view, which you can add withthe following line in the OurConstants.hfile:

const unsigned int CUSTOMER_VIEW = 3;

Now you’ll add the recordset for the customer details

Adding the Customer Recordset

The process is exactly the same as you followed for the COrderSetclass Use the Add >Classmenuitem from the context menu in Class View and use the MFC ODBC Consumer template to define theCCustomerSetclass, with CRecordsetspecified as the base class Select the database as Northwind asbefore and select the Customerstable for the recordset You can select snapshot as the type of access tothe table The class should then be created with the data members shown as follows:

You will find an #includedirective for CustomerSet.hthat has already been added to DBSampleDoc.h.After saving all the files you have modified, you can create the customer dialog resource

Trang 33

Creating the Customer Dialog Resource

This process is also exactly the same as the one you went through for the orders dialog Change toResource View and create a new dialog resource with the ID IDD_CUSTOMER_FORM, not forgetting to setthe style to Childand the border to Nonein the Propertiesbox for the dialog After deleting the defaultbuttons, add controls to the dialog box to correspond to the field names for the Customerstable, asshown in Figure 19-29:

Creating the Customer View Class

You’ll create the view class for the customer recordset manually, just as you did for the COrderViewclass Add CustomerView.hand CustomerView.cppfiles to the project and insert the following code

to define the class in the CustomerView.hfile:

// CCustomerView record view

Trang 34

enum { IDD = IDD_CUSTOMER_FORM };

virtual ~CCustomerView(){}

#ifdef _DEBUGvirtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif};

The class has essentially the same members as the COrderViewclass

You can add the following initial code to the CustomerView.cpp:// CCustomerView implementation

ASSERT(m_pSet != NULL);

return m_pSet;

}CRecordset* CCustomerView::OnGetRecordset(){

return m_pSet;

Trang 35

You can process the button controls in the IDD_CUSTOMER_FORMdialog box in the same way as you did previously to add the OnOrders()and OnProducts()functions to the CCustomerViewclass; right-click each button and select the Add Event Handler from the pop-up The code for these is similar

to the corresponding functions in the other views The code you need to add to OnOrders()in theCustomerView.cppfile is:

Trang 36

m_pSet = &pDoc->m_CustomerSet; // Initialize the recordset pointer// Set the DB for the customer recordset

CString strTitle = m_pSet->m_pDatabase->GetDatabaseName();

CString strTable = m_pSet->GetTableName();

if(!strTable.IsEmpty())strTitle += _T(“:”) + strTable;

GetDocument()->SetTitle(strTitle);

}EndWaitCursor();

}

After getting a pointer to the document, you store the address of the CCustomerSetobject member ofthe document in the m_pSetmember of the view You know the database is already open, so you can setthe database pointer in the customer recordset to that stored in the CProductSetobject

The parameter for the filter will be defined in the m_CustomerIDparammember of CCustomerSet.You’ll add this member to the class in a moment It’s set to the current value of the m_CustomerIDmem-ber of the COrderSetobject owned by the document You will define the filter in such a way that thecustomer recordset contains only the record with the same customer ID as that in the current order.The OnActivateView()function handles activation of the customer view, and you can implement it inCustomerView.cppas follows:

void CCustomerView::OnActivateView(BOOL bActivate,

CView* pActivateView, CView* pDeactiveView){

if(bActivate){

if(!m_pSet->IsOpen())return;

CDBSampleDoc* pDoc = static_cast<CDBSampleDoc*>(GetDocument());

// Set current customer ID as parameterm_pSet->m_CustomerIDparam = pDoc->m_OrderSet.m_OrdersCustomerID;

m_pSet->Requery(); // Get data from the DBCRecordView::OnInitialUpdate(); // Redraw the dialog// Check for empty recordset

if(m_pSet->IsEOF())AfxMessageBox(L”No customer details for the current customer ID”);

CString strTitle = _T(“Table Name:”);

CString strTable = m_pSet->GetTableName();

Trang 37

if(!strTable.IsEmpty())strTitle += strTable;

elsestrTitle += _T(“Multiple Tables”);

The m_CustomerIDparammember for the CCustomerSetrecordset object that is associated with thisview object is set to the customer ID from the orders recordset object that is stored in the document Thiswill be the customer ID for the current order The call to the Requery()function for the CCustomerSetobject retrieves records from the database using the filter you have set up The result are that the detailsfor the customer for the current order are stored in the CCustomerSetobject and then passed to theCCustomerViewobject for display in the dialog

You will need to add the following #includestatements to the beginning of the CustomerView.cppfile:

Implementing the Filter Parameter

Add a publicvariable of type CStringto the CCustomerSetclass to correspond with the type of them_CustomerIDmember of the recordset and give it the name m_CustomerIDparam If you used the Add

> Add Variable mechanism from Class View to do this, the new member will already be initialized in theconstructor; otherwise, add the initialization as in the code that follows You can set the parameter count

in the CCustomerSetconstructor as follows:

Trang 38

RFX_Text(pFX, _T(“[CustomerID]”), m_CustomerID);

RFX_Text(pFX, _T(“[CompanyName]”), m_CompanyName);

RFX_Text(pFX, _T(“[ContactName]”), m_ContactName);

RFX_Text(pFX, _T(“[ContactTitle]”), m_ContactTitle);

RFX_Text(pFX, _T(“[Address]”), m_Address);

RFX_Text(pFX, _T(“[City]”), m_City);

RFX_Text(pFX, _T(“[Region]”), m_Region);

RFX_Text(pFX, _T(“[PostalCode]”), m_PostalCode);

RFX_Text(pFX, _T(“[Country]”), m_Country);

RFX_Text(pFX, _T(“[Phone]”), m_Phone);

RFX_Text(pFX, _T(“[Fax]”), m_Fax);

pFX->SetFieldType(CFieldExchange::param); // Set parameter modeRFX_Text(pFX, _T(“CustomerIDParam”), m_CustomerIDparam);

}

I have omitted comment lines from the beginning of this function to save space After setting the parammode by calling the SetFieldType()member of the pFXobject, you call the RFX_Text()function topass the parameter value for substitution in the filter You use RFX_Text()because the parameter vari-able is of type CString There are various RFX_()functions supporting a range of parameter types.After you have completed this modification, you can save the CustomerSet.cppfile

Linking the Order Dialog to the Customer Dialog

To permit a switch to the customer dialog, you require a button control on the IDD_ORDERS_FORMlog, so open it in Resource View and add an extra button, as shown in Figure 19-30

Trang 39

dia-Figure 19-30

I have rearranged the original controls a little — you can arrange them to please yourself You can definethe ID for the new button control as IDC_CUSTOMER After you save the dialog, you can add a handlerfor the button by right-clicking it and select Add Event Handlerfrom the pop-up The handler requiresonly one line of code to be added to it, as follows:

void COrderView::OnCustomer()

{

static_cast<CMainFrame*>(GetParentFrame())->SelectView(CUSTOMER_VIEW);

}

This obtains the address of the frame window and uses it to call the SelectView()member of

CMainFrameto switch to a customer view The penultimate step to complete the program is to add thecode to the SelectView()function that deals with the CUSTOMER_VIEWvalue being passed to it Thisrequires just three additional lines of code, as follows:

void CMainFrame::SelectView(UINT ViewID)

{

CView* pOldActiveView = GetActiveView(); // Get current view

// Get pointer to new view if it exists

// if it doesn’t the pointer will be null

CView* pNewActiveView = static_cast<CView*>(GetDlgItem(ViewID));

// If this is 1st time around for the new view,

// the new view won’t exist, so we must create it

if (pNewActiveView == NULL)

{

switch(ViewID){

case ORDER_VIEW: // Create an Order viewpNewActiveView = new COrderView;

Trang 40

}// Switching the views// Obtain the current view context to apply to the new viewCCreateContext context;

context.m_pCurrentDoc = pOldActiveView->GetDocument();

pNewActiveView->Create(NULL, NULL, 0L, CFrameWnd::rectDefault,

this, ViewID, &context);

pNewActiveView->OnInitialUpdate();

}SetActiveView(pNewActiveView); // Activate the new viewpOldActiveView->ShowWindow(SW_HIDE); // Hide the old viewpNewActiveView->ShowWindow(SW_SHOW); // Show the new viewpOldActiveView->SetDlgCtrlID(m_CurrentViewID); // Set the old view IDpNewActiveView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);

m_CurrentViewID = ViewID; // Save the new view IDRecalcLayout();

}

The only change necessary is the addition of a casestatement in the switchto create a CCustomerViewobject when one doesn’t exist Each view object will be re-used next time around, so they get createdonly once The code to switch between views works with any number of views, so if you want this func-tion to handle more views, you just need to add another casein the switchfor each new view that youwant Although you are creating view objects dynamically here, you don’t need to worry about deletingthem Because they are associated with a document object, they are deleted by the framework when theapplication closes

Because you reference the CCustomerViewclass in the SelectView()function, you must add an

#includestatement for the CustomerView.hfile to the block at the beginning of MainFrm.cpp

To complete the application you can add the implementation of the DoDataExachange()function forthe CCustomerViewclass to CustomerView.cpp:

void CCustomerView::DoDataExchange(CDataExchange* pDX){

Ngày đăng: 13/08/2014, 18:20