Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

The Explorer Imperative - Going Native

4.87/5 (12 votes)
18 Dec 2012CPOL60 min read 28.6K   804  
File search synchronised with TreeView/ListView. Climbing up and down the tree.

Prolog: Going Native

When I travel, I don't want to be a tourist so I avoid the tour buses and just explore on my own. When I began to feel like a tourist in "F#", I decided to go back to "C++". If I used Microsoft's Foundation Classes I could just add a TreeView to my dialog that would populate itself, and other magic stuff. But that would be kinda touristy too. So I decided to go native.

Exploring (searching and browsing) the file system using Native (Win32 API) code in C++

Image 1

Image 2

To search the file system for file names containing ".cpp", click the "Start Search" button. To change the search argument, click on the entry field, delete the text, and enter your argument. Do not use wildcards. Enter either the filename or a part of it. The search begins in the current directory. Files found that match your argument are added to the listview along with their full path. The current contents of the listview are not deleted. This allows you to "collect specimens", er... I mean results of multiple searches. The ListView can be expanded to full screen by resizing the TextBox to nothing then using the drag bar to drag the ListView to the left edge. Oh!!! There is no drag bar. But it looks like there's a drag bar and it acts like a drag bar, so, you know what they say: if it smells like a duck and it quacks like a duck - it must be a duck. We'll just call it a duck -I mean drag bar. Don't tell anybody, okay?

Image 3

Image 4

To locate the file and path in the TreeView select a name in the listview and press the "F3" Function key. To replace the contents of the ListView select a folder in the TreeView and press "Enter" or double click the mouse.

To locate the file and path in the TreeView, select a name in the ListView and press the "F3" Function key. This will climb down the tree to the root and then climb back up to the folder for the path and select the TreeView node matching the file name. I put in a sleep statement to slow the process down but it is only for visual effect. Without it you might think it happened instantaneously unless your machine is really slow.

Introduction

As the screen prints above suggest, this article is about searching the file system using only the Win32 API. Nothing but native code. The search is recursive and uses the FindFirstFile, FindNextFile, FindClose sequence to read the contents of a directory. The matching logic is the simplest algorithm I could come up with, looking for a substring in the file names returned. When a match is found the filename and path are added to the ListView. This is not a wildcard search. If you enter an asterisk as part of the filename it will not find anything. There are two primary reasons for this approach. The first is to reduce the number of lines of code, the second is, this is not an example of a search engine nor a sample of regular expressions. The substring method works for me. You can roll your own if you want to.

The other sample routines included in the program code are necessary to support the search function as part of the GUI experience (careful there! don't drip that gooey stuff on your keyboard!). I will strive mightily to explain those things which I struggled with, those things I thought were "cool" and those things that will probably cause an eyebrow to raise. However I will make no attempt to explain those things that you may feel are corrupt practices.

Background

Why I think this is Beginner level

First, some background on myself. I have been programming for thirty plus years, on various platforms and using various languages. I first programmed in C++ in ninety two but shortly thereafter, I began to participate more and more in administration and management, therefore I did less and less programming professionally. I mostly wrote code only when I needed to be creative. Recently I decided to learn Modern Programming and began exploring other languages. I learned a bit of "F#" and thought it was so cool. I wanted to share my pleasure. So I contributed some code to CodeProject.

But there were some 'things' I wanted to do that required going outside the language, using "P Invoke" to execute some C/C++ code. Then my explorations revealed to me that I could do functional programming in C++. I really like F# but I have decided to learn C++ Functional Programming. There isn't any of that in this article though, this is a purely imperative exploration of C/C++.

Why I want to contribute this article

Now, as for the reason I wanted to share this code, I read a question recently on some forum asking how to make the menu (and other fonts) in Windows Explorer large enough to be read by someone who doesn't have 20/20 vision. The answer given was to adjust the Desktop Theme. This works well for XP but with some problems. The other versions of Windows I can't comment on. I did this long ago on my XP but I haven't found a way to do this on Win7. Possibly because of the built-in magnifier software, it was felt that this is no longer an issue but I find that sometimes it can be clumsy and frustrating to use anything beyond 100%. I generally provide a larger font or a font dialog because I require it. This sample code only includes the ability to browse the file system. It is not intended to be used for file management. It can be used to switch to the real Windows Explorer and open anything that has a file association entry defined for it. To do this, simply add it to the listview, select it, and either use the "Enter" key on the keyboard or double click it. Depending on the file associations on your computer, this will open a ".cpp" in Visual Studio, or open a ".sln" in the Visual Studio Start Page, or a folder in "Windows Explorer" (with 8pt font menus).

Using the code

This solution (.sln) was created by the "New Project..." command on the Visual Studio Start page. I chose C++ and Win32 Project, nothing else except Finish. I did not change any Project Properties. I used the #pragma comment to include the libraries I needed to include. In the Resource View I added a bitmap resource with six 32 by 32 icons (pardon my amateurish artwork). You can use them in any way you wish or replace them if you don't like them. I added a menu and added some buttons to it. I added five dialogs for the controls I wanted to use. In the code editor I duplicated and changed the wizard generated "About" dialog proc as a model for each of the five dialogs. The generated code created the main window and the message loop for it. In the WM_CREATE message handler, I used the CreateDialog function to create the Edit, TreeView, and ListView child controls and the search dialog popup. In the ListView Proc I used the DialogBox function to create the Item Removal dialog (delBarProc) to clean up the Listview. This procedure produced four files that you should either add to your project or replace the generated files with. They are ".bmp", ".cpp", ".rc" and "Resource.h". If you are using C++ Express or a batch compile, this should work with no problem. If you have a full version of Visual Studio you may have to import these files.

Using dialogs, it is quicker and takes less code to create a window but you must handle the additional layer of control. If you use "CreateWindow()" the control is specified as a Window Style, so you have a control in a window (i.e., WC_TREEVIEW). But with "CreateDialog()" you have the main window created by the CreateWindow function in InitInstance, then you have the dialog window created by the CreateDialog function and the TREECTRL, which is also a window, and which you must move and size within the dialog window procedure whenever the dialog window is moved or sized, unless you are happy with the default movement and sizing. This approach has the advantage of separating the code for the controls into separate procedures, making it very easy to place the entire procedure, unchanged, into another project, while changing everything else that it interacts with.

A word about the bitmap. It is twice as wide and twice as tall as usual. If you want to use a smaller image you need to change the first two parameters for the Imagelist_Create function from 32 to 16. These parameters also control the size of the buttons for the TreeView. Make the image smaller and the buttons get smaller. Also, the images are folder closed, folder closed and selected, folder opened, folder opened and selected, non-folder, and non-folder selected. The selected version of the images are lighter in color than the non-selected versions so that an item is kind of highlighted when it is the "selected" item. The system handles this change using the iImage and iSelectedImage attributes of the tvitem. It's not so easy with folder closed and folder opened however. This must be handled in code which we do in the treeview proc, in the WM_NOTIFY message handler. It is not functionally necessary to handle this situation but I use the value of the iImage attribute to control the flow of program logic in some cases.

Explaining the code

In the first section of code below, you will see mostly the code generated from the wizard. I did add more global variables and additional forward declarations. I added the forward declarations in the order in which I want to explain them to reduce the amount of jumping around in the article. I rearranged the routines so they would be in the same order as the declarations. This is not a language requirement. Note the #pragma comments. The libraries were included in this way so that nothing needs to be added to the project properties. This gave me a clean compile on a Win7 machine with a full version of Visual Studio and an XP SP3 machine with C++ Express. I used the files from the Win7 version on the XP, simply replacing the generated files and copying in the bitmap.

Notice in the code below, I have emphasized two lines of code. Both lines load a cursor. The first one is at the global level. This could be moved to the the WndProc routine but I didn't know where it would be used. The IDC_ARROW is the class cursor but you need to change it to

IDC_SIZEWE
(that's west, east I presume) to indicate that the edges of the two windows can be moved, simulating a grab bar.

The program code:

C++
// natSyncSearch.cpp : Defines the entry point for the application.
// an Edit Control, a Tree Control and a List Control
//

#include "stdafx.h"
#include "natSyncSearch.h"
#include <commctrl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <Shellapi.h>

#pragma comment(lib, "User32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "Shell32.lib")

#define MAX_LOADSTRING 100
#define BUFSIZE 512

// Global Variables:
HINSTANCE hInst;                // current instance
TCHAR szTitle[MAX_LOADSTRING];          // The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];      // the main window class name
HWND hWnd, hEdit, hTree, hList, hAbout, hToolBar, hdelBar, hFfext, hFormView, hTreeView, hListView;
TCHAR driveRoot[MAX_PATH], searchStr[MAX_PATH]; // szTitle[MAX_LOADSTRING], szWindowClass[MAX_LOADSTRING];
HTREEITEM Selected, driveParent;
TV_ITEM tvi, tvi2;
static int tviiImage = 4; // not a folder 
static int tviiSelectedImage = 5; // not a folder, but it is selected 
TCHAR startInDir[256];
wchar_t  *sId = L"C:\\Users"; // define the start-in-directory
int sidLen;
TCHAR saveCD[256];
TCHAR nodeCD[MAX_PATH]; // searchStr[MAX_PATH], szTitle[MAX_LOADSTRING], szWindowClass[MAX_LOADSTRING];
HTREEITEM nodeParent;
HIMAGELIST hImageList;
HBITMAP hBitMap;

static BOOL shownYet = false;

HCURSOR hCursor    = LoadCursor(NULL, IDC_SIZEWE); // over-ride class cursor for Splitter Bar Effect

// Forward declarations of functions included in this code module:
ATOM        MyRegisterClass(HINSTANCE hInstance);
BOOL        InitInstance(HINSTANCE, int);
LRESULT CALLBACK  WndProc(HWND, UINT, WPARAM, LPARAM);
int recursivefileSearch(TCHAR *name, bool hidden = false);
INT_PTR CALLBACK toolBarProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK editBoxProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK treeViewProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK listViewProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
bool SetTreeviewImagelist(const HWND hTv);
BOOL InitTreeViewItems(HWND hwndTV);
void getDirectories(HTREEITEM hDir, LPTSTR lpszItem);
HTREEITEM AddItemToTree(HWND hwndTV, HTREEITEM hDir, LPTSTR lpszItem);
BOOL getNodeFullPath(HWND thisHwnd,  HTREEITEM  nmtvi2);
INT_PTR CALLBACK  About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
  UNREFERENCED_PARAMETER(hPrevInstance);
  UNREFERENCED_PARAMETER(lpCmdLine);

   // TODO: Place code here.
  MSG msg;
  HACCEL hAccelTable;

  // Initialize global strings
  LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
  LoadString(hInstance, IDC_NATSYNCSEARCH, szWindowClass, MAX_LOADSTRING);
  MyRegisterClass(hInstance);

  // Perform application initialization:
  if (!InitInstance (hInstance, nCmdShow))
  {
    return FALSE;
  }

  hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_NATSYNCSEARCH));

  // Main message loop:
  while (GetMessage(&msg, NULL, 0, 0))
  {
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg) ||
       !IsDialogMessage(msg.hwnd, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
    return (int) msg.wParam;
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
  WNDCLASSEX wcex;

  wcex.cbSize = sizeof(WNDCLASSEX);

  wcex.style      = CS_HREDRAW | CS_VREDRAW;
  wcex.lpfnWndProc  = WndProc;
  wcex.cbClsExtra    = 0;
  wcex.cbWndExtra    = 0;
  wcex.hInstance    = hInstance;
  wcex.hIcon      = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_NATSYNCSEARCH));
  wcex.hCursor    = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground  = (HBRUSH)(COLOR_HOTLIGHT);
  wcex.lpszMenuName  = MAKEINTRESOURCE(IDC_NATSYNCSEARCH);
  wcex.lpszClassName  = szWindowClass;
  wcex.hIconSm    = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

  return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

WndProc: The Window Procedure!

WM_CREATE: Give it some children!

WndProc is the message handler for the main window. Here the messages are handled in place. They could easily be separated into individual functions for each message. This way is easier to explain though. The first message, WM_CREATE is where the child amd popup windows are created whether you use "CreateDialog" or "CreateWindow". The important difference is using CreateWindow means you have to handle all of the messages in WndProc, while using CreateDialog enables you to separate the message handling into separate procedures for each control without needing to sub-class the controls. You can, therefore, put a large portion of the code for a control in its own procedure, making it easy to quickly combine any number of controls into a unique User Interface.

Here's how it works - using CreateWindow you cast the control's ID, IDC_TREE1, for example to an HMENU for the Menu parameter of the CreateWindow function call (that's the NULL parameter before the hInstance parameter in the InitInstance CreateWindow above.). Then you handle the notifications sent by the control in the WM_NOTIFY message of WndProc or WM_COMMAND message for some controls. If you have multiple controls you will need multiple switches in WM_NOTIFY to handle each control separately, as well as a switch within that switch to handle the notifications sent by the control.

Using CreateDialog you MAKEINTRESOURCE of the dialog IDD_FORMVIEW1 you defined in the Dialog Editor, specifying NULL for the GetModuleHandle argument in the first parameter (which means the resource you want to load is in the current load module), the parent window's handle, hWnd, and the DLGPROC (treeViewProc, to process the messages sent by the control) and name the handle to the window hTreeView. If the CreateDialog function succeeded you use GetDialogItem to get the control in the hTreeView window, IDC_TREE1 and name it hTree. Then you show the window, like this:

C++
hTreeView = CreateDialog(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_FORMVIEW1),hWnd,treeViewProc);
if (hTreeView != NULL) { hTree = GetDlgItem(hTreeView, IDC_TREE1); ShowWindow(hTreeView, SW_SHOW); }

Of course this is only for the TreeView control, but it's pretty much the same for the other children. As for the popups, I created one in the WM_CREATE message, the hToolBar, because I want it to be the first window with focus. Later I may move it to the WM_COMMAND message handler so the user can invoke it from the menu. The "Show" and "Hide" menu items aren't really necessary. They're just there for show.

We also need to get the Client Rectangle for the main window, hWnd, and use rect.right to get the width of the main window. The height doesn't really matter for now. We just want to set the height and width of the edit control and set the listview's left edge a little farther to the right relative to the right edge of the treeview and set them both a little lower than the bottom of the edit control. All three windows will be sized properly in the WM_SIZE notification for the main window. The controls are sized in the WM_SIZE for the control's parent. This additional layer of control is a small inconvenience for the simplicity it provides.

We also set the treeview imagelist and set the current directory to "sId", which is "C:\Users", a folder that exists on Win7 but not on XP, unless you have created one with that name. The SetCurrentDirectory function will set the current directory if that folder exists. We get the current directory and compare it with "sId", ignoring case (i.e., a==A). If they are not equal we set the current directory to a folder that should exist on XP but not on Win7. If we did not set this value, the Search dialog would start searching in the folder containing the load module. That's the value of the current directory when the program starts and is the value used as the starting point.

We also must initialize the treeview, which we call a function named InitTreeViewItems to accomplish. Now the Main window is all set up. We could end WM_CREATE at this point and break, but the user might not realize the purpose of the article, which is the "Search" function. So, we create the hToolBar popup and put it up above the window title bar so that it does not cover anything in the window displayed. We can place it anywhere because we specified POPUP instead of CHILD for the dialog style. A hint is provided to inform the user the search argument can be changed. The user is not forced to start the search immediately. The program responds to user actions in a fashion that Windows Explorer users will probably feel comfortable with. The user can exit the program by clicking on the close button (X) on the Title Bar or ALT+F4. This is to retain intuitive essence.

