Back to  main page

 

Chapter 14 Views

We are going to introduce various types of views in this chapter. In MFC, there are several types of standard views that are supported by MFC classes. These views include edit view, rich edit view, list view, form view and tree view. The classes that can be used to implement them are CEditView, CRichEditView, CListView, CFormView and CTreeView respectively. They can be used to display plain text, formatted text, tree, list, etc.

14.1 Edit View

Edit view is most suitable for implementing plain text editor. Remember in Chapter 9 when building one-line text editor, we had to write a lot of code in order to add some basic editing functions. Actually, if we use edit view to implement text editor, we can build a notepad-like application within just few minutes. With the edit view, the serialization can be implemented by the member function of class CEditView, so we don't even need to write code for handling data loading and storing. Also, this class supports commands such as string search & replace, cut, copy and paste.

Generating the Application

A plain text editor can be generated through using Application Wizard. We can let the editor support files with ".txt" extension as we go through the steps of generating application skeleton (In order to do this, we need to click "Advanced" button in step 4, and input appropriate file extension into the edit box labeled with "File extension"), or we can edit IDR_MAINFRAME string resource after the application is created. We need to select CEditView as the base class of the view in the last step. By doing so, after the application is first compiled, we will have a very simple notepad-like application.

Sample 14.1\NotePad is generated this way. We even don't need to customize function CDocument:: Serialize(...) in order to support file I/O. Lets take a look at the default implementation of serialization:

void CNotePadDoc::Serialize(CArchive& ar)

