Интро
Рано или поздно, но любой WinCE программер (впрочем как и программер пишущий под другие ОС) сталкивается с необходимостью использования диалога для выбора файлов. И вот тут начинаются проблемы. Стандартный диалог открытия файла в WM, мягко говоря, неудобный. нет, если вам надо выбирать файлы, лежащие только в стандартных папках My Pictures, My Documents и т.д., то подойдет и стандартный диалог, но если нужно дать пользователю возможность выбора файлов из любой папки с переходом по подкаталогам, то стандартный диалог нам в этом не товарищ. Как выходят из этой ситуации? Очень просто - пишут свой диалог выбора файлов. =) Вот чем-то подобным мы сейчас и займемся.
Как там с этим у Windows?
Стандартный диалог вызывается следующим образом:
TCHAR szFileName[MAX_PATH] ={0};
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hInstance = g_hInst;
ofn.hwndOwner = hWnd;
ofn.lpstrFile = szFileName;
ofn.lpstrFilter = _T("All Files (*.*)\0*.*\0\0");
ofn.nMaxFile = MAX_PATH;
ofn.lpstrInitialDir = L"Windows\\";
GetOpenFileName(&ofn);
в результате получаем следующее:
красиво, но не удобно. =)
Попробуем создать что-то более правильное.
Поехали...
Сначала определимся с функционалом. Наш диалог должен уметь отображать файлы и каталоги по маске, давать возможность ходить по каталогам, выбирать файлы или каталоги, отображать необходимую информацию о них. Диалог должен легко интегрироваться в любой проект и не нести с собой лишних файлов и поддерживать любые разрешения экрана.
Диалог должен вызываться одной функцией, и иметь минимум входных параметров.
Чтобы не передавать в функцию диалога кучу параметров, сведем их в единую структуру (что позволит нам ее расширять в дальнейшем)
typedef struct tagFSD {
DWORD cbSize;
HWND hwndOwner;
TCHAR szFileName[MAX_PATH];
TCHAR szTitle[32];
TCHAR szInitialDir[MAX_PATH];
DWORD Flags;
} FILESYSTEMDLGW, *LPFILESYSTEMDLG;
Рассмотрим парамеры структуры:
- cbSize - размер структуры (для будущего использования)
- hwndOwner - хендл родительского окна (нужен для правильной организации диалога)
- szFileName - буфер полученного имени файла
- szTitle - заголовок окна (для будущего использования)
- szInitialDir - начальный каталог сканирования (для будущего использования, пока ищем от корня)
- Flags - флаги всякие (для будущего использования)
Перейдем непосредствено к диалогу. Для первой версии диалога нам достаточно двух дочерних контролов EDIT и LISTVIEW. Поскольку у нас стоит условие не использования лишних файлов, то придется отказаться от файла ресурсов. Само окно будем создавать с помощью WinAPI функции DialogBoxIndirect, структуру описания диалога для которой можно собрать руками в памяти. С помощью этой функции можно создавать диалоги с любым количеством дочерних контролов, но мы опишем только родительское окно, а дочерние контролы создадим с помощью CreateWindow. Это проще, чем разбираться с достаточно сложной структурой описания контролов.
Проделаем это.
1. Создадим буфер в памяти
HGLOBAL membuf = GlobalAlloc(GMEM_ZEROINIT,1024);
if (!membuf) return FALSE;
2. Заполним буфер данными
DLGTEMPLATE* dt = (DLGTEMPLATE*)GlobalLock(membuf);
dt->cdit = 0;
dt->style = WS_POPUP | DS_MODALFRAME | DS_SETFOREGROUND ; // window styles
dt->cx = dt->cy = 200; // window size
LPWORD p = (LPWORD)(dt+1);
*p++=0; // end of data
*p++=0;
3. Запустим диалог
GlobalUnlock(membuf);
BOOL bResult = FALSE;
if (IDOK == DialogBoxIndirect(GetModuleHandle(NULL),(LPDLGTEMPLATE)membuf,g_pFSData->hwndOwner,WndProc))
{
lstrcpy(Data->szFileName, g_szSelectedPath);
bResult = TRUE;
}
GlobalFree(membuf);
return bResult;
Теперь нужно разобраться с диалоговой функцией окна (у нас она зовется WndProc).
Берем станндартный шаблон диалоговой функции и добавляем реакцию на WM_INITDIALOG:
// Create a Done button and size it.
SHINITDLGINFO shidi;
shidi.dwMask = SHIDIM_FLAGS;
shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;
shidi.hDlg = hDlg;
SHInitDialog(&shidi);
// Create controls
g_hwndInfo = CreateWindow(L"STATIC",0,WS_CHILD | WS_VISIBLE | SS_LEFT | WS_BORDER,0,0,100,10,hDlg,0,GetModuleHandle(NULL),NULL);
::SetWindowText(g_hwndInfo,L"Select file");
g_szCurrentPath[0] = 0;
g_szSelectedPath[0] = 0;
g_hwndList = CreateWindow(L"LISTBOX",0,WS_CHILD | WS_VISIBLE | WS_VSCROLL | LBS_NOTIFY | WS_BORDER,0,10,100,10,hDlg,0,GetModuleHandle(NULL),NULL);//::GetDlgItem(hDlg,8001);
BuildFilesList(g_hwndList);
и на WM_SIZE:
RECT rt;
::GetClientRect(g_hwndInfo,&rt);
int nHeight = 16;
::GetClientRect(hDlg,&rt);
::SetWindowPos(g_hwndInfo,NULL,0,0,rt.right-rt.left,nHeight,SWP_NOZORDER);
::SetWindowPos(g_hwndList,NULL,0,nHeight,rt.right-rt.left,rt.bottom-rt.top-nHeight,SWP_NOZORDER);
Пытливый взгляд заметит неопознанную функцию BuildFilesList. Ага, как видно из названия, функция строит список файлов. Поглядим, что она делает:
void BuildFilesList(HWND hItem, LPTSTR szInitDir = NULL)
{
::SendMessage(hItem,LB_RESETCONTENT,0,0);
TCHAR szDir[MAX_PATH] = {0};
lstrcpy(szDir,g_szCurrentPath);
if (NULL != szInitDir)
{
lstrcat(szDir,szInitDir);
}
if (szDir[lstrlen(szDir)] != L'\\')
{
lstrcat(szDir,L"\\");
}
lstrcpy(g_szCurrentPath,szDir);
lstrcat(szDir,L"*.*");
WIN32_FIND_DATA fd ={0};
HANDLE hFind = FindFirstFile(szDir,&fd);
if (INVALID_HANDLE_VALUE != hFind)
{
do
{
TCHAR szFile[MAX_PATH] = {0};
lstrcpy(szFile,fd.cFileName);
if (0 != (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
::SendMessage(hItem,LB_INSERTSTRING,0,(LPARAM)szFile);
::SendMessage(hItem,LB_SETITEMDATA,0,1);
} else
{
::SendMessage(hItem,LB_ADDSTRING,0,(LPARAM)szFile);
int cnt = (int)::SendMessage(hItem,LB_GETCOUNT,0,0);
if (cnt>0)::SendMessage(hItem,LB_SETITEMDATA,cnt-1,0);
}
}while (FindNextFile(hFind,&fd)>0);
}
FindClose(hFind);
if (lstrcmp(g_szCurrentPath,L"\\"))
{
::SendMessage(hItem,LB_INSERTSTRING,0,(LPARAM)L"..");
::SendMessage(hItem,LB_SETITEMDATA,0,2);
}
::SendMessage(hItem,LB_SETCURSEL,0,0);
}
Вобщем-то ничего особенного, стандартный поиск файлов с заполнением ListBox. В будущем расширим эту функцию для поска только каталогов или поиска файлов по маске, пока же будет так, как есть.
Теперь осталось сделать навигацию по каталогам и вывести информацию о текущем файле.
Снова идем в WndProc и добавляем обработку событий ListBox
case WM_COMMAND:
// select OK button
if (LOWORD(wParam) == IDOK)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
// select file by stylus
if (LBN_DBLCLK == HIWORD(wParam))
{
HWND hList = (HWND)lParam;
TCHAR szFileName[MAX_PATH] = {0};
int idx = (int)::SendMessage(hList,LB_GETCURSEL,0,0);
::SendMessage(hList,LB_GETTEXT,idx,(LPARAM)szFileName);
int Data = ::SendMessage(hList,LB_GETITEMDATA,idx,0);
if (Data == 2)
{
OneDirUp(g_szCurrentPath);
BuildFilesList(hList);
} else if (Data == 1) BuildFilesList(hList,szFileName);
SetFileInfo(g_hwndInfo);
}
// select file by keyboard
if (LBN_SELCHANGE == HIWORD(wParam))
{
HWND hList = (HWND)lParam;
TCHAR szFileName[MAX_PATH] = {0};
int idx = (int)::SendMessage(hList,LB_GETCURSEL,0,0);
::SendMessage(hList,LB_GETTEXT,idx,(LPARAM)szFileName);
lstrcpy(g_szSelectedPath,g_szCurrentPath);
lstrcat(g_szSelectedPath,szFileName);
SetFileInfo(g_hwndInfo);
}
break;
Текущий каталог мы храним в глобальной переменной g_szCurrentPath. Функция OneDirUp предназначена для перехода в родительский каталог:
void OneDirUp(LPTSTR szPath)
{
int MaxCnt = lstrlen(szPath);
TCHAR *p = szPath + MaxCnt-2;
while ((--MaxCnt >0)&&(*p != L'\\'))p--;
*p = 0;
}
Функция SetFileInfo заполняет StaticText информацией о выбранном файле/каталоге:
void SetFileInfo(HWND hInfo = NULL)
{
TCHAR szFileInfo[300] = {0};
HANDLE hFile = CreateFile(g_szSelectedPath,GENERIC_READ,0,0,OPEN_EXISTING,0,0);
DWORD dwSize = 0;
DWORD dwAttr = GetFileAttributes(g_szSelectedPath);
if (INVALID_HANDLE_VALUE != hFile)
{
BY_HANDLE_FILE_INFORMATION hfi = {0};
if (GetFileInformationByHandle(hFile,&hfi))
{
dwSize = hfi.nFileSizeLow;
} else
{
dwSize = GetFileSize(hFile,NULL);
}
}
CloseHandle(hFile);
TCHAR pref = L' ';
if (dwSize>1000)
{
dwSize = dwSize / 1024;
pref = L'K';
}
if (dwSize>1000)
{
dwSize = dwSize / 1024;
pref = L'M';
}
TCHAR *p = g_szSelectedPath + lstrlen(g_szSelectedPath)-1;
while ((p > g_szSelectedPath)&&(*p!=L'\\')) p--;
p++;
if (0 == (dwAttr & FILE_ATTRIBUTE_DIRECTORY))
{
wsprintf(szFileInfo,L"%s %d%cb",p,dwSize,pref);
} else
{ if (!lstrcmp(p,L".."))
lstrcpy(szFileInfo,g_szCurrentPath);
else
wsprintf(szFileInfo,L"\\%s",p);
}
if (NULL != hInfo)
::SetWindowText(hInfo,szFileInfo);
else
::MessageBox(0,szFileInfo,L"File info",MB_ICONINFORMATION | MB_OK);
}
Ну вот, простой диалог выбора файла готов.
Теперь можно подумать над расширением функционала диалога.
Для начала, выводить расширенную информацию об объектах.
продолжение следует...
|