The window procedure code

C++
//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_CREATE - creates child windows for edit control, treeview control and listview control
//  and gets the dialog controls fpr each window, gives them their initial size and initializes 
//  the treeview. Also creates the popup too;bar for the search dialog and suzes it. 
//  WM_COMMAND  - process the application menu
//  WM_LBUTTONUP, WM_LBUTTONDOWN and WM_MOUSEMOVE to provide the visual effect of a drag-bar. 
//  WM_SIZE - adjusts the sizes of the child windows when the main window is re-sized or moved. 
//  WM_PAINT  - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;

    switch (message)
    {
    case WM_CREATE:
      {
        RECT lrccl, crccl, drccl;
        hFormView = CreateDialog(GetModuleHandle(NULL),
          MAKEINTRESOURCE(IDD_FORMVIEW),hWnd,editBoxProc);
        if (hFormView != NULL)
          {
          hEdit = GetDlgItem(hFormView, IDC_EDIT1);
          SetWindowText(hEdit, L"Enter EXTENSION or PART of filename.");
          ShowWindow(hFormView, SW_SHOW);
          }
        SetWindowText(hEdit, L"Default search string is '.cpp'! Change it above.");

        hTreeView = CreateDialog(GetModuleHandle(NULL),
          MAKEINTRESOURCE(IDD_FORMVIEW1),hWnd,treeViewProc);
        if (hTreeView != NULL)
          {
          hTree = GetDlgItem(hTreeView, IDC_TREE1);
          ShowWindow(hTreeView, SW_SHOW);
          }

        hListView = CreateDialog(GetModuleHandle(NULL),
          MAKEINTRESOURCE(IDD_FORMVIEW2),hWnd,listViewProc);
        if (hListView != NULL)
          {
          hList = GetDlgItem(hListView, IDC_LIST1);
          ShowWindow(hListView, SW_SHOW);
          }
        GetClientRect(hWnd,&crccl);
        MoveWindow(hFormView,0,0,crccl.right,50,true);
        MoveWindow(hTreeView,0,60,675,490,true);
        MoveWindow(hListView,685,60,305,490,true);
        SetTreeviewImagelist(hTree);
        SetCurrentDirectory(sId);
        GetCurrentDirectory(255, startInDir);
        if (_wcsicmp(startInDir, sId)!=0)
          {
          wsprintf(nodeCD,L"%s", L"C:\\Documents and Settings"); // define the start-in-directory for XP
          SetCurrentDirectory(nodeCD);
          GetCurrentDirectory(255, startInDir);
          }
        else
          {
          wsprintf(nodeCD,L"%s", sId); // initialize in case user did not select anything betore clicking 'search'.
          }
        InitTreeViewItems(hTree);
        hToolBar = CreateDialog(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_DIALOGBAR),hWnd,toolBarProc);
        if (hToolBar != NULL)
          {
          hFfext = GetDlgItem(hToolBar, IDC_EDIT1);
          SetWindowText(hFfext, L"Enter EXTENSION or PART of filename.");
          GetWindowRect(hWnd,&lrccl);
          GetClientRect(hWnd,&crccl);
          GetClientRect(hToolBar,&drccl);
          if (lrccl.top<40)
            {
            MoveWindow(hToolBar,lrccl.left+8,lrccl.top+36,crccl.right,50,true);
            }
          else
            {
            MoveWindow(hToolBar,lrccl.left+8,lrccl.top-36,crccl.right,50,true);
            }
          ShowWindow(hToolBar, SW_SHOW);
          }
        SetWindowText(hEdit, L"Default search string is '.cpp'! Change it above.");
        SetFocus(hTree);
        return 0;

      }break;

WM_COMMAND: Would you like to see a menu?

This is the place where menu commands and messages from older controls that are not "Common Controls" are processed. If you create child windows instead of child dialogs that have these older controls, those messages are processed here but the "Common Controls" are processed in the WM_NOTIFY switch. Since the Search dialog can be hidden or destroyed by user action, we will either Show or Create It here. The processing of the Search dialog is handled in the toolBarProc specified in the DLGPROC parameter of the CreateDialog function. In the case of IDM_ABOUT, the About Box, the DialogBox function is called to create a modal dialog box, which means control is passed to the DlGPROC and you can't do anything until you end the dialog. Notice the 'IDM_' in IDM_ABOUT and IDM_EXIT, contrasted with 'ID_' in ID_SEARCH. This 'IDM_' indicates that IDM_ABOUT is a popup sub-item on the Menu Bar. The 'ID_' indicates that ID_SEARCH is an action button on the menu, not a popup sub-item.

The WM_COMMAND code

C++
case WM_COMMAND:
  wmId    = LOWORD(wParam);
  wmEvent = HIWORD(wParam);
  // Parse the menu selections:
  switch (wmId)
  {
  case IDM_ABOUT:
    DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
    break;
  case ID_SEARCH:
  case ID_SHOW:
    {
    RECT lrccl, crccl, drccl;
    if (hToolBar != NULL)
      {
      hFfext = GetDlgItem(hToolBar, IDC_EDIT1);
      SetWindowText(hEdit, L"Enter EXTENSION or PART of filename in Text Box aboce.");
      GetWindowRect(hWnd,&lrccl);
      GetClientRect(hWnd,&crccl);
      GetClientRect(hToolBar,&drccl);
      if (lrccl.top<40)
        {
        MoveWindow(hToolBar,lrccl.left+8,lrccl.top+36,crccl.right,50,true);
        }
      else
        {
        MoveWindow(hToolBar,lrccl.left+8,lrccl.top-36,crccl.right,50,true);
        }
      ShowWindow(hToolBar, SW_SHOW);
      }
    else
      {
      hToolBar = CreateDialog(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_DIALOGBAR),hWnd,toolBarProc);
      if (hToolBar != NULL)
        {
        SetWindowText(hEdit, L"Enter EXTENSION or PART of filename in Text Box above.");
        GetWindowRect(hWnd,&lrccl);
        GetClientRect(hWnd,&crccl);
        GetClientRect(hToolBar,&drccl);
        if (lrccl.top<40)
          {
          MoveWindow(hToolBar,lrccl.left+8,lrccl.top+36,crccl.right,50,true);
          }
        else
          {
          MoveWindow(hToolBar,lrccl.left+8,lrccl.top-36,crccl.right,50,true);
          }
              ShowWindow(hToolBar, SW_SHOW);
              }
          }
        return 0;
        }
        break;
    case ID_HIDE:
        ShowWindow(hToolBar, SW_HIDE);
        ShowWindow(hdelBar, SW_HIDE);
        break;

    case IDM_EXIT:
        DestroyWindow(hWnd);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    break;

The imaginary splitter bar: Doctor, I'm seeing things that aren't there!

Has your older brother ever held his index finger a quarter of an inch from your eye and proclaimed "I'm not touching you!". You try to knock his hand away but he just moves it out of your reach and goes to your other eye. You instinctively dodge his finger but he's faster than you. You know that sooner or later he is going to poke you in the eye unless you close your eyes. When you open them again, you discover that your last piece of candy is gone but he denies any knowledge of your candy. That never happened to me, but okay, now replace his index finger with the mouse pointer. Your eyes become the windows containing the tree control and list control. When the finger, I mean, Mouse Pointer, moves (WM_MOUSEMOVE), the window is moved to keep the pointer away from it. Of course, we only do this if WM_LBUTTONDOWN (where we set capture). We detect left button down with wParam being equal to MK_LBUTTON. We set the cursor before we test wParam so the cursor is the west-east cursor when the splitter bar effect can be activated. We use the GetCursorPos function to get the position of the pointer and the GetClientRect and GetWindowRect functions to determine which way and how far to move each window. As the windows are moved they are also re-sized in the WM_SIZE notification. This process continues until the left button is released, at which point we process a WM_LBUTTONUP message and release the mouse capture. This code moves and sizes the dialog windows, not the controls. They are sized in the DLGPROC of the respective containing dialogs. All of the other messages use the default processing as provided by the Project Creation Wizard.

The (non-)imaginary Splitter Bar code

C++
    case WM_LBUTTONUP:
      ReleaseCapture();
      break;

    case WM_LBUTTONDOWN: 
      SetCapture(hWnd);
      break;

    case WM_MOUSEMOVE: 
      {
      int rcoff;
      POINT spt, cpt;
      POINT sptc, cptc;
      RECT erc, lrc, trc, wrc ;
      RECT erccl, lrccl, trccl, wrccl ;
      GetCursorPos(&spt);
      cpt.x = (short)LOWORD(lParam);  // horizontal position of cursor 
      cpt.y = (short)HIWORD(lParam);  // vertical position of cursor 
      cptc=cpt;sptc=spt;
      ClientToScreen(hWnd, &sptc);
      ClientToScreen(hWnd, &cptc);
      GetClientRect(hWnd,&wrccl);
      GetWindowRect(hWnd,&wrc);
      GetClientRect(hFormView,&erccl);
      GetWindowRect(hFormView,&erc);
      if (erccl.bottom < 20)
        {
        erc.bottom = erc.bottom + 20;
        }
      rcoff=erc.top-wrc.top;
      GetClientRect(hTreeView,&trccl);
      GetWindowRect(hTreeView,&trc);
      GetClientRect(hListView,&lrccl);
      GetWindowRect(hListView,&lrc);
      if (spt.y > trc.top)
        {
        SetCursor(hCursor);
        }
      if (wParam == MK_LBUTTON)
        {
        if (spt.y > erc.bottom)
          if (spt.x > trc.right || spt.x < lrc.left) 
            {
            sptc.x = sptc.x-wrc.left;
            OffsetRect(&erc, -wrc.left, -wrc.top); 
            OffsetRect(&trc, -wrc.left, -wrc.top); 
            OffsetRect(&lrc, -wrc.left, -wrc.top); 
            MoveWindow(hFormView,0,erc.top-rcoff,wrccl.right,erc.bottom-rcoff,true);
            MoveWindow(hTreeView,0,erc.bottom-rcoff+4,cpt.x-4,wrccl.bottom-erc.bottom+rcoff,true);
            MoveWindow(hListView,cpt.x+4,erc.bottom-rcoff+4,wrccl.right-cpt.x-4,wrccl.bottom-erc.bottom+rcoff,true);
            UpdateWindow(hWnd);
            }
        }
      return 0;
      }break;


  case WM_SIZE: 
    {
    int rcoff;
    RECT erc, lrc, trc, wrc ;
    RECT erccl, trccl, wrccl ;
    int cptx = (short)LOWORD(lParam);  // wideh of window
    int cpty = (short)HIWORD(lParam);  // hieght of window
    GetClientRect(hWnd,&wrccl);
    GetWindowRect(hWnd,&wrc);
    erccl=wrc;
    OffsetRect(&erccl, -wrc.left, -wrc.top); 
    GetWindowRect(hFormView,&erc);
    rcoff=erc.top-wrc.top;
    GetClientRect(hTreeView,&trccl);
    GetWindowRect(hTreeView,&trc);
    GetWindowRect(hListView,&lrc);
    OffsetRect(&erc, -wrc.left, -wrc.top); 
    OffsetRect(&trc, -wrc.left, -wrc.top); 
    OffsetRect(&lrc, -wrc.left, -wrc.top); 
    MoveWindow(hFormView,0,erc.top-rcoff,wrccl.right,erc.bottom-rcoff,true);
    MoveWindow(hTreeView,0,erc.bottom-rcoff+4,trc.right-trc.left,cpty-trc.top+rcoff+4,true);
    MoveWindow(hListView,lrc.left-trc.left,erc.bottom-rcoff+4,cptx-lrc.left+8,cpty-trc.top+rcoff+4,true);
    return 0;
    }break;


  case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    // TODO: Add any drawing code here...
    EndPaint(hWnd, &ps);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}

The search function

An imperative for exploration

The File Management function FindFirstFile searches a directory supplied by your code for a file or subdirectory with a name that matches the name you supply (or partial name if wildcards are used). It searches only the directory with the name you supply. If you want to include all files and sub directories you simply use "dirName\*". You also supply a buffer mapped by a WIN32_FIND_DATA structure to contain the information returned. It returns a search handle that can be used in a call to FindNextFile or FindClose. We use it in two different ways. The first is to get the files and sub-directories for a single directory in each call. In this call we populate that treenode. This is in a function named getDirectories. It is called from InitTreeviewItems as well as the TVN_ITEMEXPANDING notification.

In the second use of the FindFirstFile triad, we look for file-names that contain the specified sub-string and add them to the ListView or if they are sub-directories we call the search function recursively to search them. We use the file attribute FILE_ATTRIBUTE_HIDDEN to filter out hidden directories and files. In a recursive loop this makes a very noticeable difference in speed. You probably don't want to see those files anyway, so why waste the time looking for them?

I should also mention my usage of wsprintf as a replacement for wcscpy and wcscat combinations. Although wsprintf may be slower than wcscpy/wcscat, I did not perceive any change in the speed while changing from one to the other, in either direction. The wsprintf actually eliminated a large number of Security Warnings. In this usage we look at the next to last character of the name that is passed to us. When it is a backslash we add an asterisk, otherwise we add a backslash and asterisk to set up for FindFirstFile. This is equivalent to a wcscpy followed by a wcscat followed by another wcscat. In setting up to make the recursive call we also look at the next to last character, add either a backslash and the sub directory or just the sub directory. I placed the recursive call last in the nested 'If' statement which caused a match on a directory to be found. This would prevent that sub directory from being searched. To prevent this I used the FILE_ATTRIBUTE_DIRECTORY file attribute to skip the matching logic on a directory and again to prevent a recursive call on a file. The match logic itself is just a wcsstr function looking for "searchStr" in "ffd.cFileName". If you want a different matching routine this is where you do it. When we have a match we insert the name into the ListView with zero as the iItem index value and a iSubItem column index value. Since the ListView is sorted in ascending order the iItem is inserted where it belongs, returning the value to be used to set any iSubItem attribute value. We use it to set the pszText attribute for iSubItem (path column). Any column you add, you set their value here. All of the attributes are available at this point but there are only two columns for the filename and path.

The recursive file search code

C++
// FUNCTION: Find all files whose name contains the search argument.
// Does not search hidden directories. This results in a significant 
// increase in speed. No attempt is made to analyze position of 
// argument in filename. This may cause unexpected matches.  
int recursivefileSearch(TCHAR *name, bool hidden){
    WIN32_FIND_DATA ffd;
    HANDLE hFind = INVALID_HANDLE_VALUE;
    DWORD dwError=0;
    TCHAR recursiveSearchPath[MAX_PATH];

    if(name[wcslen(name)-1]!=L'\\')
      { // Prepare string for use with FindFile functions.  First, copy the
      wsprintf(recursiveSearchPath,L"%s\\*", name);
      // string to a buffer, then append "\*" to the directory name. 
      }
    else 
      {
      wsprintf(recursiveSearchPath,L"%s*", name); 
      }
   
    hFind = FindFirstFile(recursiveSearchPath, &ffd); // Find the first file in the directory.

   do    { // List all the files in the directory with some info about them.
     if (hFind == INVALID_HANDLE_VALUE)  //Done checking this folder
       return -1; // no files were found in this recursion
     if ((wcscmp(ffd.cFileName, L"."))== 0 //If the file is a self-reference
       || (wcscmp(ffd.cFileName, L".."))== 0)
       {;} // do nothing
     else if (ffd.dwFileAttributes 
       & FILE_ATTRIBUTE_HIDDEN 
       && !hidden)
       {;} // the file is hidden and hidden search is turned off
     else if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
       && (wcsstr(ffd.cFileName, searchStr)) )
       { // it is a FILE containing the argument, put it in the list
       LVITEM item;
       memset(&item,0,sizeof(item));
       item.mask = LVIF_TEXT;
       item.iItem = 0;
       item.iSubItem = 0;
       item.cchTextMax = 260;
       item.pszText = ffd.cFileName;
       int ret =  ListView_InsertItem(hList, &item);
       item.iItem = ret;
       item.iSubItem = 1;
       item.pszText = name;
       ListView_SetItem(hList, &item);
       }
     else if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
       { // it is a directory
       if(name[wcslen(name)-1]!=L'\\')
         {
         wsprintf(recursiveSearchPath,L"%s\\%s", name, ffd.cFileName);
         }
       else
         {
         wsprintf(recursiveSearchPath,L"%s%s", name, ffd.cFileName);
         }
       recursivefileSearch(recursiveSearchPath, hidden); // search this one too
       }
     }
   while (FindNextFile(hFind, &ffd) != 0);

   dwError = GetLastError();
   if (dwError != ERROR_NO_MORE_FILES)
     if (dwError != ERROR_ACCESS_DENIED) 
       {
       // set the text in ID_EDITBOX
       SetWindowText(hEdit, L"Error in Find File Routine !"); 
       }

     FindClose(hFind);
     return dwError;
  }