{

((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);

}

Everything is handled by function CEditView::SerializeRaw(...), which includes both data reading and writing.

Also, there is no need for us to write message handlers for Edit | Undo, Edit | Cut, Edit | Copy and Edit | Paste commands in order to enable them. If the mainframe menu contains the following IDs for these commands (which is the default feature for any project generated by Application Wizard), the application will automatically support the above commands:

(Table omitted)

The reason for this is that CEditView already maps commands with the above-mentioned IDs to its built-in member functions that handle undo, cut, copy and paste commands. The name of these functions are not documented in the current version of Visual C++, this means these function are not guaranteed to be supported in the future.

If we want to use other command IDs instead of the recommended ones, we need to implement command message mapping by ourselves. In order to do so, we need to look at the MFC source code that contains member functions of class CEditView, find out the function names that support these commands, and map the WM_COMMAND type messages to the appropriate functions.

Search Related Commands

In the sample, three other standard commands, Search | Find..., Search | Replace..., and Search | Find Next are implemented this way. The three commands are used for searching a specific string in the text contained in the view, replacing an old string with a new one, or repeating searching. The default IDs for these commands are listed in the following table:

(Table omitted)

Message UPDATE_COMMAND_UI is also handled for the above commands.

In the sample, we use following non-standard command IDs to implement find, replace, and repeat commands:

(Table omitted)

The message mapping is done in the implementation file "NPView.cpp":

(Code omitted)

With the above implementation, there is no need for us to declare and define new member functions to handle the above commands, everything will be handled automatically.

Other Commands

In the sample, some other commands that are not supported by class CEditVew are also implemented. These command include Edit | Delete, which can be used to delete the current selection; Edit | Select All, which can be used to select all the text contained in the window, and Edit | Time/Date, which can be used to insert a time/date stamp at the current caret position. For these commands, the message mapping and message handlers need to be implemented by ourselves.

Edit view is implemented by an embedded edit control, which can be accessed by calling function CEditView::GetEditCtrl(). Once this is done, we can call any member function of CEdit and make change to the text contained in the window. For example, if we want to replace the selected text with a new string or insert a string at the current caret position, we can call function CEdit::ReplaceSel(...) to do so. If we want to select all the text, we can call function CEdit::SetSel(...) and pass 0 and -1 to its first two parameters. If we want to delete the selected text, we just need to call function CEdit::Clear().

The following shows how command Edit | Time/Date is implemented in the sample:

(Code omitted)

First the current time is obtained by calling function CTime::GetCurrentTime(), which is stored in a CTime type variable. Then it is formatted and output to a CString type variable by calling function CTime:: Format(...). Finally, function CEdit::ReplaceSel(...) is called to insert the time stamp.

By now, our sample is almost the same with standard "Notepad" application. Obviously deriving class from standard MFC class saves us a lot of work. If we implement the file I/O and formatted text display by ourselves, we need to write a lot of source code.

14.2 Rich Edit View

Sample 14.2\Wordpad is an SDI application generated from Application Wizard, it will be implemented as a "Wordpad" application.

The edit view has very limited feature: it can store only text less than 64Kbytes, and it supports only one type of font. Furthermore, we can not edit graphics in the editor.

A more advanced editor can be implemented by using Rich edit view and Rich edit document. Two classes that support this type of view and document are CRichEditView and CRichEditDoc respectively. With the two classes, we can easily build a Wordpad-like application, which supports rich text format (one of the formats supported by Microsoft Word), OLE container and many default editing functions.

It seems that building a Wordpad-like application is very difficult, because it has too many features. However, with CRichEditView and CRichEditDoc classes, this procedure is almost equally simple with creating a "Notepad" application as we did in the previous section. To build a Wordpad-like application, we can start from Application Wizard to build a standard SDI application. To support ".rtf" file extension in our application (which is the default extension for rich edit format files), in step 4, we need to click "Advanced" button, input "rtf" in the edit box labeled with "File extension". In the final step, we also need to select CRichEditView as the base class for view implementation.

Generally, the base class for implementing application's document can not be customized. It will be derived from CDocument by default. However, if class CRichEditView is selected as base class for view implementation, the base class for document implementation will automatically be changed to CRichEditDoc. Also, some new commands will be added to the default mainframe menu IDR_MAINFRAME under "Edit" sub-menu (These new commands will be added by Application Wizard).

In order to include graphic objects (or other OLE server objects), we need to make the application an OLE container. By doing this, we can embed all OLE servers in the application. One example of such type of applications is "Paint". With this implementation, the application can be used to edit not only text, but also graphic objects. The OLE container selection can be set in the second step of Application Wizard. If we forgot this, we will be prompted to do so when button "Finish" is clicked in the final step.

By only working with Application Wizard, we are able to create a simple Wordpad. If we compile the application at this point, we will have a standard editor that allows us to insert various types of objects. The type of objects that can be embedded in the application depends on which OLE servers have registered in the system. To insert an object, we can execute command Edit | Insert New Object..., and select an object from the popped dialog box. If we select "Bitmap Image" object, the application will turn into a "Paint" application, with will let us edit bitmap in place.

Another feature of this editor is Edit | Paste Special... command. Because the editor supports not only text, but also graphic editing now, the paste command should support multiple-format data. If we execute command Edit | Paste Special..., a dialog box will pop up indicating the available data formats contained in the clipboard, from which we can make the selection. For example, if we paste a bitmap, we have the choice to paste it either as bitmap format, metafile format or DIB format.

The third feature of this editor is font selection. We may want to format different portion of the text using a different font. This is the default feature of class CRichEditView. The command ID that can be used to format the selected text using a specific font is ID_FORMAT_FONT (class CRichEditView supports this ID). So all we need to do is adding a Format | Font... command to menu IDR_MAINFRAME. With this simple implementation, we are able to format the text with any font that is available in the system.

Although it is very easy to build a fully functional application with a lot of enticing features, it is relatively difficult to make modifications. For example, the standard Wordpad application under Windows( has a ruler and a format bar, if we want to add these features, we need to add them by ourselves.

Customizing File Open Dialog Box

Class CRichEditView and CRichEditDoc can handle not only rich text format, but also plain text format (or ASCII format). By default, the two classes interpret input data using rich text format, if the format is different, the file will not be loaded. To let the application also support plain text format, we need to include multiple document types in "File Open" and "Save As" file dialog boxes, this gives the user an option for specifying file type. Although we can register more than one type of document to implement this, in the case of rich edit view, it is not an efficient way. This is because both formats are already supported by class CRichEditDoc.

To implement customized "File Open" dialog box, we can override function CWinApp::OnFileOpen(...). We need to provide "File Open" dialog box, let the user pick up a file name, and pass this name to function CWinApp::OpenDocumentFile(...). Here we need to pay special attention to file formats. Because we support more than one file format here, we need to implement a "File Open" dialog box supporting multiple file filters, and inform document the file format that was selected by the user. To customize a file open dialog box, we need the knowledge of Chapter 6; to let the document support a different file format, we can set a Boolean type member variable of class CRichEditDoc: CRichEditDoc::m_bRTF, which is a public member variable. If this variable is set to TRUE, the data in the file will be treated as formatted data stream; if it is set to FALSE, the data will be treated as unformatted data stream (plain ASCII text). We need to set this flag before function CWinApp::OpenDocumentFile(...) is called.

The following code fragment shows how function CWordPadApp::OnFileOpen() is implemented in the sample:

(Code omitted)

We must map command ID_FILE_OPEN to this function in order to make it effective. In the sample, WM_COMMAND message mapping for this command is customized as follows:

Original mapping:

ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)

New mapping:

ON_COMMAND(ID_FILE_OPEN, OnFileOpen)

Customizing "Save As" Dialog Box

Besides file open, we also need to think about file saving. This is more complex than file open, because we need to allow the user to save the file being edited with a different format. In case the user changes data format, we must also change the original file extension (from "rtf" to "txt" or vice versa).

To customize file saving to support multiple file formats, we need to override function CDocument:: DoSave(...). This is an undocumented member function of MFC. Unfortunately, because file dialog box is implemented within this function, we have no other choice to support multiple file format without modifying it. Although using undocumented functions is not recommendable, sometimes we have to do so in order to make our applications perfect.

Function CDocument::DoSave(...) has two parameters:

BOOL CDocument::DoSave(LPCTSTR lpszPathName, BOOL bReplace);

The first parameter is a pointer to the buffers containing the file name, the second is a Boolean type variable indicating if the file name should be changed. Actually, this parameter is always set to TRUE so we can neglect its value.

Pointer lpszPathName gives us the file name that should be used for saving data. But this pointer can also be NULL. If the file being edited is created through File | New command and the user has selected File | Save or File | Save As command, lpszPathName will be NULL. The following table lists the values of lpszPathName and bReplace under different situations:

(Table omitted)

Based on the above analysis, we can override function CDocument::DoSave(...) as follows: obtaining the file name from lpszPathName, if it is NULL, we implement the "Save As" dialog box with multiple file filters. After the user has selected a file name, we need to add extension to it according to the filter selected by the user. Also, we need to set data format for file saving. Then we can call function CDocument:: OnSaveDocument(...) and pass the file name to it to implement file saving.

In the derived function CWordPadDoc::DoSave(...), we need to implement the customized "Save As" dialog box and also, add file extension, change file format if necessary. The following is a portion of this function:

(Code omitted)

We can compare CWordPadDoc::DoSave(...) with the default MFC function CDocument::DoSave(...).

Formatting Text

Another feature we want to let this editor have is text formatting. For example, we may let the user format the selected text using bolded, italic or underlined style. Or we may let the user change the alignment of the selected paragraph (make the whole paragraph aligned left, centered or aligned right). The two types of formatting are called Character Formatting and Paragraph Formatting respectively, they can be implemented through calling functions CRichEditView::SetParaFormat(...) and CRichEditView:: SetCharFormat(...). The following shows the formats of the two functions:

void CRichEditView::SetParaFormat(PARAFORMAT &pf);

void CRichEditView::SetCharFormat(CHARFORMAT cf);

Because there are many properties we can set, we need to use structures PARAFORMAT and CHARFORMAT to specify which properties will be customized. The following is the format of structure PARAFORMAT:

typedef struct _paraformat {

UINT cbSize;

_WPAD _wPad1;

DWORD dwMask;

WORD wNumbering;

WORD wReserved;

LONG dxStartIndent;

LONG dxRightIndent;

LONG dxOffset;

WORD wAlignment;

SHORT cTabCount;

LONG rgxTabs[MAX_TAB_STOPS];

} PARAFORMAT;

We need to set the corresponding bits of member dwMask in order use other members of this structure. For example, if we want to set paragraph alignment, we need to assign member wAllignment an appropriate value, and set PFM_ALIGNMENT bit of member dwMask. If this bit is not set, member wAlignment will have no effect when function CRichEditView::SetParaFormat(...) is called.

There are a lot of features we can set through using this function, which include text numbering (using bullets at the beginning of each line), paragraph start indent, right indent, second line offset, paragraph alignment and tabs.

The usage of function CRichEditView::SetCharFormat(...) is similar. Here we have another structure CHARFORMAT that could be used to set appropriate properties for the selected text:

typedef struct _charformat {

UINT cbSize;

_WPAD _wPad1;

DWORD dwMask;

DWORD dwEffects;

LONG yHeight;

LONG yOffset;

COLORREF crTextColor;

BYTE bCharSet;

BYTE bPitchAndFamily;

CHAR szFaceName[LF_FACESIZE];

_WPAD _wPad2;

} CHARFORMAT;

Again, member dwMask should be used to specify which properties will be customized. We can make change to character effects (make it bolded, italic, strikeout, underlined, or change its color), modify the size of characters, customize character's offset from the base line (this is useful for implementing superscript or subsript), or select a different type of font.

Counterpart functions of CRichEditView::SetParaFormat(...) and CRichEditView:: SetCharFormat(...) are CRichEditView::GetParaFormat(...) and CRichEditView:: GetCharFormat(...) respectively. They allow us to retrieve the properties of the current paragraph or the selected text (If no text is selected, the properties indicate the text at the current caret position). Similarly, we need to specify corresponding bits of member dwMask in order to retrieve certain properties: those members who have corresponding zero bits in member dwMask will not be stuffed with the paragraph or character information.

It seems that by using the above four functions, we can build a very useful editor that supports rich edit text format. However, in class CRichEditView, there exist more powerful functions that can be used to format the selected text or paragraph. These functions are also undocumented, but using them can save us much effort:

Functions used for paragraph formatting:

CRichEditView::OnParaCenter();

CRichEditView::OnParaRight();

CRichEditView::OnParaLeft();

Functions used for character formatting:

CRichEditView::OnCharBold();

CRichEditView::OnCharUnderline();

CRichEditView::OnCharItalic();

Functions used to handle UPDATE_COMMAND_UI messages:

CRichEditView::OnUpdateCharBold();

CRichEditView::OnUpdateCharUnderline();

CRichEditView::OnUpdateCharItalic();

CRichEditView::OnUpdateParaCenter();

CRichEditView::OnUpdateParaLeft();

CRichEditView::OnUpdateParaRight();

Instead of implementing our own message handlers, we can just add commands to the mainframe menu or tool bar, then map the commands to these functions. In the sample, we add six buttons for character and paragraphing formatting (Figure 14-1), and map the command messages to the above functions as follows:

(Code omitted)

With the above implementation, the editor can let the user set the character and paragraph properties.

14.3 Simple Explorer, Step 1: Preparation

Starting from this section we are going to create an Explorer-like application using classes CTreeView and CListView. The application is based on an SDI application whose client window is implemented by a 2-pane splitter window. We will use CTreeView to create the left pane, and use CListView to create the right pane. One the left pane, the file system (directories) will be displayed in a tree form, the user can click on any node to select a directory, or double click on it to expand the node (show all the sub-directories). On the right pane, all files and sub-directories contained in the currently selected directory will be listed, they can be displayed in one of the four styles supported by list view.

We have introduced how to create splitter window in Chapter 3. Obviously here we need to create static splitter windows. Application Wizard does have a choice to let us create splitter window, however, it can only help us with creating dynamic splitter window. We can modify the dynamic splitter window to static splitter window after the application is generated. To let the Application Wizard generate code for implementing dynamic splitter window, we can click "Advanced..." button (in step 4) and check "Use split widow" check box in the popped up dialog box.

In order to create a splitter window with two panes implemented by different types of views, first we must implement two view classes. Here, one of the views can be implemented as we go through the Application Wizard's project creation steps: in the final step, we can select CListView as the view's base class. The other class can be added after the project is generated by Class Wizard.

Sample 14.3\Explorer is created this way. It is a standard SDI application, with first view generated by Application Wizard whose name is CExplorerView. The second view is added by Class Wizard, whose base class is CTreeView and the class name is CDirView. In function CMainFrame::OnCreateClient(...), the splitter window is created using the above two classes:

(Code omitted)

Functions CSplitterWnd::CreateStatic(...) and CSplitterWnd::CreateView(...) are called to create the splitter window and each individual pane. Please note that we must include "afxcview.h" in the header files of both class CExplorerView and CDirView, otherwise the compilation will generate errors.

The only result of this sample is a two-way splitted window. We will add further features in the next several sections.

14.4 Simple Explorer, Step 2: List Drives

Sample 14.4\Explorer is based on sample 14.3\Explorer.

We will display four types of items in the tree view window (left pane of splitter window): desktop, computer, drives, directories. The root node is desktop node, and there will be only one such type of node. Under the desktop node, there will be a computer node, which lists the computer name. Under the computer node, all the available drives will be listed, under each drive node, directories will be listed. With this structure, the file system of the whole computer can be displayed.

Creating Image List

Each node can have a label and also an associated image. Although they both are optional features, implementing them can make our application look more professional. To use images, we must create an image list and select it into the tree control. The image list can be created from either DIB images or icons. As we know from Chapter 5, the simplest way to create image list is to prepare images as the resources then load them at run time. In the sample, five images are prepared for the tree control, whose usage is listed in the following table:

(Table omitted)

Like all other types of views, the best place to initialize the tree is in function CDirView:: OnInitialUpdate(). In order to do so, we need to create the image list, select the image list into the tree control, and create the tree. Image list creation can be implemented by calling functions CImageList:: Create(...) and CImageList::Add(...). We can use the first function to create the image list, specify the image size and number of images that will be included in the list. Then we can call the second function to add each single image. In the sample, this procedure is implemented as follows:

(Code omitted)

Alternative Ways of Creating Image List

The image list is created using bitmap images. It can also be created from icons. Also, we can use one single image to create an image list that contains several images. In order to do so, we need to combine all the images together to form one image (align them horizontally, just as the image used for creating tool bar), and call one of the following versions of function CImageList::Create(...):

BOOL CImageList::Create(UINT nBitmapID, int cx, int nGrow, COLORREF crMask);

BOOL CImageList::Create(LPCTSTR lpszBitmapID, int cx, int nGrow, COLORREF crMask);

The image list can also be created from two existing image lists by calling the following version of this function:

(Code omitted)

We set the background color to white so that all image's portion with white color will be treated as transparent region.

Setting Styles of Tree Control

We can set the styles of the tree control by calling function ::SetWindowLong(...). This function is not the member function of class CTreeView, and can be called to change the styles of any window. In order to know which styles can be customized, we can look at the documentation of function CTreeCtrl:: Create(...). Generally, any style that can be set to parameter dwStyle of this function can also be used by function ::SetWindowLong(...) to change the style of a tree control. The following code fragment shows how the default styles of the tree control are customized in the sample (within function CDirView:: OnInitialUpdate()):

(Code omitted)

Style TVS_HASLINES will let the nodes be connected by dotted lines, TVS_LINESATROOT will add a line at the root node, and TVS_HASBUTTONS will add a rectangle button (displays either "+" or "-") for each expandable node. If we do not specify these styles, the tree control will look slightly different.

These styles can also be customized in function CView::PreCreateWindw(...). In order to do so, we need to set the corresponding style flags for member dwExStyle of structure CREATESTRUCT. The difference between two methods is that using ::SetWindowLong(...) can let us change the styles of a window dynamically.

Adding Root Node

The next step is to add nodes to the tree. We need to call function CTreeCtrl::InsertItem(...) to add a node to the tree. To call this function, we need to prepare a TV_INSERTSTRUCT type object, and specify the properties of node. For example, we can specify the node's parent, associated image, label and states. This procedure has been discussed in chapter 5. One thing we need to pay attention to is that since InsertItem(...) is not a member function of CTreeView, we must first call function CTreeView::GetTreeCtrl() to obtain the tree control before adding any node. The following portion of function CDirView::OnInitialUpdate() shows how the root node is added:

(Code omitted)

Finding out Available Drives in the System

By stuffing TV_INSERTSTRUCT type object and calling function CTreeCtrl::InsertItem(...) repeatedly, the tree can be created. However, before proceeding to create other nodes, we need to find out all the available drives in the system.

Currently there can be at most 26 drives contained in one system, which are labeled from "A:" to "Z:". We can call runtime function _chdrive(...) to change the current working drive (Calling this function has the same effect with typing command "a:" in a DOS prompt window). Function _chdrive(...) has one parameter:

int _chdrive(int drive);

Parameter drive specifies target drive. It can be any number from 1 to 26, which represents drive A:, B:, C:... and so on. The function will return 0 if the working drive is changed successfully, otherwise it returns -1.

So we can call this function repeatedly by passing 1, 2, 3...26 to it and examining the returned value. If the function returns 0, this means the drive is available, and we need to add it to the tree control. If the function returns -1, we can just go on to check the next drive.

Because we do not want to change the default working drive, we need to save the current working drive before calling function _chdrive(...), and resume it after the checking is over. The current working drive can be retrieved by calling runtime function _getdrive(). The following portion of function CDirView:: OnInitialUpdate() shows how the drives are added to the tree view window:

(Code omitted)

When program exits, we must do some cleanup job, which includes removing all the nodes and destroying the image list. In the sample, this is implemented in WM_DESTROY message handler:

(Code omitted)

14.5 Simple Explorer, Step 3: Listing Directories

Sample 14.5\Explorer is based on sample 14.4\Explorer.

In the previous section, we called function _chdrive(...) to find out all the available drives in the system. In order to add the directories (sub-directories) to the tree view window, we need to enumerate directories and files.

Enumerating Files and Directories

In MFC, class CFileFind can be used to enumerate files and directories under certain path. If we call this function for all the directories and sub-directories contained in the system, finally we will get all the information about the file system.

To enumerate files and directories contained in the current working directory, we can start from calling function CFileFind::FindFile(). Then we can call function CFileFind::FindNextFile() repeatedly until it returns a FALSE value. The following code fragment shows how to enumerate all the files and directories contained in the current working directory:

(Code omitted)

Note we can also use wildcard characters when calling function CFileFind::FindFile(...) to match file name with specific patterns. If we do not pass any parameter to it, it will be equal to passing "*.*" to the function. In this case all the files and directories will be enumerated. If function CFileFind:: FindNextFile() returns a non-zero value, we can call several other member functions of class CFileFind to obtain the properties of the enumerated file such as file name, file path, file attributes, created time and updated time.

Adding Directory Nodes

In order to add directory node to the tree view window, we need to implement a loop and enumerate directories for each available drive, then add the enumerated directories to the tree.

Besides directories, we also need to enumerate sub-directories for the expanded nodes. This is because our tree control supports buttons (we have set TVS_HASBUTTONS style), which indicates whether an item has child items or not. For a node that contains child items, its button will contain a "+" sign when the node is in collapsed state, indicating that the node is expandable. For a node that does not contain child items, there will be no such type of buttons.

So the directory enumeration can be added to the loop of enumerating all drives: after an existing drive is found, we can change the current working directory to the root directory of this drive, then enumerate all the first-level directories and all the sub-directories of each first-level directory.

Function CDirView::AddDirs(...) is implemented in the sample, it will be used to add directory items to a specified node. It has two parameters, the first is the handle of the target tree item, and the second is a Boolean type variable indicating if we should further add sub-directories for each added directory node. The following is the format of this function:

void CDirView::AddDirs(HTREEITEM hTreeItem, BOOL bFindChild);

Before calling this function, we need to change the current working directory to the directory we want to examine. So in function CDirView::OnInitialUpdate(...), after one drive node is added to the tree view window, we change the current working directory to root directory of that drive, and call function CDirView::AddDirs(...) to add nodes for the directories. The following is the modified portion of function CDirView::OnInitialUpdate(...):

(Code omitted)

For the root directory, we need to find out not only the directories under it, but also the sub-directories of each first-level directory. So we pass a TRUE value to the second parameter of function CDirView:: AddDir(...). The function will recursively enumerate sub-directories for all the directories found within the function if the parameter is TRUE.

At the beginning of function CDirView::AddDirs(...), we initialize a TV_INSERTSTRUCT type object and call function CFileFind::FindFile(). If it returns TRUE, we can further call function CFileFind:: FindNextFile() and get all the attributes of the enumerated file (directory). Then we repeat file (directory) enumerating until function CFileFind::FindNextFile() returns a FALSE value:

(Code omitted)

We can examine if the enumerated object is a directory or a file by calling function CFileFind:: IsDirectory(). This is necessary because only the directories will be added to the tree view window. A directory node is added by first stuffing TV_INSERTSTRUCT type object then calling function CTreeCtrl:: InsertItem(...):

(Code omitted)

If parameter bFindChild is TRUE, we need to enumerate sub-directories for each added directory node. However, since "." and ".." are also two types of directories (indicating the current and parent directories respectively), if we apply this operation on them, it will cause infinite loop. To examine if a directory is one the above two types of directories, we can call function CFileFind::IsDots(). If the function returns FALSE, we can call function CDirView::AddDirs(...) again to add sub-directory nodes. Before calling this function, we also need to change the current working directory. After the function is called, we need to resume the original working directory:

(Code omitted)

If we execute the sample, we need to wait for a while before the procedure of building directory tree is completed. This waiting time is especially long for a system containing many drives and directories. This is why we only add fist and second level directories to the tree view window at the beginning. If we build the whole directory map before bringing up the window, the user will experience a very long waiting time. We will add new nodes to the tree only when a node is expanded and its sub-level contents need to be revealed.

14.6 Simple Explorer, Step 4: Displaying Files

In the sample, all files will be listed in the list view that is located at the right pane of the client splitter window. Like what is implemented in class CDirView, we need to enumerate files under a directory and add corresponding nodes to the list control in order to display the files.

The list view window will display all the directories and files contained in the selected directory. If the currently selected directory changes, we must destroy the list view and create a new one. For this purpose, in the sample, a new function CExplorerView::ChangeDir() is implemented, which can be used to create the list view from the currently selected directory.

Image Lists

Before adding any file to the list view, we need to prepare image lists. This procedure is almost the same with that of tree view. The only difference between the two is that for list view we have more choices. This is because the items contained in a list view can be displayed in different styles, and for each style we can use a different type of images.

A list view can display items in one of the four styles: big icon (default style), small icon, list, report. We can prepare two image lists, one for big icon style, one for other three styles.

We can display different file types using different icons, this is how the files are displayed in real "Explorer" application. Under Windows(, each type of files can register both big and small icons in the system, and "Explorer" will use the registered icons for file displaying. To get the registered icons, we need to call some special functions. We will implement this method in later sections. Here, we will prepare our own icons for displaying files. In the sample, two sets of image resources are included in the applications, one of them will be used for displaying directories and the other for displaying files. Their IDs are IDB_BITMAP_CLOSEFOLDERBIG, IDB_BITMAP_CLOSEFOLDER, IDB_BITMAP_FILEBIG and IDB_BITMAP_FILE.

In the sample, big icon image list is created from IDB_BITMAP_CLOSEFOLDERBIG and IDB_BITMAP_FILEBIG. Small icon image list is created from IDB_BITMAP_CLOSEFOLDER and IDB_BITMAP_FILE. The creation of image list is the same with what we did for the tree view. When an image list is selected into the list control, we must specify the type of image list. The following portion of function CExplorerView::ChangeDir() shows how the image lists are selected into the list control in the sample:

(Code omitted)

Here pointer pilSmall and pilNormal point to two different image lists. We use LVSIL_SMALL and LVSIL_NORMAL to specify the type of the image list.

Adding Columns

First we need to add columns to the list control. The columns will appear in the list control window when the items contained in it are displayed in "Report" style. For each item, usually the small icon associated with the item and item label will be displayed at the left most column (column 0). For other columns, we can display text to list other properties of the item.

The columns are added through stuffing LV_COLUMN type object and calling function CListCtrl:: InsertColumn(...). Like other structures such as TV_INSERTSTRUCT, LV_COLUMN also has a member mask that lets us specify which of the other members of this structure will be used. For example, we can specify text alignment format (is the text aligned left, right or is it centered?) by setting LVCF_FMT bit of member mask and assigning appropriate value to member fmt; we can specify the width of each column by setting LVCF_WIDTH bit and using cx member; we can set the column caption by setting LVCF_TEXT bit and using pszText member. In the sample, text of each column is aligned left, the width of each column is set to 150, and the column texts are: "Name", "Size", "Type", and "Modified" respectively.

To make it convenient to add columns, the following global variables are declared in the sample:

#define NUM_COLUMNS 4

(Code omitted)

In function CExplorerView::ChangeDir(), the columns are added as follows:

(Code omitted)

Listing Files

In the list view, each item represents a file under certain directory. When the items are displayed in "big icon", "small icon" and "list" styles, each file is represented by an icon contained in the list view window. When they are displayed in the "report" style, the file is represented by both an icon and several text strings. In this case, column 0 contains icon and the file name, and the rest columns contain other information about the file (These items are called the sub-items).

The procedure of adding items to list control is similar to adding directory nodes to tree control, except that we don't need to worry about enumerating sub-directories here. Also, for each item, we need to set not only the image number and item text (contained in column 0), but also the sub-item text (contained in the rest of the columns). For this purpose, we can store the text of sub-items in a string array. After all the items are added, we can set sub-item text for each item.

The file enumerating can be implemented by calling functions CFileFind::FindFile() and CFileFind::FindNextFile() repeatedly. After a file is found, we stuff an LV_ITEM type object and call CListCtrl::InsertItem(...) to add a new item to the list control. Here is how it is implemented in function CExplorerView::ChangeDir():

(Code omitted)

Unlike tree control, there is no handle here to identify a special item. All the items are identified by their indices, this means if we display items in "list" or "report" style, the item located at the first row is item 0, the next row is item 1... and so on. When inserting an item, we need to specify the item index by using member iItem of LV_ITEM structure.

We store file size (for directory, display nothing), file type ("File" or "Folder"), the updated time in a string array that will be used to add text for the sub-items. These attributes of file can be retrieved by calling functions CFileFind::GetLength(), CFileFind::IsDirectory() and CFileFind:: GetLastWriteTime(...). When calling the third function to obtain the update time of a file, we get a CTime type variable. To store the time in a CString type variable in ASCII format, we need to call function CTime::Format(...). The following portion of function CExplorerView::ChangeDir() shows how the string array is created:

(Code omitted)

The text of sub-items is added by calling function CListCtrl::SetItemText(...). This can also be implemented by stuffing LV_ITEM type object (specifying item and sub-item indices) and calling function CListCtrl::SetItem(...). The following portion of function CExplorerView::ChangeDir() shows how this is implemented in the sample:

(Code omitted)

Destroying the Old List

Whenever the current working directory is changed, we need to call function CExplorerView:: ChangeDir() to create new file list. Before building a new one, we need to delete the old list. In the sample, this is implemented by function CExplorerView::DestroyList(). Within this function, both list items and image lists are deleted:

(Code omitted)

Since we can retrieve the pointers of image list from the list control, there is no need for us to store them as variables. This function is called in function CExplorerView::ChangeDir() and WM_DESTROY message handler.

Using Function CExplorerView::ChangeDir()

At this point, we still do not allow the user to select a directory by clicking on a directory node in the tree view window. So we can only display the files and directories contained in the root directory when the application is first invoked. This is implemented in function CExplorerView::OnInitialUpdate(), where we find the first available drive, and call function CExplorerView::ChangeDir() to create the list view:

(Code omitted)

14.7 Simple Explorer, Step 5: Displaying Registered Icons

Windows( encourages all types of files to register specific icons to the system, so that when they are displayed in certain applications such as "Explorer", the registered icons (also called Shell Icon) can be used to distinguish between different type of files. However, some file types do not have registered icons and some files contain icons within themselves (such as files with ".exe" or ".dll" extension).

Which Icon to Use

Windows( always try to display a file using the appropriate icons. If a file contains icon itself, this icon will be used. If a file doesn't contain any icon but has registered icons (such as some special document files like "*.bmp", "*.doc"), the registered icons will be used. If no registered icons are found, a default icon will be assigned to the file.

There is a shell function that can be used to retrieve the icon information for a file:

WINSHELLAPI DWORD WINAPI SHGetFileInfo

(

LPCTSTR pszPath, DWORD dwFileAttributes, SHFILEINFO FAR *psfi,

UINT cbFileInfo, UINT uFlags

);

Here parameter pszPath is a pointer to a string specifying the file path; dwFileAttributes specifies the file attributes, and the file information can be retrieved into a SHFILEINFO type object which is pointed by pointer psfi; cbFileInfo specifies the size of SHFILEINFO structure; uFlags specifies what information is being retrieved. In our case, we can combine SHGFI_ICON with one of the following flags and pass the result to parameter uFlags:

(Table omitted)

To display each file with embedded or registered icons, before adding an item to the list control, we need to first customize the image list. If any icon is found by calling function ::SHGetFileInfo(), we will add it to the image list. If we could not find an icon using this method, the default icon will be associated with the corresponding file.

Sample

Sample 14.7\Explorer is based on sample 14.6\Explorer. In this sample, the embedded and registered icons are retrieved for displaying files in the list view.

In the sample, a new member function is added for retrieving icons for a file:

HICON CExplorerView::GetIconFromFile(CString szFileName, UINT uFlags);

The returned value is an icon handle. Within this function, ::SHGetFileInfo(...) is called to get the icon information of a file. The following is the implementation of this function:

(Code omitted)

Function CExplorerView::ChangeDir() is modified as follows: after a file is found, function CExplorerView::GetIconFromFile(...) is called to find its embedded or registered icons; if this is successful, the newly obtained icons will be added to the image list and associated with the file; otherwise the default images will be used. The following portion of function CExplorerView::ChangeDir() shows how we try to find the embedded icons of a file:

(Code omitted)

In rare cases, some files may have small embedded or registered icon but no corresponding big icon, or vice versa. In any case, the embedded or registered icon has the highre priority to be used. The newly obtained icon is added to the image list by calling function CImageList::Add(...).

14.8 Simple Explorer, Step 6: Clicking and Double Clicking

Sample 14.8\Exoplorer is based on sample 14.7\Explorer. In this sample, when the user clicks or double clicks the left button on a directory node contained in the tree control, the current working directory will be changed and the contents of the list view will also be updated.

If the double clicking expands a node, we need to pay attention to the newly revealed nodes: if any directory contains sub-directories, we need to add new nodes so that the node button will be automatically enabled.

Tree Control Messages

Mouse clicking events are sent through WM_NOTIFY messages. For tree control, various activities of tree items can be handled by processing this message. In MFC, this message can be mapped to a member function by using macro ON_NOTIFY_REFLECT. This macro has two parameters, the first specifies the event type, the second specifies the member function name. There are many types of events, for example, mouse button clicking event is defined as NM_CLICK, and the node expanding event is defined as TVN_ITEMEXPANDING. Fortunately, in MFC, message mapping for these events can be easily implemented through using Class Wizard. In sample 14.8\Explorer, we trap mouse clicking and node expanding events to functions CDirView:: OnClick(...) and CDirView::OnItemExpanding(...) respectively.

Obtaining Full Path

When the user clicks mouse on a node, first we need to find out the path represented by that node. Although the directory name is stored as the item text for each node, it is not a full path. We need a full path in order to change the current working directory. In the sample, function CDirView::GetDir(...) is implemented to obtain the full path represented by any item. Within this function, we keep on retrieving the item's parent node until root is reached, and combining the obtained directory names to form a full path.

Finding out the Clicked Item

After receiving NM_CLICK notification, we can call function CTreeCtrl::HitTest(...) to find out the handle of the item that was clicked. We need to pass the current mouse position to this function. The returned value should be the handle of the item that is currently under the mouse cursor. If the mouse is not over any item, the function will return NULL. Please note that when calling this function, the coordinates of the mouse cursor should be in the coordinate system of the client window. We need to call function CWnd::ScreenToClient(...) to make the conversion.

When an Item Is Clicked

Special attention needs to be paid to directory nodes labeled with "." and "..". When they are clicked, the current working directory should be changed differently. For the purpose of demonstration, in the sample, no change will be made if the user clicks any of the two types of directories. If the user clicks a normal directory node, we need to change the current working directory to the selected one and notify the list view to update its contents. This notification is made through calling function CBrowserDoc::ChangePath(), within which function CBrowserView::ChangeDir() is called. However, if the user clicks on the currently selected directory, no change will be made. The following is the message handler for mouse left button clicking:

(Code omitted)

When a Node Expands

When a node is expanding, we need to check the newly revealed nodes to see if they always contain child nodes. If not, we need to add child nodes (if they have sub-directories) to them because this will enable node button automatically. In the sample, function CDirView::AddChildrenChildren() is implemented to let us add new nodes for all the child nodes of a given node. Within this function, we check each child node to see if it already has child items (which means the sub-directories have already been added for this node). If not, we call function CDirView::AddDirs(...) to add new nodes to it. A node's child item can be enumerated by calling function CTreeCtrl::GetChildItem(...) first then calling CTreeCtrl::GetNextSiblingItem(...) repeatedly until it returns NULL value. Also, we can call function CTreeCtrl::ItemHasChildren(...) to examine if a node already has child nodes. The following is the implementation of function CDirView::AddChildrenChildren(...):

(Code omitted)

This function is called when a node is about to expand in function CDirView::OnItemExpanding(...) as follows:

(Code omitted)

To make the application more user friendly, the rest part of this function swaps the directory node image from the image representing open directory (IDB_BITMAP_OPENFOLDER) to the one representing closed directory (IDB_BITMAP_CLOSEFOLDER) when the node is collapsing and vice versa when it is expanding.

14.9 Simple Explorer, Step 7: File Sort

Sample 14.9\Explorer is based on sample 14.8\Explorer, it implements file sorting.

When the list items are displayed in "Report" style, one thing we can implement is to sort all the files by different attributes. For example, if we click on "Name" column, all the files should be sorted by their names; if we click on "Size" column, all files should be sorted by their sizes; if we click on "Type" column, all the files should be sorted by their extensions; if we click on "Updated" column, all the files should be sorted by their updated dates and times.

Sort Related Functions

We can call function CListCtrl::SortItems(...) to implement item sorting. This function has two parameters:

BOOL CListCtrl::SortItems(PFNLVCOMPARE pfnCompare, DWORD dwData);

The function's first parameter is a little special, which is the pointer to a callback function provided by the programmer. The callback function will be used to perform actual comparison. This is because when comparing two items, class CListCtrl has no way of knowing which item should precede the other. In order to provide our own rules of making comparison, we need to implement the callback function.

The callback function has the following format:

int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);

In order to compare two items, we need to provide each item with a parameter, which is an LPARAM type value. When two items are compared, their parameters will be passed to the callback function, which will return different values indicating which item should precede the other. If the first item (whose parameter is lParam1) should precede the second item (whose parameter is lparam2), the function needs to return -1; if the first item should follow the second item, the function needs to return 1; if the two items are equal, the function needs to return 0.

When calling function CListView::SortItems(...), we can pass different pre-defined values to parameter dwData, which will be further passed to parameter lParamSort of the callback function. This provides us with a way of specifying different types of sorting methods.

Adding Parameters to Items

By now, when creating an item, we did not specify any parameter for it. Actually, any item in the list control can store a 32-bit parameter which can be used to distinguish one item from another. Of course the item index can also be used for this purpose. However, since the relative positions of two items can change frequently, the index of an item is also not fixed. Since the only information passed to the callback function about an item is its parameter, we must make it unique for any item.

In the sample, function CBrowserView::ChangeDir() is customized as follows: when a new item is added to the list control, we set its parameter to its initial index and use it as the identification of this item. This parameter will not change throughout its lifetime:

(Code omitted)

Functions Implementing Comparisons

Four static member functions are implemented for doing different types of comparisons:

(Code omitted)

Actually, in the callback function, one of the above functions is called to perform the comparison according to parameter lParamSort:

(Code omitted)

Please note that the callback function must be either a global function or a static member function. So within it we cannot call CListView::GetListCtrl() directly to obtain the list control. Instead, we must first obtain the current instance of list view, then use it to call function CListView::GetListCtrl() and obtain the list control. This is why at the beginning of the callback function the current active document is first obtained, from which the current active list view (and the list control) is obtained.

Using Parameter to Find an Item

Within the function that implements comparison, the only information we know about an item is its parameter. This is not enough for making comparison. We need to obtain the item and get more information (In our sample, this includes file name, extension, type, and updated time) before proceeding to compare the two items.

An item can be obtained from its parameter by calling function CListCtrl::FindItem(...). In order to call this function, we need to stuff a LV_FINDINFO type object specifying what information is provided for item searching. To search an item by its parameter, we need to set LVFI_PARAM bit of member flags of the structure, and assign the parameter to member lParam. In the sample, a static member function CExplorerView::FindItem(...) is implemented, it can be called from any static member function to find an item using its parameter:

(Code omitted)

Here a LV_FINDINFO type object is stuffed, with LVFI_PARAM bit of member flags set to "1" and the item parameter assigned to member lParam. Then the object is passed to function CListCtrl:: FindItem(...) to search the item in the list control. Function CExplorerView::FindItem(...)'s second parameter is a CListCtrl type reference, this is because within static member function, we must use the instance of an object to call any of its non-static functions. This function returns the current index of the corresponding item.

Comparing Two Items by File Names

The procedure of comparing two items is described in the following paragraphs.

First we pass the parameters of the items to function CExplorerView::FindItem(...) to retrieve their current indices. After the indices are obtained, we stuff a LV_ITEM type object, set LVIF_IMAGE bit of member mask to "1" and call function CListCtrl::GetItem(...). Since in the sample, a directory item is always associated with the default image (In the image list, the image index is 0), we can use an item's image index to tell if it represents a directory or a file. For different situations, the comparing function will return different values (In the sample, a directory always preceeds a file item):

(Code omitted)

In case both items are directories or files, we need to further compare their names. Since file names under Windows( are case insensitive, we neglect character case when performing the comparison. The comparison is done within a for loop, which starts from the first characters and ends under one of the following situations: 1) The two compared characters are different, in which case the character that has the greater value belongs to the item that should follow the other. 2) One of the strings reaches its end. In this case the item with longer file name should follow the other. If two strings are exactly the same, the function returns 0:

(Code omitted)

Notification LVN_COLUMNCLICK

When the user clicks on one of the columns, the list control sends a notification message LVN_COLUMNCLICK to its parent window. If we want to handle this message within the list view, we need to use macro ON_NOTIFY_REFLECT to map the message to one of its member functions. In the sample, this message mapping is added through using Class Wizard:

(Code omitted)

Within message handler CExplorerView::OnColumnClick(...), function CListCtrl::SortItems(...) is called to perform file sorting:

(Code omitted)

14.10 Using Form View

Form view is easy to use because the procedure of implementing it is similar to that of a dialog box. We can start from building a dialog template, then adding common controls to the template. Generally everything we can implement in a dialog box can also be implemented in the form view. The difference between the two is that when creating dialog template for the form view, we must set its style to "child" and "no border" (Figure 14-2).

Both tree control and list control can be implemented in a form view. Sample 14.10\Explorer is based on sample 14.9\Explorer whose left pane of the splitter window is implemented by a form view. Within the form view, a tree control is implemented for displaying directories. We will see, it is almost the same to use tree control in a form view with using a tree view directly.

New Class and Dialog Template

We must add a new view that is based on class CFormView. This can be implemented by using Class Wizard. Because a form view must be associated with dialog template, before adding the new class, we need to add a dialog template (Of course, we can first generate the class then the dialog template, and change the ID contained in the class to the ID of the dialog template later). When creating dialog template, we need to delete all the default controls, and customize its style to "Child" and "No border". Then we can add a tree control to the template, set the following styles: "Has button", "Has lines", "Line at root". This equals to calling function ::SetWindowLong(...) and setting styles TVS_HASLINES, TVS_LINESATROOT, TVS_HASBUTTONS for the tree control.

When generating the new class, we can associate the ID of the dialog template to it. Then, we can add a control variable for the tree control. By doing this, the tree control can be accessed by directly referring to this variable instead of calling function CWnd::GetDlgItem(...). In the sample, the newly generated class is named CDirFormView, the ID of the dialog template is IDD_DIALOG_VIEW, the ID of the tree control is IDC_TREECTRL, and the variable added for the tree control is CDirFormView::m_tcDir.

Implementing New Member Functions

We can just copy all the member functions from CTreeView and implement them in CDirFormView. The only difference is that we must replace all function calls of CTreeView::GetTreeCtrl() by m_tcDir. Also, we can implement a GetTreeCtrl() function in class CDirFormView and let it return the reference of m_tcDir. By doing this, we don't need to change anything else.

The following variables and functions are declared in class CDirFormView, they are implemented exactly the same as in class CDirView:

char CDirFormView::m_szPath[_MAX_PATH];

CString CDirFormView GetDir(HTREEITEM);

void CDirFormView AddDirs(HTREEITEM, BOOL);

void CDirFormView::AddChildrenChildren(HTREEITEM);

void CDirFormView::OnInitialUpdate();

afx_msg void CDirFormView::OnClickTreeCtrl(NMHDR* pNMHDR, LRESULT* pResult);

afx_msg void CDirFormView::OnItemExpandingTreeCtrl(NMHDR* pNMHDR, LRESULT* pResult);

Resizing Tree Control

Since the controls contained in the form view does not get resized or repositioned automatically, we need to move or resize them after receiving message WM_SIZE. This will make the form view better balanced.

In the sample, function CDirFormView::ResizeTreeView() is implemented to resize the tree control. After this function is called, the tree control will be resized so that it just fits within the form view window (A border is left around the tree control). The following is the implementation of this function:

(Code omitted)

Mouse Cursor Coordinates

Because the dimension of the tree control is not the same with that of the form view, when function CTreeCtrl::HitTest(...) is called in response to notification NM_CLICK, we need to convert the coordinates of mouse cursor from form view window to the tree control window. The following code fragment shows how function CTreeCtrl::HitTest(...) is called when mouse's left button is pressed:

(Code omitted)

Replacing CDirView with CDirFormView

Now we can replace class CDirView with CDirFormView when creating the splitter window. This is fairly simple, all we need is to use class CDirFormView to create the left pane of the splitter window in function CMainFrame::OnCreateClient(...):

(Code omitted)

This new version of Explorer behaves exactly the same with the previous one. However, with form view, we can add other common controls such as buttons to the left pane. This will give us more flexibility in improving our application.

Summary

1) A standard text editor can be implemented by class CEditView. This class contains some member functions that can be used to implement a lot of useful commands:

i) Serialization can be implemented by function CEditView::SerializeRaw(...).

