The Explorer Imperative - Two Stage Search
Least frequent character offset exact match algorithm used in GUI keyword search
Introduction: The Explorer Imperative Two Stage Search using the Least Frequent Character Offset
What Is the Least Frequent Character Offset and Why Should I Care?
The Least Frequent Character Offset is the position of LFC in a pattern you want to find. Any character in the pattern could be used but by using the LFC, it will be found much less often. With SSE SIMD Instructions, the compare can examine sixteen bytes at a time. The first character of the pattern must occur at the correct prefixed offset to the LFC or they will both disappear when the SIMD registers are 'ANDED' together. This means the SSE intrinsics can scan a file in 16 byte increments, finding only those instances of the LFC that are at the proper offset from a character that matches the first character of the pattern being searched for. When the LFC and the 'other ' character are found, then a string
compare is executed to confirm or deny that a match has been found.
This program, U2Charfunc
, was written to run on a machine that had only the SSE Instructions or possibly only the headers for SSE. I am not sure at this point, and I no longer have that machine but I know that someone does and they can still use this program. SSE2 has some additional instructions that make it a little faster, notably the _mm_and_si128
which allows anding the 16 bytes as bytes whereas SSE has only the _mm_and_ps
which ands them as 32 bit floating point, so you can't use it to 'and' 2 16 byte SSE registers. In other words, you have to store them in an intermediate DWORD
and use a '&
' to 'and
' the intermediate results. This makes a tighter loop and a faster search possible with SSE2, but it only works if you have the SSE2 Instruction Set.
The LFC Algorithm uses a Control String and a short preprocessing routine to determine the LFC in the pattern you supply. The Control String contains the characters of the alphabet arranged in the order of their frequency, most frequent to least frequent in 'C ' and 'C++' on my computer. They were counted by U2Charfunc
. It will count characters, pairs of characters and strings, tested to a length of 64 characters. The preprocessing routine tries to find the last character in the LFCARRAY Control string
in the search pattern. It scans backwards until a character is found. This is the Least Frequent Character. It is the OFFSET of this character in the search pattern that is the LFC Offset used in the scanning loop. Once the LFC and its Offset are found, the file is opened and read. There are separate routines for a single character, two character and more than two character patterns. The single character routine uses one SSE register and pops the count of occurrences. The two character routine uses two registers but it also pops the count. Neither of these routines requires a compare to verify the match.
In the three or more character routine, the (MEMORY
) register is aligned on a 16 byte boundary and loaded aligned from the current file position, nWts
. These 16 bytes are compared to a 16 bytes containing copies of the first character (of the pattern) to match, called fCtm
, using _mm_cmpeq_epi8
and the resulting mask is moved to first character of match, fCom
. Now memory is loaded from nWts
plus the LFC Offset. This is compared to the 16 copies of the Least Frequent Character and the mask is moved to sCom
, the second character to match. Now fCom
and sCom
are anded, causing any 1 bits that aren't matched to disappear. If the anded results are zero, nWts
is increased by 16 and the window To scan, wTs
, is loaded from nWts.
When the results of the AND operation are not zero, we use _BitScanForward
to find the least significant bit that is set. This is the match Index or mIdx, which is added to nWts
to get the address of a possible match. Here, we use a _memicmp
function to do the compare and possibly increment a count. If you only wanted to know whether a file contained a string
and didn't care about the number of times it contained it, you could just return a flag to indicate that it did or at the end of the program return a flag to indicate that it didn't.
Here Is the Code for U2Charfunc as a Standalone Program. The 'main() ' Function Is Commented Out.
The u2Charfunc Function
// u2charFunc.c() loads the memory segments of the 'line ' buffer in
// sixteen byte increments in a 16 byte aligned SIMD Instruction and
// compares each byte for equality with the least frequent character,
// then it loads 16 bytes from an offset of the aligned address that
// matches the offset of the second least frequent character from the
// first. The results are and-ed, eliminating any occurrence of either
// character which does not have an occurrence of the other at the proper
// offset. Since it jumps ahead 16 bytes at a time, it is fast. Also,
// since it looks for the least frequent characters of the search string,
// it should be faster when the characters chosen are, indeed, less
// frequently the other characters. The exact effect on the speed of a
// search depends on the use of the proper control string and the specific
// string being searched for.
//
#include <intrin.h>
#include <stdio.h>
#define MAXLINE 1024 * 4
#define MAXPATH 260
#pragma warning( disable: 4706)
// unaligned two character scan function
int u2Charfunc ( CHAR * chBuf, char * pattern, int patlen ) {
// Analyze search string and scan files in Listview
// Frequency Rank -----------1---------2---------3---------4---------5-
// most to least 0123456789012345678901234567890123456789012345678901
// frequent char etroniaslcdpETSCmIRuLDAhOfPNgwMUbBFyWvGxVHkzKYXjQqZJ
char lFcArray [ ] = "etroniaslcdpETSCmIRuLDAhOfPNgwMUbBFyWvGxVHkzKYXjQqZJ ";
int lfclen = ( int ) strlen ( lFcArray );
int prefixLen;
char lFcIndex = 0;
char secLFcIndex = 0;
char searchChar = 0;
char searchChar2 = 0;
char *p, *q, *r; p = q = r = 0;
size_t j = 0;
for ( int i = 1; i < lfclen; i++ ) {
p = strrchr ( pattern, lFcArray [ lfclen - i ] );
if ( p != 0 ) {
lFcIndex = ( char ) ( p - pattern );
searchChar = pattern [ lFcIndex ];
j = ( size_t ) lfclen - i - 1;
p = strchr ( pattern, searchChar );
secLFcIndex = ( char ) ( p - pattern );
searchChar2 = pattern [ secLFcIndex ];
break;
}
}
if ( secLFcIndex == lFcIndex ) {
for ( size_t i = 0; i < j; i++ ) {
p = strrchr ( pattern, lFcArray [ j - i ] );
if ( p != 0 ) {
secLFcIndex = ( char ) ( p - pattern );
searchChar2 = pattern [ secLFcIndex ];
break;
}
}
}
char scanChar = searchChar;
char scanChar2 = searchChar2;
prefixLen = lFcIndex;
int charOffset = -prefixLen;
int maskOffset = secLFcIndex - lFcIndex;
HANDLE hSearch;
char * line = NULL;
char searchFile [ 260 ];
int numfound = 0;
scanChar = pattern [ lFcIndex ];
scanChar2 = pattern [ secLFcIndex ];
charOffset = -lFcIndex;
maskOffset = secLFcIndex - lFcIndex;
int totBytes = 0;
p = strchr ( chBuf, '\r ' );
if ( p ) *p = '\0 ';
memset ( &searchFile, 0, sizeof ( searchFile ) );
sprintf_s ( searchFile, 260, "%s ", chBuf );
numfound = 0;
BOOL bResult = FALSE;
DWORD nbytesToRead = 1024 * 64;
DWORD nbytesRead = 0;
hSearch = CreateFileA ( searchFile, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_READONLY | FILE_FLAG_SEQUENTIAL_SCAN, NULL );
if ( hSearch == INVALID_HANDLE_VALUE ) {
DWORD dwErr = GetLastError ( );
if ( dwErr != 0 && dwErr != 5 && dwErr != 32 && dwErr != 2 ) {
MessageBoxA ( 0, searchFile, "Can 't Create File: ", MB_OK | MB_ICONEXCLAMATION );
return -1;
}
}
else {
unsigned long maxLen = 0;
LARGE_INTEGER maxLenQ;
PLARGE_INTEGER maxLenL = &maxLenQ;
maxLenL->QuadPart = 0;
GetFileSizeEx ( hSearch, maxLenL );
if ( maxLenL == 0 )
return 0;
maxLen = ( LONG ) maxLenL->QuadPart;
if ( maxLen > 1024 * 1024 - 1024 ) {
nbytesToRead = 1024 * 1024 - 1024;
maxLen -= 1024 * 1024 - 1024;
}
else
nbytesToRead = ( DWORD ) maxLenL->QuadPart;
line = ( char * ) calloc ( ( size_t )
nbytesToRead + 1024, 1 ); // Buffer the buffer - doubled buffered
errno_t nErr;
_get_errno ( &nErr );
if ( patlen != 0 ) {
bResult = ReadFile ( hSearch, &line [ 16 ], nbytesToRead, &nbytesRead, NULL );
while ( bResult && nbytesRead != 0 ) {
int numRead = nbytesRead;
if ( numRead != 0 ) {
totBytes += nbytesRead;
numRead = nbytesRead;
if ( patlen == 1 ) {
// Next Window to Scan
char * nWts = line + 16;
//Match InDeX
//unsigned long mIdx = 0ul;
// The Window to Scan
__m128i wTs;
// First Character To Match
__m128i fCtm = _mm_set1_epi8 ( *pattern );
// Comparison flags to be moved to fcom
__m128i tempMask;
// First Character of Match
unsigned fCom = 0;;
// Scan Matches
// Alignment Offset
unsigned aO = 15 & ( UINT_PTR ) nWts;
nWts -= aO; // align the buffer
// Pointer To Matches
wTs = _mm_load_si128 ( ( __m128i const * )nWts );
while ( 1 ) {
tempMask = _mm_cmpeq_epi8 ( fCtm, wTs );
fCom = _mm_movemask_epi8 ( tempMask );
numfound += __popcnt ( fCom );
if ( numRead <= 0 )
break;
nWts += 16;
wTs = _mm_load_si128 ( ( __m128i const * )nWts );
numRead -= 16;
}
}
else if ( patlen > 1 ) {
if ( numRead == 4096 ) {
SetFilePointer ( hSearch, -( patlen - 1 ),
NULL, FILE_CURRENT );
}
if ( patlen == 2 ) {
char * nWts = line + 16; // Next Window to Scan
__m128i wTs; // The Window to Scan
// First Character To Match
__m128i fCtm = _mm_set1_epi8 ( scanChar );
// Second Character To Match
__m128i sCtm = _mm_set1_epi8 ( scanChar2 );
unsigned fCom = 0; // First Character of Match
unsigned sCom = 0; // Second Character of Match
unsigned sM = 0; // Scan Matches
// Alignment Offset
unsigned aO = 15 & ( uintptr_t ) nWts;
nWts -= aO; // align the buffer
wTs = _mm_load_si128 ( ( __m128i const * ) nWts );
while ( 1 ) {
//#pragma warning( disable: 4706)
if ( ( fCom = _mm_movemask_epi8 ( _mm_cmpeq_epi8 ( fCtm, wTs ) ) != 0 ) ) {
wTs = _mm_loadu_si128 ( ( __m128i const * )( nWts + maskOffset ) );
//#pragma warning( disable: 4706 )
if ( ( sCom = _mm_movemask_epi8 ( _mm_cmpeq_epi8 ( sCtm, wTs ) ) != 0 ) ) {
sM = fCom & sCom;
}
if ( sM ) {
numfound += __popcnt ( sM );
}
}
if ( numRead <= 0 )
break;
nWts += 16;
wTs = _mm_load_si128 ( ( __m128i const * ) nWts );
numRead -= 16;
}
}
else if ( patlen > 2 ) {
// Next Window to Scan
char * nWts = line + 16;
//Match InDeX
unsigned long mIdx = 0ul;
// The Window to Scan
__m128i wTs;
// First Character To Match
__m128i fCtm = _mm_set1_epi8 ( scanChar );
// Second Character To Match
__m128i sCtm = _mm_set1_epi8 ( scanChar2 );
// Comparison flags to be moved to fcom and scom
__m128i tempMask;
// First Character of Match
unsigned fCom = 0;;
// Second Character of Match
unsigned sCom = 0;
// Scan Matches
unsigned sM = 0;
// Alignment Offset
unsigned aO = 15 & ( uintptr_t ) nWts;
nWts -= aO; // align the buffer
// Pointer To Matches
char const* pTm = NULL;
wTs = _mm_load_si128 ( ( __m128i const * )nWts );
while ( 1 ) {
tempMask = _mm_cmpeq_epi8 ( fCtm, wTs );
fCom = _mm_movemask_epi8 ( tempMask );
wTs = _mm_loadu_si128 ( ( __m128i const * )( nWts + maskOffset ) );
tempMask = _mm_cmpeq_epi8 ( sCtm, wTs );
sCom = _mm_movemask_epi8 ( tempMask );
sM = fCom & sCom;
while ( sM ) {
_BitScanForward ( &mIdx, sM );
pTm = nWts + mIdx + charOffset; //address of a possible match
if ( !_memicmp ( pTm, pattern, patlen ) ) {
numfound++;
}
sM &= sM - 1;
}
nWts += 16;
if ( numRead <= 0 )
break;
numRead -= 16;
wTs = _mm_load_si128 ( ( __m128i const * )nWts );
}
}
}
memset ( &line [ 16 ], 0, nbytesRead );
bResult = ReadFile ( hSearch, &line [ 16 ], nbytesToRead, &nbytesRead, NULL );
}
}
}
}
CloseHandle ( hSearch );
free ( line );
return numfound;
}
/*
// To run u2charfunc as a stand-alone program, you need
// a main function, similarto the one below but when you
// call it as a subroutine, you must pass the pattern
// length as the third parameyer.
int main ( int argc, char *argv [ ] ) {
char * path [ 260 ] = { 0 };
char * pattern [ 64 ] = { 0 };
int patlen = 0;
// Verify correct number of args (exename filename string2search4
if ( argc != 3 ) {
printf_s ( "Usage: lfcMatch.exe <path\file> <pattern>\n " );
// Copy args
//strcpy ( pattern, tPattern );
//strcpy ( path, tPath );
return 0;
}
else {
// Copy args
patlen = sprintf_s ( path, 260, "%s ", *++argv );
if ( 260 < patlen ) {
printf_s ( "Path is too long \n " );
}
patlen = sprintf_s ( pattern, 64, "%s ", *++argv );
}
int retnum = u2Charfunc ( path, pattern, patlen );
printf_s ( "Found %s %d times\n ", pattern, retnum );
return retnum;
}
*/
This program, U2Charfunc
, is the reason for this article. But I realize that only those developers that have a pre-existing interest in string
matching techniques will find this of interest. Therefore, I have included a short windows program to demonstrate a possible use of this program.
Background
Why another find utility? Simple. I don't like the ones I have available. I have tried many different products and I can 't find the one I need. There may be times when I need one of them and their capabilities but primarily I want to find a keyword and the context in which it is used. Most of the time, I want to see how I used it in my own code or maybe how someone else used it. I need this when I read the Visual Studio Documentation, which says can't find requested topic, or sometimes when it can find requested topic. I need it to not take my focus away from what I am trying to do. I don't want to take 30 minutes to figure out how to use it. I have tried to provide these features while trying to not let the program grow to large.
But This Is a Batch Program! What's It Got to Do With Windows?
This can only be a Batch program if you uncomment the 'main()
' function. Otherwise, it is a function in a Windows program. U2Charfunc
is not called by 'main()
' in a Windows program, instead it is called by the findStringInAllFiles()
function. This function takes no arguments and returns a BOOL
. The pattern to find is taken from the Environment Variable 'FSIAF
', which is the acronym for 'Find string in all files
'. The File Name and Path are retrieved from the currently selected item in the List View. The pattern length is determined by _snprintf_s
as the number of characters copied from fsiaf
to pattern. It gets the first path, which is the current item in ListView
. Then gets the item count and loops through the ListView
item count times, getting the next path, setting the window text for the main window and Edit Box and calling u2Charfunc(path, pattern, patlen)
. If the return value is greater than zero, the 'OCCURS
' Column in the ListView
is updated with the count
. Then it gets the next Item's path and peeks at the message queue so that the user won't cause the program to become un-responsive by attempting to multi-task. Then we loop until all of the items have been searched. When the loop finishes, the current item in the ListView
is the same as it was in the beginning. We send an F5 key to the ListView
, causing it to search the 'Occurs
' Column for the first file after the current one to contain an occurrence.
findStringInAllFiles() Function
//////////////////////////////////////////////
//
// findStringInAllFiles() analyzes the Search String to find the Least Frequent
// and Second Least Frequent characters using a Control String that is specific
// to the C and C++ Language, based on the C++ files on My Computer. It then calls
// U2CharFunc() on each file in the ListView File Set to count the occurrences
// of the Search String, if any. After each file is scanned, the message queue
// is peeked and dispatched to animate the progress bar and retain the windows
// responsiveness.
//
////////////////////////////////////////////
BOOL findStringInAllFiles ( ) {
# pragma region Initialize_Storage
TCHAR searchCap [ 256 ]; // The Window Caption for Search Function
TCHAR curntFname [ 256 ];
TCHAR curntPath [ MAX_PATH ];
int hits = 0;
memset ( &curntFname, 0, sizeof ( curntFname ) );
memset ( &curntPath, 0, sizeof ( curntPath ) );
TCHAR curntHitCount [ 256 ];
int result;
CHAR chBuf [ 4096 ];
memset ( &chBuf, 0, sizeof ( chBuf ) );
char pattern [ MAX_PATH ];
TCHAR fsiaf [ 256 ] = { 0 }; // Find this String In A File
GetEnvironmentVariable ( L "FSIAF ", fsiaf, 255 );
int patlen = _snprintf_s ( pattern, 255, 255, "%S ", fsiaf );
HWND hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 );
HWND hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
int startPos = -1, endPos = 0;
SendMessage ( hEdit, EM_SETSEL, ( WPARAM ) &startPos, ( LPARAM ) &endPos );
# pragma endregion findStringInAllFiles(...)
SetFocus ( hEdit );
StringCchPrintf ( searchCap, 255, L "Searching for %s in ALL listed files. ", fsiaf );
SetWindowText ( hMainWnd, searchCap );
int iCount = ListView_GetItemCount ( hList );
for ( int i = 0; i < iCount; ++i ) {
memset ( &curntPath, 0, sizeof ( curntPath ) );
memset ( &curntFname, 0, sizeof ( curntFname ) );
// Get column one text string
ListView_GetItemText ( hList, i, 0, curntFname, MAX_PATH - 1 );
// Get column three text string
ListView_GetItemText ( hList, i, 2, curntPath, MAX_PATH - 1 );
memset ( &chBuf, 0, sizeof ( chBuf ) );
// either of the following 2 lines can be used because u2Charfunc
// changes backslash r to backslash zero
sprintf_s ( chBuf, 4096, "%S\\%S\r\n ", curntPath, curntFname );
curntPath [ 259 ] = 0;
SetWindowText ( hMainWnd, curntPath );
SetWindowText ( hEdit, curntPath );
int numbFound = u2Charfunc ( chBuf, pattern, patlen );
if ( numbFound > 0 ) {
hits++;
StringCchPrintf ( curntHitCount, 255, L "%d occurrences of '%s '. ", numbFound, fsiaf );
LVITEM lvi;
int ret = i;
memset ( &lvi, 0, sizeof ( lvi ) );
lvi.mask = LVIF_TEXT;
lvi.iItem = ret;
lvi.iSubItem = 1;
lvi.cchTextMax = 255;
lvi.pszText = curntHitCount;
ListView_SetItem ( hList, &lvi );
ListView_SetItemState ( hList, i, LVIS_SELECTED, LVIS_SELECTED );
ListView_EnsureVisible ( hList, i, TRUE );
MSG msg;
while ( PeekMessage ( &msg, NULL, 0, 0, PM_REMOVE ) ) {
DispatchMessage ( &msg );
}
}
} //endfor
result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
if ( result == -1 )
result = 0;
ListView_SetItemState ( hList, result, LVIS_SELECTED, LVIS_SELECTED );
StringCchPrintf
( searchCap, 255,
L "Searched %d Files, Found %d files containing 1 or more occurrences of '%s '. ",
iCount, hits, fsiaf );
SetWindowText ( hMainWnd, searchCap );
SetWindowText ( hEdit, searchCap );
ListView_SetItemState ( hList, -1, 0, LVIS_SELECTED );
ListView_SetItemState ( hList, result, LVIS_SELECTED, LVIS_SELECTED );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
StringCchPrintf ( searchCap, 255, L "%d Files with matches. ", hits );
SendMessage ( hwndStatus, SB_SETTEXT, 1 | SBT_POPOUT, ( LPARAM ) searchCap );
sendMeMyKey ( hList, VK_F5 );
return 1;
}
Putting the File Name and Path into the ListView: recursivefileSearch() Function
Most developers have a primary language in which they code most of their work. This suggests they might want a 'default' type of file. We don't know what that is in advance, so the initial 'default ' is set to "*.cpp;*.c". This will result in the ListView
being filled with files having the extensions 'cpp
' and 'c
', with the root folder being the %USERPROFILE%
Environment Variable. Both of these 'defaults' can be changed and the new value will be stored in the registry using the setx
function. The set of extensions is selected or entered using the combo box on the left. The user can click on the dropdown row he wants or the EditCtrl
and type in their choice, then push ENTER or click the START Button on the left.
In the screen print above, the combo box dropdown shows a long string of extensions preceded by an asterisk and a period and separated by semicolons. The semicolon is necessary when more than one extension is used. The asterisk must be used unless you are searching for a specific title or suffix, such 'the Wind' might find 'Gone With the Wind' but not find 'The Wind blows Cold' because it looks at the ending of the argument. These three cases are handled by three different routines which are recursive. Since the cases can be differentiated by the presence of a semicolon and asterisk, we use a non recursive function to dispatch them. This makes the logic much simpler for the individual case but does require more code. Here is the code for the non recursive 'recursiveFileSearch
' function.
Choosing the Right Recursive File Search
The recursivefileSearch Function
// analyze search request string and dispatch recursivefileSearch functions accordingly
int recursivefileSearch ( TCHAR *name, BOOL hidden ) {
TCHAR searchStr [ MAX_PATH ] = { 0 };
GetEnvironmentVariable ( L "SEARCHSTR ", searchStr, 255 );
if ( ( wcschr ( searchStr, L '* ' ) == NULL ) ) {
recursivefileSearch1 ( name, searchStr, hidden );
return 0;
}
else if ( wcschr ( ( LPWSTR ) searchStr, L '; ' ) != NULL ) {
recursivefileSearch2 ( name, searchStr, hidden );
return 0;
}
recursivefileSearch3 ( name, searchStr, hidden );
return 0;
}
Looking for a Matching Suffix: recursivefileSearch1
Now, the criteria has been established to build the File List for the ListView
. We are just entering the function but we don't know how many times we have already been here. Since it could have been a while, we peek at the message queue to make sure we remain responsive. This is in a loop so we don't need to loop de loop. We make sure the path has a backslash on the end and add an '*
' to the ending. We create a handle to a find with FindFirstFileExW
. This fills a structure with information about the initial findings. If this is a file, we compare the suffix of the filename with our argument. If it matches, we put it in the ListView
. If it's a dot or dot dot, we ignore it. When it's a directory, we add the directory to the end of the path and put the path in the Title of the main window and the Edit Box. Then we call recursivefileSearch1
. We do this until FindNextFile
returns a zero instead of new information from the handle to the hFind
. Here's the code for the recursivefileSearch1
function.
The recursivefileSearch1 Function
// FUNCTION: Find all files whose name contains the search argument.
// Does not search hidden directories. This results in a significant
// increase in speed. Pattern is assumed to be a suffix. It can be any
// reasonable length, so it is not limited to extensions. It can not
// contain wildcards. The pattern is compared to the end of the file
// name to identify files to list.
int recursivefileSearch1 ( TCHAR *name, TCHAR * searchStr, BOOL hidden ) {
WIN32_FIND_DATA ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
DWORD dwError = 0, addcount = 0;
TCHAR recursiveSearchPath [ MAX_PATH ], text [ 32 ];
HWND hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 );
HWND hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
MSG msg;
while ( PeekMessage ( &msg, NULL, 0, 0, PM_REMOVE ) )
DispatchMessage ( &msg );
if ( name [ wcslen ( name ) - 1 ] != L '\\ ' ) {
// Prepare string for use with FindFile functions. First, copy the
// string to a buffer, then append "\* " to the directory name.
wsprintf ( recursiveSearchPath, L "%s\\* ", name );
}
else {
wsprintf ( recursiveSearchPath, L "%s* ", name );
}
// Find the first file in the directory.
hFind = FindFirstFileExW ( recursiveSearchPath, FindExInfoBasic,
&ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH );
// List all the files in the directory with some info about them.
do {
if ( hFind == INVALID_HANDLE_VALUE )
// Done checking this folder
// no files were found in this recursion
return -1;
int fnlen = ( int ) wcslen ( ffd.cFileName );
int extlen = ( int ) wcslen ( searchStr );
if ( ( wcscmp ( ffd.cFileName, L ". " ) ) == 0
|| ( wcscmp ( ffd.cFileName, L ".. " ) ) == 0 ) {
; //If the file is a self-reference 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 ) ) {
if ( wcsncmp ( ffd.cFileName + fnlen - extlen, searchStr, extlen ) == 0 )
{ // 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;
ListView_InsertItem ( hList, &item );
item.iSubItem = 2;
item.pszText = name;
ListView_SetItem ( hList, &item );
wsprintf ( text, L "%I64u ", ( ( ULONGLONG )
( ffd.nFileSizeHigh ) * ( ( ULONGLONG ) MAXDWORD + 1 ) ) + ffd.nFileSizeLow );
item.iSubItem = 3;
item.pszText = text;
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 );
}
SetWindowText ( hEdit, recursiveSearchPath );
addcount = ListView_GetItemCount ( hList );
TCHAR tStr [ MAX_PATH ] = { 0 };
StringCchPrintf ( tStr, MAX_PATH - 1, L "[%d] %s ", addcount, recursiveSearchPath );
SetWindowText ( hMainWnd, tStr );
recursivefileSearch1 ( recursiveSearchPath, searchStr, 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 IDC_EDIT2
SetWindowText ( hEdit, L "Error in Find File Routine ! " );
}
FindClose ( hFind );
return dwError;
}
Listing Multiple Extensions:recursivefileSearch2()
The recursivefileSearch2()
function is very similar to recursivefileSearch1
. But there is one major difference. recursivefileSearch1
compares the argument with the end of the file name whereas recursivefileSearch2
uses the PathMatchSpec
function to determine if the filename extension is in the arguments set of extensions. If it is, it is added to the File List. Since everything else is the same, the code block is not shown here.
Splitting the Recursion:recursivefileSearch3
When there is only one extension and there is an asterisk in the argument, we can tell the FindFirstFileExW
function we only want to see files with this extension and no directories, by using the FindExSearchNameMatchflag
in the FINDEX_SEARCH_OPS
field. Then we loop through the results, putting them all in the File List, until FindNextFile
returns zero. Then we call FindFirstFileExW
again, this time using FindExSearchLimitToDirectories
as in the FINDEX_SEARCH_OPS
parameter to recurse through the directories. Since the logic is quite different, I will present it in its entirety. And here it is...
The recursivefileSearch3 Function
// FUNCTION: Find all files whose name contains the search argument.
// Does not search hidden directories. This results in a significant
// increase in speed. The pattern may contain wildcards. The
// FindExSearchNameMatchflag is used to identify files to list.
// After FindExSearchNameMatchflag processing is completed then
// the FindExSearchLimitToDirectories as is used to recursively search
// through the subdirectories.
int recursivefileSearch3 ( TCHAR *name, TCHAR * searchStr, BOOL hidden ) {
WIN32_FIND_DATA ffd;
HANDLE hFind = INVALID_HANDLE_VALUE;
DWORD dwError = 0;
TCHAR recursiveSearchPath [ MAX_PATH * 2 ], text [ 32 ];
HWND hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1);
HWND hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
MSG msg;
while ( PeekMessage ( &msg, NULL, 0, 0, PM_REMOVE ) )
DispatchMessage ( &msg );
if ( name [ wcslen ( name ) - 1 ] != L '\\ ' ) {
// Prepare string for use with FindFile functions. First, copy the
// string to a buffer, then append "\* " to the directory name.
wsprintf ( recursiveSearchPath, L "%s\\*%s ", name, searchStr );
}
else {
wsprintf ( recursiveSearchPath, L "%s* ", name );
}
// Find the first file in the directory.
hFind = FindFirstFileExW ( recursiveSearchPath, FindExInfoBasic, &ffd,
FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH );
if ( hFind != INVALID_HANDLE_VALUE ) {
// List all the files in the directory with some info about them.
do {
LVITEM item;
memset ( &item, 0, sizeof ( item ) );
item.mask = LVIF_TEXT;
item.iItem = 0;
item.iSubItem = 0;
item.cchTextMax = 260;
item.pszText = ffd.cFileName;
ListView_InsertItem ( hList, &item );
item.iSubItem = 2;
item.pszText = name;
ListView_SetItem ( hList, &item );
wsprintf ( text, L "%I64u ", ( ( ULONGLONG )
( ffd.nFileSizeHigh ) * ( ( ULONGLONG ) MAXDWORD + 1 ) ) + ffd.nFileSizeLow );
item.iSubItem = 3;
item.pszText = text;
ListView_SetItem ( hList, &item );
}
while ( FindNextFile ( hFind, &ffd ) != 0 );
dwError = GetLastError ( );
if ( dwError != ERROR_NO_MORE_FILES )
if ( dwError != ERROR_ACCESS_DENIED ) {
// set the text in IDC_EDIT2
SetWindowText ( hEdit, L "Error in Find File Routine ! " );
}
FindClose ( hFind );
}
if ( name [ wcslen ( name ) - 1 ] != L '\\ ' ) {
// Prepare string for use with FindFile functions. First, copy the
// string to a buffer, then append "\* " to the directory name.
wsprintf ( recursiveSearchPath, L "%s\\* ", name );
}
else {
wsprintf ( recursiveSearchPath, L "%s* ", name );
}
// Find the first file in the directory.
hFind = FindFirstFileExW ( recursiveSearchPath, FindExInfoBasic, &ffd,
FindExSearchLimitToDirectories, NULL, FIND_FIRST_EX_LARGE_FETCH );
do {
if ( hFind == INVALID_HANDLE_VALUE )
// Done checking this folder
// no files were found in this recursion
return -1;
if ( ( wcscmp ( ffd.cFileName, L ". " ) ) == 0
|| ( wcscmp ( ffd.cFileName, L ".. " ) ) == 0 ) {
; //If the file is a self-reference 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 ) { // 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 );
}
SetWindowText ( hMainWnd, recursiveSearchPath );
SetWindowText ( hEdit, recursiveSearchPath );
recursivefileSearch3 ( recursiveSearchPath, searchStr, 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 IDC_EDIT2
SetWindowText ( hEdit, L "Error in Find File Routine ! " );
}
}
FindClose ( hFind );
return dwError;
}
Selecting or Entering a String to Search For: ComboBox2
When you see this message box, there is a checkbox
on the lower left corner of the message box. If the checkbox
is checked, the message box will no longer be displayed. The method to recover the message box will be discussed later. Currently, all three of these message boxes use the same GUID to control the state of the Checkbox
. The message box is purely informational, since the function used (SETX
) returns the same result even if it fails. There is additional error checking to inform the user if the data was not saved. The most likely time for this function to fail is the very first attempt of the day and will only matter if it is the last attempt. since only the last value set is the one retrieved.
Selecting the combobox EditCtrl on the RIGHT and Pushing ENTER will Retrieve the Last Value SAVED. Clicking the DROPDOWN Triangle Will Show the Values Entered Today
Both ComboBox
es are subclassed in the same comboBoxSubClassProc
function. It's a very simple function. The first thing it does is get the window handles for the main window and IDC_BTN1
and IDC_BTN2
. When uMsg
is WM_KEYDOWN
and wParam
is VK_RETURN
, it is known that it is coming from the child of a combobox
, so it gets the parent combobox
, which it uses to GetDlgCtrlID
to differentiate between the two ComboBox
es and sends a BM_CLICK
message to the corresponding Button
. This clicks the button and initiates the requested function. It also processes WM_KEYUP
and WM_CHAR
for WM_KEYUP
by returning zero. Everything is returned to the DefSubclassProc
function.
Handling the Enter Key: comboBoxSubClassProc
The comboBoxSubClassProc Function
// The Combobox was subclassed to make Buttons respond to EDITCTRL Enter Key
// press as if Button was clicked. User can either click Button or, since they
// are typing in the EditCtrl, they can just press Enter.
LRESULT CALLBACK comboBoxSubClassProc ( HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) {
UNREFERENCED_PARAMETER ( uIdSubclass );
UNREFERENCED_PARAMETER ( dwRefData );
HWND hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hBtn1 = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_BUTTON1 );
HWND hBtn2 = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_BUTTON2 );
switch ( uMsg ) {
case WM_KEYDOWN:
switch ( wParam ) {
case VK_RETURN:
HWND hComBo = GetParent ( hWnd );
ULONG dlgId = GetDlgCtrlID ( hComBo );
if ( dlgId == IDC_COMBO1 ) {
SetActiveWindow ( hBtn1 );
SendMessage ( hBtn1, BM_CLICK, 0, 0 );
return 1;
}
if ( dlgId == IDC_COMBO2 ) {
SetActiveWindow ( hBtn2 );
SendMessage ( hBtn2, BM_CLICK, 0, 0 );
return 1;
}
}
break;
case WM_KEYUP:
case WM_CHAR:
switch ( wParam ) {
case VK_RETURN:
return 0;
}
default:
return DefSubclassProc ( hWnd, uMsg, wParam, lParam );
}
return 0;
}
Handling the Input and Life-Cycle of Data: The Comboboxproc Function
The Extension in ComboBox1, the 'string to search for ' and the File It was Found in
The comboBoxProc
function processes the user input for the TwoStageSearch
. Before the 'message
' switch, it determines if the message is not WM_INITDIALOG
so it can retrieve the handles to all of the windows that WM_INITDIALOG
has created. The reason this is done is a constant cannot be used twice in case
statements. By using WM_INITDIALOG
in an 'if
' construct, it can still be used in a switch
case.
The handle for ComboBox1
, hCombo1
is used to prevent the initialization of some items until all items are ready for it. After the hCombo1
handle has been initialized in WndProc
, everything else can be done. We get the Combobox
Info and SetWindowSubclass
for the proc. This is the new process for sub-classing a control. As was shown previously, we are only processing the Return Key to generate a Mouse Click of a Button. The first Combo Box will generate a mouse click for the 'Start File List Build' Button. Next, we fill the Combo Box drop down List with some possible choices for the Set of Extensions for the build and initialize the Cue Banner for the Combo Box. For ComboBox2
, we do the same things except for the drop down List which we leave empty. Next, we give the controls an initial size and move them into the initial position. The Progress Bar is positioned to cover the Comboxes but is hidden.
In the WM_COMMAND
message we process the wmId
switch, the first case is IDC_BUTTON1
. Here, we respond to the BM_CLICK
message by getting the text of the Combobox EditCtrl
. If it's non- blank, it is added to the drop down List, otherwise, the extension set used last time is retrieved and placed in the EditCtrl
and zero is returned. In other words, if the EditCtrl
is empty and the CUE
is displayed, when the user clicks on the EditCtrl
or clicks the button on the left, the previous value is placed in the EditCtrl
. If there is a value in the EditCtrl
, it is compared to the previous extension set and if they are different, the new value is saved in the Registry by calling the persist_This
function. Either way, the Progress Bar is activated to cover the ComboBox
es and prevent user multi-tasking, the status bar is updated and the recursivefileSearch
is called to Build the File List. When it returns, the status bar is updated, the Progress Bar is hidden and the current file is selected.
Also in the wmId
switch, the next case is IDC_COMBO1
. The only event we respond to is CBN_EDITUPDATE
. We get the text of the EditCtrl
and check the first character, if it's a question mark, we blank out the field and set the CUE
Banner. The processing for IDC_COMBO0
is the same as for IDC_COMBO1
. IDC_BUTTON2
handles BM_CLICK
in the same way as IDC_BUTTON1
but calls the findStringInAllFiles
function instead of the recursivefileSearch
function.
The WM_SIE
message gets the position and size for all of the controls in the hFormView
container and moves them to their proper positions. The only problem child(window) was ComboBox2
. If I used the size from the GetWindowRect
function, it would sometimes show the combobox
button, and sometimes it wouldn't show it. In full screen, it always showed it but I don't think it would be reasonable to ask a user to go full screen when they wanted to click the drop down triangle. To make sure the drop down button was always displayed, I chose to make it a static value unless it was full screen, in which case I used the GetWindowRect
values.
The comboBoxProc Function
// Subclass the proc to handle vk_return to simulate button click.
// Input string2search4 and file list Extension Set. Persist environment
// variables on input invoke File list Build or Find string in all files.
INT_PTR CALLBACK comboBoxProc ( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) {
UNREFERENCED_PARAMETER ( lParam );
TCHAR searchStr [ 256 ] = { 0 };
int wmId, wmEvent;
static HWND hwndEdit1, hwndEdit2;
HWND hEdit = NULL, hMainWnd = NULL, hCombo1 = NULL, hCombo2 = NULL, hProgress = NULL,
hStrtBild = NULL, hStrtSrch = NULL, hList = NULL;
RECT bRc1 = { 0 }, bRc2 = { 0 }, cRc1 = { 0 }, cRc2 = { 0 }, dRc = { 0 }, pRc = { 0 };
RECT bRccl1 = { 0 }, bRccl2 = { 0 }, cRccl1 = { 0 },
cRccl2 = { 0 }, dRccl = { 0 }, pRccl = { 0 };
if ( WM_INITDIALOG != message ) {
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
if ( 0 == hMainWnd ) { return 0;}
hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
if ( 0 == hEdit ) { return 0; }
hCombo1 = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_COMBO1 );
if ( 0 == hCombo1 ) {return 0; }
hCombo2 = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_COMBO2 );
if ( 0 == hCombo2 ) { return 0;}
hProgress = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_PROGRESS1 );
if ( 0 == hProgress ) { return 0;}
}
switch ( message ) {
case WM_INITDIALOG:
{
hCombo1 = getThisWindowHandle ( hDlg, IDC_COMBO1 );
if ( 0 != hCombo1 )
{
hStrtBild = GetDlgItem ( hDlg, IDC_BUTTON1 );
hCombo1 = GetDlgItem ( hDlg, IDC_COMBO1 );
COMBOBOXINFO cbi = { 0 };
cbi.cbSize = { sizeof ( COMBOBOXINFO ) };
GetComboBoxInfo ( hCombo1, &cbi );
hwndEdit1 = cbi.hwndItem;
SetWindowSubclass ( hwndEdit1, comboBoxSubClassProc, ( UINT_PTR ) IDC_COMBO1, 0 );
DWORD dwErr = GetLastError ( );
// Add strings to combobox.
TCHAR szComboText [ ] [ 64 ] = { L "*.cpp;*.c;*.cs;*.fs;*.fsx;*.htm;*.html;*.xaml;
*.xml ",L "*.cs ",L "*.vb ",L "*.c ",
L"*.fsx;*.fs",L"*.pdf",L"*.txt" };
int nItemCount = 7;
for ( int i = 0; i < nItemCount; i++ ) {
SendMessage ( hCombo1, ( UINT ) CB_ADDSTRING,
( WPARAM ) 0, ( LPARAM ) szComboText [ i ] );
dwErr = GetLastError ( );
}
// set the CUE Banner for the ComboBox
SendMessage ( hCombo1, ( UINT ) CB_SETCUEBANNER, ( WPARAM ) 0,
( LPARAM ) L "Type any part of filename or
select extension from the drop down list below. " );
dwErr = GetLastError ( );
hStrtSrch = GetDlgItem ( hDlg, IDC_BUTTON2 );
dwErr = GetLastError ( );
hCombo2 = GetDlgItem ( hDlg, IDC_COMBO2 );
GetComboBoxInfo ( hCombo2, &cbi );
hwndEdit2 = cbi.hwndItem;
SetWindowSubclass ( hwndEdit2, comboBoxSubClassProc, ( UINT_PTR ) IDC_COMBO2, 0 );
dwErr = GetLastError ( );
// set the CUE Banner for the ComboBox
//SendMessage ( hCombo2, ( UINT ) CB_SETEXTENDEDUI, ( WPARAM ) 0, 0 );
SendMessage ( hCombo2, ( UINT ) CB_SETCUEBANNER, ( WPARAM ) 0,
( LPARAM ) L "Type a word to search for in the list of files shown below. " );
dwErr = GetLastError ( );
hProgress = GetDlgItem ( hDlg, IDC_PROGRESS1 );
ShowWindow ( hProgress, SW_HIDE );
GetWindowRect ( hDlg, &dRc );
MoveWindow ( hDlg, 0, 10, dRc.right, 30, TRUE );
MoveWindow ( hStrtBild, 10, 0, 104, 23, TRUE );
MoveWindow ( hCombo1, 120, 0, 410, 23, TRUE );
MoveWindow ( hStrtSrch, 540, 0, 104, 23, TRUE );
MoveWindow ( hCombo2, 650, 0, dRc.right-650, 23, TRUE );
MoveWindow ( hProgress, 0, 0, dRc.right, dRc.bottom, TRUE );
return FALSE;
}
}
break;
case WM_COMMAND:
wmId = LOWORD ( wParam );
wmEvent = HIWORD ( wParam );
switch ( wmId ) {
case IDC_BUTTON1:
{
TCHAR ListItem [ 256 ];
TCHAR searchCap [ 256 ];
TCHAR nodeCD [ 256 ];
TCHAR * tStr;
TCHAR cStr [ 255 ]; tStr = cStr;
LRESULT itemIndex = SendMessage ( hCombo1,
( UINT ) CB_GETCURSEL, ( WPARAM ) 0, ( LPARAM ) 0 );
if ( itemIndex < 0 )
if ( ComboBox_GetText ( hCombo1, ListItem, 255 ) )
itemIndex = SendMessage ( hCombo1,
( UINT ) CB_ADDSTRING, ( WPARAM ) 0, ( LPARAM ) ListItem );
else {
if (!*searchStr ) {
GetEnvironmentVariable ( L "SEARCHSTR ", searchStr,255 );
ComboBox_SetText ( hCombo1, searchStr );
}
return 0;
}
SendMessage ( hCombo1, ( UINT ) CB_SETCURSEL, ( WPARAM ) itemIndex, ( LPARAM ) 0 );
SendMessage ( hCombo1, ( UINT ) CB_GETLBTEXT,
( WPARAM ) itemIndex, ( LPARAM ) ListItem );
GetEnvironmentVariable ( L "SEARCHSTR ", searchStr,255 );
if ( wcscmp(ListItem, searchStr)!=0 ) {
StringCchPrintf ( searchStr, 255, L "%s ", ListItem );
SetEnvironmentVariable ( L "SEARCHSTR ", searchStr );
StringCchPrintf ( cStr, 255, L " /k Setx SEARCHSTR \ "%s\ " ", searchStr );
persist_This ( tStr );
}
GetCurrentDirectory ( 255, nodeCD );
StringCchPrintf ( searchCap, 255, L "Searching --- %s ", nodeCD );
SetWindowText ( hMainWnd, searchCap );
hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 );
if ( 0 == hList ) {
return 0;
}
ShowWindow ( hProgress, SW_NORMAL );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_NORMAL, 0 );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_STATUS );
StringCchPrintf ( tStr, MAX_PATH, L "Searching for %s in ", searchStr );
SendMessage ( hwndStatus, SB_SETTEXT, 0, ( LPARAM ) tStr );
SendMessage ( hwndStatus, SB_SETTEXT, 1 | SBT_POPOUT, ( LPARAM ) nodeCD );
recursivefileSearch ( nodeCD, FALSE );
SendMessage ( hwndStatus, SB_SETTEXT, 0, ( LPARAM ) nodeCD );
int iCount = ListView_GetItemCount ( hList );
int result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
StringCchPrintf ( tStr, MAX_PATH, L "Number of Files %d. ", iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 1 | SBT_POPOUT, ( LPARAM ) tStr );
StringCchPrintf ( tStr, MAX_PATH, L "%d of %d Files. ", result + 1, iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) tStr );
SetFocus ( hList );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_PAUSED - 3, 0 );
ShowWindow ( hProgress, SW_HIDE );
SendMessage ( hwndStatus, SB_SETTEXT,
3 | SBT_POPOUT, ( LPARAM ) L "Line: 0 Column: 0 " );
return FALSE;
}
break;
case IDC_COMBO1:
{
hCombo1 = ( HWND ) lParam;
if ( wmEvent == CBN_EDITUPDATE ) {
TCHAR ListItem [ 256 ];
ComboBox_GetText ( hCombo1, ListItem, 255 );
if ( *ListItem == L '? ' ) {
SetWindowText ( hEdit, L " " );
// set the CUE Banner for the ComboBox
Edit_SetCueBannerText ( hEdit,
( LPARAM ) L "Type last part of filename in the ComboBox or
select an extension from the drop down list below the ComboBox. " );
}
}
}
break;
case IDC_BUTTON2:
{
TCHAR ListItem [ 256 ];
TCHAR fsiaf [ 256 ] = { 0 };
TCHAR searchCap [ 256 ];
TCHAR nodeCD [ 256 ];
TCHAR * tStr;
TCHAR cStr [ 255 ]; tStr = cStr;
LRESULT itemIndex = SendMessage
( hCombo2, ( UINT ) CB_GETCURSEL, ( WPARAM ) 0, ( LPARAM ) 0 );
if ( itemIndex == CB_ERR )
if ( ComboBox_GetText ( hCombo2, ListItem, 255 ) )
itemIndex = SendMessage
( hCombo2, ( UINT ) CB_ADDSTRING, ( WPARAM ) 0, ( LPARAM ) ListItem );
else {
if ( !*fsiaf ) {
GetEnvironmentVariable ( L "FSIAF ", fsiaf, 255 );
ComboBox_SetText ( hCombo2, fsiaf );
}
return 0;
}
SendMessage ( hCombo2, ( UINT ) CB_SETCURSEL, ( WPARAM ) itemIndex, ( LPARAM ) 0 );
SendMessage ( hCombo2, ( UINT ) CB_GETLBTEXT, ( WPARAM ) itemIndex, ( LPARAM ) ListItem );
GetEnvironmentVariable ( L "FSIAF ", fsiaf, 255 );
if ( wcscmp(ListItem,fsiaf)!=0 ) {
StringCchPrintf ( fsiaf, 255, L "%s ", ListItem );
SetEnvironmentVariable ( L "FSIAF ", fsiaf );
StringCchPrintf ( cStr, 255, L " /k Setx FSIAF \ "%s\ " ", fsiaf );
persist_This ( tStr );
}
GetCurrentDirectory ( 255, nodeCD );
StringCchPrintf ( searchCap, 255, L "Searching --- %s ", nodeCD );
SetWindowText ( hMainWnd, searchCap );
hList = getThisWindowHandle ( hMainWnd, IDC_LIST1 );
int result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
int searchStartRow = result;
ShowWindow ( hProgress, SW_NORMAL );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_NORMAL, 0 );
findStringInAllFiles ( );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_PAUSED - 3, 0 );
ShowWindow ( hProgress, SW_HIDE );
HWND hRich = getThisWindowHandle ( hMainWnd, IDC_EDIT1 );
#pragma region select_first_file_with_a_match
if ( hRich && IsWindowVisible ( hRich ) ) {
result = searchStartRow - 1;
if ( searchStartRow == 0 ) {
result++;
}
ListView_SetItemState
( hList, -1, 0, LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState
( hList, result, LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_EnsureVisible ( hList, result, TRUE );
return 0;
}
#pragma endregion and make sure it is visible
}
break;
case IDC_COMBO2:
{
hCombo2 = ( HWND ) lParam;
if ( wmEvent == CBN_EDITUPDATE ) {
TCHAR ListItem [ 256 ];
ComboBox_GetText ( hCombo2, ListItem, 255 );
if ( *ListItem == L '? ' ) {
SetWindowText ( hEdit, L " " );
// set the CUE Banner for the ComboBox
Edit_SetCueBannerText ( hEdit,
( LPARAM ) L "Type a word to search for in the list of files
shown in the ListView below. " );
}
}
}
break;
default:
break;
}
break;
case WM_SIZE:
{
hStrtBild = getThisWindowHandle ( hDlg, IDC_BUTTON1 );
hStrtSrch = getThisWindowHandle ( hDlg, IDC_BUTTON2 );
GetWindowRect ( hDlg, &dRc );
GetClientRect ( hDlg, &dRccl );
GetWindowRect ( hStrtBild, &bRc1 );
GetClientRect ( hStrtBild, &bRccl1 );
GetWindowRect ( hStrtSrch, &bRc2 );
GetClientRect ( hStrtSrch, &bRccl2 );
GetWindowRect ( hCombo1, &cRc1 );
GetClientRect ( hCombo1, &cRccl1 );
GetWindowRect ( hCombo2, &cRc2 );
GetClientRect ( hCombo2, &cRccl2 );
GetWindowRect ( hProgress, &pRc );
GetClientRect ( hProgress, &pRccl );
OffsetRect ( &bRc1, -dRc.left, -dRc.top );
OffsetRect ( &cRc1, -dRc.left, -dRc.top );
OffsetRect ( &bRc2, -dRc.left, -dRc.top );
OffsetRect ( &cRc2, -dRc.left, -dRc.top );
MoveWindow ( hStrtBild, bRc1.left, bRc1.top, bRc1.right-bRc1.left, bRc1.bottom, TRUE );
MoveWindow ( hCombo1, cRc1.left, cRc1.top, cRc1.right-cRc1.left, cRc1.bottom, TRUE );
MoveWindow ( hStrtSrch, bRc2.left, bRc2.top, bRc2.right-bRc2.left, bRc2.bottom, TRUE );
HWND dtHwnd = GetDesktopWindow ( );//MoveWindow
// ( hCombo2, cRc2.left, cRc2.top, dRc.right - 720, cRc2.bottom, TRUE );
RECT dtRc; GetWindowRect ( dtHwnd, &dtRc );//
if ( dtRc.right <= dRc.right ) {
MoveWindow ( hCombo2, cRc2.left, cRc2.top, dRc.right - cRc2.left, cRc2.bottom, TRUE );
}
else
MoveWindow ( hCombo2, cRc2.left, cRc2.top, 350, cRc2.bottom, TRUE );
MoveWindow ( hProgress, 0, 0, pRccl.right, pRccl.bottom, TRUE );
return 0;
}break;
}
return ( INT_PTR ) FALSE;
}
Using the Viewer Panel: richEditProc
In the WM_INITDIALOG
message handler, the RichEdit
Control is in a window, hFormView3
which is used for sizing of the RichEdit
Control. The window hFormView3
is in a window, hBottom
, that is used for sizing hFormView1
, hFormView2
, hForView3
and hMiddle
. To prevent RichEdit
from being created in hBottom
but not in hFormview3
, we get the handles for the main window and hDlg
. Because hDlg
can be hBottom
or hFormView3, we differentiate between them by getting the parent of hDlg
. If that is the main window, we return 0
. We then make sure there is no RichEdit
before we load the "MSFTEDIT
" DLL which contains RICHEDIT50W
, which is used to create the RichEdit
Control. Next, we send RichEdit
an EM_SETEVENTMASK
telling it we want key events, mouse events and requestresize
. Then the zoom ratio is set so that the user can zoom the text font with the mouse wheel and ctrl key.
In the WM_THEMECHANGED
message handler, we need to correct a very bad(for me) color combination when the theme is changed during program execution. The other controls change their colors to an acceptable combination, but RichEdit50w
does not. We send it an EM_SETBKGNDCOLOR
with the wParam
set to one, telling it to set the color to the system color. With the wParam
set to 1
, it ignores the color you send it but you still have to send it a color. I don't know if this happens with other versions of RichEdit
or with other controls.
In the WM_NOTIFY
message handler, for the pMsgFilter
member msg equal to WM_KEYUP
, we get the position of the CARET
or if there is a selection, the first character of the selection. This is translated into the Line Number by the EM_EXLINEFROMCHAR
and EM_LINEINDEX
messages and the Column
by subtracting the beginning of the line from the CARET
position. Then the status bar is updated with the Line number and Column.
The member msg WM_LBUTTONDOWN
handler performs the same processing as WM_KEYUP
, that is getting the Line Number and Column for the status bar, But with WM_LBUTTONDOWN
, we also get the mouse position, convert it from Client coordinates to screen coordinates and save it in a static Point
variable named 'rpt
'. If the left button was clicked on a word, this Point may be used to select the word programmatically.
This is handled when pMsgFilter
member msg is WM_KEYDOWN
and wParam
is 'f
' and the control key is down. Again, we use EM_GETSEL
. This time, we compare the startsel
and endsel
to determine if we have a selection. If they are, we don't so we send a mouse double click to the Point
'rpt
'. When RichEdit
receives a mouse double click, it selects the word under the mouse. Then we send an 'f
' to RichEdit
which causes the same block of code to be executed because the control key is still down. This time there is a selection, so the 'else
' block is executed. Now an EM_GETSELTEXT
message is sent to RichEdit
and the text is copied to 'fsiaf
' and the Environment Variable is set. The value is also put into ComboBox2
. The word in RichEdit
is then highlighted.
The message handler for pMsgFilter
member msg
equals WM_KEYDOWN
and wParam
is VK_F3(F3)
and the static
variable keyshifted is set, we set the table entry keyst[VK_SHIFT]
to 0x80
, Set the Keyboard State, copying keyst
to the system table. This is done to prevent RichEdit
from capitalizing the word we are searching for.
The message handler for pMsgFilter
member msg
equals WM_KEYDOWN
and wParam
is VK_F3(F3)
and there is nothing in 'fsiaf
' the user may have left double clicked and pushed F3. Here, we get the selection and copy it into 'fsiaf
'. Now we determine if the SHIFT Key is down and if it is, we get the keyboard state table and put it in keyst
. Setting the keyst
entry for VK_SHIFT
to zero, we set the keyboard state table to keyst
and set the static
variable keyshifted to true
. Then we find the previous occurrence of the word and highlight it. If the SHIFT Key is not down, we simply find the next occurrence and highlight it. If nothing is found, then a VK_F5
is sent to the ListView
requesting the next file containing a match be found. VK_F17
is sent if the SHIFT Key is down. If the find
function was successful, we place the CARET in the middle of the screen. This requires we get the first character of the line containing the selection, chline
, the first visible line, fvline
. Scroll down a page to get the page size, scroll backup a page, divide page size by 2 and add it to fvline
, subtract that from chline
to find the number of lines to scroll. You also have the information to update the status bar. Piece of cake, piece of candy!
The message handler for pMsgFilter
member msg
equals WM_KEYDOWN
and wParam
is VK_F6(F6)
is provided as an alternative to the shift F3 processing. It always finds previous and when it fails, it send a VK_F17
to the ListView
to find the previous file containing a match. When it succeeds, the CARET
is moved to the middle of the screen and the status bar is updated.
The message handler for pMsgFilter
member msg equals WM_KEYDOWN
and wParam
is VK_F5(F5)
will simply send the key to ListView
.
The message handler for when pMsgFilter
member msg
is EN_REQUESTRESIZE
, if it gives me a size request, I'll give it the room.
The richEditProc Function
// The richEditProc offers the user a Viewer where the context of a code snippet
// can examined to assist in understanding the concept of it 's usage. The find
// function in the Viewer is the built-in RichEdit find function, however, the
// find function for the file search is U2Charfunc - an Exact Match algorithm.
// This means that it will miss some apparent matches and RichEdit will find more
// of the string than is indicated in the OCCURS Column of the ListView. For some
// languages, this could be undesirable but for 'C/C++ ' it filters out non-keywords
// in the file search but the point is to speed-up the process. RICHEDIT50W in MSFTEDIT.DLL
// has a feature that CAPITALIZES the first letter of a word
// when the user types shift plus VK_F3.
// Common convention is to use this combination for 'find previous '.
// To provide this functionality without the capitalization
// it is necessary to manipulate the KEYSTATE Table to trick RichEdit.
// To extend 'find previous ' to the file search, richEditProc
// sends VK_17( which is shift + F5) to the ListView.
// The RichEdit Control is READ/WRITE to allow the user to type in a 'string to find '
// then select it and press Control plus 'F ' to begin a search,
// instead of using the Combobox.
// The richEditProc adds the string to the Combobox Dropdown List and
// saves it in the Environment
// Variables. It does not save the full list, only the last string entered.
// The richEditProc also positions the Caret(EM_LINESCROLL)
// in the middle of the screen to reduce Eye-Strain. This Version
// of RichEdit appears to not repaint the client area when the THEME is changed by the user.
// This can result in a less than optimum color combination.
// We send it a EM_SETBKGNDCOLOR with a wParam value
// of 1 to tell it to repaint the client area with the system(THEME) color.
INT_PTR CALLBACK richEditProc ( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) {
UNREFERENCED_PARAMETER ( lParam );
int wmId, wmEvent;
static POINT rpt = { 0, 0 };
static BOOL keyshifted = FALSE;
LONG intRc = 0;
BYTE keyst [ 256 ] = { 0 };
static HTHEME thme = NULL;
HWND hMainWnd = NULL, hRich = NULL, hTree = NULL, hList = NULL,
hEdit = NULL, hFormView3 = NULL;
if ( message != WM_INITDIALOG ) {
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
hRich = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT1 );
}
switch ( message ) {
case WM_INITDIALOG:
{
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hBottom = GetAncestor ( hDlg, GA_PARENT );
if ( hMainWnd == hBottom ) {
return 0;
}
hRich = FindWindowEx ( hDlg, 0, L "RICHEDIT50W ", 0 );
if ( hRich == 0 ) {
RECT rc = { 0, 0, 380, 380 };
GetClientRect ( hMainWnd, &rc );
HINSTANCE hLib; // for Rich Edit
hLib = LoadLibrary ( L "Msftedit.dll " );
hRich = CreateWindowExW ( WS_EX_CLIENTEDGE, // extended styles
L "RICHEDIT50W ", //control 'class ' name
L " ", //control caption
//control style
WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL |
ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE |
ES_NOHIDESEL | ES_WANTRETURN,
rc.left, //position: left
rc.top, //position: top
rc.right, //width
rc.bottom, //height
hDlg, //parent window handle
//control 's ID
( HMENU ) ( IDC_EDIT1 ),
hInst, //application instance
0 ); //user defined info
SendMessage ( hRich, EM_SHOWSCROLLBAR, SB_VERT, TRUE );
ShowWindow ( hRich, SW_SHOW );
SendMessage ( hRich, EM_SETEVENTMASK, 0, ENM_KEYEVENTS |
ENM_REQUESTRESIZE | ENM_MOUSEEVENTS );
SendMessage ( hRich, EM_SHOWSCROLLBAR, SB_VERT, TRUE );
SendMessage ( hRich, EM_SETZOOM, 3, 4 );
SendMessage ( hRich, EM_SETEDITSTYLEEX, SES_EX_NOACETATESELECTION,
SES_EX_NOACETATESELECTION );
ShowWindow ( hFormView3, SW_SHOW );
return ( INT_PTR ) TRUE;
}
}
case WM_THEMECHANGED:
{
// RICHEDIT does not change the background color when HIGH CONTRAST
// is turned on . Turn it on progmattically using the new background color
// by setting wparam to any non zero value. The hex lparam value is ignored.
SendMessage ( hRich, EM_SETBKGNDCOLOR, 1, 0x008888ff );
return ( HRESULT ) FALSE;
}
case WM_NOTIFY:
{
wmId = LOWORD ( wParam );
wmEvent = HIWORD ( wParam );
TCHAR fsiaf [ 256 ] = { 0 }; // Find this String In A File
GetEnvironmentVariable ( L "FSIAF ", fsiaf, 255 );
FINDTEXTEXW fndFrst;
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 );
hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
hRich = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT1 );
hTree = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_TREE1 );
hFormView3 = hDlg;
switch ( wmId ) {
case IDC_EDIT1:
{
hRich = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT1 );
MSGFILTER *pMsgFilter = ( MSGFILTER * ) lParam;
if ( pMsgFilter->msg == WM_KEYUP ) {
TCHAR Text [ 256 ] = { 0 };
DWORD dwSelStart = 0, dwSelEnd = 0;
SendMessage ( hRich, EM_GETSEL, ( WPARAM ) &dwSelStart, ( LPARAM ) &dwSelEnd );
int chLine = ( int ) SendMessage ( hRich, EM_EXLINEFROMCHAR, 0, dwSelStart );
LONG strtLine = ( LONG ) SendMessage ( hRich, EM_LINEINDEX, chLine, 0 );
LONG colPos = dwSelStart - strtLine;
StringCchPrintf ( Text, 255, L "Line: %d Column: %d ", chLine, colPos );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT, ( LPARAM ) Text );
}
if ( pMsgFilter->msg == WM_LBUTTONDOWN && pMsgFilter->wParam == MK_LBUTTON ) {
#pragma region user_clicked_somewhere_in_richedit
DWORD lbd = ( DWORD ) pMsgFilter->lParam;// NOTE this is the lParam of the lParam
rpt.x = GET_X_LPARAM ( lbd );
rpt.y = GET_Y_LPARAM ( lbd );
ClientToScreen ( hRich, &rpt );
TCHAR Text [ 256 ] = { 0 };
DWORD dwSelStart = 0, dwSelEnd = 0;
SendMessage ( hRich, EM_GETSEL, ( WPARAM ) &dwSelStart, ( LPARAM ) &dwSelEnd );
int chLine = ( int ) SendMessage ( hRich, EM_EXLINEFROMCHAR, 0, dwSelStart );
LONG strtLine = ( LONG ) SendMessage ( hRich, EM_LINEINDEX, chLine, 0 );
LONG colPos = dwSelStart - strtLine;
StringCchPrintf ( Text, 255, L "Line: %d Column: %d ", chLine, colPos );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT, ( LPARAM ) Text );
return 0;
#pragma endregion save mouse position for control 'f '
}
if ( pMsgFilter->msg == WM_KEYDOWN && pMsgFilter->wParam == 0x46 ) {
#pragma region control-f_was_pressed_by_ user
short nVirtKey = GetKeyState ( VK_CONTROL );
if ( nVirtKey & 0x8000 ) {
CHARRANGE cr;
DWORD dwSelStart = 0, dwSelEnd = 0;
SendMessage ( hRich, EM_GETSEL, ( WPARAM ) &dwSelStart, ( LPARAM ) &dwSelEnd );
if ( dwSelStart == dwSelEnd ) {
SetCursorPos ( rpt.x, rpt.y );
INPUT lbtnPr;
lbtnPr.type = INPUT_MOUSE;
lbtnPr.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE;
lbtnPr.mi.dx = rpt.x;
lbtnPr.mi.dy = rpt.y;
SendInput ( 1, &lbtnPr, sizeof ( INPUT ) );
lbtnPr.mi.dwFlags = MOUSEEVENTF_LEFTUP;
SendInput ( 1, &lbtnPr, sizeof ( INPUT ) );
lbtnPr.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
SendInput ( 1, &lbtnPr, sizeof ( INPUT ) );
lbtnPr.mi.dwFlags = MOUSEEVENTF_LEFTUP;
SendInput ( 1, &lbtnPr, sizeof ( INPUT ) );
INPUT keyPr;
keyPr.type = INPUT_KEYBOARD;
keyPr.ki.dwFlags = 0; // KEYEVENTF_KEYDOWN?;
keyPr.ki.wScan = 0;
keyPr.ki.time = 0;
keyPr.ki.dwExtraInfo = 0;
keyPr.ki.wVk = 0x46;
SendInput ( 1, &keyPr, sizeof ( INPUT ) );
keyPr.ki.dwFlags = KEYEVENTF_KEYUP;
SendInput ( 1, &keyPr, sizeof ( INPUT ) );
}
else {
cr.cpMin = dwSelStart;
cr.cpMax = dwSelEnd;
intRc = dwSelStart;
SendMessage ( hRich, EM_GETSELTEXT, 0, ( LPARAM ) fsiaf );
SetEnvironmentVariable ( L "FSIAF ", fsiaf );
TCHAR ListItem [ 256 ];
StringCchPrintf ( ListItem, 255, L "%s ", fsiaf );
HWND hCombo2 = getThisWindowHandle ( hMainWnd, IDC_COMBO2 );
ComboBox_SetCurSel ( hCombo2, -1 );
ComboBox_SetText ( hCombo2, ListItem );
LRESULT itemIndex = SendMessage ( hCombo2, ( UINT ) CB_GETCURSEL,
( WPARAM ) 0, ( LPARAM ) 0 );
if ( itemIndex < 0 )
if ( ComboBox_GetText ( hCombo2, ListItem, 255 ) )
itemIndex = SendMessage ( hCombo2, ( UINT ) CB_ADDSTRING,
( WPARAM ) 0, ( LPARAM ) ListItem );
else
itemIndex = 0;
SendMessage ( hCombo2, ( UINT ) CB_SETCURSEL,
( WPARAM ) itemIndex, ( LPARAM ) 0 );
SendMessage ( hCombo2, ( UINT ) CB_GETLBTEXT,
( WPARAM ) itemIndex, ( LPARAM ) ListItem );
CHARFORMAT cf;
cf.cbSize = sizeof ( cf );
cf.dwMask = CFM_BOLD | CFM_UNDERLINE;
cf.dwEffects = CFE_BOLD | CFE_UNDERLINE;
SendMessage ( hRich, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM ) &cf );
DWORD dwerr = GetLastError ( );
dwerr = GetLastError ( );
}
}
return TRUE;
#pragma endregion send double click and get text for search string
}
if ( pMsgFilter->msg == WM_KEYUP && pMsgFilter->wParam == VK_F3 ) {
#pragma region vk_f3-released
if ( keyshifted ) {
keyst [ VK_SHIFT ] = 0x80;//keysh [ VK_SHIFT ];
SetKeyboardState ( keyst );
}
#pragma endregion so release shift key
}
// Richedit will capitalize an uncapitalized word, capitalize the
// the entire word if the first letter is a capital and if the
// entire word is all caps it will make them all lower case when
// the key combination is VK_F3 and VK_SHIFT. To prevent this from happening
// the user can hold down the control key, VK_CONTROL. To prevent it
// in code, one way is to copy the keyboard state table and unset
// the VK_SHIFT entry. When Richedit processes the VK_F3 message it
// will be unshifted but the previous occurrence has been found
if ( pMsgFilter->msg == WM_KEYDOWN && pMsgFilter->wParam == VK_F3 ) {
if ( !*fsiaf ) {
#pragma region select_marked_text
CHARRANGE cr;
DWORD dwSelStart = 0, dwSelEnd = 0;
cr.cpMin = dwSelStart; cr.cpMax = dwSelEnd;
SendMessage ( hRich, EM_GETSELTEXT, 0, ( LPARAM ) fsiaf );
CHARFORMAT cf;
cf.cbSize = sizeof ( cf );
cf.dwMask = CFM_BOLD | CFM_UNDERLINE;
cf.dwEffects = CFE_BOLD | CFE_UNDERLINE;
SendMessage ( hRich, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM ) &cf );
DWORD dwerr = GetLastError ( );
dwerr = GetLastError ( );
#pragma endregion if search string is blank and text is high-lighted
}
short nVirtKeySh = GetKeyState ( VK_SHIFT );
short nVirtKey = GetKeyState ( VK_CONTROL );
WPARAM wp = 1;
if ( nVirtKeySh & 0x8000 || nVirtKey & 0x8000 ) {
#pragma region find_backwards
if ( GetKeyboardState ( keyst ) == TRUE ) {
#pragma region prevent_richedit_from _changing_case
keyst [ VK_SHIFT ] = 0;
SetKeyboardState ( keyst );
keyshifted = TRUE;
#pragma endregion along with find previous
wp = 0;
DWORD dwSelStart = 0, dwSelEnd = 0;
SendMessage ( hRich, EM_GETSEL, ( WPARAM ) &dwSelStart, ( LPARAM ) &dwSelEnd );
fndFrst.chrg.cpMin = dwSelStart - 1;
fndFrst.chrg.cpMax = intRc;
fndFrst.lpstrText = fsiaf;
intRc = ( LONG ) SendMessage ( hRich, EM_FINDTEXTEXW,
( WPARAM ) wp, ( LPARAM ) &fndFrst );
CHARFORMAT cf;
cf.cbSize = sizeof ( cf );
cf.dwMask = CFM_BOLD | CFM_UNDERLINE;
cf.dwEffects = CFE_BOLD | CFE_UNDERLINE;
SendMessage ( hRich, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM ) &cf );
DWORD dwerr = GetLastError ( );
dwerr = GetLastError ( );
}
else {
// can 't get keyboard state table
return FALSE;
}
#pragma endregion when vk_f3 is shifted
}
else {
#pragma region find-forward
keyshifted = FALSE;
DWORD dwSelStart = 0, dwSelEnd = 0;
SendMessage ( hRich, EM_GETSEL, ( WPARAM ) &dwSelStart, ( LPARAM ) &dwSelEnd );
fndFrst.chrg.cpMin = dwSelEnd + 1;
fndFrst.chrg.cpMax = -1;
fndFrst.lpstrText = fsiaf;
intRc = ( LONG ) SendMessage ( hRich, EM_FINDTEXTEXW,
( WPARAM ) wp, ( LPARAM ) &fndFrst );
CHARFORMAT cf;
cf.cbSize = sizeof ( cf );
cf.dwMask = CFM_BOLD | CFM_UNDERLINE;
cf.dwEffects = CFE_BOLD | CFE_UNDERLINE;
SendMessage ( hRich, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM ) &cf );
DWORD dwerr = GetLastError ( );
dwerr = GetLastError ( );
#pragma endregion when vk_f3 is not shifted
}
if ( intRc == -1 ) {
#pragma region string_not_found
if ( wp ) {
return sendMeMyKey ( hList, VK_F5 );
}
else {
if ( keyshifted ) {
keyst [ VK_SHIFT ] = 0x80;//keysh [ VK_SHIFT ];
SetKeyboardState ( keyst );
}
return sendMeMyKey ( hList, VK_F17 );
}
#pragma endregion so send request to listview
}
else {
#pragma region put_caret_in_middle of screen
SendMessage ( hRich, EM_SETSEL, fndFrst.chrgText.cpMin, fndFrst.chrgText.cpMax );
SendMessage ( hRich, EM_SCROLLCARET, 0, 0 );
int chLine = ( int ) SendMessage ( hRich, EM_EXLINEFROMCHAR, 0, intRc );
int fvLine = ( int ) SendMessage ( hRich, EM_GETFIRSTVISIBLELINE, 0, 0 );
DWORD lvLine = ( DWORD ) SendMessage ( hRich, EM_SCROLL, SB_PAGEDOWN, 0 );
WORD pgSize = LOWORD ( lvLine );
lvLine = ( DWORD ) SendMessage ( hRich, EM_SCROLL, SB_PAGEUP, 0 );
DWORD desiredPos = pgSize / 2 + fvLine;
int noffSet = chLine - desiredPos;
SendMessage ( hRich, EM_LINESCROLL, 0, noffSet );
SendMessage ( hRich, EM_SCROLLCARET, 0, 0 );
TCHAR Text [ 255 ] = { 0 };
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
LONG strtLine = ( LONG ) SendMessage ( hRich, EM_LINEINDEX, chLine, 0 );
LONG colPos = intRc - strtLine;
StringCchPrintf ( Text, 255, L "Line: %d Column: %d ", chLine, colPos );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT, ( LPARAM ) Text );
intRc = fndFrst.chrgText.cpMax + 1;
#pragma endregion to reduce visual fatigue
}
return -1;
}
if ( pMsgFilter->msg == WM_KEYDOWN && pMsgFilter->wParam == VK_F6 ) {
#pragma region find_previous_unshifted
#pragma region find_previous_unshifted
WPARAM wp = 0;
DWORD dwSelStart = 0, dwSelEnd = 0;
SendMessage ( hRich, EM_GETSEL, ( WPARAM ) &dwSelStart, ( LPARAM ) &dwSelEnd );
fndFrst.chrg.cpMin = dwSelStart - 1;
fndFrst.chrg.cpMax = intRc;
fndFrst.lpstrText = fsiaf;
intRc = ( LONG ) SendMessage ( hRich, EM_FINDTEXTEXW,
( WPARAM ) wp, ( LPARAM ) &fndFrst );
CHARFORMAT cf;
cf.cbSize = sizeof ( cf );
cf.dwMask = CFM_BOLD | CFM_UNDERLINE;
cf.dwEffects = CFE_BOLD | CFE_UNDERLINE;
SendMessage ( hRich, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM ) &cf );
DWORD dwerr = GetLastError ( );
dwerr = GetLastError ( );
#pragma endregion (just using vk_f6, no shift or control)
if ( intRc == -1 ) {
#pragma region when_not_found_backwards
return sendMeMyKey ( hList, VK_F17 );
#pragma endregion send vk_f5 to listview
}
else {
#pragma region put_the_caret_in_middle of screen
SendMessage ( hRich, EM_SETSEL, fndFrst.chrgText.cpMin, fndFrst.chrgText.cpMax );
SendMessage ( hRich, EM_SCROLLCARET, 0, 0 );
int chLine = ( int ) SendMessage ( hRich, EM_EXLINEFROMCHAR, 0, intRc );
int fvLine = ( int ) SendMessage ( hRich, EM_GETFIRSTVISIBLELINE, 0, 0 );
DWORD lvLine = ( DWORD ) SendMessage ( hRich, EM_SCROLL, SB_PAGEDOWN, 0 );
WORD pgSize = LOWORD ( lvLine );
lvLine = ( DWORD ) SendMessage ( hRich, EM_SCROLL, SB_PAGEUP, 0 );
DWORD desiredPos = pgSize / 2 + fvLine;
int noffSet = chLine - desiredPos;
SendMessage ( hRich, EM_LINESCROLL, 0, noffSet );
SendMessage ( hRich, EM_SCROLLCARET, 0, 0 );
intRc = fndFrst.chrgText.cpMax + 1;
#pragma endregion to reduce visual fatigue
}
return 1;
#pragma endregion (just using vk_f6, then center caret)
}
if ( pMsgFilter->msg == WM_KEYDOWN && pMsgFilter->wParam == VK_F5 ) {
#pragma region send_find_next_to_listview
return sendMeMyKey ( hList, VK_F5 );
#pragma endregion to find string in a file
}
if ( pMsgFilter->msg == EN_REQUESTRESIZE ) {
#pragma region Richedit_requested_more_space
REQRESIZE * resrc = ( REQRESIZE * ) lParam;
MoveWindow ( hRich, resrc->rc.left, resrc->rc.top, resrc->rc.right,
resrc->rc.bottom, TRUE );
return 0;
#pragma endregion lParam points to REQRESIZE structure with width and height
}
}
break;
}
return 0;
}
break;
case WM_NCCALCSIZE:
if ( wParam ) {
return WVR_ALIGNLEFT | WVR_ALIGNTOP | WVR_REDRAW;
}
else
return DefWindowProc ( hRich, message, wParam, lParam );
break;
case WM_SIZE:
{
#pragma region Richedit_parent_window_resized
UINT width = GET_X_LPARAM ( lParam );
UINT height = GET_Y_LPARAM ( lParam );
if ( wParam ) {
MoveWindow ( hRich, 0, 0, width, height, TRUE );
}
return 0;
#pragma endregion get size from richedit and make it so
}break;
case WM_ACTIVATE:
{
if ( LOWORD ( wParam ) & WA_ACTIVE ) {
SendMessage ( hRich, EM_SHOWSCROLLBAR, SB_VERT, TRUE );
if ( hRich ) {
SetFocus ( hRich );
return 0;
}
return 0;
}
else
return -1;
}break;
}
return ( INT_PTR ) FALSE;
}
Finding the Next or Previous File with a Match: The listViewProc Function
In the message switch, the WM_INITDIALOG
message is received for hBottom
and hFormView2
. If it's hBottom
, we just get out. If it's hFormView2
, a font is created for a larger text size. Then the ListView
Control is created and the Column Headers are put in. Having created the ListView
, a WM_SETFONT
message is sent to it to set the font we created earlier. Now for a word about hFormView2
. Looking in the Resource View, you can see that it has a RESIZING Border. This is used instead of Emulating a Splitter Bar. The RESIZING
Border can give you a 3 by 3 grid if you want it but we want a 1 by 3 row from that grid. In order to do this, the Top and Bottom Borders of hFormView2
must be dis-abled. The Cursor
is clipped to achieve this. The sizing of all of the controls in hBottom
is controlled by hFormView2
and is performed in the WM_SIZING
message handler. There is an issue with using the RESIZING
Border instead of a splitter Bar. The issue is the size of the RECTS
received by the size control messages. I know how to fix the issue properly but for now, I just use hard coded values to give them the proper sizes.
The WM_COMMAND
message is used to process the WM_SETFOCUS
message. When an item is selected, it is highlighted here if the ListView
receives focus but nothing is selected, the first item in ListView
is selected and highlighted.
In the WM_NOTIFY
message handler, we get the value of fsiaf
from the environment block. We also get all of the window handles we might need. In the switch
for the LPNMHDR lParam
member code, we handle the code for NM_CLICK
by getting the path of the selected file and loading it into RichEdit
and update the status bar unless it is larger than 64 megabytes, in which case we use the Open with Dialog to open the file in another application.
The code for NM_DBLCLK
is handled by opening the folder or file in File Explorer with the item selected.
The code for NM_RCLICK
is handled by invoking the Open With Dialog. It does not display a context menu.
The code for LVN_KEYDOWN
is handled by creating a pointer to a NMLVKEYDOWN
structure and calling it pnkd
and initializing it to the lParam
cast to a LPNMLVKEYDOWN
'. The pnkd
member wVKey
will contain the virtual key code that was typed.
The wVKey
code for VK_DOWN
is handled by moving down one and loading that file into RichEdit
unless it is larger 64 megabytes.
The wVKey
code for VK_TAB
is handled by setting the focus on the RichEdit
Control, simulating a tab key.
The wVKey
code for VK_UP
is handled by moving up one and loading that file into RichEdit
unless it is larger than 64 megabytes.
The wVKey
code for VK_F5
and VK_F17*SHIFT F5)
is handled by getting the text for the current item's second Column, the 'Number of Occurrences' to determine if it contains fsiaf
. If it does, it calls U2Charfunc
to make sure and highlights the file name in ListView
, then places the highlighted line in the middle of the visible list, loads the file into RichEdit
and the RichEdit
find function is used to place the RichEdit CARET
on the match and scroll it to the middle of the screen.
When a next or previous file cannot be found in the ListView
Occurs Column (determined by counting the number of misses until it equals or exceeds the number of rows in the List View), if the number of rows is greater, it displays a message box informing that the next step could take a while. Then it calls findStringInAllFiles
search the File List itself to populate the Occurs Column with the counts. Next, it finds the next file containing a match and loads it into RichEdit
, positioning the highlights in the middle of the screen. Note this will only happen when the user has entered the string
to search for in the RichEdit
Viewer Panel.
When the number of hits exceeds the number of rows in the ListView
, a message box is displayed to inform the user that NO FILES containing the Requested STRING
were found, and suggesting that they check the spelling.
The wVKey
code for VK_F3
and VK_F6
is handled by sending the keys to the RichEdit
proc.
The wVKey
code for VK_F4
is handled by displaying the Properties for the file.
NM_RETURN
is a WM_NOTIFY
message. We process it the same as a NM_DBLCLK
with the file location being opened in File Explorer.
NM_SETFOCUS
is a WM_NOTIFY
message. We process it the same as a WM_SETFOCUS
. If there is anything in the ListView
, we make sure it's highlighted.
For the message WM_NCLBUTTONUP
, we cancel the ClipCursor
by setting it to NULL
so that the cursor can move freely.
For the WM_SIZING
message, we get the drag rectangle from the lParam
. We also get all of the window handles, window rectangles and client rectangles, and offset the window rectangles to make their positions relative to the main window. Then we determine which border is being moved. If it's not the left or right, then we clip the cursor to prevent vertical movement. The same formula works for the top and bottom. The cursor is only allowed to move from the left border to the right border in either direction. For the right border, we clip to prevent the user from covering the RichEdit
Viewer Panel with the ListView
. Then we move hFormView3
, the container for the RichEdit
Control, to its new position and fill it with RichEdit
. If it is the left border, we clip the cursor to prevent the ListView
from covering the entire treeview
and move the treeview
to its new size and position. For both the left and right border, we move the ListView Control
into hFormView2
.
The listViewProc Function
// Message handler for ListView. Processes WM_NOTIFY for the following messages:
// NM_CLICK - select the current file and load it into RichEdit
// NM_RCLICK - select the current file and Invokes OpenWith Dialog
// NM_DBLCLK - opens selected file location in File Explorer with Mouse input
// NM_RETURN - opens selected file location in File Explorer with Keyboard input
// F3 is sent to RichEdit to find next string in the current file.
// Shift with F3 searches backwards
// F4 Displays Properties for current file
// F5 finds next fie containing string. Shift with F5 searches backwards
// F6 is sent to RichEdit to find previous string or
// last occurrence of string in current file
// F17 (shifted F5) is sent to ListView by Richedit when F6 reaches the begining of the file
// WM_NCLBUTTONUP will nullify the ClipCursor, thus freeing the Cursor to move freely
// WM_SIZING wil<span lang="en-us">l</span> re-size the TreeView control and RichEdit
// when User drags Left or Right sizing grip. If User attempts to drag
// the TOP or BOTTOM size grip the Cursor is Clipped preventing
// UP or Down movement. The Cursor is also clipped to
// prevent the user from moving the left or
// right side sizing grips too far, covering up the other windows and
// causing a loss of function.
INT_PTR CALLBACK listViewProc ( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
HDC hdc;
TCHAR curntFname [ 256 ];
TCHAR numbOfOccurs [ 256 ];
TCHAR curntPath [ MAX_PATH ];
TCHAR Text [ 256 ] = { 0 };
TCHAR text [ 256 ] = { 0 };
TCHAR teXt [ 256 ] = { 0 };
TCHAR tStr [ MAX_PATH ];
TCHAR searchCap [ 256 ]; // The Window Caption for Search Function
static int currentRow = 0;
CHAR chBuf [ 4096 ];
HWND hMainWnd = NULL, hTree = NULL, hList = NULL, hEdit = NULL,
hRich = NULL, hListView, hFormView2;
HWND hWndFocus = NULL;
switch ( message ) {
case WM_INITDIALOG:
{
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hBottom = GetAncestor ( hDlg, GA_PARENT );
if ( hMainWnd == hBottom ) {
return 0;
}
hList = FindWindowEx ( hDlg, 0, L "SysListView32 ", 0 );
if ( hList == 0 ) {
HFONT hFont = CreateFont ( 32, 10, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, ANSI_CHARSET, \
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
DEFAULT_PITCH | FF_SWISS, L "Arial " );
RECT sRc;
GetClientRect ( hDlg, &sRc );
hList = CreateWindowEx ( 0,
WC_LISTVIEW, NULL,
WS_CHILD | WS_VISIBLE | DS_3DLOOK | WS_BORDER |
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_TABSTOP |
WS_EX_OVERLAPPEDWINDOW | WS_EX_STATICEDGE |
WS_EX_COMPOSITED | WS_EX_NOINHERITLAYOUT |
LVS_REPORT | LVS_SHOWSELALWAYS,
sRc.left, sRc.top, sRc.right, sRc.bottom - 5, hDlg,
( HMENU ) IDC_LIST1, hInst, 0 );
LVCOLUMN lvCol;
memset ( &lvCol, 0, sizeof ( lvCol ) );
lvCol.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; // Type of mask
lvCol.pszText = ( LPWSTR ) L "Name ";
lvCol.cx = 0x96; // width of first column
ListView_InsertColumn ( hList, 0, &lvCol );
lvCol.pszText = ( LPWSTR ) L "Number of Occurrences ";
lvCol.cx = 0x96; // width of second column
ListView_InsertColumn ( hList, 1, &lvCol );
lvCol.pszText = ( LPWSTR ) L "Path ";
lvCol.cx = 0x64; // width of third column
ListView_InsertColumn ( hList, 2, &lvCol );
lvCol.pszText = ( LPWSTR ) L "File Size ";
lvCol.cx = 0x072; // width of fourth column
ListView_InsertColumn ( hList, 3, &lvCol );
SendMessage ( hList, WM_SETFONT, WPARAM ( hFont ), TRUE );
return 0;
}
}
break;
case WM_PAINT:
if ( hMainWnd ) {
hdc = BeginPaint ( hMainWnd, &ps );
EndPaint ( hMainWnd, &ps );
}
return 0;
break;
case WM_COMMAND:
{
switch ( HIWORD ( wParam ) ) {
case WM_SETFOCUS:
{
// if anything is in List make sure something is selected
if ( hList && ListView_GetItemCount ( hList ) >= 1 ) {
int selectedItem = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
if ( selectedItem == -1 ) {
// select first one
ListView_SetItemState ( hList, 0, LVIS_SELECTED, LVIS_SELECTED );
return 0;
}
else {
ListView_SetItemState ( hList, selectedItem, LVIS_SELECTED, LVIS_SELECTED );
return 0;
}
}
}
break;
}
}
break;
case WM_LBUTTONUP:
return 0;
break;
case WM_LBUTTONDOWN:
if ( hList ) {
hWndFocus = hList;
}
return 0;
break;
case WM_NOTIFY:
{
LONG intRc = 0;
TCHAR fsiaf [ 256 ] = { 0 }; // Find this String In A File
GetEnvironmentVariable ( L "FSIAF ", fsiaf, 255 );
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
if ( 0 == hMainWnd ) {
return 0;
}
hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 );
hTree = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_TREE1 );
if ( 0 == hList ) {
return 0;
}
hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
if ( 0 == hEdit ) { return 0;}
hRich = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT1 );
if ( 0 == hRich ) { return 0;}
hListView = GetParent ( hList );
hFormView2 = hListView;
switch ( ( ( LPNMHDR ) lParam )->code ) {
case NM_CLICK:
{
#pragma region mouse_left_ckick
// Left Click
NMLISTVIEW * pnmlv;
LVITEM lvi;
INT ret;
pnmlv = ( LPNMLISTVIEW ) lParam;
memset ( &lvi, 0, sizeof ( lvi ) );
ret = ( pnmlv )->iItem;
if ( ret == -1 ) {
ret = currentRow;
lvi.iItem = ret;
}
else {
currentRow = ret;
lvi.iItem = ( 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 = 2;
ListView_GetItem ( hList, &lvi );
wsprintf ( teXt, L "\ "%s\\%s\ " ", text, Text );
SetWindowText ( hEdit, teXt );
ListView_SetItemState ( hList, -1, 0, LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState ( hList, ret, LVIS_DROPHILITED, LVIS_DROPHILITED );
ListView_SetItemState ( hList, ret, LVIS_SELECTED, LVIS_SELECTED );
ListView_SetItemState ( hList, ret, LVIS_FOCUSED, LVIS_FOCUSED );
int result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
ListView_GetItemText ( hList, result, 3, text, MAX_PATH - 1 );
text [ 255 ] = 0;
int fileSize = _wtoi ( text );
ListView_GetItemText ( hList, result, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, result, 2, curntPath, MAX_PATH - 1 );
StringCchPrintf ( tStr, MAX_PATH, L "%s\\%s ", curntPath, curntFname );
SetWindowText ( hMainWnd, tStr );
if ( fileSize > 64 * 1024 * 1024 ) {
MessageBox ( hMainWnd, L "File Size is too much for me!
Can 't LOAD ", text, MB_OK | MB_ICONEXCLAMATION );
OPENASINFO opWith;
opWith.pcszFile = ( LPCTSTR ) &tStr;
opWith.pcszClass = NULL;
SHOpenWithDialog ( NULL, &opWith );
return 0;
}
FillRichEditFromFile ( hRich, ( LPCTSTR ) &tStr );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
int iCount = 0;
if ( hList ) iCount = ListView_GetItemCount ( hList );
StringCchPrintf ( Text, 255, L "%d of %d Files ", result + 1, iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) Text );
SetFocus ( hList );
#pragma endregion select item and load richedit
}break; // case NM_CLICK
case NM_DBLCLK:
{
#pragma region user_left_double_clicked_an_item
// Left Double Click
NMLISTVIEW * pnmlv;
LVITEM lvi;
int result = 0;
struct _stat64 buf;
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 = 2;
ListView_GetItem ( hList, &lvi );
wsprintf ( teXt, L "%s\\%s ", text, Text );
result = _wstat64 ( teXt, &buf );
#pragma endregion get the full path and attributes
if ( ( buf.st_mode & _S_IFDIR ) ) {
#pragma region for_a_directory
wsprintf ( teXt, L "\ "%s\\%s\ " ", text, Text );
SetWindowText ( hEdit, teXt );
ShellExecute ( NULL, L "explore ", teXt, NULL, NULL, SW_SHOWNORMAL );
#pragma endregion open it with file explorer
}
if ( result == 0 ) {
if ( ( buf.st_mode & _S_IFREG ) ) {
#pragma region not_a_directory_but_a_regular_file
wsprintf ( teXt, L "/select, \ "%s\\%s\ " ", text, Text );
SetWindowText ( hEdit, teXt );
ShellExecute ( NULL, L "open ", L "explorer.exe ", teXt, text, SW_SHOW );
#pragma endregion open it with current program association
}
}
}break; // case NM_DBLCLK
case NM_RCLICK:
{
#pragma region mouse_right_click
// Right Click
NMLISTVIEW * pnmlv;
LVITEM item;
pnmlv = ( LPNMLISTVIEW ) lParam;
memset ( &item, 0, sizeof ( item ) );
item.iItem = ( pnmlv )->iItem;
int result = item.iItem;
item.mask = LVIF_TEXT;
item.iSubItem = 0;
item.cchTextMax = 260;
item.pszText = Text;
ListView_GetItem ( hList, &item );
ListView_GetItemText ( hList, result, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, result, 2, curntPath, MAX_PATH - 1 );
StringCchPrintf ( tStr, MAX_PATH, L "%s\\%s ", curntPath, curntFname );
SetWindowText ( hMainWnd, tStr );
OPENASINFO opWith;
opWith.pcszFile = ( LPCTSTR ) &tStr;
opWith.pcszClass = NULL;
opWith.oaifInFlags = OAIF_EXEC;
SHOpenWithDialog ( NULL, &opWith );
return 0;
#pragma endregion get fullpath and call Open-With dialog
} break; // case NM_RCLICK
case LVN_KEYDOWN:
{
NMLVKEYDOWN * pnkd;
pnkd = ( LPNMLVKEYDOWN ) lParam;
if ( ( pnkd )->wVKey == VK_DOWN ) {
#pragma region select_next_item
LVITEM lvi = { 0 };
int result = 0;
int itemCount = 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;
ListView_GetItemText ( hList, result, 0, Text, MAX_PATH - 1 );
ListView_GetItemText ( hList, result, 2, text, MAX_PATH - 1 );
wsprintf ( teXt, L "%s\\%s ", text, Text );
SetWindowText ( hEdit, teXt );
SetWindowText ( hMainWnd, teXt );
ListView_SetItemState ( hList, -1, LVIS_SELECTED | LVIS_FOCUSED |
LVIS_DROPHILITED, LVIS_CUT | LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState ( hList, result, LVIS_FOCUSED, LVIS_CUT |
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
// select this one and scroll to it 's position
ListView_SetItemState ( hList, lvi.iItem, LVIS_SELECTED, LVIS_SELECTED );
ListView_EnsureVisible ( hList, lvi.iItem, TRUE );
currentRow = lvi.iItem;
ListView_GetItemText ( hList, currentRow, 3, curntFname, MAX_PATH - 1 );
curntFname [ 255 ] = 0;
int fileSize = _wtoi ( curntFname );
if ( fileSize > 64 * 1024 * 1024 ) {
MessageBox ( hMainWnd, L "File Size is too much for me!
Can 't LOAD ", curntFname, MB_OK | MB_ICONEXCLAMATION );
return 0;
}
ListView_GetItemText ( hList, currentRow, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, currentRow, 2, curntPath, MAX_PATH - 1 );
StringCchPrintf ( tStr, MAX_PATH, L "%s\\%s ", curntPath, curntFname );
SetWindowText ( hMainWnd, tStr );
SetWindowText ( hEdit, tStr );
FillRichEditFromFile ( hRich, ( LPCTSTR ) &tStr );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
int iCount = 0;
if ( hList ) iCount = ListView_GetItemCount ( hList );
StringCchPrintf ( Text, 255, L "%d of %d Files ", currentRow + 1, iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) Text );
SetFocus ( hList );
return 1;
#pragma endregion and load it into richedit
}
if ( ( pnkd )->wVKey == VK_TAB ) {
SetFocus ( hRich );
return 0;
}
if ( ( pnkd )->wVKey == VK_UP ) {
#pragma region select_previous_item
LVITEM lvi;
int result = 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;
ListView_GetItem ( hList, &lvi );
wsprintf ( teXt, L "%s\\%s ", text, Text );
SetWindowText ( hEdit, teXt );
ListView_SetItemState ( hList, -1, LVIS_SELECTED | LVIS_FOCUSED |
LVIS_DROPHILITED, LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED | LVIS_CUT);
// select this one and scroll to it 's position
ListView_SetItemState ( hList, lvi.iItem, LVIS_SELECTED, LVIS_SELECTED );
ListView_EnsureVisible ( hList, lvi.iItem, TRUE );
currentRow = lvi.iItem;
ListView_GetItemText ( hList, currentRow, 3, curntFname, MAX_PATH - 1 );
curntFname [ 255 ] = 0;
int fileSize = _wtoi ( curntFname );
if ( fileSize > 64 * 1024 * 1024 ) {
MessageBox ( hMainWnd, L "File Size is too much for me!
Can 't LOAD ", curntFname, MB_OK | MB_ICONEXCLAMATION );
return 0;
}
ListView_GetItemText ( hList, currentRow, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, currentRow, 2, curntPath, MAX_PATH - 1 );
StringCchPrintf ( tStr, MAX_PATH, L "%s\\%s ", curntPath, curntFname );
SetWindowText ( hMainWnd, tStr );
FillRichEditFromFile ( hRich, ( LPCTSTR ) &tStr );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
int iCount = 0;
if ( hList ) iCount = ListView_GetItemCount ( hList );
StringCchPrintf ( Text, 255, L "%d of %d Files ", currentRow + 1, iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) Text );
SetFocus ( hList );
return 0;
#pragma endregion and load it into richedit
}
if ( ( pnkd )->wVKey == VK_F5 || ( pnkd )->wVKey == VK_F17 ) {
#pragma region cycle _thru_OCCURRENCES_Column
LVITEM lvi;
MSG msg;
memset ( &lvi, 0, sizeof ( lvi ) );
int numbFound = 0;
int iCount = 0;
if ( hList ) iCount = ListView_GetItemCount ( hList );
int result = currentRow;;
int misses = 0;
short nVirtKey = GetKeyState ( VK_CONTROL );
short nVirtKeySh = GetKeyState ( VK_SHIFT );
// async key states
nVirtKey = GetAsyncKeyState ( VK_CONTROL );
if ( wcscmp ( fsiaf, L " " ) != 0 ) {
do {
if ( nVirtKeySh & 0x8000|| ( pnkd )->wVKey == VK_F17) {
#pragma region shift_key_down
// get previous file with matches
result = ListView_GetNextItem ( hList, result, LVNI_ABOVE );
if ( result == -1 ) {
result = iCount - 1;
ListView_SetItemState ( hList, -1, 0,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState ( hList, result, LVIS_DROPHILITED |
LVIS_SELECTED | LVIS_FOCUSED,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_EnsureVisible ( hList, result, TRUE );
ListView_Update ( hList, result );
#pragma endregion select previous file
}
}
else {
#pragma region shift_key_up
// get next file with matches
result = ListView_GetNextItem ( hList, result, LVNI_BELOW );
if ( result == -1 )
result = 0;
#pragma endregion select nextt file
}
#pragma region does_str-to-search-for_occur_in_occurs_text
// Get column two text string
ListView_GetItemText ( hList, result, 1, numbOfOccurs, 255 );
numbOfOccurs [ 255 ] = 0;
if ( wcsstr ( (LPWSTR)numbOfOccurs, (LPWSTR)fsiaf ) != 0 ) {
// search string is a substring of Number of Occurrences column
// Get column one text string
ListView_GetItemText ( hList, result, 0, curntFname, MAX_PATH - 1 );
// Get column three text string
ListView_GetItemText ( hList, result, 2, curntPath, MAX_PATH - 1 );
sprintf_s ( chBuf, 4096, "%S\\%S\r\n ", curntPath, curntFname );
char pattern [ 260 ] = { 0 };
int patlen = sprintf_s ( pattern, 259, "%S ", fsiaf );
// daa-ble chek. Call u2Charfunc just to make sure it has a match
if ( ( numbFound = u2Charfunc ( chBuf, pattern, patlen ) ) != 0 ) {
ListView_SetItemState ( hList, -1, 0, LVIS_DROPHILITED |
LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState ( hList, result, LVIS_DROPHILITED |
LVIS_SELECTED | LVIS_FOCUSED,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
// Remove Highlight from all files that are highlighted
result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
ListView_SetItemState ( hList, -1, 0,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
// Highlight all files with matches
ListView_SetItemState ( hList, result,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_EnsureVisible ( hList, result, TRUE );
ListView_Update ( hList, result );
currentRow = result;
int topLine = 0, linesPer = 0;
topLine = ListView_GetTopIndex ( hList );
linesPer = ListView_GetCountPerPage ( hList );
ListView_Scroll ( hList, 0, ( ( ( size_t ) currentRow - topLine ) -
( linesPer / 2 )+3 ) * 16 );
iCount = ListView_GetItemCount ( hList );
ListView_GetItemText ( hList, currentRow, 3, curntFname, MAX_PATH - 1 );
curntFname [ 255 ] = 0;
int fileSize = _wtoi ( curntFname );
if ( fileSize > 64 * 1024 * 1024 ) {
MessageBox ( hMainWnd, L "File Size is too much for me!
Can 't LOAD ", curntFname, MB_OK | MB_ICONEXCLAMATION );
ListView_GetItemText ( hList, result, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, result, 2, curntPath, MAX_PATH - 1 );
curntPath [ 259 ] = 0;
wsprintf ( teXt, L "/select, \ "%s\\%s\ " ", curntPath, curntFname );
SetWindowText ( hEdit, teXt );
ShellExecute ( NULL, L "open ",
L "explorer.exe ", teXt, curntPath, SW_SHOW );
return 0;
}
ListView_GetItemText ( hList, currentRow, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, currentRow, 2, curntPath, MAX_PATH - 1 );
StringCchPrintf ( Text, 255, L "[%d of %d]%s\\%s ",
result + 1, iCount, curntPath, curntFname );
SetWindowText ( hMainWnd, Text );
StringCchPrintf ( tStr, MAX_PATH, L "%s\\%s ", curntPath, curntFname );
SetWindowText ( hEdit, tStr );
FillRichEditFromFile ( hRich, ( LPCTSTR ) &tStr );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
StringCchPrintf ( Text, 255, L "%d of %d Files ", result + 1, iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) Text );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT,
( LPARAM ) L "Line: 0 Column: 0 " );
SendMessage ( hwndStatus, WM_SIZE, 0, 0 );
if ( fsiaf ) {
SetFocus ( hRich );
FINDTEXTEX fndFrst;
fndFrst.lpstrText = fsiaf;
if ( ( pnkd )->wVKey == VK_F17 ) {
GETTEXTLENGTHEX lastChar = { 0 };
lastChar.flags = GTL_DEFAULT;
lastChar.codepage = 1200;
intRc = ( LONG ) SendMessage ( hRich, EM_GETTEXTLENGTHEX,
(WPARAM)&lastChar, ( LPARAM ) 0 );
fndFrst.chrg.cpMin = intRc;
fndFrst.chrg.cpMax = 0;
intRc = ( LONG ) SendMessage ( hRich, EM_FINDTEXTEXW, 0,
( LPARAM ) &fndFrst );
}
else {
fndFrst.chrg.cpMin = 0;
fndFrst.chrg.cpMax = -1;
intRc = ( LONG ) SendMessage ( hRich, EM_FINDTEXTEXW, 1,
( LPARAM ) &fndFrst );
}
CHARFORMAT cf;
cf.cbSize = sizeof ( cf );
cf.dwMask = CFM_BOLD | CFM_UNDERLINE;
cf.dwEffects = CFE_BOLD | CFE_UNDERLINE;
SendMessage ( hRich, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM ) &cf );
DWORD dwerr = GetLastError ( );
dwerr = GetLastError ( );
if ( intRc == -1 ) {
#pragma region str-to-find was not found in this file
if ( MessageBox ( hMainWnd, L "Could Not FIND
Requested String!\rDo you want to Continue? ", fsiaf,
MB_YESNO | MB_ICONINFORMATION | MB_DEFBUTTON2 ) == IDOK ) {
StringCchPrintf ( searchCap, 255, L "Could Not
FIND Requested String!\tDo you want to Continue? " );
SetWindowText ( hMainWnd, searchCap );
SetWindowText ( hEdit, searchCap );
SetFocus ( hRich );
return 1;
}
else {
intRc = 0;
SetFocus ( hRich );
}
#pragma endregion str-to-find was not found in this file
}
else {
#pragma region str-to-find was found in this file
hWndFocus = hRich;
SetFocus ( hWndFocus );
SendMessage ( hRich, EM_SETSEL, fndFrst.chrgText.cpMin,
fndFrst.chrgText.cpMax );
SendMessage ( hRich, EM_SCROLLCARET, 0, 0 );
int chLine = ( int ) SendMessage ( hRich, EM_EXLINEFROMCHAR, 0, intRc );
int fvLine = ( int ) SendMessage ( hRich, EM_GETFIRSTVISIBLELINE, 0, 0 );
LONG strtLine = ( LONG ) SendMessage ( hRich, EM_LINEINDEX, chLine, 0 );
LONG colPos = intRc - strtLine;
DWORD lvLine = ( DWORD ) SendMessage ( hRich, EM_SCROLL, SB_PAGEDOWN, 0 );
int pgSize = 0;
pgSize = LOWORD ( lvLine );
lvLine = ( DWORD ) SendMessage ( hRich, EM_SCROLL, SB_PAGEUP, 0 );
DWORD desiredPos = pgSize / 2 + fvLine;
int noffSet = chLine - desiredPos;
SendMessage ( hRich, EM_LINESCROLL, 0, noffSet );
SendMessage ( hRich, EM_SCROLLCARET, 0, 0 );
intRc = fndFrst.chrgText.cpMax + 1;
StringCchPrintf ( Text, 255, L "Line: %d Column: %d ", chLine, colPos );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT, ( LPARAM ) Text );
#pragma endregion make sure caret is centered in the middle of the screen
}
}
SetFocus ( hRich );
}
if ( !( nVirtKeySh & 0x8000 ) ) {
// Key-repeat can cause find-in-previous-file to appear
// to skip files that have matches. This prevents that, whilr
// remaining responsive for find-in-next-file
PeekMessage ( &msg, NULL, 0, 0, PM_REMOVE );
DispatchMessage ( &msg );
}
nVirtKey = GetAsyncKeyState ( VK_CONTROL );
if ( nVirtKey & -1 ) {
nVirtKey = GetAsyncKeyState ( 0x43 );// 'c ' key
if ( nVirtKey & -1 )
return 0; //user hit control-c. stop finding
}
}
#pragma endregion load richedit and find string in file
else {
if ( misses++ == iCount ) {
#pragma region when_entire_column_has_been_searched_and_no_match_found
if ( iCount >= 5000 ) {
MessageBox ( NULL, L "NO Matches found in the presearched files!\rPress
ENTER or SELECT OK to search file-list.
This could take a while! ", fsiaf, MB_OK | MB_ICONHAND | MB_SYSTEMMODAL );
}
int searchStartRow = currentRow;
ListView_SetItemState ( hList, -1, 0,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
findStringInAllFiles ( );
#pragma endregion search every file in listview
SetFocus ( hRich );
#pragma region select_first_file_with_a_match
if ( hRich && IsWindowVisible ( hRich ) ) {
result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
if ( searchStartRow != 0 ) {
result = searchStartRow + 1;
}
ListView_SetItemState ( hList, result,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED,
LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_GetItemText ( hList, result, 3, curntFname, 255 );
curntFname [ 255 ] = 0;
int fileSize = _wtoi ( curntFname );
if ( fileSize > 64 * 1024 * 1024 ) {
MessageBox ( hMainWnd, L "File Size is too much for me!
Can 't LOAD ", curntFname, MB_OK | MB_ICONEXCLAMATION );
ListView_GetItemText ( hList, result, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, result, 2, curntPath, MAX_PATH - 1 );
wsprintf ( teXt, L "/select, \ "%s\\%s\ " ", curntPath, curntFname );
SetWindowText ( hEdit, teXt );
curntPath [ 259 ] = 0;
ShellExecute ( NULL, L "open ",
L "explorer.exe ", teXt, curntPath, SW_SHOW );
return 0;
}
ListView_GetItemText ( hList, result, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, result, 2, curntPath, MAX_PATH - 1 );
StringCchPrintf ( tStr, MAX_PATH, L "%s\\%s ", curntPath, curntFname );
SetWindowText ( hMainWnd, tStr );
SetWindowText ( hEdit, tStr );
FillRichEditFromFile ( hRich, ( LPCTSTR ) &tStr );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
iCount = 0;
if ( hList ) iCount = ListView_GetItemCount ( hList );
StringCchPrintf ( Text, 255, L "%d of %d Files ", result + 1, iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) Text );
ShowWindow ( hRich, SW_SHOW );
SetFocus ( hRich );
if ( fsiaf ) {
FINDTEXTEX fndFrst;
fndFrst.chrg.cpMin = 0;
fndFrst.chrg.cpMax = -1;
fndFrst.lpstrText = fsiaf;
intRc = ( LONG ) SendMessage
( hRich, EM_FINDTEXTEXW, 1, ( LPARAM ) &fndFrst );
CHARFORMAT cf;
cf.cbSize = sizeof ( cf );
cf.dwMask = CFM_BOLD | CFM_UNDERLINE;
cf.dwEffects = CFE_BOLD | CFE_UNDERLINE;
SendMessage ( hRich, EM_SETCHARFORMAT, SCF_SELECTION, ( LPARAM ) &cf );
DWORD dwerr = GetLastError ( );
dwerr = GetLastError ( );
if ( intRc == -1 ) {
if (1){
StringCchPrintf ( searchCap, 255,
L "Could Not FIND Requested String!\tDo you want to Continue? " );
SetWindowText ( hMainWnd, searchCap );
SetWindowText ( hEdit, searchCap );
sendMeMyKey ( hList, VK_F5 );
intRc = ( LONG ) SendMessage ( hRich, EM_FINDTEXTEXW, 1,
( LPARAM ) &fndFrst );
if ( intRc == -1 ) {
intRc = 0;
SetFocus ( hRich );
}
}
}
else {
SendMessage ( hRich, EM_SETSEL,
fndFrst.chrgText.cpMin, fndFrst.chrgText.cpMax );
hwndStatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
int chLine = ( int ) SendMessage ( hRich, EM_EXLINEFROMCHAR, 0, intRc );
LONG strtLine = ( LONG ) SendMessage ( hRich, EM_LINEINDEX, chLine, 0 );
LONG colPos = intRc - strtLine;
StringCchPrintf ( Text, 255, L "Line: %d Column: %d ", chLine, colPos );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT, ( LPARAM ) Text );
return 1;
}
}
}
#pragma endregion and load it into richedit to find match
}
else if ( misses > iCount ) {
MessageBox ( NULL, L "NO FILES containing Requested STRING were found!\rCheck
the spelling of STRING. ", fsiaf, MB_OK | MB_ICONHAND );
return 0;// searched every file in listvie and found no matchesw
}
}
}
while ( !numbFound );
SetFocus ( hList );
return 0;
}
#pragma endregion looking for string-to-search-for in text
}
if ( ( pnkd )->wVKey == VK_F3 ) {
#pragma region send_vk-f3
sendMeMyKey ( hRich, VK_F3 );
return 0;
#pragma endregion to richedit
}
if ( ( pnkd )->wVKey == VK_F6 ) {
#pragma region send_vk-f6
sendMeMyKey ( hRich, VK_F6 );
return 0;
#pragma endregion to richedit
}
if ( ( pnkd )->wVKey == VK_F4 ) {
short nVirtKey = GetKeyState ( VK_MENU );
if ( nVirtKey & -128 ) {
if ( hMainWnd ) {
return DefWindowProc ( hMainWnd, message, wParam, lParam );
}
else {
return 0;
}
}
#pragma region get_full_path_and_file_attributes
NMLISTVIEW * pnmlv;
LVITEM lvi;
int result = 0;
struct _stat64 buf;
pnmlv = ( LPNMLISTVIEW ) lParam;
memset ( &lvi, 0, sizeof ( lvi ) );
result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
if ( result == -1 ) result = 0;
lvi.iItem = result;
lvi.mask = LVIF_TEXT;
lvi.cchTextMax = 255;
lvi.pszText = Text;
if ( hList ) ListView_GetItem ( hList, &lvi );
lvi.pszText = text;
lvi.iSubItem = 2;// get the path
ListView_GetItem ( hList, &lvi );
wsprintf ( teXt, L "%s\\%s ", text, Text );
result = _wstat64 ( teXt, &buf );
if ( result == 0 ) {
if ( ( buf.st_mode & _S_IFREG ) ) {
wsprintf ( teXt, L "\ "%s\\%s\ " ", text, Text );
SetWindowText ( hEdit, teXt );
SHELLEXECUTEINFO ShExecInfo = { 0 };
ShExecInfo.cbSize = sizeof ( SHELLEXECUTEINFO );
ShExecInfo.fMask = SEE_MASK_INVOKEIDLIST;
ShExecInfo.hwnd = hList;
ShExecInfo.lpVerb = L "properties ";
ShExecInfo.lpFile = Text; //can be a file as well
ShExecInfo.lpParameters = L " ";
ShExecInfo.lpDirectory = text;
ShExecInfo.nShow = SW_SHOW;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx ( &ShExecInfo );
}
if ( ( buf.st_mode & _S_IFDIR ) ) {
wsprintf ( teXt, L "\ "%s\\%s\ " ", text, Text );
if ( hEdit ) {
SetWindowText ( hEdit, teXt );
}
ShellExecute ( NULL, L "properties ", teXt, NULL, NULL, SW_SHOWNORMAL );
}
}
#pragma endregion and open with current file association
}
}
break;
case NM_RETURN:
{
#pragma region get_full_path_and_file_attributes
// Keyboard RETuRN Key
NMLISTVIEW * pnmlv;
LVITEM lvi = { 0 };
int result = 0;
struct _stat64 buf;
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 = 2;// get the path
ListView_GetItem ( hList, &lvi );
wsprintf ( teXt, L "%s\\%s ", text, Text );
result = _wstat64 ( teXt, &buf );
if ( result == 0 ) {
if ( ( buf.st_mode & _S_IFREG ) ) {
wsprintf ( teXt, L "/select, \ "%s\\%s\ " ", text, Text );
SetWindowText ( hEdit, teXt );
ShellExecute ( NULL, L "open ", L "explorer.exe ", teXt, text, SW_SHOW );
}
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 );
}
}
#pragma endregion and open with current file association
}break; // case NM_RETURN
case NM_SETFOCUS:
{
int rslt = 0;
rslt = ListView_GetItemCount ( hList );
if ( rslt >= 1 ) {
if ( ( rslt = ListView_GetNextItem ( hList, -1, LVNI_SELECTED ) ) == -1 ) {
ListView_SetItemState ( hList, 0, LVIS_SELECTED | LVIS_DROPHILITED | LVIS_FOCUSED,
LVIS_SELECTED | LVIS_DROPHILITED | LVIS_FOCUSED ); // select this one
return 1;
}
}
} // case NM_SETFOCUS
break;
} // switch (((LPNMHDR)lParam)->code)
}break; // case WM_NOTIFY
case WM_NCLBUTTONUP:
{
ClipCursor ( NULL );
return 0;
}break;
case WM_SIZING:
{
RECT* dragRc = ( RECT* ) lParam;
RECT mrc = { 0 }, wrc = { 0 }, wrccl = { 0 }, crc = { 0 }, lrc = { 0 }, lrccl = {0 },
trc = { 0 }, trccl = { 0 },rrc = { 0 }, rrccl = {0 }, temprc = {0 },
drc = { 0 }, src = { 0 }, srccl = { 0 }, brc = { 0 },
brccl = { 0 }, toprc = { 0 }, toprccl = { 0 }, f0rc = { 0 },
f0rccl = { 0 }, erc = { 0 }, erccl = { 0 }, oldCliprc = { 0 }, clipRc = { 0 };
POINT xPos = { 0 };
GetCursorPos ( &xPos );
HWND hWnd = FindWindow ( L "TwoStageSearch ", 0 );
GetWindowRect ( hWnd, &wrc );
GetClientRect ( hWnd, &wrccl );
HWND hTopView = FindWindowEx ( hWnd, 0, L "#32770 ", 0 );
GetWindowRect ( hTopView, &toprc );
GetClientRect ( hTopView, &toprccl );
HWND hFormView0 = FindWindowEx ( hTopView, 0, L "#32770 ", 0 );
GetWindowRect ( hFormView0, &f0rc );
GetClientRect ( hFormView0, &f0rccl );
hEdit = FindWindowEx (hFormView0, 0, L "Edit ", 0 );
GetWindowRect ( hEdit, &erc );
GetClientRect ( hEdit, &erccl );
GetWindowRect ( hDlg, &lrc );
HWND hBottomView = GetParent ( hDlg );
HWND hFormView1 = GetNextWindow ( hDlg, GW_HWNDNEXT );
HWND hFormView3 = GetNextWindow ( hDlg, GW_HWNDPREV );
HWND hStatusView = GetNextWindow ( hFormView3, GW_HWNDPREV );
hFormView2 = hDlg;
hList = getThisWindowHandle ( hFormView2, IDC_LIST1 );
hTree = getThisWindowHandle ( hFormView1, IDC_TREE1 );
hRich = getThisWindowHandle ( hFormView3, IDC_EDIT1 );
HWND hwndStatus = getThisWindowHandle ( hStatusView, IDC_STATUS );
GetWindowRect ( hStatusView, &src );
GetClientRect ( hwndStatus, &srccl );
GetClientRect ( hTree, &trccl );
GetClientRect ( hList, &lrccl );
GetClientRect ( hRich, &rrccl );
GetWindowRect ( hBottomView, &brc );
GetClientRect ( hBottomView, &brccl );
GetClientRect ( hWnd, &crc );
GetWindowRect ( hWnd, &wrc );
GetWindowRect ( hDlg, &drc );
GetWindowRect ( hFormView1, &trc );
GetWindowRect ( hFormView2, &lrc );
GetWindowRect ( hFormView3, &rrc );
OffsetRect ( &toprc, -wrc.left, -wrc.top );
OffsetRect ( &f0rc, -wrc.left, -wrc.top );
OffsetRect ( &erc, -wrc.left, -wrc.top );
OffsetRect ( &brc, -wrc.left, -wrc.top );
OffsetRect ( &trc, -wrc.left, -wrc.top );
OffsetRect ( &lrc, -wrc.left, -wrc.top );
OffsetRect ( &rrc, -wrc.left, -wrc.top );
OffsetRect ( &src, -wrc.left, -wrc.top );
CopyRect ( &temprc, dragRc );
OffsetRect ( &temprc, -wrc.left, -wrc.top );
if ( wParam != WMSZ_RIGHT &&
wParam != WMSZ_LEFT )
{
GetClipCursor ( &oldCliprc );
clipRc.left = drc.left;
clipRc.top = xPos.y;
clipRc.right = drc.right;
clipRc.bottom = xPos.y + 1;
ClipCursor ( &clipRc );
}
if ( wParam == WMSZ_RIGHT ) {
GetClipCursor ( &oldCliprc );
clipRc.left = drc.left+100;
clipRc.top = xPos.y;
clipRc.right = wrc.right-wrc.left - 20;
clipRc.bottom = xPos.y + 1;
ClipCursor ( &clipRc );
MoveWindow ( hFormView3, lrc.right-8, 0, wrccl.right - lrc.right+8,
lrc.bottom - lrc.top, TRUE );
MoveWindow ( hRich, 0, 0, wrccl.right - lrc.right+8, lrc.bottom - lrc.top, TRUE );
}
if ( wParam == WMSZ_LEFT ) {
GetClipCursor ( &oldCliprc );
clipRc.left = wrc.left + 50;
clipRc.top = xPos.y;
clipRc.right = drc.right-30;
clipRc.bottom = xPos.y + 1;
ClipCursor ( &clipRc );
MoveWindow ( hFormView1, 0, 0, temprc.left-4, temprc.bottom - temprc.top+4, TRUE );
GetWindowRect ( hFormView1, &trc );
OffsetRect ( &trc, -wrc.left, -wrc.top );
}
MoveWindow ( hList, 0, 0, lrc.right - lrc.left - 16, lrc.bottom - lrc.top - 16, TRUE );
return 0;
}break;
case WM_SIZE:
{
UINT width = GET_X_LPARAM ( lParam );
UINT height = GET_Y_LPARAM ( lParam );
MoveWindow ( hList, 0, 0, width, height, TRUE );
return 0;
}
break;
case WM_ACTIVATE:
{
if ( LOWORD ( wParam ) & WA_ACTIVE ) {
if ( hWndFocus ) {
SetFocus ( hWndFocus );
return 0;
}
else {
hWndFocus = hList;
SetFocus ( hList );
if ( ListView_GetItemCount ( hList ) >= 1 ) {
if ( ListView_GetNextItem ( hList, -1, LVNI_SELECTED ) == -1 ) {
// select this one
ListView_SetItemState ( hList, 0, LVIS_SELECTED, LVIS_SELECTED );
}
}
return 0;
}
}
else
return -1;
}
break;
default:
break;
}
return 0;
}
How to Change the ROOT Folder for the File List: The Menu and the Tree View
The Windows program that the user sees resembles the Windows File Explorer superficially. It has a TreeView
on the left, used to navigate the file system and select a current directory path, a ListView
in the middle and a file viewer on the right side. But it's not intended for file management. Its only purpose is to find a string
in a file and display it in the context in which it is used. It finds forward with PF3 and finds backwards when PF3 is used with shift. It will also find backwards with PF6. When finding backwards, if no more occurrences can be found, it will find the first occurrence in the next containing a match or the last occurrence in a previous file with a match. You can immediately go to the next file with PF5 or the preceding file with PF5 and shift. There is other functionality in the program but it is there to support the find
functions.
The TwoStageSearch
Menu is very sparse, and that is intentional. In the screen shot below, you see the menu with Change Directory selected. The text in the Title Bar of the main window says 'Two Stage Search - Build ... '. In the slightly smaller screen shot on the right, we see the Title Text now says 'Use CURSOR KEYS to Navigate in the ... '. In the TreeView
, the user moves the selection from 'ASUS
' To 'All Users
'. The Edit Box contains 'c:\Users\All Users'. The status bar still says 'c:\Users\asus'. The last step is to push 'ENTER ' to accept the change. The user could just click on the folder of their choice, but doing this deletes the contents of the ListView
and populates it with the children of the selected folder, if any. To get files from more than one Root
, you must use the Menu
.
The significance of the Title Text is it is used by the TreeView
Proc to detect the request to change directories. When the focus is on the TreeView
and the user pushes 'ENTER', the NM_RETURN
handler gets the Title Text. It is compared to several different control string
s. If the control string
matches the Title Text, the corresponding request is executed. The Change Directory request will change the root folder and save it in the Registry. The second request, Temp_Change_CD
will temporarily change it and the third request, CD_AND_Build
will change it and build the file list.
Changing the Start In Directory: Click on the Menu
The TreeView Proc also Handles the Other Messages Sent to the TreeView Control
In the WM_INITDIALOG
message handler, we make sure that we're not in hBottom
itself, but are in hFormView1
. Then we create a font, create the treectrl
and send it the WM_SETFONT
message. Then we call SetTreeviewImagelist
and get the search root, startInDir
from the Environment Variable to set the current Directory. We set the TreeView
extended styles to double buffered in order to cut down on flickering. Then we show the main window.
The message handler for WM_NOTIFY
contains the switch for the (LP)NMHDR lParam
code for TVN_SELCHANGING
, which gets the NODE
's full path, back to the drive and ensures that it is visible.
The NMHDR lParam
code for TVN_SELCHANGED
checks if the action is TVC_UNKNOWN
and startInDir
is not blank, it populates and expands the tree nodes until startInDir
is located. If the action is TVC_BYKEYBOARD
, the current item is selected and highlighted. If the action is TVC_BYMOUSE
and current item is a folder, the contents of the folder replaces the ListView
contents. If it is not a folder, the item is added to the ListView
.
The NMHDR lParam
code for TVN_ITEMEXPANDING
populates the child nodes with files and folders, always and only one level deeper. This creates the populate on demand behavior of the TreeView
.
The NMHDR lParam
code for TVN_ITEMEXPANDED
will, if startInDir
is not blank, populate and expand the tree nodes until startInDir
is located. This will show the Root Folder for the File List Build as the selected TreeView
Item.
The NMHDR lParam
code for NM_CLICK
populates ListView
when node is already selected by keyboard and then clicked by mouse.
The NMHDR lParam
code for NM_RETURN
populates ListView
when node is selected by keyboard and then Enter is pressed. When TreeView
receives focus from Menu Selection, it gets text from Title of Main Window to determine whether to save CD temporarily, permanently or just to use it to perform file list build, also gets environment variable for extension set.
The NMHDR lParam
code for TVN_KEYDOWN
will handle VK_TAB
and VK_F5
by setting the focus to ListView
.
The WM_SIZE
message handler will re-size the TreeView
control when the container FormView1
is moved by the ListView
control's sizing border on the LEFT side of the control, based on the current relationship of the two controls, to fill the containing window but leaving a narrow strip in between them.
The treeViewProc Function
// Message handler for tree view. Processes 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
// highlighted 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. When TreeView
// receives focus from Menu Selection it gets text from Title of MainWindow
// to determine whether to save CD
// temporarily, permanently or just to perform file list build,
// also gets environment variable for extension set.
// WM_SIZE wil re-size the TreeView control when the container
// FormView1 is moved by the ListView
// control 's sizing border on the LEFT side of the control
// based on the current relationship of the
// two controls, to fill the containing window but leaving a narrow strip
// in between them.
INT_PTR CALLBACK treeViewProc
( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) {
UNREFERENCED_PARAMETER ( wParam );
TCHAR nodeCD [ MAX_PATH ] = { 0 };
TCHAR startInDir [ MAX_PATH ] = { 0 };
HWND hMainWnd = NULL, hTree = NULL, hList = NULL, hEdit = NULL;
TV_ITEM tvi;
DWORD sidLen = 0;
if ( WM_INITDIALOG == message ) {
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hBottom = GetAncestor ( hDlg, GA_PARENT );
if ( hMainWnd == hBottom ) {
return 0;
}
hTree = FindWindowEx ( hDlg, 0, L "SysTreeView32 ", 0 );
if ( hTree == 0 ) {
HFONT hFont = CreateFont ( 32, 10, 0, 0, FW_BOLD, FALSE,
FALSE, FALSE, ANSI_CHARSET, \
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
DEFAULT_PITCH | FF_SWISS, L "Arial " );
RECT sRc;
GetClientRect ( hDlg, &sRc );
hTree = CreateWindowEx ( WS_EX_OVERLAPPEDWINDOW | WS_EX_STATICEDGE |
WS_EX_COMPOSITED | WS_EX_NOINHERITLAYOUT,
WC_TREEVIEW, NULL,
WS_CHILD | WS_VISIBLE |
DS_3DLOOK | WS_BORDER | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN | WS_TABSTOP |
TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT,
sRc.left, sRc.top, sRc.right, sRc.bottom, hDlg,
( HMENU ) IDC_TREE1, hInst, 0 );
SendMessage ( hTree, WM_SETFONT, WPARAM ( hFont ), TRUE );
GetClientRect ( hDlg, &sRc );
SetTreeviewImagelist ( hTree );
sidLen = GetEnvironmentVariable ( L "startInDir ", startInDir, 255 );
SetCurrentDirectory ( startInDir );
if ( _wcsicmp ( startInDir, nodeCD ) != 0 ) {
StringCchPrintf ( nodeCD, 255, L "%s ", startInDir ); // define the
// start-in-directory for XP
SetCurrentDirectory ( nodeCD );
GetCurrentDirectory ( 255, startInDir );
}
else {
StringCchPrintf ( nodeCD, 255, L "%s ", startInDir ); // initialize
// in case user did not select anything before clicking 'search '
}
sidLen = GetEnvironmentVariable ( L "HOMEDRIVE ", startInDir, 255 );
RECT wrc = { 0 };
GetWindowRect ( hMainWnd, &wrc );
TreeView_SetExtendedStyle ( hTree, TVS_EX_DOUBLEBUFFER, TVS_EX_DOUBLEBUFFER );
MoveWindow ( hMainWnd, wrc.left, wrc.top, wrc.right - wrc.left,
wrc.bottom - wrc.top, TRUE );
ShowWindow ( hMainWnd, SW_NORMAL );
return ( INT_PTR ) TRUE;
}
}
else {
if ( WM_NOTIFY == message ||
WM_SIZE == message ) {
if ( ( hMainWnd = FindWindow ( L "TwoStageSearch ", 0 )) == 0 ) return 0;
if ( ( hTree = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_TREE1 )) == 0)
return 0;
if ( ( hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 ) ) == 0 )
return 0;
if ( ( hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 )) == 0)
return 0;
}
}
switch ( message ) {
case WM_ERASEBKGND:
return 1;
case WM_NOTIFY:
{ // messages from child window common controls
if ( ( hMainWnd = FindWindow ( L "TwoStageSearch ", 0 ) ) == 0 ) return 0;
if ( ( hTree = getThisWindowHandle
( hMainWnd, ( ULONG ) IDC_TREE1 ) ) == 0 ) return 0;
if ( ( hList = getThisWindowHandle
( hMainWnd, ( ULONG ) IDC_LIST1 ) ) == 0 ) return 0;
if ( ( hEdit = getThisWindowHandle
( hMainWnd, ( ULONG ) IDC_EDIT2 ) ) == 0 ) return 0;
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; // Handle to the NM TreeView STRUCTURE
pnmtv = ( LPNMTREEVIEW ) lParam; // set the Handle 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 );
TreeView_EnsureVisible ( hTree, tvi.hItem );
return FALSE;
} break; // case TVN_SELCHANGING
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;
sidLen = GetEnvironmentVariable ( L "STARTINDIR ", startInDir, 255 );
nmtvi = TreeView_GetSelection ( hTree );
TCHAR Text [ 256 ] = { 0 };
TCHAR text [ 256 ] = { 0 };
DWORD 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
GetCurrentDirectory ( MAX_PATH - 1, nodeCD );
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 '\\ ' ) {
// concat in text buffer
wsprintf ( text, L "%s\\%s ", nodeCD, tvi.pszText );
}
else {
// concat in text buffer
wsprintf ( text, L "%s%s ", nodeCD, tvi.pszText );
}
result = ( int ) wcslen ( text );
// when lengths are the same compare
if ( sidLen == result && ( _wcsicmp
( startInDir, text ) == 0 ) ) {
// when strings are same
SetCurrentDirectory ( nodeCD );
memset ( startInDir, 0, sizeof ( startInDir ) );
TreeView_Select ( hTree, nmtvi, TVGN_CARET );
TreeView_Select ( hTree, nmtvi, TVGN_DROPHILITE );
// comment this out to position at bottom
TreeView_Select ( hTree, nmtvi, TVGN_FIRSTVISIBLE );
SetFocus ( hTree );
return 0;
}
else {
wsprintf ( text, L "%s\\ ", text ); // concat a back-slash
result = ( int ) 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 {
// get the next child 's handle
nmtvi = TreeView_GetNextSibling ( hTree, nmtvi );
}
}
}
} // 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
}
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 - 1, nodeCD );
item.mask = LVIF_TEXT;
item.iItem = 0;
item.iSubItem = 0;
item.cchTextMax = 260;
item.pszText = tvi.pszText;
int ret = 0, nRet = 0;
ret = ListView_InsertItem ( hList, &item );
item.iItem = ret;
item.iSubItem = 2;
item.pszText = nodeCD;
ListView_SetItem ( hList, &item );
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_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
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 );
SetFocus ( hList );
} // 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 - 1, 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 = 2;
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
case TVN_ITEMEXPANDING:
{ // populates the child nodes with files and folders, always one level deeper.
NMTREEVIEW * pnmtv;
HTREEITEM nmtvi = { 0 };
sidLen = GetEnvironmentVariable ( L "STARTINDIR ", startInDir, 255 );
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
TreeView_SelectItem ( hTree, tvi.hItem );
}
if ( ( pnmtv )->action & TVE_COLLAPSE ) { // the folder is closing,
// set its 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. This is 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;
if ( TreeView_GetItem ( hTree, &tvi ) ) { // just in case the handle is corrupted
DWORD dwLen = GetCurrentDirectory ( MAX_PATH - 1, nodeCD );
if ( dwLen == 0 || dwLen > MAX_PATH - 2 )
return 0;
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
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;
sidLen = GetEnvironmentVariable ( L "STARTINDIR ", startInDir, 255 );
pnmtv = ( LPNMTREEVIEW ) lParam;
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 ) { // 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;
DWORD dwLen = GetCurrentDirectory ( MAX_PATH - 1, nodeCD );
if ( dwLen == 0 || dwLen > MAX_PATH - 2 )
return 0;
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.
SetCurrentDirectory ( text );
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
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;
HWND hwndFrom = ( lpnmh )->hwndFrom;
TVHITTESTINFO lpht = { 0 };
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!
SetFocus ( hTree );
HTREEITEM nmClickedtvi = lpht.hItem;
nmtvi = TreeView_GetSelection ( hwndFrom );
if ( nmtvi != nmClickedtvi ) // the clicked node hasn 't been
{
return 0;
} // selected yet. don 't do anything 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 - 1, 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 = 2;
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 |
LVIS_SELECTED | LVIS_FOCUSED );
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 );
SetFocus ( hList );
} // 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 - 1, 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 = 2; // 3d 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
case TVN_KEYDOWN:
{
NMTVKEYDOWN * ptvkd;
ptvkd = ( LPNMTVKEYDOWN ) lParam;
if ( ( ptvkd )->wVKey == VK_TAB ) {
SetFocus ( hList );
}
if ( ( ptvkd )->wVKey == VK_F5 ) {
#pragma region search_current_folder_to_build_file_list
GetCurrentDirectory ( MAX_PATH - 1, nodeCD );
SetFocus ( hList );
return 0;
#pragma endregion then send vk_f5 to listview
}
break;
case NM_RETURN:
{ // populates ListView when node is selected by keyboard and
// then Keyboard ENTER Key is pressed.
HTREEITEM nmtvi = { 0 };
TCHAR Text [ 256 ] = { 0 };
TCHAR text [ 256 ] = { 0 };
TCHAR tStr [ 256 ] = { 0 };
TCHAR searchStr [ 256 ] = { 0 };
TCHAR titleText [ 256 ] = { 0 };
GetWindowText ( hMainWnd, titleText, 255 );
if ( wcscmp ( titleText, L " USE CURSOR KEYS to Navigate in the TREEVIEW,
select a folder path, then push ENTER to save path temporarily. " ) == 0 ) {
if ( GetWindowTextLength ( hEdit ) ) {
GetWindowText ( hEdit, nodeCD, 255 );
SetEnvironmentVariable ( L "STARTINDIR ", nodeCD );
}
else {
GetCurrentDirectory ( 255, nodeCD );
}
Edit_SetCueBannerTextFocused ( hEdit, L " ", TRUE );
HWND hwndstatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
SendMessage ( hwndstatus, SB_SETTEXT, 0, ( LPARAM ) nodeCD );
return TRUE;
}
if ( wcscmp ( titleText, L " USE CURSOR KEYS to Navigate in the TREEVIEW,
select a folder path, then push ENTER to search path. " ) == 0 ) {
if ( GetWindowTextLength ( hEdit ) ) {
GetWindowText ( hEdit, nodeCD, 255 );
SetEnvironmentVariable ( L "STARTINDIR ", nodeCD );
}
else {
GetCurrentDirectory ( 255, nodeCD );
}
Edit_SetCueBannerTextFocused ( hEdit, L " ", TRUE );
HWND hwndStatus = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_STATUS );
GetEnvironmentVariable ( L "SEARCHSTR ", searchStr, 255 );
StringCchPrintf ( tStr, MAX_PATH, L "Searching for %s in ", searchStr );
SendMessage ( hwndStatus, SB_SETTEXT, 0, ( LPARAM ) tStr );
SendMessage ( hwndStatus, SB_SETTEXT, 1 | SBT_POPOUT, ( LPARAM ) nodeCD );
HWND hProgress = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_PROGRESS1 );
ShowWindow ( hProgress, SW_NORMAL );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_NORMAL, 0 );
recursivefileSearch ( nodeCD, FALSE );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_PAUSED - 3, 0 );
ShowWindow ( hProgress, SW_HIDE );
SendMessage ( hwndStatus, SB_SETTEXT, 0, ( LPARAM ) nodeCD );
int iCount = ListView_GetItemCount ( hList );
int result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
StringCchPrintf ( tStr, MAX_PATH, L "Number of Files %d. ", iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 1 | SBT_POPOUT, ( LPARAM ) tStr );
StringCchPrintf ( tStr, MAX_PATH, L "%d of %d Files. ", result + 1, iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) tStr );
return TRUE;
}
if ( wcscmp ( titleText, L " USE CURSOR KEYS to Navigate in the TREEVIEW
to select a folder path, then push ENTER.
The path will be saved permanently " ) == 0 ) {
if ( GetWindowTextLength ( hEdit ) ) {
GetWindowText ( hEdit, nodeCD, 255 );
SetEnvironmentVariable ( L "STARTINDIR ", nodeCD );
}
else {
GetCurrentDirectory ( 255, nodeCD );
}
StringCchPrintf ( Text, 255, L " /k Setx STARTINDIR \ "%s\ " ", nodeCD );
SetEnvironmentVariable ( L "STARTINDIR ", nodeCD );
persist_This ( Text );
HWND hwndstatus = getThisWindowHandle ( hMainWnd, IDC_STATUS );
SendMessage ( hwndstatus, SB_SETTEXT, 0, ( LPARAM ) nodeCD );
return TRUE;
}
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 - 1, 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 = 0;
ret = ListView_InsertItem ( hList, &item ); // insert in sorted order
item.iItem = ret; // ret is the index of the one we inserted
item.iSubItem = 2; // sub-item two is third 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_DROPHILITED |
LVIS_SELECTED | LVIS_FOCUSED );
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 );
SetFocus ( hList );
}
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 - 1, 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 = 0;
ret = ListView_InsertItem ( hList, &item );
item.iItem = ret;
item.iSubItem = 2;
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
} // TVN_KEYDOWN
} // switch (((LPNMHDR)lParam)->code)
} break;// case IDC_TREE1
} // switch (((LPNMHDR)lParam)->idFrom)
} break; // case WM_NOTIFY:
case WM_SIZE:
{
UINT width = GET_X_LPARAM ( lParam );
UINT height = GET_Y_LPARAM ( lParam );
MoveWindow ( hTree, 0, 0, width, height, TRUE );
return 0;
}break;
}
return ( INT_PTR ) FALSE;
}
SetTreeviewImagelist Function: For the Tree
These images are just for the TreeView
because it is assumed that most of the files in the ListView
will be of the same type and thus of a lower value in most cases. This certainly would not be true if the user selected a set of extensions with 5 or 6 members but the icon for the extension would add nothing to the search process.
// 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 hImageList = ImageList_Create ( 16, 16, ILC_COLOR32, 6, 1 );
HBITMAP hBitMap = LoadBitmap ( hInst, MAKEINTRESOURCE ( IDB_BITMAP1 ) );
ImageList_Add ( hImageList, hBitMap, NULL );
DeleteObject ( hBitMap );
TreeView_SetImageList ( hTv, hImageList,
TVSIL_NORMAL ); // attach image lists to tree view common control
return true;
}
getNodeFullPath: Find the Parents Recursively
This function accepts 2 parameters, a handle to the Tree Control and the handle to the current HTREEITEH
. Getting the item, it checks to see if the second character is a colon, found on in the ROOT
Item. If this condition is met, it sets the current directory and returns TRUE
, stopping the recursion, otherwise, it calls TreeView_GetParent
, getting the item 's parent node and calling getNodeFullPath
with the parent. Having completed the recursion, we concatenate the items text to the current directory with each return from the recursion. In effect, we reversed the order of the nodes as they were on the stack.
The getNodeFullPath Function
// FUNCTION: recursively get the current item '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;
HWND hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
hTree = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_TREE1 );
TCHAR nodeCD [ 250 ] = { 0 };
if ( TreeView_GetItem ( hTree, &tvi ) ) {
if ( tvi.pszText [ 1 ] == L ': ' ) {
// you found the drive, the root of the folders
StringCchPrintf ( nodeCD, 255, L "%s ", tvi.pszText ); // concat in text buffer
SetCurrentDirectory ( nodeCD );
SetWindowText ( hEdit, nodeCD ); // set the text in IDC_EDIT2
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 ( *nodeCD == 0 ) {
GetCurrentDirectory ( 255, nodeCD );
}
if ( tvi.pszText ) {
if ( nodeCD [ wcslen ( nodeCD ) - 1 ] != L '\\ ' ) {
StringCchPrintf
( nodeCD, 255, L "%s\\%s ", nodeCD, tvi.pszText ); // concat in text buffer
}
else {
StringCchPrintf
( nodeCD, 255, L "%s%s ", nodeCD, tvi.pszText ); // concat in text buffer
}
SetCurrentDirectory ( nodeCD );
SetWindowText ( hEdit, nodeCD );
}
}
}
return true; // return current value of tvi.pszText
}
The Main Window Procedure: WndProc
When we enter the WndProc
, we allocate all of the variables we need on the stack. Then we handle WM_CREATE
in an if
statement. As it turned out, the else
block was not needed, so it could have been in the message switch. The first dialog we create is the container hTopView
, the place where the controls that I thought would not have to be moved around were placed. This container window has one control and another dialog in it. The control is an Edit control, named hEdit
. The Dialog
is hFormView0
which contains two buttons, two Combo Boxes and a Progress Bar. The Progress Bar will fill hFormView0
completely when shown, preventing user input.
The next dialog to be created is hBottom
. It will contain hFormView1
, hFormview2
, hFormView3 hMiddle
. hFormView1
contains the TreeView
, hFormView2
is the RESIZIBG Window
and contains the ListView
, hFormView3
contains the RichEdit
Control and finally, hMiddle
contains the status bar. If all of these windows were created successfully, we create RECT
s for them and establish their initial sizes and move them into position and show the main window, hWnd
.
Now, we get the Environment Variable HOMEDRIVE
and call the InitTreeViewItems
function. Next, we get the Environment Variable STARTINDIR
if it exists, otherwise we get USERPROFILE
to set the current directory. We get SEARCHSTR
, which is the set of file extensions last used, or set searchStr
to '*.cpp;*.c' and set SEARCHSTR
. The Progress Bar is turned on and displayed before we call the recursivefileSearch
function. On return, the Progress Bar is hidden, the status bar is updated, the Caption is set, the first file in the ListView
is selected and loaded into the RichEdit
Viewer Panel, focus is set to the ListView
and WndProc
returns zero.
The WM_SIZE
message handler gets the window rectangle for hBottom
and moves it to left=0, top = 90, right=width, bottom=feight
. This causes hFormView
(1,2,3
,and hNiddle
) to be sent WM_SIZE
messages. This is done so that all of the windows that are re-sized by the RESIZING
Border of hFormView2
can be set properly. The top=90
is the combined height of the Menu
and hTopView
. The Controls in hTopView
are more or less stationary and therefore don't get moved as much as the hTree
, hList
and hRich
, so they only need to be SIZE_RESTORED
and SIZE_MAXIMIZED
. I also used a little trickery when wParam
was SIZED_RESTORED
, I sent the same message again but changed wParam
to SIZE_MAXIMIZED
and added a SIZE_MAXSHOW
message. This was done to eliminate some blocks of code that were just copies of the SIZE_MAXIMIZED
block.
In the WM_COMMAND
message switch, the ID_FILE_CD
command handler will set the Title Text for the main window to ' USE CURSOR KEYS
to Navigate
in the TREEVIEW
, select a folder path, then push ENTER
to search path. '. Then it will blank out hEdit
and set the CUE Banner and set focus to hTree
. The user can cancel the command by not pushing ENTER
, but if the user does push ENTER
, then the selected tree node will be searched and the files found will be added to the ListView
.
The ID_FILE_TEMP
command handler will set the Title Text for the main window to ' USE CURSOR KEYS
to Navigate in the TREEVIEW
, select a folder path, then push ENTER to save path temporarily.' Then it will blank out hEdit
and set the CUE Banner, set the focus to hTree
. The user can cancel the command by not pushing ENTER
, but if the user does push ENTER
, then the selected tree node will be saved to the Environment Block and the Current Directory will be set but the value will not be written to the Registry.
The ID_FILE_CHANGE
command handler will set the Title Text for the main window to 'USE CURSOR KEYS
to Navigate in the TREEVIEW
to select a folder path, then push ENTER
. The path will be saved permanently.' Then it will blank out hEdit
, set the CUE Banner and set the focus to hTree
. The user can cancel the command by not pushing ENTER
, but if the user does push ENTER
, then the selected tree node will be saved to the Environment Block and the Current Directory will be set and the value will be written to the Registry.
The IDM_EXIT
command under the File Menu destroys the main window at program termination.
The ID_EDIT_CMD
command sets the Window TEXT in hEdit
to '#&cmd/f:on /k set prompt=&P&_&l&D&G&L&T&G && ver && echo Path Completion
is turned ON
. For directory use control + D. Control + Shift + F for Files. 'Then it sends a RETURN Key to hEdit
. hEdit
is subclassed to get the RETURN Key and process the Window Text., resulting in a CMD Prompt being started.
The ID_VIEW_RESET
and ID_VIEW_FO
commands are used to start the process to reset the Checkbox
in the SHMessageBoxCheck
message box. The command sets the Window Text in hEdit
to '#@WHOAMI /USER /FO CSV /NH | clip
' for ID_VIEW_RESET
and '##WHOAMI /USER /NH |clip
' for ID_VIEW_FO
. The hEdit
subclass proc uses the first two characters to determine whether to reset the Check Box nicely by asking 'Are You Sure?
' for '#@
' or just 'FOrce
' it for '##
'. Next we send a RETURN
Key to hEedit
.
The Help Menu Item displays a dropdown with the following commands:
The About.. command handled by IDM_ABOUT
. It displays the usual About Dialog.
The About Extensions command handled by ID_HELP_EXTENSIONS
. It displays a Dialog explaining the file extensions and how to set them.
The About Strings command handled by IDD_HELP_STRING
. It displays a Dialog explaining the string to search for and how to set them.
The About Path command handled by IDD_HELP_PATH
. It displays a Dialog explaining how to set the Path.
The About CheckBox command handled by IDD_HELP_CHECKBOX
. It displays a Dialog explaining how to reset the message box check box.
The About CMD.EXE command handled by IDD_HELP_CMD
. It displays a Dialog explaining how to USE the cmd.exe prompt.
The WM_INITMENU
message handler sets the default menu item for the Edit Menu command. This enables a double click to activate the default
The WM_SYSCOMMAND
message handler removes or restores the Menu when user pushes the ALT Key.
The WM_ACTIVATE
message handler sets the focus to the ListView
.
The Help Menu Command is About more than 'About '
The WndProc Function
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_CREATE - process main window creation and create all dialogs. Retrieve state and
// perform initial file list build, load first file into RichEdit
// WN_SIZE - process sizing message
// WM_COMMAND - process the application menu
// WM_SYSCOMMAND process SC_MENU to show or hide menu
// WM_ACTIVATE - set initial focus to ListView
// 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 wmEvent;
int mnh = 0;//MeNu Height
TCHAR curntFname [ 256 ];
TCHAR curntPath [ MAX_PATH ];
TCHAR tStr [ MAX_PATH ];
TCHAR extensionCue [ MAX_PATH ];
TCHAR nodeCD [ MAX_PATH - 1 ] = { 0 };
TCHAR searchStr [ MAX_PATH ] = { 0 };
TCHAR startInDir [ MAX_PATH ] = { 0 };
static RECT rcClip, rcOldClip;
HWND hWndFocus = NULL, hEdit = NULL, hTree = NULL, hList = NULL,
hRich = NULL, hFormView0 = NULL,
hFormView1 = NULL, hFormView2 = NULL, hCombo1 = NULL,
hCombo2 = NULL, hProgress = NULL,
hFormView3 = NULL, hTopView = NULL, hBottomView = NULL,
hwndStatus = NULL, hStatusView = NULL;
if ( WM_CREATE == message ) {
RECT rc = { 0, 0, 1380, 480 };
hTopView = CreateDialog ( GetModuleHandle ( NULL ),
MAKEINTRESOURCE ( IDD_TOP ), hWnd, editBoxProc );
if ( hTopView != NULL ) {
hEdit = GetDlgItem ( hTopView, IDC_EDIT2 );
ShowWindow ( hEdit, SW_SHOW );
ShowWindow ( hTopView, SW_SHOW );
}
hFormView0 = CreateDialog ( GetModuleHandle ( NULL ),
MAKEINTRESOURCE ( IDD_FORMVIEW0 ), hWnd, comboBoxProc );
if ( hFormView0 != NULL ) {
hCombo1 = GetDlgItem ( hFormView0, IDC_COMBO1 );
hCombo2 = GetDlgItem ( hFormView0, IDC_COMBO2 );
hProgress = GetDlgItem ( hFormView0, IDC_PROGRESS1 );
ShowWindow ( hCombo1, SW_SHOW );
ShowWindow ( hCombo2, SW_SHOW );
ShowWindow ( hProgress, SW_SHOW );
ShowWindow ( hFormView0, SW_SHOW );
}
else return FALSE;
hBottomView = CreateDialog ( GetModuleHandle ( NULL ),
MAKEINTRESOURCE ( IDD_BTTOM ), hWnd, bottomProc );
if ( hBottomView != NULL ) {
hStatusView = CreateDialog ( GetModuleHandle ( NULL ),
MAKEINTRESOURCE ( IDD_MIDDLE ), hBottomView, statusBarProc );
hwndStatus = GetDlgItem ( hStatusView, IDC_STATUS );
ShowWindow ( hwndStatus, SW_SHOW );
ShowWindow ( hStatusView, SW_SHOW );
ShowWindow ( hBottomView, SW_SHOW );
if ( hwndStatus ) {
SendMessage ( hwndStatus, SB_SETTEXT, 0, ( LPARAM ) L "Ready " );
SendMessage ( hwndStatus, SB_SETTEXT, 1 |
SBT_POPOUT, ( LPARAM ) L "Blah blah blah " );
SendMessage ( hwndStatus, SB_SETTEXT, 2 |
SBT_POPOUT, ( LPARAM ) L "Blah blah blah " );
SendMessage ( hwndStatus, SB_SETTEXT, 3 |
SBT_POPOUT, ( LPARAM ) L "|H:32|W:10| " );
SendMessage ( hwndStatus, WM_SIZE, 0, 0 );
}
hFormView3 = CreateDialog ( GetModuleHandle ( NULL ),
MAKEINTRESOURCE ( IDD_FORMVIEW3 ), hBottomView, richEditProc );
if ( hFormView3 != 0 ) {
hRich = GetDlgItem ( hFormView3, IDC_EDIT1 );
ShowWindow ( hRich, SW_SHOW );
ShowWindow ( hFormView3, SW_SHOW );
}
hFormView2 = CreateDialog ( GetModuleHandle ( NULL ),
MAKEINTRESOURCE ( IDD_FORMVIEW2 ), hBottomView, listViewProc );
if ( hFormView2 != 0 ) {
hList = GetDlgItem ( hFormView2, IDC_LIST1 );
ShowWindow ( hList, SW_SHOW );
ShowWindow ( hFormView2, SW_SHOW );
}
hFormView1 = CreateDialog ( GetModuleHandle ( NULL ),
MAKEINTRESOURCE ( IDD_FORMVIEW1 ), hBottomView, treeViewProc );
if ( hFormView1 != 0 ) {
hTree = GetDlgItem ( hFormView1, IDC_TREE1 );
ShowWindow ( hTree, SW_SHOW );
ShowWindow ( hFormView1, SW_SHOW );
}
ShowWindow ( hBottomView, SW_SHOW );
}
RECT wrc = { 0 }, wrccl = { 0 }, src = { 0 }, srccl = { 0 }, crc = { 0 }, crccl = { 0 },
brc = { 0 }, brccl = { 0 }, toprc = { 0 },
toprccl = { 0 }, rrc = { 0 }, rrccl = { 0 },
erc = { 0 }, erccl = { 0 }, lrc = { 0 }, lrccl = { 0 }, trc = { 0 }, trccl = { 0 };
GetWindowRect ( hWnd, &wrc );
GetClientRect ( hWnd, &wrccl );
GetWindowRect ( hTopView, &toprc );
GetClientRect ( hTopView, &toprccl );
GetWindowRect ( hBottomView, &brc );
GetClientRect ( hBottomView, &brccl );
GetWindowRect ( hFormView0, &erc );
GetClientRect ( hFormView0, &erccl );
GetWindowRect ( hFormView1, &trc );
GetClientRect ( hFormView1, &trccl );
GetWindowRect ( hFormView2, &lrc );
GetClientRect ( hFormView2, &lrccl );
GetWindowRect ( hFormView3, &rrc );
GetClientRect ( hFormView3, &rrccl );
GetWindowRect ( hStatusView, &src );
GetClientRect ( hStatusView, &srccl );
OffsetRect ( &toprc, -toprc.left, -toprc.top );
OffsetRect ( &erc, -wrc.left, -wrc.top );
OffsetRect ( &brc, -wrc.left, -wrc.top );
MoveWindow ( hFormView0, 0, 0, wrc.right, 30, TRUE );
MoveWindow ( hTopView, 0, 40, wrc.right, 50, TRUE );
MoveWindow ( hEdit, 10, 10, wrc.right, 30, TRUE );
MoveWindow ( hBottomView, 0, 90, brc.right-brc.left, wrc.bottom, TRUE );
MoveWindow ( hFormView1, 0, 0, trc.right - trc.left, lrc.bottom - lrc.top+4, TRUE );
MoveWindow ( hFormView2, trc.right - trc.left, 0,
lrc.right - lrc.left, lrc.bottom - lrc.top, TRUE );
GetWindowRect ( hFormView2, &lrc );
OffsetRect ( &lrc, -wrc.left, -wrc.top );
MoveWindow ( hFormView3, lrc.right-8, 0,
wrccl.right - lrc.right+8, lrc.bottom - lrc.top, TRUE );
MoveWindow ( hRich, 0, 0, rrccl.right+8, rrccl.bottom , TRUE );
MoveWindow ( hList, 0, 0, lrc.right - lrc.left - 16, lrc.bottom - lrc.top - 16, TRUE );
MoveWindow ( hStatusView, 0, wrccl.bottom - srccl.bottom - 85,
wrccl.right, srccl.bottom, TRUE );
MoveWindow ( hwndStatus, 0, 0, wrccl.right, srccl.bottom, FALSE );
ShowWindow ( hWnd, SW_NORMAL );
ShowWindow ( hwndStatus, SW_NORMAL );
DWORD sidLen = GetEnvironmentVariable ( L "HOMEDRIVE ", startInDir, 255 );
InitTreeViewItems ( hTree, startInDir );
sidLen = GetEnvironmentVariable ( L "STARTINDIR ", startInDir, 255 );
if ( 0 == sidLen ) {
GetEnvironmentVariable ( L "USERPROFILE ", startInDir, 256 );
}
SetCurrentDirectory ( startInDir );
GetCurrentDirectory ( MAX_PATH - 1, nodeCD );
sidLen = GetEnvironmentVariable ( L "SEARCHSTR ", searchStr, 255 );
if ( 0 == sidLen ) {
wsprintf ( searchStr, L "%s ", L "*.cpp;*.c " );
SetEnvironmentVariable ( L "SEARCHSTR ", searchStr );
}
StringCchPrintf ( tStr, MAX_PATH, L "Searching for %s in ", searchStr );
SendMessage ( hwndStatus, SB_SETTEXT, 0, ( LPARAM ) tStr );
SendMessage ( hwndStatus, SB_SETTEXT, 1 | SBT_POPOUT, ( LPARAM ) startInDir );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT,
( LPARAM ) L "Number of Files......... " );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT,
( LPARAM ) L "Line: 0 Column: 0......... " );
MoveWindow ( hStatusView, 0, wrccl.bottom - srccl.bottom - 85,
wrccl.right, srccl.bottom, TRUE );
MoveWindow ( hwndStatus, 0, 0, wrccl.right, srccl.bottom, FALSE );
SendMessage ( hwndStatus, WM_SIZE, 0, 0 );
ShowWindow ( hProgress, SW_NORMAL );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_NORMAL, 0 );
recursivefileSearch ( nodeCD, FALSE );
SetFocus ( hList );
SendMessage ( hProgress, PBM_SETMARQUEE, PBST_PAUSED - 3, 0 );
ShowWindow ( hProgress, SW_HIDE );
int iCount = ListView_GetItemCount ( hList );
int result = ListView_GetNextItem ( hList, -1, LVNI_SELECTED );
StringCchPrintf ( tStr, MAX_PATH, L "Number of Files %d. ", iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 0, ( LPARAM ) startInDir );
SendMessage ( hwndStatus, SB_SETTEXT, 1 | SBT_POPOUT, ( LPARAM ) tStr );
StringCchPrintf ( tStr, MAX_PATH, L "%d of %d Files. ", result+1 , iCount );
SendMessage ( hwndStatus, SB_SETTEXT, 2 | SBT_POPOUT, ( LPARAM ) tStr );
SendMessage ( hwndStatus, SB_SETTEXT, 3 | SBT_POPOUT,
( LPARAM ) L "Line: 0 Column: 0 " );
SendMessage ( hwndStatus, WM_SIZE, 0, 0 );
SetWindowText ( hWnd, L "Two Stage Search - Build File Set to SEARCH,
then SEARCH File Set for a strings! File List Initial
Build uses Path and Extensions used in previous invocation. " );
SetWindowText ( hEdit, L "To SEARCH list, select a string in View panel.
Hold control key down and push 'F 'to begin search.
PF3 will find next occurrence. When last occurrence is found,
next file containing string is searched for. " );
ListView_GetItemText ( hList, 0, 0, curntFname, MAX_PATH - 1 );
ListView_GetItemText ( hList, 0, 2, curntPath, MAX_PATH - 1 );
StringCchPrintf ( tStr, MAX_PATH, L "%s\\%s ", curntPath, curntFname );
FillRichEditFromFile ( hRich, ( LPCTSTR ) &tStr );
SetFocus ( hList );
return 0;
}
switch (message)
{
case WM_SIZE:
{
UINT width = GET_X_LPARAM ( lParam );
UINT height = GET_Y_LPARAM ( lParam );
RECT erc = { 0 }, erccl = { 0 }, rrc = { 0 }, rrccl = { 0 },
lrc = { 0 }, lrccl = { 0 }, trc = { 0 },
trccl = { 0 }, wrc = { 0 }, wrccl = { 0 }, brc = { 0 },
brccl = { 0 }, src = { 0 }, srccl = { 0 };
HWND hMainWnd = FindWindow ( L"TWOSTAGESEARCH", 0 );
hTopView = FindWindowEx ( hMainWnd, 0, L"#32770", 0 );
hEdit = getThisWindowHandle ( hTopView, IDC_EDIT2 );
hFormView0 = FindWindowEx ( hMainWnd, hTopView, L"#32770", 0 );
hCombo1 = getThisWindowHandle ( hFormView0, IDC_COMBO1 );
hCombo2 = getThisWindowHandle ( hFormView0, IDC_COMBO2 );
hBottomView = FindWindowEx ( hMainWnd, hFormView0, L"#32770", 0 );
hTree = getThisWindowHandle ( hBottomView, IDC_TREE1 );
hStatusView = FindWindowEx ( hBottomView, 0, L"#32770", 0 );
hwndStatus = FindWindowEx ( hStatusView, 0, L"MSCTLS_STATUSBAR32", 0 );
hFormView3 = FindWindowEx ( hBottomView, hStatusView, L"#32770", 0 );
hFormView2 = FindWindowEx ( hBottomView, hFormView3, L"#32770", 0 );
hFormView1 = FindWindowEx ( hBottomView, hFormView2, L"#32770", 0 );
hList = FindWindowEx ( hFormView2, 0, L"SYSLISTVIEW32", 0 );
hTree = FindWindowEx ( hFormView1, 0, L"SYSTREEVIEW32", 0 );
hRich = FindWindowEx ( hFormView3, 0, L"RICHEDIT50W", 0 );
if ( 0 == hTree ) { return 0;}
if ( 0 == hList ) { return 0;}
if ( 0 == hEdit ) { return 0;}
if ( 0 == hRich ) { return 0;}
if ( 0 == hFormView0 ) {return 0;}
if ( 0 == hFormView1 ) {return 0;}
if ( 0 == hFormView2 ) {return 0;}
if ( 0 == hFormView3 ) {return 0;}
MoveWindow ( hBottomView, 0, 90, width, height, TRUE );
GetClientRect ( hBottomView, &brccl );
GetWindowRect ( hBottomView, &brc );
GetClientRect ( hFormView3, &rrccl );
GetWindowRect ( hFormView3, &rrc );
GetClientRect ( hWnd, &wrccl );
GetClientRect ( hWnd, &erccl );
GetWindowRect ( hWnd, &erc );
GetWindowRect ( hFormView0, &erc );
GetClientRect ( hFormView1, &trccl );
GetWindowRect ( hFormView1, &trc );
GetWindowRect ( hFormView2, &lrc );
GetWindowRect ( hWnd, &wrc );
GetClientRect ( hFormView2, &lrccl );
GetWindowRect ( hFormView3, &rrc );
GetClientRect ( hRich, &rrccl );
GetWindowRect ( hStatusView, &src );
GetClientRect ( hStatusView, &srccl );
OffsetRect ( &erc, -wrc.left, -wrc.top );
OffsetRect ( &trc, -wrc.left, -wrc.top );
OffsetRect ( &lrc, -wrc.left, -wrc.top );
OffsetRect ( &rrc, -wrc.left, -wrc.top );
OffsetRect ( &src, -wrc.left, -wrc.top );
OffsetRect ( &brc, -wrc.left, -wrc.top );
if ( wParam == SIZE_RESTORED ) {
MoveWindow ( hTopView, 0, 40, wrc.right, 50, TRUE );
MoveWindow ( hFormView0, 0, 0, wrc.right, 30, TRUE );
MoveWindow ( hEdit, 10, 0, wrc.right, 30, TRUE );
SendMessage ( hWnd, message, SIZE_MAXIMIZED, lParam );
}
if ( wParam == SIZE_MAXIMIZED || wParam == SIZE_MAXSHOW ) {
MoveWindow ( hTopView, 0, 40, wrc.right, 50, TRUE );
MoveWindow ( hFormView0, 0, 0, wrc.right, 30, TRUE );
MoveWindow ( hEdit, 10, 0, wrc.right, 30, TRUE );
MoveWindow ( hList, 0, 0, lrc.right - lrc.left - 16, lrc.bottom- lrc.top-16, TRUE );
MoveWindow ( hFormView1, 0, 0, trc.right - trc.left, height-brc.top+12, TRUE );
MoveWindow ( hFormView2, trc.right - trc.left - 8, 0,
lrc.right - lrc.left, height - brc.top+16, TRUE );
MoveWindow ( hTree, trccl.left, trccl.top,
lrc.left - trc.left, height-brc.top+12, TRUE );
MoveWindow ( hFormView3, lrc.right-8, 0,
width - lrc.right+8, height - brc.top+16, TRUE );
MoveWindow ( hRich, 0, 0, width - lrc.right+8, height-brc.top+16, TRUE );
MoveWindow ( hStatusView, 0, lrc.bottom - lrc.top+8,
brc.right-brc.left, srccl.bottom, TRUE );
MoveWindow ( hwndStatus, 0, 0, wrccl.right, srccl.bottom, TRUE );
if ( wParam == SIZE_MAXIMIZED ) {
SendMessage ( hWnd, message, SIZE_MAXSHOW, lParam );
}
}
return 0;
} // case WM_SIZE:
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
wmEvent = HIWORD ( wParam );
// For menu items lparam is zero
// Parse the menu selections:
switch (wmId)
{
case ID_FILE_CD:
{
hEdit = getThisWindowHandle ( hWnd, IDC_EDIT2 );
hTree = getThisWindowHandle ( hWnd, IDC_TREE1 );
if ( hEdit && GetWindowTextLength ( hEdit ) < 255 ) {
SetWindowText ( hWnd, L " USE CURSOR KEYS to Navigate in the TREEVIEW,
select a folder path, then push ENTER to search path. " );
SetWindowText ( hEdit, L " " );
StringCchCopy ( extensionCue, 259,
L "Navigate in the TreeView using the CURSOR Keys, " );
StringCchCat ( extensionCue, 259,
L "Left will close an open folder but Right will open a folder. " );
StringCchCat ( extensionCue, 259,
L " Push 'ENTER ' Key to begin build. " );
Edit_SetCueBannerTextFocused ( hEdit, extensionCue, TRUE );
SetFocus ( hTree );
return 0;
}
}
break;
case ID_FILE_TEMP:
{
hEdit = getThisWindowHandle ( hWnd, IDC_EDIT2 );
hTree = getThisWindowHandle ( hWnd, IDC_TREE1 );
if ( hEdit && GetWindowTextLength ( hEdit ) < 255 ) {
SetWindowText ( hWnd, L " USE CURSOR KEYS to Navigate in the TREEVIEW,
select a folder path, then push ENTER to save path temporarily. " );
SetWindowText ( hEdit, L " " );
StringCchCopy ( extensionCue, 259,
L "Navigate in the TreeView using the CURSOR Keys, " );
StringCchCat ( extensionCue, 259,
L "Left will close an open folder but Right will open a folder. " );
StringCchCat ( extensionCue, 259,
L " Push 'ENTER ' Key to select folder. " );
Edit_SetCueBannerTextFocused ( hEdit, extensionCue, TRUE );
SetFocus ( hTree );
return 0;
}
}
break;
case ID_FILE_CHANGE:
{
hEdit = getThisWindowHandle ( hWnd, IDC_EDIT2 );
hTree = getThisWindowHandle ( hWnd, IDC_TREE1 );
if ( hEdit && GetWindowTextLength ( hEdit ) < 255 ) {
SetWindowText ( hWnd, L " USE CURSOR KEYS to Navigate in the TREEVIEW
to select a folder path, then push ENTER. The path will be saved permanently " );
SetWindowText ( hEdit, L " " );
StringCchCopy ( extensionCue, 259,
L "Navigate in the TreeView using the CURSOR Keys, " );
StringCchCat ( extensionCue, 259,
L "Left will close an open folder but Right will open a folder. " );
StringCchCat ( extensionCue, 259,
L " Push 'ENTER ' Key to select folder. " );
Edit_SetCueBannerTextFocused ( hEdit, extensionCue, TRUE );
SetFocus ( hTree );
return 0;
}
}
break;
case ID_EDIT_CMD:
{
hEdit = getThisWindowHandle ( hWnd, IDC_EDIT2 );
SetWindowText ( hEdit, L "#&cmd /f:on /k set prompt=&P&_&l&D&G&L&T&G &&
ver && echo Path Completion is turned ON. For diewctory use control +
D. Control + Shift + F for Files. " );
sendMeMyKey ( hEdit, VK_RETURN );
return 0;
}
break;
case ID_VIEW_RESET:
case ID_VIEW_FO:
{
hEdit = getThisWindowHandle ( hWnd, IDC_EDIT2 );
if ( wmId == ID_VIEW_RESET ) {
SetWindowText ( hEdit, L "#@WHOAMI /USER /FO CSV /NH |clip " );
}
else {
SetWindowText ( hEdit, L "##WHOAMI /USER /NH |clip " );
}
// This command "wmic USERACCOUNT get sid|findstr "1001 "|clip "
// or this one "wmic USERACCOUNT get sid|find "1001 "|clip " " will
// produce a string ending with 2 spaces and 2 crlf line endings
// This could be used by setting L '\0 ' six bytes before end of line
// as is done with '#@whoami ' to cut the suffix from the string.
sendMeMyKey ( hEdit, VK_RETURN );
return 0;
}
break;
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case ID_HELP_EXTENSIONS:
DialogBox ( hInst, MAKEINTRESOURCE ( IDD_HELP_EXT ), hWnd, AboutExt );
break;
case IDD_HELP_STRING:
DialogBox ( hInst, MAKEINTRESOURCE ( IDD_HELP_STRING ), hWnd, AboutString );
break;
case IDD_HELP_PATH:
DialogBox ( hInst, MAKEINTRESOURCE ( IDD_HELP_PATH ), hWnd, AboutPath );
break;
case IDD_HELP_CHECKBOX:
DialogBox ( hInst, MAKEINTRESOURCE ( IDD_HELP_CHECKBOX ), hWnd, AboutCheckBox );
break;
case IDD_HELP_CMD:
DialogBox ( hInst, MAKEINTRESOURCE ( IDD_HELP_CMD ), hWnd, AboutCMD );
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_INITMENU:
{
HMENU hMenu = GetMenu ( hWnd ); // == the application menu
HMENU hsubmenu = GetSubMenu ( hMenu, 1 ); // == the Edit item of the menu
BOOL bsmdi = SetMenuDefaultItem ( hsubmenu, ID_EDIT_CMD, FALSE );
DWORD dwerr = GetLastError ( );
return 0;
}
break;
case WM_SYSCOMMAND:
{
switch ( LOWORD ( wParam ) & 0xfff0 ) {
case SC_KEYMENU:
if ( ( !GetMenu ( hWnd ) ) ) {
HMENU hMenu = LoadMenu ( hInst, MAKEINTRESOURCE ( IDC_TWOSTAGESEARCH ) );
SetMenu ( hWnd, hMenu );
mnh = GetSystemMetrics ( SM_CYMENU );//MeNu Height
UpdateWindow ( hWnd );
return 0;
}
else {
SetMenu ( hWnd, NULL );
mnh = 0;
UpdateWindow ( hWnd );
return 0;
}
default:
return DefWindowProc ( hWnd, message, wParam, lParam );
}
}
break;
case WM_ACTIVATE:
{
if ( LOWORD ( wParam ) & WA_ACTIVE ) {
hWndFocus = GetFocus ( );
hList = getThisWindowHandle ( hWnd, IDC_LIST1 );
if ( hWndFocus ) {
SetFocus ( hWndFocus );
return 0;
}
else {
hWndFocus = hList;
SetFocus ( hList );
if ( hList && ListView_GetItemCount ( hList ) >= 1 ) {
if ( ListView_GetNextItem ( hList, -1, LVNI_SELECTED ) == -1 ) {
// select this one
ListView_SetItemState ( hList, 0, LVIS_SELECTED, LVIS_SELECTED );
}
}
return 0;
}
}
else
return -1;
}break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return DefWindowProc ( hWnd, message, wParam, lParam );
}
InitTreeViewItems: Give them a Path to Follow
This function receives 2 parameters, the handle to the Tree Control and the HOMEDRIVE
. It adds the ROOT
Item to the Tree Control, then gets the logical drive string and calls the AddItemToTree
function adding each of the drives to the Tree
and calling the getDirectories
function for each drive to populate the first level, producing the populate on demand behavior of the TreeView
and expanding the HOMEDRIVE
. This is the one that should contain the start in directory, expanding it results in a TVN_ITEMEXPANDING
message bring sent to the TreeView
Control, followed by a TVN_ITEMEXPANDED
message. When control returns, the HOMEDRIVE
has been populated and expanded to the start in directory. Next, it selects the highlighted tree node, which should be the start in directory. The two additional functions called by this routine are also called by the TreeView
repeatedly.
The InitTreeViewItems Function
// 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, TCHAR * startPoint ) {
// Define the ROOT of each drive
HTREEITEM nodeParent;
HTREEITEM hti; // the root folder item to insert.
TV_ITEM tvi = { 0 };
// Defibe the temporary drive string
TCHAR szDTemp [ ] = TEXT ( " :\\ " ); // first position will contain drive letter.
// Define string to hold Drive List.
TCHAR szTemp [ 512 ];
// add a root item to the tree
hti = ( HTREEITEM ) TVI_ROOT; //initialize root item
if ( GetLogicalDriveStrings ( 512 - 1, szTemp ) ) { // szTemp contains 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, 0, 1 ); // 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 == *startPoint ) {
TreeView_Expand ( hwndTV, nodeParent, TVE_EXPAND );
}
while ( *p++ ); // loop until p points at a '\0 ', incrementing p afterwards.
}
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 ( hwndTV );
TreeView_Select ( hwndTV, nodeParent, TVGN_CARET );
return TRUE; // go back and tell them it 's okay.
}
AddItemToTree : Populate the Branches
There isn't too much to say about this routine, after all, it is just initializing the parameters to the TreeView_InsertItem
macro. But look closely at what it is doing. One of the members of the tvins structure used by TreeView_InsertItem
is hInsertAfter
. This is being set to hPrev
. This means hPrev
must retain its state between calls, so we make it a static
variable. If it weren't static
, hPrev
would always be TVI_FIRST
. This would cause the Tree
to be sorted Descending
with the last item first and the first item last.
The AddItemToTree Function
//
// 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, int tviiImage, int tviiSelectedImage ) {
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 = 0; // 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;
}
getDirectories: and hang 'm in the Tree
This function accepts 2 parameters, an HTREEITEM
and an LPTSTR
. It uses the FindFirstFileExW
- FindNextFile
sequence loop to get all of the child items of the HTREEITEM
, hDir
. It calls AddItemToTree
for each item, setting the parameters to show the appropriate images, indicating a file or folder.
The getDirectories Function
// FUNCTION: FindFirstFile in specified directory then repeatedly
// FindNextFile until there aint no mo(aren 't any left to find).
// Sets image index to show 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;
HWND hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hTree = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_TREE1 );
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 ) {
AddItemToTree ( hTree, hDir, win32fd.cFileName, 0, 1 );
}
else {
AddItemToTree ( hTree, hDir, win32fd.cFileName, 4, 5 );
}
}
}
while ( FindNextFile ( hFind, &win32fd ) != 0 );
FindClose ( hFind );
}
The editBoxProc and its Alter-ego: The editBoxSubClassProc
The editBoxProc
is a very simple function. THE WM_INITDIALOG
message handler creates the control and sets the Window Subclass
for hEdit
to editBoxSubClassProc
. The third parameter, IDC_EDIT2
is the control's id but here it is the uIdSubclass
parameter and it could be any UINT_PTR
as long as it uniquely identifies the subclass procedure to the system. The fourth parameter is the dwRefData
and is passed to the procedure with each invocation. WM_INITDIALOG
also establishes the position of hFormView0(hDlg)
and moves hEdit
into it. The WM_COMMAND
message processes only the WM_SETFOCUS
message to set the focus on the ListView
. WM_SIZE
resizes the control when necessary.
The editBoxProc Function
// Subclass the proc and handle vk_f3, vk_f6 and vk_f6. handle vk_return to
// invoke system commands.
INT_PTR CALLBACK editBoxProc ( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) {
UNREFERENCED_PARAMETER ( lParam );
int wmId, wmEvent;
static HWND hwndEdit2;
HWND hEdit = NULL, hMainWnd = NULL, hList = NULL;
if ( WM_INITDIALOG != message ) {
hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
if ( 0 == hMainWnd ) { return 0;}
hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
if ( 0 == hEdit ) { return 0;}
}
switch ( message ) {
case WM_INITDIALOG:
{
hEdit = FindWindowEx ( hDlg, 0, L "Edit ", 0 );
if ( 0 == hEdit ) {
RECT rc = { 0 }; // 20, 1380, 20
GetClientRect ( hDlg, &rc );
hEdit = CreateWindowExW ( WS_EX_CLIENTEDGE, // extended styles
L "EDIT ", //control 'class ' name
L " ", //control caption
//control style
WS_CHILD | WS_VISIBLE |
ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_NOHIDESEL, // | ES_WANTRETURN,
10, //position: left
42, //position: top
rc.right, //width
25, //height
hDlg, //parent window handle
//control 's ID
( HMENU ) ( IDC_EDIT2 ),
hInst, //application instance
0 ); //user defined info
//BOOL bTrue =
SetWindowSubclass ( hEdit, editBoxSubClassProc, ( UINT_PTR ) IDC_EDIT2, 0 );
RECT eRc = { 0 };
GetWindowRect ( hDlg, &eRc );
MoveWindow ( hDlg, 0, 10, eRc.right, 30, TRUE );
MoveWindow ( hEdit, 10, 0, eRc.right, 23, TRUE );
return FALSE;
}
}
case WM_COMMAND:
wmId = LOWORD ( wParam );
wmEvent = HIWORD ( wParam );
switch ( wmId ) {
case IDC_EDIT2:
if ( HIWORD ( wParam ) == WM_SETFOCUS ) {
hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 );
if ( 0 == hList ) {
return 0;
}
if ( ListView_GetItemCount ( hList ) >= 1 )
if ( ListView_GetNextItem ( hList, -1, LVNI_SELECTED ) == -1 ) {
SetFocus ( hList );
// select first one
ListView_SetItemState
( hList, -1, 0, LVIS_DROPHILITED | LVIS_SELECTED | LVIS_FOCUSED );
ListView_SetItemState ( hList, 0, LVIS_SELECTED, LVIS_SELECTED );
return 0;
}
}
break;
default:
break;
}
break;
case WM_SIZE:
{
UINT width = GET_X_LPARAM ( lParam );
UINT height = GET_Y_LPARAM ( lParam );
if ( hEdit ) {
MoveWindow ( hEdit, 0, 0, width, height, TRUE );
}
return 0;
}break;
}
return ( INT_PTR ) FALSE;
}
The editBoxSubClassProc
, on the other hand, is a bit more complicated. In the uMsg switch
, the WM_GETDLGCODE
handler listens for wParam
equal to VK_F3
, Vk_F5
, VK_F6
and VK_RETURN
. For all of these, it returns DLGC_WANTMESSAGE
, preventing the control, hEdit
, from performing the default processing for these keys. Instead, we will process them. For the function keys, we send VK_F3
and VK_F6
to Richedit
, VK_F5
is sent to ListView
.
For VK_RETURN
, we get the window text from hEdit
. Comparing the first two characters with '#!
', when they are equal, they are stripped off and the _tsystem
function is called with the rest of the text. What the user sees depends on the command they entered. The command 'time' they would see the console window with the current time and a prompt to enter the new time, but if the command was 'time /t
', it would execute so quickly they would see nothing but the command's return code in hEdit. In such situations, the user could add && pause to the end of the command to keep the console open.
Comparing the first two characters with '#@
', when they are equal, they are stripped off and the _tsystem
function is called with the rest of the text. The text should have been put there by the Reset_CheckBox
Menu selection. The next step is to wait for a short time for the command to complete, then paste the results into hEdit
and get the window text again. This is the output of the 'WMIC /USER /FO CSV /NH |clip
' command. It has the username
and SID
separated by a comma. We split the string
at the comma. The SID
is enclosed in double quotes, so to get rid of the first, we add one to the pointer. To get rid of the last, we overlay it with a character zero. We use the SID
to construct the Registry Key to the CheckBox Flag and put it in the Environment Variable. Then we construct the Registry Query to find the reset flag entry and delete it. The Reg Delete asks if you are sure. The query and delete were ran by '#!
' which writes the return code to the hEdit
window. What the user sees is the console window with the confirmation prompt and command's return code in hEdit
.
Forcing the Reset of the SHMessageBoxCheck Checkbox
Comparing the first two characters with '##
', when they are equal, they are stripped off and the _tsystem
function is called with the rest of the text. The text should have been put there by the Force_Reset_CheckBox
selection. The next step is to wait for a short time for the command to complete, then paste the results into hEdit
and get the window text again. This is the output of the 'WMIC /USER /NH |clip
' command. It has the username
and SID
separated by a space. We split the string
at the space. We use the SID
to construct the Registry Key to the CheckBox
Flag and put it in the Environment Variable. Then we construct the Registry Query to find the reset flag entry and delete it. The Reg Delete uses the '/;F
' switch, which forces the delete and does not ask if you are sure. The query and delete were run by _tsystem
to get the return code and inform the user of success or failure using the SHMessageBoxCheck
message box.
The ID_EDIT_CMD
Menu selection uses the prefix '#&
' in the window text of hEdit
to start a command console. Instead of _tsystem
, it uses the ShellExecute
function. The cmd.exe is started with the /f switch
set on and executes the 'ver
' built-in function to display the version of windows, just like a regular console. It sets the prompt and prints the message 'Path Completion is on...
'. The reason this Menu Entry was included is if the user tried to start a console using '#!
' Two Stage Search would become unresponsive until the console was exited. This console shows the Date and Time in the prompt. The path completion is very useful once you learn how to use it.
The editBoxSubClassProc Function
// The EditBox was subclassed to intercept and relay vk_f3, vk_f5 and vk_f6 when the
// EditBox had keyboard focus. It also receives vk_return to invoke system commands.
LRESULT CALLBACK editBoxSubClassProc
( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData ) {
UNREFERENCED_PARAMETER ( uIdSubclass );
UNREFERENCED_PARAMETER ( dwRefData );
HWND hMainWnd = FindWindow ( L "TwoStageSearch ", 0 );
HWND hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1 );
HWND hEdit = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT2 );
HWND hRich = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_EDIT1 );
TCHAR searchStr [ MAX_PATH ] = { 0 };
TCHAR nodeCD [ MAX_PATH - 1 ] = { 0 };
TCHAR fsiaf [ 256 ] = { 0 }; // Find this String In A File
switch ( uMsg ) {
case WM_GETDLGCODE:
if ( lParam ) {
LPMSG lpmsg = ( LPMSG ) lParam;
if ( lpmsg->message == WM_KEYDOWN )
switch ( lpmsg->wParam ) {
case VK_F3:
case VK_F5:
case VK_F6:
case VK_RETURN:
return DLGC_WANTMESSAGE;
break;
default:
return DefSubclassProc ( hWnd, uMsg, wParam, lParam );
}
break;
}
break;
case WM_KEYDOWN:
switch ( wParam ) {
case VK_F3:
{
return sendMeMyKey ( hRich, VK_F3 );
}
break;
case VK_F5:
{
return sendMeMyKey (hList, VK_F5 );
}
break;
case VK_F6:
{
return sendMeMyKey ( hRich, VK_F6 );
}
break;
case VK_RETURN:
{
TCHAR titleText [ 256 ] = { 0 };
TCHAR tStr [ 256 ] = { 0 };
int retCode = 0;
GetWindowText ( hEdit, titleText, 255 );
if ( wcsncmp ( titleText, L "#! ", 2 ) == 0 ) {
TCHAR * cmdLine = titleText; cmdLine += 2;
TCHAR rgky [ 256 ];
retCode = _tsystem ( cmdLine );
StringCchPrintf ( rgky, 255, L "Return Code was %d ", retCode );
SetWindowText ( hEdit, rgky );
return 0;
}
if ( wcsncmp ( titleText, L "#@ ", 2 ) == 0 ) {
TCHAR * cmdLine = titleText; cmdLine += 2;
TCHAR rgky [ 256 ], *prgky;
_tsystem ( cmdLine );
Sleep ( 10 );
SetWindowText ( hEdit, L " " );
SendMessage ( hEdit, WM_PASTE, 0, 0 );
GetWindowText ( hEdit, rgky, 255 );
TCHAR * token = wcstok_s ( rgky, L ", ", &prgky );
token = wcstok_s ( nullptr, L " ", &prgky );
++token; token [ wcslen ( token ) - 1 ] = L '\0 ';
StringCchPrintf ( rgky, 255, L
"hku\\%s\\Software\\Microsoft\\Windows\\
CurrentVersion\\Explorer\\DontShowMeThisDialogAgain ",
token );
SetEnvironmentVariable ( L "rgky ", rgky );
StringCchCopy ( titleText, 255, L "#!reg query %rgky% /f
{51842606-D64C-4EDE-AF3F-4EE7AD3755A3} /e &® delete %rgky% " );
StringCchCat ( titleText, 255, L "
/v {51842606-D64C-4EDE-AF3F-4EE7AD3755A3}&&pause " );
GetEnvironmentVariable ( L "rgky ", fsiaf, 255 );
SetWindowText ( hEdit, titleText );
sendMeMyKey ( hEdit, VK_RETURN );
return 0;
}
if ( wcsncmp ( titleText, L "## ", 2 ) == 0 ) {
TCHAR * cmdLine = titleText; cmdLine += 2;
TCHAR rgky [ 256 ], *prgky;
_tsystem ( cmdLine );
Sleep ( 10 );
SetWindowText ( hEdit, L " " );
SendMessage ( hEdit, WM_PASTE, 0, 0 );
GetWindowText ( hEdit, rgky, 255 );
TCHAR * token = wcstok_s ( rgky, L " ", &prgky );
token = wcstok_s ( nullptr, L " ", &prgky );
StringCchPrintf ( rgky, 255, L "hku\\%s\\Software\\Microsoft\\
Windows\\CurrentVersion\\Explorer\\DontShowMeThisDialogAgain ", token );
SetEnvironmentVariable ( L "rgky ", rgky );
StringCchCopy ( titleText, 255, L "reg query %rgky% /f
{51842606-D64C-4EDE-AF3F-4EE7AD3755A3} /e &® delete %rgky% " );
StringCchCat ( titleText, 255, L " /v {51842606-D64C-4EDE-AF3F-4EE7AD3755A3} /f " );
retCode = _tsystem ( titleText );
if ( retCode == 0 ) {
SetWindowText ( hEdit, L "MessageBox CheckBox Successfully RESET " );
SHMessageBoxCheck ( 0, L "MessageBox CheckBox
Successfully RESET ", L "MessageBox CheckBox Successfully RESET ",
MB_OK | MB_ICONINFORMATION | MB_APPLMODAL, IDOK,
L "{51842606-D64C-4EDE-AF3F-4EE7AD3755A3} " );
}
else {
SetWindowText ( hEdit, L "MessageBox CheckBox FAILED To RESET " );
SHMessageBoxCheck ( 0, L "MessageBox CheckBox
FAILED To RESET (It 's not set!). ",
L "MessageBox CheckBox FAILED To RESET ",
MB_OK | MB_ICONINFORMATION | MB_APPLMODAL,
IDOK, L "{51842606-D64C-4EDE-AF3F-4EE7AD3755A3} " );
}
return 0;
}
if ( wcsncmp ( titleText, L "#& ", 2 ) == 0 ) {
TCHAR * cmdLine = titleText; cmdLine += 2;
GetCurrentDirectory ( 255, nodeCD );
ShellExecute ( NULL, L " ",
L "cmd.exe ", cmdLine, nodeCD, SW_SHOW );
SetWindowText ( hEdit, L '\0 ' );
return 0;
}
return TRUE;
}
break;
}
default:
return DefSubclassProc ( hWnd, uMsg, wParam, lParam );
}
return 0;
}
Retaining State: Now WHERE Was I?
The Relationship between SET and SETX
The first time Two Stage Search is used, there is no pre-existing place in the file system to search or file extension to seek out. The ListView
could be left blank and the user could choose the root path and set of file extensions to build the File List with. But arbitrary choices have been made to act as an example of what the program does. If the user makes their own choices, the values they selected should be honored the next time they want to use the Two Stage Search. There are many ways to save these values allowing them to be recalled. The method chosen here is to use the 'SETX ' function. The problem is, 'SETX
' is not a Windows function, it is a built-in CMD.EXE function. It is a batch command. It requires the use of ShellExecuteEx
with the SHELLEXECUTEINFO
structure initialized with the required information, the cmdToExeCute
must contain the SETX
verb, the variable name and its value. The value must be enclosed in double quotes if it contains white space. The syntax resembles the 'SET
' verb but the comma is replaced by a space. The 'SET
' verb, or the SetEnvironmentVariable
Windows function writes the information to the Environment Block being used by the program. The 'SRTX
' verb writes the information to the Registry. This means the new value cannot be retrieved with 'SET
' or GetEnvironmentVariable
until the program is re-started. If you want to retrieve it immediately, you have to save it with GetEnvironmentVariable
. Using 'SET
' and 'SETX
' together, we can persist the state of the program across function calls and across program invocations. ShellExecuteEx
is used to get the return code from CMD.EXE because 'SETX
' always succeeds if the parameters are correct, but this doesn't mean the value was saved. We get the output from the command to show to the user but we also get the process return code to report the possible failure of the command.
The persist_This function
// This function writes to the Registry using the Batch SETX
// command to save environment variables permanently, until
// they are changed by the User. Also, the SHMessageBoxCheck
// function writes to the Registry to prevent future displays
// of the message box. Refer to Menu Command VIEW>Reset_CheckBox
// to restore display of message
DWORD WINAPI persist_This ( TCHAR * tStr ) {
TCHAR cmdToExeCute [ 512 ] = { 0 };
// Below is a sample output from both a successful and
// an unsuccessful function execution. Notice that
// SETX sets ERRORLEVEL to zero either way, or possibly
// doen 't set it at all. All we can do is give the
// process enough time to finish before we close the
// handle and report any error code returned by ShellExecuteEx
//
// --- Successful Execution.Notice the path is enclosed in quotes.
// SUCCESS: Specified value was saved.
// errorlevel is 0
// Command Line was "C:\Windows\System32\cmd.exe "
// /k Setx STARTINDIR "C:\Users\All Users "
// errorlevel is %ERRORLEVEL%
// Command Line was %CMDCMDLINE%
//
// ---Un-Succeddful Execution. Notice the path is not enclosed in quotes.
// This is seen as a third default parameter by SETX
// ERROR: Invalid syntax. Default option is not allowed more than '2 ' time(s).
// Type "SETX /? " for usage.
// errorlevel is 0
// Command Line was "C:\Windows\System32\cmd.exe "
// /k Setx STARTINDIR C:\Users\All Users
// errorlevel is %ERRORLEVEL%
// Command Line was %CMDCMDLINE%
//
// Get a temporary path
TCHAR nodeCD [ MAX_PATH - 1 ] = { 0 };
GetCurrentDirectory ( MAX_PATH - 1, nodeCD );
TCHAR tempFname [ MAX_PATH ];
TCHAR tempPath [ MAX_PATH ];
TCHAR tempStr [ 1024 ] = { 0 };// buffer for setx output
CHAR setXresults [ 1024 ] = { 0 };// buffer for setx output
TCHAR resultFileName [ MAX_PATH ];
DWORD dwResult = GetTempPath ( MAX_PATH, tempPath );
UINT uiResult = 0;
// The length of the temp path(result) must be between 0 and MAX_PATH
if ( dwResult < MAX_PATH || dwResult != 0 )
// Get a temp file name based on temp path and 'search '
uiResult = GetTempFileName ( tempPath, L "persist_This ", 0, tempFname );
if ( uiResult == 0 ) {
// if this user can 't get temp file name,
GetEnvironmentVariable ( L "USERPROFILE ", tempPath, 255 );
// and create your own temp file name
StringCchPrintf ( resultFileName, 255, L "%s\\result1.txt ", tempPath );
}
else {
StringCchPrintf ( resultFileName, 255, L "%s ", tempFname );
}
#pragma region Line Continuation
StringCchCopy ( tempStr, 511, tStr );
StringCchCat ( tempStr, 511, L " >> %s & " );
StringCchCat ( tempStr, 511, L "echo errorlevel is %%ERRORLEVEL%% >> %s & " );
StringCchCat ( tempStr, 511, L "echo Command Line was: " );
StringCchCat ( tempStr, 511, L "%%CMDCMDLINE%% >> %s " );
StringCchPrintf ( cmdToExeCute, 511, tempStr, tempFname, tempFname, tempFname );
#pragma endregion for Command to Execute String
SHELLEXECUTEINFO ShExecInfo = { 0 };
ShExecInfo.cbSize = sizeof ( SHELLEXECUTEINFO );
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = NULL;
ShExecInfo.lpFile = L "cmd.exe ";
ShExecInfo.lpParameters = cmdToExeCute;
ShExecInfo.lpDirectory = nodeCD;
ShExecInfo.nShow = SW_HIDE;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx ( &ShExecInfo );
DWORD dwCode = STILL_ACTIVE;
BOOL gotReturn = FALSE;
for ( size_t i = 0; i < 100 && dwCode == STILL_ACTIVE; ++i ) {
if ( ShExecInfo.hProcess ) gotReturn = GetExitCodeProcess ( ShExecInfo.hProcess, &dwCode );
MSG msg; while ( PeekMessage ( &msg, NULL, 0, 0, PM_REMOVE ) ) DispatchMessage ( &msg );
Sleep ( 10 );
} // process is still active
// if process is still active it will eventually return 0
if ( STILL_ACTIVE == dwCode ) dwCode = 0;
if ( 0 != dwCode ) {
StringCchPrintf ( cmdToExeCute, 255, L "Return Code is %d.
File System ERROR.\nDATA was not be saved. Try it again! ", dwCode );
MessageBox ( 0, cmdToExeCute, L "persist_This Return Code ",
MB_OK | MB_ICONEXCLAMATION | MB_APPLMODAL );
}
CloseHandle ( ShExecInfo.hProcess );
HANDLE resultFile = NULL;
DWORD dwRead = 0;
resultFile = CreateFile ( tempFname, GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
BOOL bSuccess = ReadFile ( resultFile, setXresults, MAX_PATH, &dwRead, NULL );
if ( !bSuccess || dwRead == 0 ) {
DWORD dwErr = GetLastError ( );
StringCchPrintf ( cmdToExeCute, 255, L "Return Code is %d.
\nThe data was not saved!\nMaybe you should do it again. ", dwErr );
MessageBox ( 0, cmdToExeCute, L "persist_This Results File Read Failed! ",
MB_OK | MB_ICONEXCLAMATION );
}
if ( dwRead != 0 ) {
StringCchPrintf ( cmdToExeCute, 255, L "%S ", setXresults );
TCHAR * pCmd = cmdToExeCute;
for ( size_t i = 0; i < 4; i++ ) {
pCmd = wcschr ( ( LPWSTR ) pCmd, L '\r ' );
pCmd++;
}
*pCmd = L '\0 ';
SHMessageBoxCheck ( 0, cmdToExeCute, L "SET and
PERSIST this ENVIRONMENT VARIABLE Return Code ",
MB_OK | MB_ICONINFORMATION | MB_APPLMODAL, IDOK,
L "{51842606-D64C-4EDE-AF3F-4EE7AD3755A3} " );
if ( uiResult>0 ) {
StringCchPrintf ( cmdToExeCute, 255, L "%s\\per*.tmp ", tempPath );
DeleteFile ( cmdToExeCute );
}
}
CloseHandle ( resultFile );
return 0;
}
EnumChildWindows and EnumChildProc: Retaining the STATE of the Window Handles
The Window handles are initialized by the Create Window functions, so we can 't persist them with 'SETX
'. The handles are a part of the Window, so all we have to do is find the Window to get the handle. To find the main window, we can use FindWindow
with the Class Name or Window Name, called inline. We could FindWindowEx
to find a child window but the EnumChildProc
is a simpler method to use. But you do have to write a function to call it. There are actually 2 functions but it is still the easiest to use. The first function in the sequence is getThisWindowHandle
, which accepts the parent window handle and the control Id of the child window of that parent and returns the handle of that child window. It uses a struct
variable containing a handle and a uint control id. getThisWindowHandle
sets the handle member to NULL
and the uint
member to the control id. Now it calls EnumChildWindows
with the parent handle, the address of EnumChildProc
, cast to a WNDENUMPROC
and the address of the struct
containing the control id. On return, the structure contains the child window handle which can be NULL
. EnumChildWindows
is an enumerator function. It works like a 'for (child in parents tree) do child processing', calling EnumChildProc
for every child handle it finds. EnumChildProc
calls GetDlgCtrlID
with the handle of the child window and returns the id of that control. If that id matches the id in the struct
, the child window handle is copied into the struct
's window handle and b_isNotDone
is set to false
, which ends enumeration. Control passes to getThisWindowHandle
, which returns the child window handle to the caller. Here are the struct
and the two functions.
The getThisWindowHandle and EnumChildProc Functions
// Struct to Identify Window by CTRL-ID
typedef struct ecwData {
HWND hCWind;
UINT iD;
} ecpWnd, lpecpWnd;
// EnumChildProc is called repeatedly by EnumChildWindows function
// it returns FALSE or cannot find any more child windows of the HWND
// that EnumChildWindows gave it. In this version of EnumChildProc,
// we find the control id of the Child Window enumerations and compare
// it to our controlId. If it matches we set the HWND of our ECPWND
// structure to the hwndChild of the enumeration and set b_isNotDone
// to false. We return b_isNotDone to EnumChildWindows, which stops
// calling EnumChildProc so that the bext statement is executed and
// our ECPWND may now contain the Child HWND or zero. Therefore we
// have to test thr HWND to satisfy the Structured Attributes Language
// specification because we don 't want SAL to be dis-satisfied.
BOOL WINAPI EnumChildProc ( HWND hwndChild, LPARAM lPar ) {
lpecpWnd * pecw = (lpecpWnd*)(LPARAM )lPar;
BOOL b_isNotDone = TRUE;
ULONG dlgId = GetDlgCtrlID (hwndChild );
if ( dlgId == pecw->iD ) {
pecw->hCWind = hwndChild;
b_isNotDone = FALSE;// signal end of enumeration
}
return b_isNotDone;
}
// put an ECPWND structure on the stack and put it 's address
// in a pointer variable and call the EnumChildWindows function
// with the hWnd, the address of the EnumChildProc and controlId.
HWND WINAPI getThisWindowHandle ( HWND hWnd, UINT controlId ) {
ecpWnd ecw;
ecw.hCWind = NULL;
ecw.iD = controlId;
lpecpWnd * pecw = &ecw;
EnumChildWindows ( hWnd, ( WNDENUMPROC ) EnumChildProc, ( LPARAM ) pecw );
return ecw.hCWind;
}
Sample getThisWindowHandle Function Call
HWND hList = getThisWindowHandle ( hMainWnd, ( ULONG ) IDC_LIST1);
if (hLidt == 0) return 0;
Connecting the Viewer to the List: The sendMeMyKey Function
The richEditProc
calls a function named sendMeMyKey
from several in the WM_NOTIFY
message handler and apparently stops processing. In reality, it has passed the ball(of execution) to another runner. The sendMeMyKey
Function takes 2 parameters, a window handle and a virtual key code, returning a BOOL
. It uses the SendInput
function to insert an array of input events into the input stream of the window whose handle you supplied to sendMeMyKey
. It doesn't send message to a window. It synthesizes the event in the window that has focus. So, the first thing it does is set focus to the window you sent and then send the array consisting of a key down and key up of the virtual character. If the input is sent as 2 single events, other programs were sometimes grabbing focus, causing problems, the most serious of which was a program dead-lock. The way this works is almost like the user set the focus and then typing the keys, but this is subject to UIPI blocking so you can only send input to applications of equal or lower integrity level.
The sendMeMyKey Function
// Set the focus to intended target abd send it a keydown and keyup sequence
BOOL __forceinline sendMeMyKey ( HWND hFocus, unsigned char pfKey ) {
SetFocus ( hFocus );
INPUT keyPr [ 2 ] = { 0 };
keyPr [ 0 ].type = INPUT_KEYBOARD;
keyPr [ 0 ].ki.dwFlags = 0; // KEYEVENTF_KEYDOWN?;
keyPr [ 0 ].ki.wScan = 0;
keyPr [ 0 ].ki.time = 0;
keyPr [ 0 ].ki.dwExtraInfo = 0;
keyPr [ 0 ].ki.wVk = pfKey;
keyPr [ 1 ].type = INPUT_KEYBOARD;
keyPr [ 1 ].ki.dwFlags = KEYEVENTF_KEYUP;
keyPr [ 1 ].ki.wScan = 0;
keyPr [ 1 ].ki.time = 0;
keyPr [ 1 ].ki.dwExtraInfo = 0;
keyPr [ 1 ].ki.wVk = pfKey;
SendInput ( 2, keyPr, sizeof ( INPUT ) );
return TRUE;
}
FillRichEditFromFile: How to Load a File into RichEdit
When a key press or mouse click is received, one of the most common actions executed is to load a file into RichEdit
. The FillRichEditFromFile
function is called from five different points in the listViewProc
and once from the WndProc
. All of these calls, except for the one from WndProc
, occur as the result of a keypress or mouse click. So it seemed appropriate to discuss the function here, after sendMeMyKey
. It is code written by and copyrighted by Microsoft. It receives two parameters, The handle to a RICHEDIT
Instance and the path and name of the file to load and if successful, creates an EDITSTREAM
structure with the address of the EditStreamCallback
function and the dword
pointer to the file handle created earlier. It sends an EM_STREAMIN
message to the RICHEDIT
Control with the wParam
set to SF_TEXT
and the EDITSTREAM
cast to an LPARAM
. if the sendmessage returns non-zero and the structure 's dwerror
member is zero, it sets fSuccess
to TRUE
and closes the file handle. The EditStreamCallback
function is called multiple times to read the entire file. The Microsoft Visual Studio Online Documentation Article "How to Use Streams " is the source document of these two functions. If you need more information, you can find it there.
Tips on Using Two Stage Search
When the program is executed for the first time, it checks the Environment Block for two variables, The STARTINDIR
and the SEARCHSTR
. If they are not present, they are initialized to the same value as your USERPROFILE
for STARTINDIR
and "*.cpp;*.c" for SEARCHSTR
. These values can be changed at any time thereafter and the value will be put into the Registry and therefore, they will be found and used in subsequent executions of the program.
Changing the STARTINDIR: The ROOT Folder of the File List
To change the start in directory, select the 'File ' menu command and choose one of Change_Directory|Temp_Change_CD|CD_AND_Build. The first item, Change_Directory
will allow you to change the directory and save the change to the Registry. The second item will allow you to change the directory for the current execution but will not save the change to the Registry. The third item will also change the directory but not save it to the Registry. For each of these items, the TreeView
is given keyboard focus. Using the cursor keys to move to the desired folder does not disturb the entries in the ListView
. When the Tree Control receives an ENTER Key, it set the current Tree Node as the current directory. This allows files from several roots to be included in the File List. It is possible to click on the Tree Node with the mouse but this will clear the ListView
and populate it with the child items of the node.
Changing the SEARCHSTR: The File Extension Set Used in the File List Build
To change the types of files included in the File List, click on the Combobox EditCtrl
on the left side of the application window or the dropdown button for that Combobox
. When ENTER is pushed or the Start Button is clicked the Combobox
message handler compares the EditCtrl
text with the current value of SEARCHSTR
and if they differ the new value is saved in the Registry
. If the EditCtrl
is empty, the last known value is retrieved and used.
The Rules for the File Extension Set
A single suffix or ending part of a File Name. It can be the entire File Name but not the beginning part with the ending part missing.
An asterisk followed by a period and a file type extension
One or more of the previous rule, separated by semicolons, i.e.,*.a;*.b;*.c;*.d;
...etc.
How To ENTER the String To Search For
There are two different ways to enter the String To Search For. Select a string
in the Viewer Panel and push 'f
' while holding down the control key. You can select a string
of characters with no white space in the string
by clicking on any of the characters. To select a string
with spaces, click on one end and drag the highlight to the other end.
The other method is to click on the Combobox EditCtrl
on the right side of the application window and typing in the string
. The string
is added to the Combobox List
allowing a previously entered string
to be recalled. The string
is saved in the Registry
but a new string
replaces the old one.
How to Find the Next or Previous Occurrence of the String to Search For
To find the next occurrence of the string you have entered push PF3. If the search reaches the end of file it searches the next file(s) until a match is found. If it reaches the end of the file list it wraps around and searches from the beginning. If the Shift is held down, the search direction is backwards. PF6 will always search backwards. PF5 will find the next file containing a match. When searching backwards and the search reaches the beginning of the file, the last occurrence of the previous file will be found.
How to Open the File in Another App
To use the 'OPEN WITH ' Dialog, Right Click on the file in the ListView
. To open the file location in File Explorer, Double left click or if the file is selected you can push ENTER.
How to Run a Batch Command in Two Stage Search
To execute a cmd.exe command or batch file, click on the EditBox
Control under the ComboBox
es and enter '#!
' followed by the command to execute. That will open a Console Window to execute the command. If you want to see the output of the command enter '&& pause
' after the command. This causes the system to wait for user input. Do not attempt to keep this window open permanently. Instead, select "Edit>CMD Console" This will give you an open Console Window without freezing Two Stage Search.
How to Get Rid of the Annoying Message Box That Is Shown When You Enter a String
The SHMessageBoxCheck
is displayed whenever a value is saved in the Registry. Since it is purely informational, you can get rid of it by CHECKING the Checkbox
in the lower left corner of the dialog. After you get rid of the annoying message, you may still be annoyed by an occasional message box displaying an error message. When this happens, the new value may not have been saved, therefore you should consider re-entering the value.
How to Re-Display the Annoying Message Box
On the Main Menu, select "View>Reset_CheckBox | Force_Reset_CheckBox". The first choice, Reset_CheckBox
, will display a console window to ask for confirmation. If you have changed your mind, type an 'n
' to cancel the request or a 'y
' to reset it. The second choice, Force_Reset_CheckBox
does not ask for confirmation, it just does it.
The Help Menu
The Help Menu has 6 choices, they are :
- About...____________ Two Stage Search
- About Extensions__ How to Change Extensions
- About Strings______ How to enter String to search for
- About Path ________ How to Change the Path for File List build
- About CheckBox___ How to reset Checkbox
- About CMD.EXE____ How to invoke a CMD.EXE Console
These 6 help panels, always available at the click of a button, should provide enough information to make Two Stage Search easy enough to use that a separate Help File will not be necessary. If enough readers disagree and want a Help file, I will be happy to provide one.
Points of Interest
When I first decided to publish the Least Frequent Character Offset Algorithm, I considered using it in a program that used SSE2 or AVX2 Instructions, but when I tested it on an older computer, it didn't work. The computer didn't have the instructions. So I pulled out an older program, U2Charfunc
, that used SSE Instructions and discovered that it performed just as well on a Windows 8.1 machine, which only had SSE Instructions, as it did on a Windows 10 machine. So I am going with the older program.
The window in the middle, the RESIZING ListView
window, seemed to me to be a much simpler concept than using the client background of the main window to simulate a splitter bar. Especially if you want more than 2 windows, say 3 or 9. For 3 panels, you do have to keep the top and bottom borders from being moved but that is simple with cursor clipping. With 9 panels, you can use all 4 borders of the center panel to control the other 8 panels. For the panels between 3 and 9, you have to use at least 1 more resizing border if you want to make them resizable.
History
- 30th August, 2019: Initial release