In support of the Search function: The toolBarProc

I used a ToolBar dialog as a starting point for this function but I don't know that it was the best choice. It probably has features that I didn't use. It just worked okay and I had no reason to change it. It's pretty simple. There is a Button and an Edit control. You can enter a sub-string to search for or click the button to start the search. The button gets the handle for the item in the tree that is highlighted and finds its full path. Then it invokes the search function and hides the dialogbox for later use. It can be destroyed by clicking anywhere in the dialog except the button and then pressing Alt + F4. Pressing this combination twice or without first clicking on the dialog cause an Exit from the program. Clicking on the Edit Control when the Hint is still showing will change the contents to the default search string. This is done when you click the button also. If you want to change the default you should change it in both places unless you specifically want to have alternate defaults.

The code for toolBarProc

C++
// Message handler for Search Dialogbar. Checks input and possibly set initial search argument to '.cpp'. 
// Gets path for search and invokes recursive file search.
INT_PTR CALLBACK toolBarProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  {
  UNREFERENCED_PARAMETER(lParam);
  switch (message)
    {
    case WM_INITDIALOG:
      return (INT_PTR)TRUE;

    case WM_COMMAND:
      if (LOWORD(wParam) == IDC_BUTTON1)
        {
        hFfext = GetDlgItem(hDlg, IDC_EDIT1);
        GetWindowText(hFfext, searchStr, MAX_PATH);
        if(wcscmp(searchStr, L"Enter EXTENSION or PART of filename.")==0)
          {
          wsprintf(searchStr,L"%s",  L".cpp");
          SetWindowText(hFfext, searchStr);
          }
        tvi.hItem = TreeView_GetDropHilight(hTree); 
        getNodeFullPath(hTree, tvi.hItem);
        recursivefileSearch(nodeCD, false); // using true is much slower
        ShowWindow(hToolBar, SW_HIDE);
        }

      if (LOWORD(wParam) == IDC_EDIT1)
        {
        if (HIWORD(wParam) == EN_SETFOCUS)
          { // the user has clicked in the edit control
          hFfext = GetDlgItem(hDlg, IDC_EDIT1);
          GetWindowText(hFfext, searchStr, MAX_PATH);
          if(wcscmp(searchStr, L"Enter EXTENSION or PART of filename.")==0)
            {
            wsprintf(searchStr,L"%s",  L".cpp");
            SetWindowText(hFfext, searchStr);
            }
          }
        }

      if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
        EndDialog(hDlg, LOWORD(wParam));
        return (INT_PTR)TRUE;
        }
      break;
    }
  return (INT_PTR)FALSE;
  }

Cleaning up the ListView: The delBarProc

This popup DialogBar is created in the listViewProc by right clicking on an item or pressing the delete key. It allows you to delete listview items only. It does not delete files from the file system. You can delete the selected item, all items of the same type, all items with the same path, or everything. With this you can collect groups of files such as cpp's, headers, and sln's, or any grouping you choose.

You can also add individual files from the tree to the ListView by clicking them in the TreeView or moving the caret to them with the cursor keys and then pressing Enter. Doing either of these to a folder will clear the contents of the ListView and insert the contents of the folder in their place. The point of this is that navigating the tree should be intuitive for a Windows user, therefore anything the user does should result in an action the user understands. Clicking on a folder in the Windows Explorer TreeView pane produces the same results. Here, we include files in the treeview and clicking on them has a similar effect. Even though it doesn't delete anything from the listview, it is not so different as to be counter-intuitive.

From this discussion it is obvious that a separate function to remove items from the lisview is not an absolute necessity. It is provided to selectively control the contents of the listview rather than simply deleting everything and then putting something else in its place.

The code for the delBarProc

C++
// Message handler for Delete Dialogbar.
// WM_COMMAND processes button clicks for 'Remove Selected', 'Remove Type',  
// 'Remove Path' and 'Remove All'. 
// NOTE: The 'REMOVE' functions do not delete anything from the file system, 
// they only remove them from the listview collection.
//
INT_PTR CALLBACK delBarProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  {
  UNREFERENCED_PARAMETER(lParam);
  switch (message)
    {
    case WM_INITDIALOG:
      return (INT_PTR)TRUE;

    case WM_COMMAND:
      if (LOWORD(wParam) == IDC_BUTTON1)
        {
        LVITEM item;
        TCHAR Text[256]={0}; 
        memset(&item,0,sizeof(item));
        item.mask = LVIF_TEXT;
        item.iSubItem = 0;
        item.cchTextMax = 260;
        item.pszText = Text;
        int ret = ListView_GetNextItem(hList,-1,LVNI_SELECTED);
        while (ret> -1)
          {
          ListView_DeleteItem(hList, ret);
          ret = ListView_GetNextItem(hList,-1,LVNI_SELECTED);
          }
        ShowWindow(hdelBar, SW_HIDE);
        }

      if (LOWORD(wParam) == IDC_BUTTON2)
        {
        LVITEM item;
        TCHAR Text[256]={0}; 
        TCHAR extPat[255]={0};
        TCHAR * pdest;
        TCHAR * ppatt;
        memset(&item,0,sizeof(item));
        item.mask = LVIF_TEXT;
        item.iSubItem = 0;
        item.cchTextMax = 255;
        item.pszText = Text;
        int ret = ListView_GetNextItem(hList,-1,LVNI_SELECTED);
        if(ret != -1) 
          {
          item.iItem = ret;
          ListView_GetItem(hList,&item);
          }
        ppatt = extPat;
        pdest = wcsrchr(Text,L'.');
        while (*pdest)
          {
          *ppatt++ = *pdest++;
          }
        ret = ListView_GetNextItem(hList,-1,LVNI_ALL);
        memset(&Text,0,sizeof(Text));
        memset(&item,0,sizeof(item));
        item.mask = LVIF_TEXT;
        item.iSubItem = 0;
        item.cchTextMax = 255;
        item.pszText = Text;
        item.iItem = ret;
        ListView_GetItem(hList,&item);
        while (ret> -1)
          {
          if (wcsstr(Text,extPat))
            ListView_DeleteItem(hList, ret--);
          ret = ListView_GetNextItem(hList,ret,LVNI_ALL);
          memset(&Text,0,sizeof(Text));
          memset(&item,0,sizeof(item));
          item.mask = LVIF_TEXT;
          item.iSubItem = 0;
          item.cchTextMax = 255;
          item.pszText = Text;
          item.iItem = ret;
          ListView_GetItem(hList,&item);
          }
        ShowWindow(hdelBar, SW_HIDE);
        }

      if (LOWORD(wParam) == IDC_BUTTON3)
        {
        LVITEM item;
        TCHAR Text[256]={0}; 
        TCHAR pathPatt[256]={0}; 
        memset(&item,0,sizeof(item));
        item.mask = LVIF_TEXT;
        item.iSubItem = 1;
        item.cchTextMax = 260;
        item.pszText = Text;
        int ret = ListView_GetNextItem(hList,-1,LVNI_SELECTED);
        item.iItem = ret;
        ListView_GetItem(hList,&item);
        wsprintf(pathPatt,L"%s", item.pszText);
        memset(&Text,0,sizeof(Text));
        memset(&item,0,sizeof(item));
        item.mask = LVIF_TEXT;
        item.iSubItem = 1;
        item.cchTextMax = 255;
        item.pszText = Text;
        ret = ListView_GetNextItem(hList,-1,LVNI_ALL);
        item.iItem = ret;
        ListView_GetItem(hList,&item);
        while (ret> -1)
          {
          if (_wcsicmp(Text,pathPatt)==0)
            ListView_DeleteItem(hList, ret--);
          ret = ListView_GetNextItem(hList,ret,LVNI_ALL);
          memset(&Text,0,sizeof(Text));
          memset(&item,0,sizeof(item));
          item.mask = LVIF_TEXT;
          item.iSubItem = 1;
          item.cchTextMax = 255;
          item.pszText = Text;
          item.iItem = ret;
          ListView_GetItem(hList,&item);
          }
        ShowWindow(hdelBar, SW_HIDE);
        }

      if (LOWORD(wParam) == IDC_BUTTON4)
        {
        ListView_DeleteAllItems(hList);
        ShowWindow(hdelBar, SW_HIDE);
        }

      if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
        EndDialog(hDlg, LOWORD(wParam));
        return (INT_PTR)TRUE;
        }
      break;
  }
  return (INT_PTR)FALSE;
}

The dialog procedure for the Edit Control

The Edit Control is intended to provide information to the user. It is a passive control in this version, meaning it doesn't need a DLGPROC. It is necessary for any functionality you might want to add to the Edit Control, to make it an active control. If you want to save a few lines of code you could use NULL for the DlGPROC parameter. This has the same effect as specifying NULL for the Menu parameter of the CreateDialog function. It isn't necessary for the functionality we are using now but if you want to add anything, you will need it. Otherwise the procedure is just a place holder.

The code for the editBoxProc

C++
// Message handler for  edit box. Place Holder. No code was added.
INT_PTR CALLBACK editBoxProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
  UNREFERENCED_PARAMETER(lParam);
  switch (message)
  {
  case WM_INITDIALOG:
    return (INT_PTR)TRUE;

  case WM_COMMAND:
    if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
    {
      EndDialog(hDlg, LOWORD(wParam));
      return (INT_PTR)TRUE;
    }
    break;
  }
  return (INT_PTR)FALSE;
}

The dialog procedure for the TreeView

I begin the discussion of the treeViewProc with the WM_INITDIALOG message. In this instance no code was added for this message because the setup was done in the main window procedure, WndProc. This is where you could call the InitTreeViewItems function and the SetTreeviewImagelist function. But if you are coding a CreateWindow version, you don't have a WM_INITDIALOG message, so you call them from the WM_CREATE message of the WndProc.

Going on to the WM_NOTIFY message; the first switch statement selects the idFrom member of the NMHDR. In a Dialog Proc we do not need to do this because we know that this is the treeview control. But in the CreateWindow version we do need to do this because we need to select the other controls in the main window. You could put this WM_NOTIFY, unchanged, into the WndProc procedure of a CreateWindow version, along with the switches you need for any other control you want to put in the main window. To use the listViewProc for this, you must add the case for IDC_LIST1 to the switch.

TVN_SELCHANGING: Keeping it synchronized

The TVN_SELCHANGING notification is the first message the treeview control sends when the user clicks an item or moves the cursor to an item. The only action we need to take is to find the full path for the item represented by the item text. Failure is not an option. If it fails, the tree will not function properly. A possible way to find the full path is to climb down the tree, taking names and kicking bu--, oh no, that's something else I'm doing. For this function, I'm just collecting names in a sub-routine called getNodeFullPath. This is a separate function because it is used in more than one place. The TVN_ITEMEXPANDING notification can be sent by clicking on the plus button of an item that has not been selected. In this case the node's full path is not known. To detect this situation we must get the selected item to determine if it is the one we are expanding. If it isn't we have to get its full path so we can populate its children if there are any. This doesn't select the item and can break the search function. Therefore we need to get the highlighted item and determine its full path. Let's look at WM_INITDIALOG and the TVN_SELCHANGING notification of WM_NOTIFY before continuing.

The code for WM_INITDIALOG, WM_NOTIFY - TVN_SELCHANGING