ii) Undo, Cut, Copy and Paste commands can be implemented by the following undocumented functions: CEditView::OnEditUndo(), CEditView::OnEditCut(), CEditView::OnEditCopy(), CEditView::OnEditPaste().

iii) String search related commands can be implemented by the following undocumented functions: CEditView::OnEditFind, CEditView::OnEditReplace, CEditView::OnEditFindRepeat().

2) Class CRichEditView and CRichEditDoc support formatted text editing. The classes support two file formats: "rtf" format and plain text format. There is a member variable contained in class CRichEditDoc: CRichEditDoc::m_bRTF. If we want to open "rtf" type files, we need to set this variable to TRUE. If we want to edit plain ASCII text, we need to set this variable to FALSE.

3) To get or set the format of a paragraph, we can stuff structure PARAFORMAT and call function CRichEditView::GetParaFormat(...) or CRichEditView::SetParaFormat(...); to get or set the format of characters, we can stuff structure CHARFORMAT and call function CRichEditView::SetCharFormat(...) or CRichEditView::SetCharFormat(...).

4) To customize "File Open" dialog box, we need to override function CWinApp::OnFileOpen(...). To customize "Save As" dialog box, we need to override undocumented function CDocument::DoSave().

5) The following undocumented member functions can be used to implement commands for formatting characters or paragraph in a rich edit view:

