Introduction
The Windows Find function only finds files by the date they were modified that I was aware of, I wanted a function to find files by the date they were created or modified, so I wrote this program.
Background
I initially thought of using the DatePicker
resource but it would take a lot of scrolling to get to a year in the past because you would have to scroll through every month. The spin control works a lot better for this purpose, you can scroll the year separately. The spin controls are initially set to today's date and there ranges set to (months) 1-12, (days) 1-31, and (years) 1970-2038. A CTime
object range is limited to these values for the year; to extend the range you would have to use COleDateTime
, its range is January 1, 100 - December 31, 9999.
Using the code
A folder browser is needed, so I copied DirDialog from Chris Maunder's Time Stamp program. You can use it in any application by including the DirDialog.h file in your program and calling it using:
void CFindWhenDlg::OnBrowse()
{
UpdateData(TRUE);
CDirDialog dlg;
dlg.m_strTitle = _T("Choose directory");
dlg.m_strSelDir = m_strFile;
if (dlg.DoBrowse())
{
m_strFile = dlg.m_strPath;
m_path = m_strFile;
UpdateData(FALSE);
}
}
The list control is confusing to most new programmers, the best and easiest to implement is from Progamming Windows with MFC by Jeff Prosise. Chapter 10 has an example program of using a structure to store your list items in, calling AddItem
which uses MFC's LVN_GETDISPINFO
callback which puts the displayable items in the list control and assigns each an address pointer that is later used to sort the items. When you click a column, LVN_COLUMNCLICK
message is sent to your application where you can sort the items in ascending or descending order. The list control header has an arrow showing which direction we are going, this is done by:
COLORMAP cm = {RGB(0, 0, 0), RGB(255, 165, 0)};
m_imglstSortIcons.Create(9, 5, ILC_COLOR24 | ILC_MASK, 2, 0);
m_bmpUpArrow.LoadMappedBitmap(IDB_HDRUP, 0, &cm, 1);
m_nUpArrow = m_imglstSortIcons.Add(&m_bmpUpArrow, RGB(255, 255, 255));
m_bmpDownArrow.LoadMappedBitmap(IDB_HDRDOWN, 0, &cm, 1);
m_nDownArrow = m_imglstSortIcons.Add(&m_bmpDownArrow, RGB(255, 255, 255));
And later calling:
void CFindWhenDlg::SetSortIcon(int nCol)
{
ASSERT(m_list1);
CHeaderCtrl* pHeaderCtrl = m_list1.GetHeaderCtrl();
ASSERT(pHeaderCtrl);
HDITEM hdrItem;
int nPhysicalCol = nCol;
int col;
for (col = pHeaderCtrl->GetItemCount(); col >= 0; col--)
{
hdrItem.mask = HDI_FORMAT | HDI_IMAGE;
pHeaderCtrl->GetItem(nPhysicalCol, &hdrItem);
if (m_nSortColumn != 0 &&
m_nSortColumn - 1 == col)
{
hdrItem.iImage = m_nUpArrow;
hdrItem.fmt = hdrItem.fmt & HDF_JUSTIFYMASK |
HDF_IMAGE | HDF_STRING | HDF_BITMAP_ON_RIGHT;
}
else if (m_nSortColumn != 0 &&
-m_nSortColumn - 1 == col)
{
hdrItem.iImage = m_nDownArrow;
hdrItem.fmt = hdrItem.fmt & HDF_JUSTIFYMASK |
HDF_IMAGE | HDF_STRING | HDF_BITMAP_ON_RIGHT;
}
else
hdrItem.fmt = hdrItem.fmt & HDF_JUSTIFYMASK | HDF_STRING;
pHeaderCtrl->SetItem(col, &hdrItem);
}
}
To show that the current search is created or modified, I added code to update the list header:
void CFindWhenDlg::SetHeaderText(TCHAR *tStr)
{
HDITEM hdi;
hdi.mask = HDI_TEXT;
hdi.pszText = tStr;
pHeaderCtrl->SetItem(1, &hdi);
}
Points of Interest
The CString CompareNoCase()
function works well on the files names for the sort routine but it doesn't sort the string version of numeric data such as the date 02/07/2005, so I save the CTime
value in my structure and display it using:
case 1:
{
CString szCreated;
if(m_lcid == 1033)
szCreated = pItem->cCreated.Format("%m/%d/%Y");
else
szCreated = pItem->cCreated.Format("%d/%m/%y");
lstrcpy(pDispInfo->item.pszText, szCreated);
}
break;
For those who prefer day/month/year format, the program gets local time info using LCID GetSystemDefaultLCID()
. The value 1033 is SUBLANG_ENGLISH_US, LANG_ENGLISH
. I tested it using 2057 which is SUBLANG_ENGLISH_UK, LANG_ENGLISH
.
For the compare: pItem1->cCreated > pItem2->cCreated
compares two integers.
Another problem found in this type of program is, the RecurseDirectories()
thread is in a very busy loop and does not allow the WM_QUIT
message to be processed. The following is added to theApp
:
void CFindWhenApp::ProcessIdleMsg()
{
MSG msg;
while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
::PostQuitMessage(0);
break;
}
if (!PreTranslateMessage(&msg))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
Now messages can be processed by calling ProcessIdleMsg
inside of the loop. This didn't solve the problem of the search thread running after the window is closed, so I added a "panic" button to stop the search. Also, my program was using 100% of the CPU time, so I added a Sleep(10)
to slow it down a little.
This program uses the Windows XP Themes that has a problem if you use the dwExStyle |= LVS_EX_GRIDLINES
in your list control. The lines are not drawn when you scroll the list. I found in the MSDN Knowledge base, the following article: MSDN Knowledge base.
Credits
DirDialog
- Chris Maunder.
History
- 08 February - Version 1.0.
- 12 February - Version 1.1. Added new functions, fixed bugs.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.