C++
// FUNCTION: Message handler for  tree view. Processess WM_NOTIFY for the following messages: 
// TVN_SELCHANGING - Gets the NODE's full path, back to the drive. 
// TVN_SELCHANGED - If action is TVC_UNKNOWN and startindir is not blank, populates and expands 
// tree until startin dir is located. If action is TVC_BYKEYBOARD current item is selected and 
// highlited If action is TVC_BYMOUSE and current item is a folder the contents of the folder 
// replaces the listview contents. If it's not a folder the item is added to the listview. 
// TVN_ITEMEXPANDING - populates the child nodes with files and folders, always and only one level deeper. 
// TVN_ITEMEXPANDED - If startindir is not blank, populates and expands tree until startin dir is located. 
// NM_CLICK - populates ListView when node is selected by keyboard and then clicked by mouse. 
// NM_RETURN - populates ListView when node is selected by keyboard and then Enter is pressed. 
// WM_SIZE wil re-size the tree control  to fit the containing window.  
INT_PTR CALLBACK treeViewProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
  UNREFERENCED_PARAMETER(lParam);
  switch (message)
  {
  case WM_INITDIALOG:
    return (INT_PTR)TRUE;

  case WM_NOTIFY:
    { // messages from child window common controls
    switch (((LPNMHDR)lParam)->idFrom)
      {
      case IDC_TREE1:
        { 
        switch (((LPNMHDR)lParam)->code)
          {
          case  TVN_SELCHANGING: { // Gets the NODE's full path, back to the drive. 
            NMTREEVIEW * pnmtv; // Hamdle to the NM TreeView STRUCTURE
            pnmtv = (LPNMTREEVIEW)lParam; // set the Hamdle to the NM TreeView STRUCTURE
            tvi=((pnmtv)->itemNew); // set the Handle to the tvitem STRUCTURE for the item that was just selected 
            getNodeFullPath(hTree, tvi.hItem);
            return false;
            }  break; // case  TVN_SELCHANGING

TVN_SELCHANGED: How did we get here?

The TVN_SELCHANGED notification processes three actions: TVC_UNKNOWN is caused by code internal to the control or code external to the control, that is, program code. The action we need to perform here is to discover if this item's text is contained in the start in directory when concatenated to the current directory. When this is so we cause this node to be expanded and focused on until we discover the two are equal, then we blank out the start in directory. This process can be started at program start-up by the InitTreeviewItems function where it is the find start in directory function or by user action in the

listViewProc
function in response to pressing the F3 key after having selected an item. This is the 'Locate File in Tree' function. Let's look at the TVC_UNKNOWN action code of the TVN_SELCHANGED notification processed by WM_NOTIFY before going on to the other two action codes.

The code for WM_NOTIFY - TVN_SELCHANGED -- TVC_UNKNOWN

C++
case TVN_SELCHANGED: 
{ // populates ListView when node is expanded by mouse. 
// picks-up process to find startinDirectory when node is 
// selected by code or by clicking '+' button on node
// updates 'HighLite' status if node is selected by keyboard
NMTREEVIEW * pnmtv; 
pnmtv = (LPNMTREEVIEW)lParam; 
HTREEITEM  nmtvi;
nmtvi = TreeView_GetSelection(hTree);
TCHAR Text[256]={};
TCHAR text[256]={};
int result = 0;
memset(&tvi,0,sizeof(tvi));
tvi.mask = TVIF_TEXT | TVIF_CHILDREN| TVIF_IMAGE;
tvi.pszText=Text;
tvi.cchTextMax = 255;
tvi.hItem = nmtvi;
if((pnmtv)->action==TVC_UNKNOWN)
  {
      if (*startInDir)
        { // is there a start-in-directory 
        if (_wcsnicmp(startInDir, nodeCD, wcslen(nodeCD)) == 0 )
          { // is it in the path
          if(TreeView_GetItem(hTree, &tvi))
            { // could you get the item 
            if (tvi.cChildren > 0 )
              { // does it have children
              if (tvi.state & TVIS_EXPANDEDONCE)
                { // node has children and has been expanded. go on to the next higher level
                nmtvi=TreeView_GetChild(hTree, nmtvi);
                while (nmtvi){ // loop to get the child's label text
                  memset(&tvi,0,sizeof(tvi));
                  memset(&Text,0,sizeof(Text));
                  tvi.mask = TVIF_TEXT | TVIF_CHILDREN| TVIF_IMAGE;
                  tvi.pszText=Text;
                  tvi.cchTextMax = 255;
                  tvi.hItem = nmtvi;
                  if(TreeView_GetItem(hTree, &tvi))
                    { 
                    if(nodeCD[wcslen(nodeCD) - 1] != L'\\')
                      {
                      wsprintf(text, L"%s\\%s", nodeCD,tvi.pszText); // concat in text buffer
                      }
                    else
                      {
                      wsprintf(text, L"%s%s", nodeCD,tvi.pszText); // concat in text buffer
                      }
                    result = wcslen(text);
                    if (sidLen == result && (_wcsicmp(startInDir, text)== 0)) // when lengths are the same compare
                      { // when strings are same 
                      SetCurrentDirectory(nodeCD);
                      memset(startInDir,0,sizeof(startInDir));
                      TreeView_Select(hTree,nmtvi,TVGN_CARET);
                      TreeView_Select(hTree,nmtvi,TVGN_DROPHILITE);
                      TreeView_Select(hTree,nmtvi,TVGN_FIRSTVISIBLE); // comment this to to position at bottom
                      ReleaseCapture();
                      SetCapture(hList);
                      SetFocus(hTree);
                      ReleaseCapture();
                      return 0;
                      }
                    else
                      {
                      wsprintf(text, L"%s\\", text); // concat a back-slash
                      result = wcslen(text); //take the length again(it just changed)
                      if (_wcsnicmp(startInDir, text, result) == 0 )
                        { // is folder in path
                        if (tvi.cChildren > 0 )
                          {
                          if (!(tvi.state & TVIS_EXPANDEDONCE )) 
                            { //node has children but has never been expanded. TVN_EXPANDED will take over now.
                            TreeView_Expand(hTree, tvi.hItem, TVE_EXPAND); // we are through here
                            return 0; //terminate the routine
                            }
                          else 
                            { // folder in path, go on to the next higher level
                            if(nodeCD[wcslen(nodeCD) - 1] != L'\\')
                              {
                              wsprintf(nodeCD,L"%s\\%s", nodeCD,tvi.pszText);
                              }
                        else
                          {
                          wsprintf(nodeCD,L"%s%s", nodeCD,tvi.pszText);
                          }
                        nmtvi=TreeView_GetChild(hTree, nmtvi);
                        }
                      }
                    else 
                      { // there are no children. end
                      return 0;
                      }
                    }
                  else
                    {
                    nmtvi = TreeView_GetNextSibling(hTree,nmtvi); // get the next child's handle
                    }
                  }
                }
              } // loop to get the child's label text
            } // node has children and has been expanded. go on to the next higher level
          } // does it have children
        } // could you get the item 
      } // is it in the path
    else
      { 
      return 0; // don't do  anything
      }
    return 0;
    } // is there a start-in-directory 
  }

WM_NOTIFY - TVN_SELCHANGED -- By keyboard or by mouse

TVC_BYKEYBOARD results from the user pressing the up or down cursor keys, moving the selection and focus. Since multiple items can be selected we must make it so by programmatically selecting and highlighting the item.

TVC_BYMOUSE indicates the user has selected the item by clicking on it. If the item isn't a folder we want to insert it into the listview, scrolling to it, and selecting it. On my XP the item showed as selected but on Win7 it did not, so I put in a sleep function call so it would show as high-lighted for one second. When it woke up the highlight went off until you moved focus to the ListView control. If the item was a folder we clear the listview and get the children of the folder item, placing them into the listview in a do loop, repeating until we run out of children. Let's look at these two action codes.

The code for WM_NOTIFY - TVN_SELCHANGED -- TVC_BYKEYBOARD and TVC_BYMOUSE

C++
if((pnmtv)->action==TVC_BYKEYBOARD)
  {
  TreeView_Select(hTree,nmtvi,TVGN_CARET);
  TreeView_Select(hTree,nmtvi,TVGN_DROPHILITE);
  return 0;
  }

if((pnmtv)->action==TVC_BYMOUSE)
  {
  TreeView_Select(hTree,nmtvi,TVGN_DROPHILITE);
  TreeView_Select(hTree,nmtvi,TVGN_CARET);
  tvi.hItem = nmtvi;
  if(TreeView_GetItem(hTree, &tvi))
    {
    if (tvi.iImage == 4)
      { // not a folder
      LVITEM item;
      memset(&item,0,sizeof(item));
      GetCurrentDirectory(MAX_PATH,nodeCD);
      item.mask = LVIF_TEXT;
      item.iItem = 0;
      item.iSubItem = 0;
      item.cchTextMax = 260;
      item.pszText = tvi.pszText;
      int ret =  ListView_InsertItem(hList, &item);
      item.iItem = ret;
      item.iSubItem = 1;
      item.pszText = nodeCD;
      ListView_SetItem(hList, &item);
      int nRet = ret+1;
      ListView_GetItemText(hList,nRet,0,text,255); 
      while (_wcsicmp(text, tvi.pszText)==0)
        { // 'text' is 'Name' in listview
        ListView_GetItemText(hList,nRet,1,text,255); 
        if (_wcsicmp(text, nodeCD)==0)
          { // now 'text' is 'Path' in listview
          ListView_DeleteItem(hList,ret); // it's a duplicate. Delete it. 
          ret = nRet;
          }
        nRet = nRet+1;
        ListView_GetItemText(hList,nRet,0,text,255); // now it's 'Name' again 
        }
      ListView_SetItemState(hList,-1,0,LVIS_SELECTED); //de-select whatever
      ListView_SetItemState(hList,-1,0,LVIS_DROPHILITED);
      ListView_SetItemState(hList,ret,LVIS_DROPHILITED,LVIS_DROPHILITED);
      ListView_SetItemState(hList,ret,LVIS_SELECTED,LVIS_SELECTED);
      ListView_SetItemState(hList,ret,LVIS_FOCUSED,LVIS_FOCUSED);
      ListView_EnsureVisible(hList,ret, true);
      ReleaseCapture();
      SetCapture(hList);
      SetFocus(hList); 
      Sleep(1000);
      ReleaseCapture();
      } // the item is not a folder, add it to the listview
    else if (tvi.cChildren == 1)
      { // it is a folder, clear listview and populate it with folder contents. 
      nmtvi=TreeView_GetChild(hTree, nmtvi);
      memset(&tvi,0,sizeof(tvi));
      memset(&Text,0,sizeof(Text));
      tvi.mask = TVIF_TEXT | TVIF_CHILDREN| TVIF_IMAGE;
      tvi.pszText=Text;
      tvi.cchTextMax = 255;
      tvi.hItem = nmtvi;
      if(TreeView_GetItem(hTree, &tvi))
        {
        ListView_DeleteAllItems(hList);
        GetCurrentDirectory(MAX_PATH,nodeCD);
        do
          {
          LVITEM item;
          memset(&item,0,sizeof(item));
          item.mask = LVIF_TEXT;
          item.iItem = 0;
          item.iSubItem = 0;
          item.cchTextMax = 260;
          item.pszText = tvi.pszText;
          int ret =  ListView_InsertItem(hList, &item);
          item.iItem = ret;
          item.iSubItem = 1;
          item.pszText = nodeCD;
          ListView_SetItem(hList, &item);
          if (ret < 1)         
            ret *= -1;
          nmtvi = tvi.hItem;
          nmtvi = TreeView_GetNextSibling(hTree,nmtvi);
          memset(&tvi,0,sizeof(tvi));
          memset(&Text,0,sizeof(Text));
          tvi.mask = TVIF_TEXT;
          tvi.pszText=Text;
          tvi.cchTextMax = 255;
          tvi.hItem = nmtvi;
          } while (TreeView_GetItem(hTree, &tvi));
        }
      }
    }
  }
} break; // case TVN_SELCHANGED

WM_NOTIFY - TVN_ITEMEXPANDING: The key to the Castle

The TVN_ITEMEXPANDING notification is sent by the tree control before expanding a node. This is the key to keeping the tree sparsely populated while making it appear to be fully populated. We need to determine if the tree is performing the TVE_COLLAPSE action. If so we set the iImage and iSelectedImage to closed-folder and closed-folder-selected, then exit, otherwise we set the indexes to opened-folder and opened-folder-selected. Then we check to see if the folder was opened previously (TVIS_EXPANDEDONCE). If this is the case we don't want to add duplicates so we exit here also. Now we have the case of a user clicking the button of a folder item that is not selected. We don't know when this might happen so we test for the situation before attempting to populate the node's grandchildren. To detect it we use the TreeView_GetSelection macro and compare the returned value to NULL and then to the itemNew.hItem member of the NMTREEVIEW structure. Since the itemOld member isn't used by the TVN_ITEMEXPANDING notification it has no information we can use. If the value is not NULL and the two values are not the same we know the button was clicked so we discard the value and use itemNew to get the full path if we want speed or with the TreeView_SelectItem macro if we want to see the folders expanding on the screen. I just left them both in but either will work alone. Before we release the node for expansion we give it grand children, that is, we populate its children with their children so that when the node is expanded its children will have their buttons!, indicating that they have some children. Any children without buttons are sterile and have no children of their own. This is one way to create a sparse tree effect but it is not the only way. One item to research on this subject is the TVN_GETDISPINFO notification used in conjunction with the LPSTR_TEXTCALLBACK attribute. Here's the code for the TVN_ITEMEXPANDING notification sent to WM_NOTIFY for processing:

The code for WM_NOTIFY - TVN_ITEMEXPANDING

C++
case TVN_ITEMEXPANDING: 
{ // populates the child nodes with files and folders, always one level deeper. 
NMTREEVIEW * pnmtv;
HTREEITEM  nmtvi;
pnmtv = (LPNMTREEVIEW)lParam; 
TCHAR Text[256]={0};
tvi=((pnmtv)->itemNew);
nmtvi =  TreeView_GetSelection(hTree);
if (NULL != nmtvi  && tvi.hItem != nmtvi)
  { // user clicked on node button(+), node is not selected
  getNodeFullPath(hTree, tvi.hItem);
  TreeView_SelectItem(hTree, tvi.hItem);
  }
if ((pnmtv)->action & TVE_COLLAPSE)
  { // the folder is closing, set it's image indexes  to show a closed folder
  tvi.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE; 
  tvi.iImage = 0; // Closed 
  tvi.iSelectedImage = 1; // Closed and selected 
  TreeView_SetItem(hTree, &tvi);
  return false;
  } 
// still here? Okay, then this node is expanding but it has no grandchildren yet. 
// Give it's CHILDREN some Children so that it's CHILDREN will have BUTTONS.
tvi.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE; 
tvi.iImage = 2;// FOLDEROPEN 
tvi.iSelectedImage = 3;// opened and selected
TreeView_SetItem(hTree, &tvi); // set the image indexes to show open folders.
if (tvi.state & TVIS_EXPANDEDONCE)
  return false; // don't duplicate children
tvi.mask = TVIF_TEXT|TVIF_CHILDREN;
tvi.pszText=Text;
tvi.cchTextMax = 255;
if(TreeView_GetItem(hTree, &tvi))
  { // you've got the requested attributes 
  if(tvi.pszText[1]==L':')
    { //the second character is a colon. It's a DRIVE 
    wsprintf(nodeCD, L"%s", Text);
    SetCurrentDirectory(nodeCD);
    nmtvi = TreeView_GetChild(hTree, tvi.hItem); 
    }
  else  
    { //the second character is not a colon.It's not a DRIVE 
    memset(&nodeCD,0,sizeof(nodeCD));
    nmtvi = TreeView_GetChild(hTree, tvi.hItem); 
    } // either way, you might now have the first child of the node
  while (nmtvi)
    { // if you have the child, do the loop 
    memset(&tvi,0,sizeof(tvi));
    memset(&Text,0,sizeof(Text));
    tvi.hItem=nmtvi;
    tvi.mask = TVIF_TEXT|TVIF_CHILDREN;
    tvi.pszText=Text;
    tvi.cchTextMax = 255;
    GetCurrentDirectory(MAX_PATH,nodeCD);
    if(TreeView_GetItem(hTree, &tvi))
      { // just in case the handle is corrupted 
      if(nodeCD[wcslen(nodeCD) - 1] != L'\\')
        { // does not end with a back-slash
        wsprintf(nodeCD, L"%s\\", nodeCD); // concat a back-slash to nodeCD 
        }
      wsprintf(nodeCD, L"%s%s", nodeCD,tvi.pszText); // concat to nodeCD 
      getDirectories(tvi.hItem, nodeCD);
      nmtvi = TreeView_GetNextSibling(hTree, tvi.hItem);
      } 
    } 
  }   
return false;
} break; // case TVN_ITEMEXPANDING

WM_NOTIFY - TVN_ITEMEXPANDED: Are we there yet?

The TVN_ITEMEXPAnDED notification is sent by the tree control after the node is expanded or collapsed. If the action is TVE_COLLAPSE we exit. Otherwise we look at the start in directory. If is is blank, that is, if the first character is zero we exit also. If the start in directory is there we get the just expanded folder's children and concatenate their item text to the current directory and compare it to the start in directory. If one of the children is a sub-string of the start in directory and it has children, we use the TREEVIEW_EXPAND macro to expand the node. This will populate the treeview node's grandchildren and continue the process in the TVN_ITEMEXPANDED notification that will be sent after expansion. If one of the children is the start in directory we zap the start in directory with memset, setting it to zeroes, then select the child node, highlight it, and give it the focus. Let's take a look at the code for the TVN_ITEMEXPANDED notification that is sent to WM_NOTIFY after the TVN_ITEMEXPANDING notification.

The code for WM_NOTIFY - TVN_ITEMEXPANDED

C++
case TVN_ITEMEXPANDED: // This folder has just been expanded. 
{ // Is it and any of it's children in the srart-in-directory?
NMTREEVIEW * pnmtv;
HTREEITEM  nmtvi;
pnmtv = (LPNMTREEVIEW)lParam; 
TCHAR zero = L'\0';
TCHAR Text[256]={0};
TCHAR teXt[256]={0};
TCHAR text[256]={0};
if ((pnmtv)->action & TVE_COLLAPSE) 
  return 0; // don't do  anything
tvi=((pnmtv)->itemNew);
tvi.mask = TVIF_TEXT|TVIF_CHILDREN;
tvi.pszText=Text;
tvi.cchTextMax = 255;
if(TreeView_GetItem(hTree, &tvi))
  { // you've got the requested attributes 
  if (*startInDir != zero)
    { // is there a start-in-directory
    memset(&nodeCD,0,sizeof(nodeCD));
    nmtvi = TreeView_GetChild(hTree, tvi.hItem); 
    while (nmtvi)
      { // if you have the child, do the loop 
      tvi.hItem=nmtvi;
      GetCurrentDirectory(MAX_PATH,nodeCD);
      if(TreeView_GetItem(hTree, &tvi))
        { // just in case the handle is corrupted 
        if(nodeCD[wcslen(nodeCD) - 1] != L'\\')
          { // if it doesn't have a backslash on the end, add one 
          wsprintf(nodeCD,L"%s\\", nodeCD);
          }
        wsprintf(text,L"%s%s", nodeCD, tvi.pszText);
        if (_wcsnicmp(startInDir, text, wcslen(text)) == 0 )
          {
          if ((_wcsicmp((startInDir), (text)))==0)
            {
            memset(startInDir,0,sizeof(startInDir));
            TreeView_Select(hTree,nmtvi,TVGN_CARET);
            TreeView_Select(hTree,nmtvi,TVGN_DROPHILITE);
            SetFocus(hTree);
            return false;
            }
          wsprintf(text,L"%s\\", text);
          if (_wcsnicmp(startInDir, text, wcslen(text)) == 0 )
            {
            if (tvi.cChildren > 0 )
              {
              if (!(tvi.state & TVIS_EXPANDEDONCE )) 
                { //node has children but has never been expanded. TVN_EXPANDED will take over now.
                Sleep(300);
                TreeView_Expand(hTree, tvi.hItem, TVE_EXPAND); // we are through here
                return false; //terminate the routine
                }
              }
            }
          }
        nmtvi = TreeView_GetNextSibling(hTree, tvi.hItem);
        } // just in case the handle is corrupted 
      } // if you have the child, do the loop 
    } // is there a start-in-directory
  } //you've got the requested attributes 
return false;
} break; // case TVN_ITEMEXPANDED

WM_NOTIFY - NM_CLICK: Did you hit me again?

The NM_CLICK notification is sent by the tree control when the user left clicks the control. It is sent before the TVN_SELCHANGING notification when the node is not selected. This allows the program to control the selection process. You can return zero to allow the selection or non-zero to disallow it. But when the item is already selected, what happens? The default answer is - nothing! The notification contains only an NMHDR structure. Its fields are hwndFrom, idFrom, and code. The TreeView node is not identified. It seems to be pointless. Is this what we want? No! This would be confusing. We expect the response to be populating the ListView. The problem is, this is what is done in TVN_SELCHANGED with the TVC_BYMOUSE action code, but only when the selection is changed BYMOUSE. We don't do it when the selection is changed BYKEYBOARD. We have a slight problem. All we know is the user has clicked on something in the treeview. But what? The only information we are given is the hwndFrom, idFrom, and code. The code is NM_CLICK. We need the cursor position which we can get with a call to GetCursorPos. We adjust it relative to hwndFrom with a call to the ScreenToClient function. We will put it into a TVHITTESTINFO structure called lpht. Then we do a TreeView_HitTest with hwndFrom and lpht. The return value from this macro is an HTREEITEM which we will compare with the selected item in the treview. We could do this in a very simple statement like this

C++
if ( TreeView_HitTest(hwndFrom, &lpht)!= (TreeView_GetSelection(hwndFrom))) return 0;

but in this instance I intentionally broke it apart to make sure it was obvious what was happening. Besides, it was easier to step through.

Now you may ask "Why do that?". The answer is, the default selection processing has not been done yet. We don't know where we are in the File System. We could call the function that would give us that information but we would be duplicating code for no good reason. So we allow the selection to occur. In truth we are duplicating some code because now we have to populate the listview the same way we would in the selection processing. We could move that processing to a function and call it from both places. I just haven't done it yet. So it's like they say - it's six on one hand, and half a dozen on the other. But the thing about having six on one hand is you can't flip the bird because you have no middle finger!

The code for WM_NOTIFY - NM_CLICK

C++
case  NM_CLICK: 
{ // populates ListView when node is selected by keyboard and then clicked by mouse. 
HTREEITEM  nmtvi;
NMHDR * lpnmh;
TCHAR Text[256]={0};
TCHAR text[256]={0};
lpnmh = (LPNMHDR) lParam;
UINT_PTR idFrom = (lpnmh)->idFrom;
UINT code = (lpnmh)->code;
HWND hwndFrom = (lpnmh)->hwndFrom;
TVHITTESTINFO  lpht ={};
POINT pt;
GetCursorPos(&pt) ; // get the screen coordinates of the cursor
ScreenToClient(hwndFrom,&pt); // convert them to client coordinates
lpht.pt  = pt ; //put it in the hit test info structure
TreeView_HitTest(hwndFrom, &lpht); // hit me!
HTREEITEM nmClickedtvi = lpht.hItem;
nmtvi =  TreeView_GetSelection(hwndFrom);
if (nmtvi != nmClickedtvi)   // the clicked nodehasn't been 
  {return 0;            }  // selected yet. don't do anthing else
if (lpht.flags && TVHT_ONITEM ) // TVHT_ONITEMBUTTON you clicked on a selected treeview node. 
  { // The selection status is not going to change. You have to handle it. 
  memset(&tvi,0,sizeof(tvi));
  tvi.mask = TVIF_TEXT|TVIF_CHILDREN|TVIF_IMAGE ;
  tvi.pszText=Text;
  tvi.cchTextMax = 255;
  tvi.hItem = nmtvi;
  if(TreeView_GetItem(hwndFrom, &tvi))
    { // you got the item that was clicked 
    GetCurrentDirectory(MAX_PATH,nodeCD);
    if (tvi.iImage == 4)
      { // the item is not a folder, add it to the listview
      LVITEM item;
      memset(&item,0,sizeof(item));
      item.mask = LVIF_TEXT;
      item.iItem = 0;
      item.iSubItem = 0;
      item.cchTextMax = 260;
      item.pszText = tvi.pszText;
      int ret =  ListView_InsertItem(hList, &item);
      item.iItem = ret;
      item.iSubItem = 1;
      item.pszText = nodeCD;
      ListView_SetItem(hList, &item);
      int nRet = ret+1;
      ListView_GetItemText(hList,nRet,0,text,255); 
      while (_wcsicmp(text, tvi.pszText)==0)
        {
        ListView_GetItemText(hList,nRet,1,text,255); 
        if (_wcsicmp(text, nodeCD)==0)
          {
          ListView_DeleteItem(hList,ret); // delete one of them
          ret = nRet;
          }
        nRet = nRet+1;
        ListView_GetItemText(hList,nRet,0,text,255); 
        }
      ListView_SetItemState(hList,-1,0,LVIS_DROPHILITED); //de-select whatever
      ListView_SetItemState(hList,-1,0,LVIS_SELECTED); //de-select whatever
      ListView_SetItemState(hList,ret,LVIS_DROPHILITED,LVIS_DROPHILITED);
      ListView_SetItemState(hList,ret,LVIS_SELECTED,LVIS_SELECTED);
      ListView_SetItemState(hList,ret,LVIS_FOCUSED,LVIS_FOCUSED);
      ListView_EnsureVisible(hList,ret, true);
      ReleaseCapture();
      SetCapture(hList);
      SetFocus(hList);
      Sleep(1000);
      ReleaseCapture();
      } // the item is not a folder, addit to the listview
    else if (tvi.cChildren == 1)
      { // the item has children, get the first one and set-up loop  
      nmtvi=TreeView_GetChild(hwndFrom, nmtvi);
      memset(&tvi,0,sizeof(tvi));
      memset(&Text,0,sizeof(Text));
      tvi.mask = TVIF_TEXT | TVIF_CHILDREN| TVIF_IMAGE;
      tvi.pszText=Text;
      tvi.cchTextMax = 255;
      tvi.hItem = nmtvi;
      if(TreeView_GetItem(hwndFrom, &tvi))
        { // delete all the listview items and re build the list 
        ListView_DeleteAllItems(hList);
        GetCurrentDirectory(MAX_PATH,nodeCD);
        do { //  while (TreeView_GetItem(hwndFrom, &tvi))
          LVITEM item;
          memset(&item,0,sizeof(item)); // zap the item
          item.mask = LVIF_TEXT; // item is textual
          item.iItem = 0; // item is sorted so start at first row
          item.iSubItem = 0; //column number one
          item.cchTextMax = 260;
          item.pszText = tvi.pszText;
          int ret =  ListView_InsertItem(hList, &item);
          item.iItem = ret; // row number where irem was inserted
          item.iSubItem = 1; // 2nd column
          item.pszText = nodeCD;
          ListView_SetItem(hList, &item); // set the 2nd column sub item text
          if (ret < 1)         ret *= -1;
          nmtvi = tvi.hItem;
          nmtvi = TreeView_GetNextSibling(hwndFrom,nmtvi); // get the next child's handle
          memset(&tvi,0,sizeof(tvi));
          memset(&Text,0,sizeof(Text));
          tvi.mask = TVIF_TEXT; 
          tvi.pszText=Text;
          tvi.cchTextMax = 255;
          tvi.hItem = nmtvi;
          } while (TreeView_GetItem(hwndFrom, &tvi)); // get the child's label or stop
        }  // delete all the listview items and re build the list 
      } // the item has children, get the first one and set-up loop  
    }  // you got the item that was clicked 
  } // you clicked on a treeview node
}break; // case  NM_CLICK

WM_NOTIFY - TVN_KEYDOWN: Get me outta this tree!

In the previous section, we saw that the user could click on an item and put that item or its children into the listview pane, but try as we might, we could not jump over to the listview pane by left clicking on the treview pane. If we wrote code to do that, it would be extremely counter intuitive. But what if the user wants to jump over to the listview pane? In the next section, we have a function that provides the same results as a left click but does jump over to the listview pane, but it does it with the 'Enter' key. At first use, this does not 'tuit' the user's horn, so we need a way that will 'tuit', loud and clear. Well, you might say, 'Why not use the TAB key?'. Okay, here's the code, short, sweet, and super simple.

The code for WM_NOTIFY - TVN_KEYDOWN

C++
case TVN_KEYDOWN: 
{
NMTVKEYDOWN * ptvkd;
ptvkd = (LPNMTVKEYDOWN) lParam;
if ((ptvkd)->wVKey==VK_TAB)
  {
  SetFocus(hList);
  }
}break;

WM_NOTIFY - NM_RETURN: Hit me with your best shot!

It may seem at first glance that NM_RETURN gives us the same sort of problem as NM_CLICK because it also does not contain any information to identify the node that sent the notification. But here there is no uncertainty about getting the proper item. The simple fact is you can not change the selection of a TreeView node by pressing Enter or hitting Return if that is what is on your keyboard. Wherever the caret is in the tree, there it is! Pressing Enter does not change it. All you have to do is get it. We can do this the same way we did for the NM_CLICK but we don't have to do a hit test. All we need is just a TreeView_GetSelection macro to get the handle to the item that is selected. We put the handle into a TVITEM member named hItem and call TreeView_GetItem. Next we insert the item into the ListView unless it is a folder, in which case we clear the ListView, get the item's children, and put them in the ListView. For a folder, this result should be what they expect. For a file, it may not be what they expect but since Windows Explorer does not put files in the tree at all, we are not confusing the user by inserting the file into the ListView, but only by placing the file into the tree. Another difference is NM_RETURN can transfer the keyboard focus to the ListView. With the same code, NM_CLICK can't do the same thing. You would have to move the cursor to the item's position in the ListView. I haven't provided code to do that because the user has control of the mouse and I don't want to take it away from them. However on the keyboard I add a file to the ListView when the user presses Enter and sets focus on that item in the ListView then move down one. I have provided mechanisms for the user to move back to the TreeView without using the mouse. We will look at that next but now let us look at the code for NM_RETURN.

The code for WM_NOTIFY - NM_RETURN

C++
        case NM_RETURN: 
        { // populates ListView when node is selected by keyboard and then Keyboard ENTER Key is pressed.
        NMTREEVIEW * pnmtv; 
        HTREEITEM  nmtvi;
        TCHAR Text[256]={0};
        TCHAR text[256]={0};
        HWND result = NULL;
        pnmtv = (LPNMTREEVIEW)lParam; 
        memset(&tvi,0,sizeof(tvi));
        tvi.mask = TVIF_TEXT|TVIF_CHILDREN|TVIF_IMAGE ;
        tvi.pszText=Text;
        tvi.cchTextMax = 255;
        nmtvi =  TreeView_GetSelection(hTree);
        tvi.hItem = nmtvi;
        if(TreeView_GetItem(hTree, &tvi))
          {
          GetCurrentDirectory(MAX_PATH,nodeCD);
          if (tvi.iImage == 4)
            { // not a folder
            LVITEM item;
            memset(&item,0,sizeof(item));
            item.mask = LVIF_TEXT;
            item.iItem = 0; // we start at zero but list is sorted, so it will find a place for it
            item.iSubItem = 0; // sub-item zero is first column
            item.cchTextMax = 260;
            item.pszText = tvi.pszText; //the file name
            int ret =  ListView_InsertItem(hList, &item); // insert in sorted order
            item.iItem = ret; // ret is the index of the one we inserted
            item.iSubItem = 1; // sub-item one is second column
            item.pszText = nodeCD; // the 'PATH' part of filename
            ListView_SetItem(hList, &item); // second column text
            int nRet = ret+1;
            ListView_GetItemText(hList,nRet,0,text,255); 
            while (_wcsicmp(text, tvi.pszText)==0)
              {
              ListView_GetItemText(hList,nRet,1,text,255); 
              if (_wcsicmp(text, nodeCD)==0)
                {
                ListView_DeleteItem(hList,ret); // delete one of them
                ret = nRet;
                }
              nRet = nRet+1;
              ListView_GetItemText(hList,nRet,0,text,255); 
              }
            ListView_SetItemState(hList,-1,0,LVIS_SELECTED); //de-select whatever
            ListView_SetItemState(hList,-1,0,LVIS_DROPHILITED);
            ListView_SetItemState(hList,ret,LVIS_DROPHILITED,LVIS_DROPHILITED);
            ListView_SetItemState(hList,ret,LVIS_SELECTED,LVIS_SELECTED);
            ListView_SetItemState(hList,ret,LVIS_FOCUSED,LVIS_FOCUSED);
            ListView_EnsureVisible(hList,ret, true);
            ReleaseCapture();
            SetCapture(hList);
            SetFocus(hList); 
            ReleaseCapture();
            }
          else if (tvi.cChildren == 1)
            {
            nmtvi=TreeView_GetChild(hTree, nmtvi);
            memset(&tvi,0,sizeof(tvi));
            memset(&Text,0,sizeof(Text));
            tvi.mask = TVIF_TEXT | TVIF_CHILDREN| TVIF_IMAGE;
            tvi.pszText=Text;
            tvi.cchTextMax = 255;
            tvi.hItem = nmtvi;
            if(TreeView_GetItem(hTree, &tvi))
              {
              ListView_DeleteAllItems(hList);
              GetCurrentDirectory(MAX_PATH,nodeCD);
              do
                {
                LVITEM item;
                memset(&item,0,sizeof(item));
                item.mask = LVIF_TEXT;
                item.iItem = 0;
                item.iSubItem = 0;
                item.cchTextMax = 260;
                item.pszText = tvi.pszText;
                int ret =  ListView_InsertItem(hList, &item);
                item.iItem = ret;
                item.iSubItem = 1;
                item.pszText = nodeCD;
                ListView_SetItem(hList, &item);
                if (ret < 1)         ret *= -1;
                nmtvi = tvi.hItem;
                nmtvi = TreeView_GetNextSibling(hTree,nmtvi);
                memset(&tvi,0,sizeof(tvi));
                memset(&Text,0,sizeof(Text));
                tvi.mask = TVIF_TEXT; 
                tvi.pszText=Text;
                tvi.cchTextMax = 255;
                tvi.hItem = nmtvi;
                } while (TreeView_GetItem(hTree, &tvi));
              }
            }
          } // if(TreeView_GetItem(hTree, &tvi))
        }  break; // case NM_RETURN - Keyboard ENTER Key
      } // switch (((LPNMHDR)lParam)->code)
    } break;// case ID_TREEVIEW
  } // switch (((LPNMHDR)lParam)->idFrom)
}break; //  case WM_NOTIFY:

The other WM_SIZE message: Now that's a tight fit!

The WM_SIZE message is sent to a window after its size has changed. The window receives this message through its WindowProc function, or, in this case, its DialogProc function. In this instance the hTreeView window was sized in the WndProc. For a window created with the CreateWindow function with a Window class of WC_TREEVIEW, that would be all that we have to do. But since this was created with the CreateDialog function, we have to size the control because it is also a window. If we don't size it, it will not fill the entire hTreeView window and would not be fun. Try commenting out this WM_SIZE and take a look at the results. Probably still workable, but, if you do the same to the WM_SIZE in the ListView procedure the results are not workable at all. This is because the control is sized to fit the initial size of the window. The frame for the control is larger than the frame of a control in a window with a Window class of WC_TREEVIEW or WC_LISTVIEW. We would like the control to fill the entire window, with no dialog frame, since any button or whatever will be in other windows. In the window procedure, WndProc, we extract the width and height from the lParam and then we did a bunch of other stuff to split-up the space between the three child windows. Doing this means the dialog window for the treeview has been sized and the window procedure for the dialog receives a WM_SIZE message. Here we also extract the width and height from the lParam. But here we don't need to do other stuff. Just move the control, htree, to fill the window, hTreeView, completely. Then we return zero to signify that the message was handled. Notice the 'break' after the closing brace, ending the code for the WM_SIZE message, followed by the right brace for the switch block, followed by a 'return' statement. This returns control from the dialog procedure back to the message loop. Next we have the closing brace to end the treeview procedure. Since the code is exactly the same except for the HWND handle used, we will not describe each individual WN_SIZE message, so you must refer to this section for an explanation of the code. Here is that code for WM_SIZE.

The code for the WM_SIZE message and the rest of the TreeView procedure

C++
  case WM_SIZE: 
    {
    UINT width  = LOWORD(lParam);
    UINT height = HIWORD(lParam);
    MoveWindow(hTree,0,0,width,height,true);
    return 0;
    }break;
  }
  return (INT_PTR)FALSE;
}