CRichEditView::OnParaCenter();

CRichEditView::OnParaRight();

CRichEditView::OnParaLeft();

CRichEditView::OnCharBold();

CRichEditView::OnCharUnderline();

CRichEditView::OnCharItalic();

6) The styles of tree view and list view can be set by calling function ::SetWindowLong(...). Any style that can be used in function CTreeCtrl::Create(...) or CListCtrl::Create(...) can be changed dynamically by using this function.

7) We can call function _chdrive(...) to test if a drive (from drive A to drive Z) exits in the system. If the function returns -1, the drive does not exit. If it returns 0, the drive is available. We can also call this function to change the current working drive.

8) Class CFileFind can be used to enumerate all the files and directories under certain directory. To enumerate files and directories, we can call function CFileFind::FileFind() first then call function CFileFind::FindNextFile() repeatedly until it returns FALSE.

9) Function CFileFind::IsDirectory() can be used to check if an enumerated object is a directory. Function CFileFind::IsDot() can be used to check if a directory is "." or "..".

10) Function ::SHGetFileInfo(...) can be used to obtain the embedded or shell icons for a file.

11) Notification NM_CLICK can be used to trap mouse clicking events on the tree control. Notification TVN_ITEMEXPANDING indicates that a node is about to expand.

12) When the user clicks mouse on the tree control, we can call function CTreeCtrl::HitTest(...) to find out the handle of the item that was clicked.

13) We can call function CListCtrl::SortItems(...) to implement item sorting in the list control. In order to do this, we must assign each item contained in the list control a parameter, which will be used as the identification of the item. Then we need to prepare a callback function, within which rules of comparison are implemented.

14) To search for a specific item by its parameter, we can stuff structure LV_FINDINFO and call function CListCtrl::FIndItem(...).

15) To respond to the mouse clicking events on the columns of the list control, we need to trap notification LVN_COLUMNCLICK.

 

BACK TO INDEX