The Window Procedure for the ListView

In this Window Procedure we use the WM_INITDIALOG message to create two columns for 'Name' and 'Path' in the listview control. In a 'CreateWindow' version of a ListView this would be done in the WndProc WM_CREATE message code as we did for the TreeView vontrol. But the purpose of putting the calls to the initialization routines in the WM_CREATE code was for demonstration. That code should really be placed in the WM_INITDIALOG section for maximum re-usability. We are using global variables for our window handles so the first thing we do is to get the handle for the control in this window. This window, hDlg, is in fact hListView and the control is IDC_LIST1, defined by me/you in the Dialog Editor or someone else, and imported by you/me. We set hList to this handle. We create an LVCOLUMN structure called lvCol and set its mask member to store text, width, and subitem index values. The mask member is the set of flags that tells the control what values to save when you are storing information and what values to give you back when you are retrieving them. Next we set the cx member of lvCol to the initial width of the first column, set the pszText member of lvCol to the name we have chosen for the column header, and call the ListView_InsertColumn function with the handle to the ListView, hList, the column index, zero, and a reference to lvCol. We repeat this, re-using lvCol, changing the text member for the next column, the cx member for the width of the column, and passing one for the column index of the second column. This will create two columns with Column Headers. To add any additional columns just repeat this procedure, increasing the column index for each one. Let's look at the code for WM_INITDIALOG.

C++
//  FUNCTION: Message handler for  list view. WM_INITDIALOG creates 2 columns for 'filename' and 
//  'path' in Listview control. WM_NOTIFY process the following notifications:
//  NM_DBLCLK and NM_RETURN to 'open' file selected using default program for type. 
//  Windows Explorer is used for folders. 
//  NM_CLICK puts full Path of item in Edit Control. NM_RCLICK and DELETE Key (VK_DELETE
//  in LVB_KETDOWN) invokes ListView Cleanup and Removal Dialog. FUNCTION KEY 3 (VK_F3) locates 
//  selected item in treeview  WM_SIZE resizes listview to fill the containing window. 
//
INT_PTR CALLBACK listViewProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
  UNREFERENCED_PARAMETER(lParam);
  switch (message)
  {
  case WM_INITDIALOG:
    {
    hList = GetDlgItem(hDlg,IDC_LIST1);
    LVCOLUMN lvCol;
    memset(&lvCol,0,sizeof(lvCol));
    lvCol.mask=LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM; // Type of mask
    lvCol.pszText=L"Name";
    lvCol.cx=0x96;          // width of first column
    ListView_InsertColumn(hList,0,&lvCol);
    lvCol.pszText=L"Path";
    lvCol.cx=0x0196;        // width of second column
    ListView_InsertColumn(hList,1,&lvCol);
    }
    return (INT_PTR)TRUE;

The ListView's WM_NOTIFY

The code for the switch block in this WM_NOTIFY block could be used in a CreateWindow version of a listview by changing only the 'case WM_NOTIFY' to 'case IDC_LIST1' This may seem non-sensical since IDC_LIST1 is a control that should be sending event notifications to a window and WM_NOTIFY should be processing the notifications. The key is this; in a CreateWindow the WM_NOTIFY handles the notifications from every common control in the window, so we are assuming this is being inserted into a WM_NOTIFY in the CreateWindow version. Also, we don't need to identify the control in this procedure because there is only one control, IDC_LIST1. In the CreateWindow version, we have to identify the control sending the notification so we end up changing WN_NOTIFY to IDC_LIST1. To clarify the changes we would make, we are deleting the WM_NOTIFY and inserting IDC_LIST1 and inserting the whole thing into a WM_NOTIFY. Now, let's discuss the notifications processed by WM_NOTIFY, beginning with NM_DBLCLK - the left button of the mouse has been double clicked. There are two structures that can be used to map the value in the lParam, NMITEMACTIVATE and NMLISTVIEW. The important difference is the uKeyFlags member of the NMITEMACTIVATE structure identifies the Modifier Key that was pressed when the button was double clicked. We don't use it here so we will use NMLISTVIEW because we are using it for the other user actions. The remainder of the code is the same in either case. We allocate our variable storage, including a LVITEM, which we call 'lvi'. We set the iItem member of lvi to the value of the member with the same name in the NMLISTVIEW structure, that is,

C++
lvi.iItem = (pnmlv)->iItem;

Then we compare it to zero to determine whether to continue processing. If we are continuing we set the mask to LVIF_TEXT. The pszText member of lvi is a pointer to a buffer and we point it at the 'Text' buffer allocated earlier. The iSubItem is zero from the memset function call. We use the ListView_GetItem macro to get the text from the first column of the row indexed by iItem. Now we set the iSubItem member to one, the pszText pointer to point at the 'text' buffer, with the lower case 't', and get the item text. We copy both of them into the 'teXt' buffer, with an upper case 'X', separating them with a backslash. This is the full path with filename of the ListView item that was double clicked. We call the '_wstat' function with the 'teXt' buffer and a reference to a '_stat' struct named buf. There is a function with the name '_stat' and a struct named '_wstat' but the only combination I could get to work was the '_stst' struct and the '_wstat' function call. The result of this function call is zero if it is successful, in which case we AND the BITS of the st_mode member of buf with the _stat structure constant _S_IFREG to determine if it is a regular file, and if it is we put quotes around the full pathname and 'open' it with a call to the ShellExecute function, specifying SW_SHOWNORMAL for the SetShowWindow command. This will open a file using the defined file association. If it is _S_IFDIR we will use 'explore'. The effect is we are opening a directory in Windows Explorer and a file with its associated program. Here is the code for the ListView WM_NOTIFY and the first notification processed by the ListView WM_NOTIFY message handler, NM_DBLCLK.

The ListView WM_NOTIFY's NM_DBLCLK:

C++
case WM_NOTIFY:
{
switch (((LPNMHDR)lParam)->code){
  case NM_DBLCLK:{ // Left Double Click
    NMLISTVIEW * pnmlv;
    LVITEM lvi;
    int result;
    struct _stat buf;
    TCHAR Text[256]={0}; 
    TCHAR text[256]={0}; 
    TCHAR teXt[256]={0}; 
    pnmlv =(LPNMLISTVIEW)lParam;
    memset(&lvi,0,sizeof(lvi));
    lvi.iItem = (pnmlv)->iItem;
    if (lvi.iItem == -1) return 0;
    lvi.mask = LVIF_TEXT;
    lvi.cchTextMax =255;
    lvi.pszText = Text;
    ListView_GetItem(hList,&lvi);
    lvi.pszText = text;
    lvi.iSubItem = 1;
    ListView_GetItem(hList,&lvi);
    wsprintf(teXt,L"%s\\%s", text,Text);
    result = _wstat(teXt, &buf );
    if (result == 0)
      {
      if((buf.st_mode & _S_IFREG))
        {
        wsprintf(teXt,L"\"%s\\%s\"", text,Text);
        SetWindowText(hEdit,teXt);
        ShellExecute(NULL, L"open", teXt, NULL, NULL, SW_SHOWNORMAL);
        }
      if((buf.st_mode & _S_IFDIR))
        {
        wsprintf(teXt,L"\"%s\\%s\"", text,Text);
        SetWindowText(hEdit,teXt);
        ShellExecute(NULL, L"explore", teXt, NULL, NULL, SW_SHOWNORMAL);
        }
      }
    }break; // case NM_DBLCLK

The ListView WM_NOTIFY's LVN_KEYDOWN - Delete, Down, Up, Tab, and F3

The LVN_KEYDOWN notification processes five keys, the delete key, the down arrow key, the tab key, the up arrow key, and the F3 key. If the key pressed was none of these it returns zero. F3 is last because it involves the most processing and takes more time to scroll through, so it's last.

For the DELETE key, we check to see if the handle to hdelBar is NULL, and call the CreateDialog function if so to create it. We get the Window Rectangle of hWnd to determine the position of the dialog box and Client Rectangle of hWnd to determine its width. For its height we use an arbitrary number, fifty, because it looks okay. Or maybe it's an imaginary number because we dreamed it up. If the handle is not NULL, it has been created, so we show it.

The UP arrow key and the DOWN arrow key provide the expected information, via the Edit Control, when the keys are pressed. Since multiple items can be selected and highlighted it is also necessary to re-set the state of any item that is selected or highlighted. Using minus one for the index will cause this to occur for all items. This is the simplest way to do it, so we call ListView_SetItemState with the handle to hList as the first parameter, minus one as the item index, zero as the state parameter, and in separate calls, the mask is LVIS_DROPHILITED and in the second call, LVIS_SELECTED. This clears the bits for everything. We do this for both the UP and DOWN keys. But since we need to get the Path and Filename of the item that will be highlighted by the control after we exit, we need to know which item we are moving from so that we can get the previous one for the UP Key or the next one for the Down Key. We also need to know when we hit a boundary so that we can do nothing but exit. For the Down Key we call the macro ListView_GetItemCount to get the number of items in the list. This will give us the boundary at the BOTTOM (the upper bound). Then we get the item we are moving from by calling ListView_GetNextItem with the handle to hList, minus one for the index to start from and LVNI_SELECTED to get the selected item. The control will select the next item unless it is at the bottom already. We test for a result less than itemCount minus one to determine whether to get the full path of the next one or just get out if it isn't. Now we get the text for the Path and Filename and copy them into a buffer, placing a backslash in between them and put them into the Edit Control. Having fetched the selected item and gotten the information for the Edit Control, we re-set the state for everything and return zero. Now the control does its processing and we see the last item selected, highlighted, and focused. It's all good and in-tuit-ive. As for the UP arrow key, we only need to make sure the result is greater than zero so we can subtract one from it. If it is less than one we return zero and exit. We do not even care how many items there are. There is one issue using the path in the Edit Control if you use the ISEARCH feature of the ListView. I haven't written code to refresh the information after the selection is moved so the intuitiveness is lost. For those who may rely upon this information, I will write that code when I get a round tuit but they are extremely hard to find.

If the KEY is a TAB we just set the focus on the treeview. We want to keep it short, sweet, and super simple. I'll let you figure out the rest of it.

The processing of the F3 key is the complicated part of the LVN_KEYDOWN notification. We are using the F3 key to 'FIND' the file that has the selected state in the ListView, and locate it in the tree. We have received no information in lParam to identify the item that has the selected state. So we call the ListView_GetNextItem macro, with three parameters, hList, minus one(-1), and LVNI_SELECTED. This tells the macro to look in hList to find the item after the minus one (zero) item that is selected. In other words, to start at the beginning. We store the result in result. That's simple. If the result is minus one we get out because nothing is selected. Otherwise we set the LVITEM index, iItem to result, iSubItem to zero, mask to LVIF_TEXT, cchTextMax to the length of the Text buffer previously allocated minus one(255), and the pazText pointer to Text. We call the macro ListView_GetItem to give us the filename from the first column. Then we change the iSubItem (the column index) to one and the pszText pointer to point to text. We call the ListView_GetItem macro again to get the path of the filename. The next thing to do is copy text, a backslash, and Text into startInDir, using wsprintf to do all of the work in one step. Next we store the wcslen(gth) of the startInDir in sidLen, for later use. Now we get theSelectedItem by calling the TreeView_GetSelection macro with the single parameter, hTree. This will get the item that is currently selected in the tree. Now we drop down to the ROOT of the tree and begin the climb back up to locate our file by calling the TreeView_GetRoot macro and storing the result in nmtvi. This gives us the handle to the first item in the Tree.

Now we begin to loop while nmtvi is not zero. If we had failed to get the Root the loop will never be executed. Now in the loop, we prepare our TVITEM tvi and set the hItem member of tvi to nmtvi, then call the TreeView_GetItem macro to get the attributes we requested in the mask member and if successful, we compare the buffer pointed at by the pszText member and startInDir, ignoring case but comparing only for the length of the text in the buffer, not including the zero that ends the text. Not matching, we get the next sibling and repeat the loop until there are no more siblings. Matching the startInDir, we check to see if the item has not been expanded, calling the TreeView_Expand macro to request a TVE_EXPAND action, expanding the folder item.

When we find an expanded item we compare nmtvi to theSelectedItem. When they match, we call the macro TreeView_GetNextItem to change theSelectedItem to TVGN_NEXT, the one after nmtvi. Now we make it the selected item by calling TreeView_SelectItem to make the new theSelectedItem the selected item. This will cause a TVN_SELCHANDING and TVN_SELCHANGED, but that processing is only a short, one time delay. Whether they match or not, the next statement will select nmtvi, getting us back on the process of locating the requested file in the tree.

The code for the ListView WM_NOTIFY's LVN_KEYDOWN notification:

C++
case LVN_KEYDOWN: {
NMLVKEYDOWN * pnkd;
pnkd = (LPNMLVKEYDOWN) lParam;
HTREEITEM  nmtvi; 
RECT lrccl, crccl, drccl;
if ((pnkd)->wVKey == VK_DELETE) 
  if (hdelBar != NULL)
    {
    GetWindowRect(hWnd,&lrccl);
    GetClientRect(hWnd,&crccl);
    GetClientRect(hdelBar,&drccl);
    MoveWindow(hdelBar,lrccl.left+8,lrccl.top+24,crccl.right,50,true);
    ShowWindow(hdelBar, SW_SHOW);
    }
  else
    {
    hdelBar = CreateDialog(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_DIALOGBAR1),hDlg,delBarProc);
    GetWindowRect(hWnd,&lrccl);
    GetClientRect(hWnd,&crccl);
    GetClientRect(hdelBar,&drccl);
    MoveWindow(hdelBar,lrccl.left+8,lrccl.top+24,crccl.right,50,true);
    ShowWindow(hdelBar, SW_SHOW);
    }

  if ((pnkd)->wVKey == VK_DOWN)
    {
    LVITEM lvi;
    int result;
    int itemCount;
    TCHAR Text[256]={0}; 
    TCHAR text[256]={0}; 
    TCHAR teXt[256]={0}; 
    memset(&lvi,0,sizeof(lvi));
    itemCount =ListView_GetItemCount(hList);
    result = ListView_GetNextItem(hList,-1,LVNI_SELECTED); 
    if (result < itemCount -1)
      lvi.iItem = result+1;
    else 
      return 0; 
    lvi.mask = LVIF_TEXT;
    lvi.cchTextMax =255;
    lvi.pszText = Text;
    ListView_GetItem(hList,&lvi);
    lvi.pszText = text;
    lvi.iSubItem = 1;
    ListView_GetItem(hList,&lvi);
    wsprintf(teXt,L"%s\\%s", text,Text);
    SetWindowText(hEdit,teXt);
    ListView_SetItemState(hList,-1,0,LVIS_DROPHILITED); //de-select whatever
    ListView_SetItemState(hList,-1,0,LVIS_SELECTED); //de-select whatever
    return 0;
    }

  if ((pnkd)->wVKey == VK_TAB)
    {
    SetFocus(hTree); 
    return 0;
    }
  if ((pnkd)->wVKey == VK_UP)
    {
    LVITEM lvi;
    int result;
    TCHAR Text[256]={0}; 
    TCHAR text[256]={0}; 
    TCHAR teXt[256]={0}; 
    memset(&lvi,0,sizeof(lvi));
    result = ListView_GetNextItem(hList,-1,LVNI_SELECTED); 
    if (result < 1) return 0;
    lvi.iItem = result-1;
    lvi.mask = LVIF_TEXT;
    lvi.cchTextMax =255;
    lvi.pszText = Text;
    ListView_GetItem(hList,&lvi);
    lvi.pszText = text;
    lvi.iSubItem = 1;
    ListView_GetItem(hList,&lvi);
    wsprintf(teXt,L"%s\\%s", text,Text);
    SetWindowText(hEdit,teXt);
    ListView_SetItemState(hList,-1,0,LVIS_DROPHILITED); //de-select whatever
    ListView_SetItemState(hList,-1,0,LVIS_SELECTED); //de-select whatever
    return 0;
    }

  if ((pnkd)->wVKey != VK_F3)
    {
    return 0;
    }

  LVITEM lvi;
  int result;
  TCHAR Text[256]={0}; 
  TCHAR text[256]={0}; 
  TCHAR teXt[256]={0}; 
  memset(&lvi,0,sizeof(lvi));
  result = ListView_GetNextItem(hList,-1,LVNI_SELECTED); 
  if (result == -1)
    {
    return 0; // couldn't get it. get out
    }
  lvi.iItem = result;
  lvi.mask = LVIF_TEXT;
  lvi.cchTextMax =255;
  lvi.pszText = Text;
  ListView_GetItem(hList,&lvi);
  lvi.pszText = text;
  lvi.iSubItem = 1;
  ListView_GetItem(hList,&lvi);
  wsprintf(startInDir,L"%s\\%s", text,Text); // set start-in-directory to selected item
  sidLen = wcslen(startInDir);
  memset(nodeCD,255,sizeof(nodeCD));
  HTREEITEM theSelectedItem = TreeView_GetSelection(hTree); 
  nmtvi = TreeView_GetRoot(hTree);
  while (_wcsnicmp(startInDir, nodeCD, wcslen(nodeCD))!=0)
    {
    memset(&tvi,0,sizeof(tvi));
    tvi.mask = TVIF_TEXT|TVIF_CHILDREN|TVIF_IMAGE ;
    tvi.pszText=Text;
    tvi.cchTextMax = 255;
    tvi.hItem = nmtvi; //put it in a tvitem structure
    if(TreeView_GetItem(hTree, &tvi))
      { // get the item's info
      if (_wcsnicmp(startInDir, tvi.pszText, wcslen(tvi.pszText))==0)
        {
        if (tvi.cChildren > 0 )
          {
          if (tvi.state == 0 ) 
            { // node has children but has never been expanded. TVN_EXPANDED will take over now.
            TreeView_SelectItem(hTree,tvi.hItem);
            Sleep(100);
            TreeView_Expand(hTree, tvi.hItem, TVE_EXPAND); // we are through here
            return 0; //terminate the routine
            }
          else if (tvi.state & TVIS_EXPANDEDONCE)
            { // folder in path, go on to the next higher level
            SetCurrentDirectory(tvi.pszText); 
            if (theSelectedItem == nmtvi) 
              { // it's pointless to select an item that is already selected. 
              theSelectedItem = TreeView_GetNextItem(hTree,nmtvi,TVGN_NEXT); 
              TreeView_SelectItem(hTree,theSelectedItem); 
              }
            TreeView_SelectItem(hTree,nmtvi); 
            return 0;
            }
          }
        }
      else
        {
        nmtvi = TreeView_GetNextSibling(hTree,nmtvi); // get the next child's handle
        }
      }
    }
} break;

The ListView WM_NOTIFY's NM_RETURN notification

The ListView sends an NM_RETURN when it has focus and the user presses Enter. This notification identifies the window, the control, and the action code, in the NMHDR structure that the lParam points to. In this dialog we know all of these things but we need to identify the item that is selected in the control, hList. We use the ListView_GetNextItem macro with three parameters, hList, minus one, and the constant LVNI_SELECTED. This tells the macro to get the next selected item, starting after the minus one item, that is the zero item - the beginning of the list. We call it result and if it is minus one, then nothing was selected and we get out. Result is the index to the Rows in the ListView. In an LVITEM we call it iItem. We initialize an LVITEM, calling it lvi, we put result in the iItem member of lvi. We call the index for the columns iSubItem. It is a "one-based index of the subitem to which this structure refers, or zero if this structure refers to an item rather than a subitem" according to the Microsoft Help files. From my perspective this means it is really a zero-based index, just like the iItem index. And it is already set to zero because we used memset to fill lvi with zeroes. We use the ListView_GetItem macro to get the pszText from the first two columns, changing the iSubItem index to one for the second call and put them into the Text and text buffers. We copy text, a backslash, and Text into the teXt buffer using a call to wsprintf to do it in a single step. We re-use result to store the result of a call to _wstat to get the file-status information about the path in teXt, storing the information in a buffer mapped by the _stat struct buf. The result is used to return error information. It will be zero if the information is obtained. For this notification we only need to know if it is a file or directory. We use ShellExecute to 'open' a file and to 'explore' a directory. A double click of the mouse will produce the same result but the item selected is provided by the NMLISTVIEW structure or by the NMITEMACTIVATE structure that is pointed to by the NM_DBLCLK notification's lParam instead of the NMHDR structure that the NM_RETURN notification's lParam points to here.

The code for the ListView WM_NOTIFY's NM_RETURN notification:

C++
case NM_RETURN: {// Keyboard RETuRN Key
NMLISTVIEW * pnmlv;
LVITEM lvi;
int result;
struct _stat buf;
TCHAR Text[256]={0}; 
TCHAR text[256]={0}; 
TCHAR teXt[256]={0}; 
pnmlv =(LPNMLISTVIEW)lParam;
memset(&lvi,0,sizeof(lvi));
result = ListView_GetNextItem(hList,-1,LVNI_SELECTED); 
if (result == -1) return 0;
lvi.iItem = result;
lvi.mask = LVIF_TEXT;
lvi.cchTextMax =255;
lvi.pszText = Text;
ListView_GetItem(hList,&lvi);
lvi.pszText = text;
lvi.iSubItem = 1;
ListView_GetItem(hList,&lvi);
wsprintf(teXt,L"%s\\%s", text,Text);
result =  _wstat(teXt, &buf );
if (result == 0)
  {
  if((buf.st_mode & _S_IFREG))
    {
    wsprintf(teXt,L"\"%s\\%s\"", text,Text);
    SetWindowText(hEdit,teXt);
    ShellExecute(NULL, L"open", teXt, NULL, NULL, SW_SHOWNORMAL);
    }
  if((buf.st_mode & _S_IFDIR))
    {
    wsprintf(teXt,L"\"%s\\%s\"", text,Text);
    SetWindowText(hEdit,teXt);
    ShellExecute(NULL, L"explore", teXt, NULL, NULL, SW_SHOWNORMAL);
    }
  }
}break; // case NM_RETURN

The ListView WM_NOTIFY's NM_CLICK notification

An NM_CLICK notification is sent by the ListView control when a listview item is clicked by the user. The lParam parameter is a pointer to a NMITEMACTIVATE structure with information provided by the control. This structure is identical to NMLISTVIEW but it has a uint field added that identifies the Modifier Key that was depressed when the user clicked. We don't care about this field so we are using NMLISTVIEW. The important work we do here is the macros to set the item state. There are five of them in a row. Why do that? Is it really necessary? My answer is, I'm not sure. The first two clear the selected bit and the drophilited bit for everything. That's the minus one parameter that tells the macro to do everything. The next two set the bits for the item that was clicked. I tried to 'OR' the bits but it did not work for me. The documentation suggested that it should, so I'm not sure what the problem is. So I did them individually for now. Some day, I may achieve understanding and or enlightenment. That is why I explore, after all.

The rest of the code just gets the full path of the item and puts it in the Edit Box. It's pretty much the same as every where else.

The code for the ListView WM_NOTIFY's NM_CLICK notification

C++
case NM_CLICK: {// Left Click
NMLISTVIEW * pnmlv;
LVITEM lvi;
TCHAR Text[256]={0}; 
TCHAR text[256]={0}; 
TCHAR teXt[256]={0}; 
pnmlv =(LPNMLISTVIEW)lParam;
memset(&lvi,0,sizeof(lvi));
ret = (pnmlv)->iItem;
lvi.iItem = (pnmlv)->iItem;
lvi.mask = LVIF_TEXT;
lvi.cchTextMax =255;
lvi.pszText = Text;
ListView_GetItem(hList,&lvi);
lvi.pszText = text;
lvi.iSubItem = 1;
ListView_GetItem(hList,&lvi);
wsprintf(teXt,L"\"%s\\%s\"", text,Text);
SetWindowText(hEdit,teXt);
ListView_SetItemState(hList,-1,0,LVIS_DROPHILITED); //de-select whatever
ListView_SetItemState(hList,-1,0,LVIS_SELECTED); //de-select whatever
ListView_SetItemState(hList,ret,LVIS_DROPHILITED,LVIS_DROPHILITED);
ListView_SetItemState(hList,ret,LVIS_SELECTED,LVIS_SELECTED);
ListView_SetItemState(hList,ret,LVIS_FOCUSED,LVIS_FOCUSED);
}break; // case NM_CLICK

The ListView WM_NOTIFY's NM_RCLICK notification

NM_RCLICK provides the same functionality as the DELETE Key (for a description of that processing, see the section on LVN_KEYDOWN(VK_DELETE)). It is really just a place holder for a short cut menu that will include this function. For now it is just another way to get to the listview clean-up dialog.

The code for the ListView WM_NOTIFY's NM_RCLICK notification:

C++
case  NM_RCLICK:{ // Right Click
    NMLISTVIEW * pnmlv;
    LVITEM item;
    pnmlv =(LPNMLISTVIEW)lParam;
    TCHAR Text[256]={0}; 
    memset(&item,0,sizeof(item));
    item.iItem = (pnmlv)->iItem;
    item.mask = LVIF_TEXT;
    item.iSubItem = 0;
    item.cchTextMax = 260;
    item.pszText = Text;
    int ret = ListView_GetItem(hList,&item);
    RECT lrccl, crccl, drccl;
    if (hdelBar != NULL)
      {
      GetWindowRect(hWnd,&lrccl);
      GetClientRect(hWnd,&crccl);
      GetClientRect(hdelBar,&drccl);
      MoveWindow(hdelBar,lrccl.left+8,lrccl.top+24,crccl.right,50,true);
      ShowWindow(hdelBar, SW_SHOW);
      }
    else
      {
      hdelBar = CreateDialog(GetModuleHandle(NULL),MAKEINTRESOURCE(IDD_DIALOGBAR1),hWnd,delBarProc);
      GetWindowRect(hWnd,&lrccl);
      GetClientRect(hWnd,&crccl);
      GetClientRect(hdelBar,&drccl);
      MoveWindow(hdelBar,lrccl.left+8,lrccl.top+24,crccl.right,50,true);
      ShowWindow(hdelBar, SW_SHOW);
      }
    } break; // case  NM_RCLICK
  } // switch (((LPNMHDR)lParam)->code)
}break; // case WM_NOTIFY

The ListView's WM_SIZE message

Looking back to the TreeView's WM_SIZE message, change the HWND hTree to hList and the code would be identical.

The code for the ListView's WM_SIZE message

C++
case WM_SIZE: 
    {
    UINT width  = LOWORD(lParam);
    UINT height = HIWORD(lParam);
    MoveWindow(hList,0,0,width,height,true);
    return 0;
    }break;
  }
  return (INT_PTR)FALSE;
}

The SetTreeviewImagelist function

The FONT for the TreeView Dialog was set in the IDD_FORMVIEW1 template using the Dialog Editor. To make this look the way it should, we need a larger set of bitmaps. I created a set of bitmaps that have different images for the selected and non-selected states of the treeview items. The shapes are the same but the colors are different, lighter for selected items. The ImageList_Create function has five parameters. The first two are the width and height of each image. This also controls the size of the plus and minus buttons of the folders. I used thirty two for each of them. If you change the font, you might need to change these values also. The third controls the numver of bits for the color. The fourth is the starting number of images and the fifth is the number of images by which the image list can grow. We save the handle to the HIMAGELIST in a storage location we named hImageList. The next function we call is the LoadBitmap function to load the IDB_BITMAP1 bitmap we created in the Resource Editor and add the bitmap to the ImageList, passing NULL for the bitmap mask. We delete the bitmap object and add the ImageList to the treeview. In the Window Procedure, WndProc, we call the ImageList_Destroy function to clean-up because the tree control doesn't destroy or delete it. You could put it in the treeview dialog proc but you would first need to add a WM_DESTROY message handler to that procedure. Six on one hand, ... Here is the code for the SetTreeviewImagelist function.

The code for the SetTreeviewImagelist function:

C++
// FUNCTION: Creates a 32 by 32 imagelist containing 6 bitmaps of folder icons. 
// The images are; closed-folder, selected-closed-folder, opened-folder, 
// selected-opened-folder, document(not a folder) and selected-document. 
// No attempt is made to identify nature of document. 
bool inline SetTreeviewImagelist(const HWND hTv)
  {
  hImageList=ImageList_Create(32,32,ILC_COLOR32,6,1);  
  hBitMap=LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP1));  
  ImageList_Add(hImageList,hBitMap,NULL);  
  DeleteObject(hBitMap);  
  TreeView_SetImageList(hTree,hImageList,TVSIL_NORMAL);  // attach image lists to tree view common control
  return true;
  }

The InitTreeViewItems function:

This function populates the treeview with any logical drive mounted on the file system, i.e., the root directory of the drive and one level of sub directories, by calling the AddItemToTree function and the getDirectories function for each drive root. It also begins the process to find the startInDir. Here's how it works. The LogicalDriveStrings that we Get have an alpha character followed by a colon, a backslash, and a zero string terminator for each drive. We define a string with space for an alpha character, the colon, backslash, and a zero string terminator. This is pointed to by the pointer 'd'. The Drive Strings is pointed to by the pointer 'p'. We copy the alpha character to our string, using the syntax '*d=*p'. This string is passed to the AddItemToTree function as szTemp, to insert the Root folder into the tree and store the handle to that item in the global variable, nodeParent. Next we call the function getDirectories with nodeParent and szTemp. It calls AddItemToTree, populating the children of the Root, but no more. In subsequent processing, we will always populate the children of a folder before we expand its parent. This makes the tree seem to be fully populated but is in reality sparsely populated.

When we find the character that is the same as the first character of the array of characters that is pointed to by startInDir, we expand the node we have just added to the treeview control. Expansion of a treeview node causes a TVN_ITEMEXPANDED notification to be generated and processing that results in another TVN_ITEMEXPANDING notification. When we locate the startInDir it is selected and the process ends. This is the process to locate the file in the tree. Here it finds the start in directory.

Next we advance the pointer 'p' with a post increment operator while it points at something that is not zero. Since it is post increment, it will be pointing at the position after the zero terminator. The string returned by GetLogicalDriveStrings has two zeroes at the end. That means the pointer 'p' is pointing at the second zero after the last string in the LogicalDriveStrings has been processed. Then the while(*p) loop terminates because 'p' is pointing at zero. This loop occurs in the 'true' block of an if statement. The 'false' block or else block consists of a 'return false' statement.

This process will leave the startInDir Hilited but not necessarily visible. To make sure it is visible we get the selected item in the tree and we select again. This will select the item and scroll it into view or re-draw it with the TVGN_DROPHILITE style.

The code for the InitTreeViewItems function:

C++
// FUNCTION: Populates treeview with logical drives and one level of sub-directories by calling 
// AddItemToTree and getDirectories for each drive. Begins process to find startindir
BOOL InitTreeViewItems(HWND hwndTV)
  { 
  /* Define the ROOT of each drive */ HTREEITEM hti; // the root folder item to insert.
  /* Defibe the temporary drive string */ TCHAR szDTemp[] = TEXT(" :\\"); // first position will cantain drive letter.
  /* Define string to hold Drive List.*/   TCHAR szTemp[BUFSIZE];
  /* add a root item to the tree */   
  hti = (HTREEITEM)TVI_ROOT; //initialize root item
  if (GetLogicalDriveStrings(BUFSIZE-1, szTemp))
    { /* szTemp comtains list of drives */
    TCHAR* p = szTemp; /* point p at 'A' in 'A:\\\0C:\\\0...\0\0' */   
    TCHAR* d = szDTemp; /* point d at blank space in ' :\\\0' */ 
    do 
      {  /* loop through string of drives */ 
      *d = *p; /* copy 1 tchar from p to d */     
      nodeParent = AddItemToTree(hwndTV, hti, szDTemp); /* add a root item to the tree at level 2 */   
      getDirectories(nodeParent, szDTemp); /*  add it's subdirectories at level 2  */  
      if (hti == NULL)  /* you couldn't add the item */ 
        return FALSE; /* go back and tell them it failed */ 
      if (NULL ==  nodeParent)
        {
          return FALSE; /* go back and tell them it failed */ 
        }
      else if (*d == *startInDir) 
        {
        TreeView_Expand(hTree, nodeParent, TVE_EXPAND);
        }
      while (*p++); // loop until p points at a '\0', incrementing p afterwaeds. 
      }
      while (*p); /* end of string when p points at second '\0' of pair.*/ 
    }
  else
    return FALSE;  /* go back and tell them you couldn't get drive string.*/
  nodeParent = TreeView_GetSelection(hTree);
  TreeView_Select(hTree,nodeParent,TVGN_FIRSTVISIBLE);
  return TRUE; /* go back and tell rhem it's okay. */ 
  }

The getDirectories function:

This function uses the File Management functions FinFirstFile, FindNextFile, FindClose and the WIN32_FIND_DATA structure. We pass this function the handle to a treeview item which we named hDir and the directory we want to get the subdirectories of, to populate our treeview item's children with. To begin the search we must supply the path and the file to search for. This means the part after the last backslash is the name we want to find. But, we want to find everything in this directory. We can use wildcards and the one that will match everything is an asterisk. We look at the character before the last one to see if it is a backslash. If it is we add an asterisk to the path, otherwise we add a back slash and an asterisk, then call FindFirstFile with this path and the WIN32_FIND_DATA structure. If the call returns a search handle we do a loop to get all of the found files or directories. We pass the image and SelectedImage indexes along with the handles to the control and the treeview item, and the cFileName to the AddItemToTree function. When there is nothing else found we pass the search handle to FindClose to close the search handle. We have dropped the self references (dot and dotdot) and did not do any error handling. Any errors will result in the folder not showing all of its children. We do not populate the listview here or store any of the file attributes, but they are available if you want to use them.

The code for the getDirectories function:

C++
// FUNCTION: FindFirstFile in specified directory then repeatedly 
// FindNextFile until there aint no mo(aren't any left to find).  
// Sets image index to dhow folder or not a folder, adds both to tree. 
void getDirectories(HTREEITEM hDir, LPTSTR lpszItem) {
  HANDLE hFind;
  WIN32_FIND_DATA win32fd;
  TCHAR szSearchPath[_MAX_PATH];
  LPTSTR szPath=lpszItem;
  DWORD dwError=0;
  if(szPath[wcslen(szPath) - 1] != L'\\')
    {
    wsprintf(szSearchPath, L"%s\\*", szPath); // concat in text buffer
    }
  else
    {
    wsprintf(szSearchPath, L"%s*", szPath); // concat in text buffer
    }

  if((hFind = FindFirstFile(szSearchPath, &win32fd)) == INVALID_HANDLE_VALUE) return;
  do  {
    if(win32fd.cFileName[0] != L'.'){
      if ((win32fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
        {
        tviiImage =  0; // folder, Closed; 
        tviiSelectedImage = 1; // folder, Closed and selected; 
        }
      else
        {
        tviiImage = 4; // not a folder: document; 
        tviiSelectedImage = 5; // not a folder: document selected;   
        }
      AddItemToTree(hTree, hDir, win32fd.cFileName);
      }
    } while(FindNextFile(hFind, &win32fd) != 0);
  FindClose(hFind);
}

The AddItemToTree function:

The AddItemToTree function inserts an item into the tree as a child of hDir, which itself starts out as TVI_ROOT. It is inserted after hPrev, the previously inserted item. This function is really just setting up the TVINSERTSTRUCT with the information supplied and calling the TreeView_InsertItem macro with that structure. It stores the handle to the item returned in the static HTREEITEM variable hPrev and then also returns it to its calling program. It seems to be a good candidate for inlining. Notice that the lParam field of the TVITEM structure is not being used. If you want to use it, go right ahead.

The code for the AddItemToTree function:

C++
// FUNCTION: Inserts an item into the tree as a child of hDir, after the 
// previously inserted item if it is a sibling. If not a sibling, previously 
// inserted item is ignored. 
HTREEITEM inline AddItemToTree(HWND hwndTV, HTREEITEM hDir, LPTSTR lpszItem){ 
  TVITEM tvi; 
  TVINSERTSTRUCT tvins; 
  static HTREEITEM hPrev = (HTREEITEM)TVI_FIRST; 
  tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; 
  tvi.pszText = lpszItem; // Set the text of the item. 
  tvi.cchTextMax = NULL; // set this to size of buffer when getting test label. Here it is ignored
  tvi.iImage = tviiImage; 
  tvi.iSelectedImage = tviiSelectedImage; 
  tvins.item = tvi; 
  tvins.hInsertAfter = hPrev; 
  tvins.hParent = hDir; // Set the parent item
  hPrev = TreeView_InsertItem(hwndTV, &tvins); // Add the item to the tree-view control. 
  return hPrev; 
}

The getNodeFullPath function:

The getNodeFullPath function recursively gets the current treeview item's parent until it finds the drive, then it returns the tvi.pszText and collects it to build the full path of the current treenode by using wsprintf to concatenate all of the item texts together in reverse order, in effect using the program stack as a FIFO stack. This routine is called when a node is selected or when a button (plus or minus) is clicked. This is necessary because clicking a button does not select a node, it only expands it or collapses it. When the tree has been fully populated this works perfectly, leaving the selected item untouched. But with the method we are using, that is, minimal population, expanding a tree node does not populate its childrens' children. Therefore the tree would break down and fall. We have to populate the node ourselves. When a request to expand a node is received, via a TVN_ITEMEXPANDING notification, the code determines if it is selected. If not then we must programmatically select the node to get its full path in order for the tree to function as the user expects. The TREENODE must accurately reflect the contents of the file system, otherwise getDirectories will not find files or directories because the PATH we pass to the function will not exist. Since the path doesn't exist, the node will have no children and no buttons.

The process to get the full path for the node is deceptively simple. It receives the handle to an item and the control it is in. It gets the item text. If the second character is a colon, it sets the text in the edit control and sets the current directory. Then it returns true, ending the recursion. Note that this is the first thing we do. We have found the root of the path but we don't know where the other end is. It could have any number of path parts. If this is the last part we will return to the routine that called getNodeFullPath, otherwise we will return to the same routine, getNodeFullPath, to collect the item text. When the second character is not a colon it gets the parent item's handle and makes the recursive call. Notice that tvi, the TVITEM is allocated on the stack. Also the buffer for tvi.pszText. We are essentially pushing the data on to the stack. How's your memory, got enough? It will loop until it finds the colon or fails to get the item attributes that were requested by tvi.mask, then it will return in both cases. The Text buffer pointed to by tvi.pszText should contain the second item's text, such as "Users", when it has returned for the first time, and nodeCD should contain the root of the path, such as, "C:\". We look at the next to the last character (the last character is zero) to determine whether or not to put a backslash between nodeCD and Text as we copy them into nodeCD using wsprintf. Then we set the current directory and the text in the edit control and return. Each time it comes back, it pops the stack and adds the pszText to the end of nodeCD. It doesn't know or care when it is finished. It just goes back to work or whatever.

The code for the getNodeFullPath function:

C++
// FUNCTION: recursively get the current hitem's parent until you find the 
// drive then return the tvi.pszText and collect it to build the full-path 
// of the current node. Call this routine when a node is selected or when 
// a button(+ or -) is clicked. This is necessary because clicking a button 
// does not automatically select a node, it only expands it.
BOOL getNodeFullPath(HWND hTree,  HTREEITEM  nmtvi){
  TVITEM tvi;
  TCHAR Text[256]={};
  tvi.hItem=nmtvi;
  tvi.mask = TVIF_TEXT; 
  tvi.cchTextMax = 255;
  tvi.pszText=Text;
  if(TreeView_GetItem(hTree, &tvi))
    {
    if (tvi.pszText[1]== L':')
      { // you found the drive, the root of the folders
      wsprintf(nodeCD, L"%s", tvi.pszText); // concat in text buffer
      SetCurrentDirectory(nodeCD);
      SetWindowText(hEdit, nodeCD);         // set the text in IDC_EDIT1
      return true; // stop recursing
      }
    else        
      {
      nmtvi = TreeView_GetParent(hTree, tvi.hItem);
      getNodeFullPath(hTree, nmtvi); // loop recursively 

      /* when you get here you are no longer recursing,
      *  you are just collecting the returned information
      *  in the reverse order of the get parent calls. This 
      *  has the effect of a last-in - first-out stack, 
      *  resulting in a 'reversal by recursion'.  */
      if (tvi.pszText)
        {
        if(nodeCD[wcslen(nodeCD) - 1] != L'\\')
          {
          wsprintf(nodeCD, L"%s\\%s", nodeCD,tvi.pszText); // concat in text buffer
          }
        else
          {
          wsprintf(nodeCD, L"%s%s", nodeCD,tvi.pszText); // concat in text buffer
          }
        SetCurrentDirectory(nodeCD);
        SetWindowText(hEdit, nodeCD);
        }
      }
    }
  return true; // return curent value of tvi.pszText
}

The About dialog proc

This Dialog Procedure was included in the project by the project creation wizard. I am including it in this article so that readers can see the basic dialog proc and compare it with a dialog proc for a common control. Specifically, note that a "Button" is handled in a WM_COMMAND block (i.e., ID_OK - 'OK' button), while a TreeView control is handled in a WM_NOTIFY block (i.e., IDC_TREE1 - the treeview control).

The code for the About dialog proc:

C++
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  {
  UNREFERENCED_PARAMETER(lParam);
  switch (message)
  {
  case WM_INITDIALOG:
    return (INT_PTR)TRUE;

  case WM_COMMAND:
    if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
    {
      EndDialog(hDlg, LOWORD(wParam));
      return (INT_PTR)TRUE;
    }
    break;
  }
  return (INT_PTR)FALSE;
  }

Epilog

An afterword on the subject of going native; I began this article with an allusion to transportation, claiming that I did not wish to be a tourist in my exploration of 'modern' computer programming. This was not meant to denigrate the work of those who may write code in VB or C# with a very tight time line for completing one phase before beginning another. I don't have a deadline, so I write code for fun. Sometimes in F# and sometimes in C/C++. I view high level languages such as VB, C#, F#, or whatever as a vehicle to carry a lot of work to completion very quickly, not necessarily caring about speed but more concerned with safety and accuracy, while C/C++ is the inner workings of that vehicle, i.e., the engine or transmission.

That high level vehicle has parts built-in that allow you to do powerful things, but the power is provided by those who wrote the language and they did not provide the ability to do everything. In some cases, it is necessary to add a new part, some function in a different language, and link it in.

C/C++ is likely to be that different language in which you write that new part. In all likelihood you could write the entire application in C but that would require some multiple number of lines of code to get the same functionality as in the higher level language. C++ has many features that can reduce the additional coding needed to get that functionality in your program. In particular, the frameworks available are in some cases even more powerful than some high level languages. Chances are you will always find something that a particular framework does not do for you. You can find another framework that does what you want or if you work hard enough you can do it yourself. As in sports, the more you train, the stronger you get! The more you rely on any shortcut, whether language or framework, the longer it takes to gain strength in programming skills. Now the question arises, 'Is the destination to learn everything or the journey there?'. You will probably never learn everything. So, for me, the destination is the journey. Why take a shortcut? Go native.

History Log

  • December 7, 2012 - Initial release. "A date that will live in infamy!" Franklin Delano Roosevelt.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)