Серверы FTP предоставляют однин из самых распространенных типов сервиса Internet. Сервера FTP обычно хранят на своих дисках большое количество различных файлов, доступных пользователям сервера. Администратор сервера FTP может разграничить доступ к серверу FTP различным категориям пользователей. Так например, для всех желающих, которые подключаются к серверу FTP под именем anonymus, могут быть доступны только некоторые каталоги, содержащие файлы общего доступа. Чтобы получить доступ к другим файлам, администратор сервера должен зарегистрировать вас и дать вам имя и пароль для подключения к серверу.
В зависимости от того, какими правами вы обладаете, вы можете иметь право не только загружать файлы с сервера, но также и записывать файлы со своего компьютера на сервер FTP, а также изменять структуру каталогов самого сервера - создавать, удалять и переименовывать каталоги.
Надо отметить, что все операции, связанные с реорганизацией структуры каталогов сервера и даже с загрузкой данных на сервер как правило, недоступны обычным пользователям. Их выполняют только администратор сервера и его помощники. Поэтому, если у вас нет собственного FTP сервера, могут возникнуть трудности с отладкой приложений загружающих файлы на сервер. Если в вашем распоряжении есть локальная сеть, мы рекомендуем установить на ней свой сервер FTP и отлаживать свои программы в рамках локальной сети.
Как и любое другое приложение, использующее для работы с Internet классы WinInet, вы должны первым делом создать сеанс связи представленный объектом класса CInternetSession.
Затем вы должны выполнить соединение с сервером FTP. Для этого надо вызвать метод GetFtpConnection класса CInternetSession. Методу GetFtpConnection надо указать адрес сервера FTP. Если указанный сервер не будет обнаружен в сети, то данный метод вызовет исключение и завершится с ошибкой. Это может быть следствием того, что вы неправильно указали имя сервера или сервер не активен в данный момент времени.
В случае успешного завершения, метод GetFtpConnection создает объект класса CFtpConnection. Этот объект будет представлять в вашем приложении указанный сервер FTP.
Вызывая методы класса CFtpConnection ваше приложение может определить и изменить текущий каталог сервера, выполнять поиск файлов и каталогов с определенными именами, обмениваться с сервером файлами, удалять и переименовывать файлы, изменять структуру каталогов - создавать, переименовывать и удалять каталоги и т. д.
Так, чтобы определить и изменить путь текущего каталога на сервере FTP надо воспользоваться методами GetCurrentDirector и SetCurrentDirectory класса CFtpConnection. Создать каталог вы можете при помощи метода CreateDirectory, удалить с помощью метода RemoveDirectory. Другие методы класса CFtpConnection, предназначенные для работы с системой каталогов сервера, вы можете просмотреть в разделе “Методы для управления каталогами”.
Чтобы загрузить файл с сервера FTP вы можете обратится к методу GetFile, а чтобы загрузить файл на сервер - к методу PutFile класса CFtpConnection. Эти методы работают как команда COPY операционной системы MS-DOS. Вы указываете им только имя файла на диске локального компьютера и имя файла на сервере. Все остальное они делают самостоятельно.
Вы также можете открыть файл на сервере FTP с помощью метода OpenFile класса CFtpConnection. Если указанный файл удалось открыть, этот метод возвращает объект класса CInternetFile. Далее вы можете использовать методы Read и Write данного класса чтобы прочитать или записать открытый файл.
Если у вас есть соответствующие права на данном сервере FTP, вы можете переименовать файлы на сервере или удалить их. Для этого надо использовать методы Rename и Remove класса CFtpConnection.
Как только приложение установило соединение с сервером FTP, вы можете просмотреть содержимое указанного каталога или попытаться найти на сервере файлы с определенным именем. Для этого надо создать объект класса CFtpFileFind, указав конструктору класса объект класса CFtpConnection, представляющий соединение с сервером. Затем с помощью методов FindFile и FindNextFile вы можете осуществить поиск файлов и каталогов с указанными именами. С помощью методов класса CFileFind, являющегося базовым классом для CFtpFileFind, вы можете определить различные характеристики обнаруженных файлов и каталогов.
В следующем разделе мы предложим вашему вниманию приложение ConsoleFtp, которое выполняет соединение с указанным сервером FTP. Вы можете использовать исходный текст этого приложения как шаблон для изучения основных приемов работы с серверами FTP.
Создайте новый проект. Выберите из меню File строку New. На экране появится диалоговая панель New. Выберите из списка, расположенного в этой панели строку Project Workspace и нажмите кнопку OK. Откроется диалоговая панель New Project Workspace. В качестве типа создаваемого приложения укажите Console Application и введите в поле Name имя проекта - ConsoleFtp. Затем нажмите кнопку Create. Microsoft Visual C++ создаст новый проект.
Мы будем использовать для нашего приложения классы библиотеки MFC. Чтобы настроить проект соответствующим образом, вы должны выбрать из меню Build строку Settings. На экране откроется диалоговая панель Project Settings, содержащая различные настройки проекта. Откройте на этой диалоговой панели страницу General, выберите из списка Microsoft Foundations Classes строку Use MFC in a Shared DLL и нажмите кнопку OK.
По умолчанию, новый проект не содержит ни одного файла. Создайте новый текстовый файл. Для этого еще раз выберите из меню File строку New и в открывшейся панели New укажите тип текстового файла - Text File. Нажмите кнопку OK. Откроется окно текстового редактора. Наберите в нем текст приложения, представленный нами в листинге 2.1. Сохраните набранный текст в файле под именем ConsoleFtp.cpp, записав его в каталог проекта.
Затем выберите из меню Insert строку Files into Project. На экране появится диалоговая панель Insert Files into Project. Выберите из списка файлов в этой панели файл ConsoleFtp.cpp и нажмите кнопку Add. Файл ConsoleFtp.cpp будет добавлен к проекту.
Листинг 2.1. Файл ConsoleFtp.cpp
//============================================================ // Приложение ConsoleFtp. Выполняет соединение с заданным // сервером FTP // // (C) Фролов Г.В., 1997 // E-mail: frolov@glas.apc.org // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov //============================================================ // Включаемый файл для библиотеки MFC #include <afx.h> // Включаемый файл для классов WinInet #include <afxinet.h> // Включаемый файл для консольного ввода/вывода #include <iostream.h> // Включаемый файл для стандартных функций #include <stdlib.h> //============================================================ // Главная функция приложения //============================================================ int main(int argc, char* argv[]) { // Если приложение запущено без параметра, отображаем на // экране формат вызова приложения if (argc != 2) { cout << "Programm format: ConsoleFtp <URL>" << endl; cout << " <URL> - URL address of FTP" << endl << endl; return -1; } // Записываем параметр, указанный при вызове приложения в // текстовую строку sUrlAdress. Он должен содержать URL // адрес сервера FTP CString sUrlAdress; sUrlAdress = argv[1]; // Отображаем адрес сервера FTP на экране cout << "URL address: " << sUrlAdress << endl << endl; // Определяем переменные, в которые будут записаны // отдельные компоненты адреса, указанного пользователем // Имя сервера CString sServer; // Имя объекта на который указывает адрес URL CString sObject; // Номер порта INTERNET_PORT nPort; // Тип сервиса или тип протокола DWORD dwServiceType; // Разбираем адрес URL, записанный в строке sUrlAdress if (!AfxParseURL(sUrlAdress, dwServiceType, sServer, sObject, nPort)) { // В случае ошибки выводим сообщение и завершаем // работу приложения cout << "AfxParseURL Error" << endl; return -1; } // Проверяем, соответствует ли указанный адрес URL // серверу FTP. Для этого тип сервиса должен быть ftp:// if(dwServiceType != AFX_INET_SERVICE_FTP) { // Если адрес не соответствует серверу FTP выводим // соответствующее предупреждение и завершаем приложение cout << "URL Address not FTP server" << endl; return -1; } // Указатель на объект класса CInternetSession CInternetSession* m_InternetSession = NULL; // Инициализируем сеанс работы с WinInet - создаем объект // класса CInternetSession catch m_InternetSession = new CInternetSession("Connecter"); // Исключения, вызванные в этом блоке try обрабатываются // следующим блоком catch try { // Определяем указатель на объект класса CFtpConnection CFtpConnection* m_FtpConnection = NULL; // Пытаемся соединииться с сервером sServer m_FtpConnection = m_InternetSession -> GetFtpConnection( sServer ); // Выводим сообщение об успешном соединении cout << "Connect to FTP server established" << endl; // Закрываем соединение с сервером FTP m_FtpConnection -> Close(); // Удаляем объект m_FtpConnection delete( m_FtpConnection ); } // Так как исключение в блоке try может быть вызвано только // методом GetFtpConnection, достаточно определить // обработчик исключения класса CInternetException catch (CInternetException* pEx) { // Обрабатываем исключение CInternetException TCHAR szErr[1024]; // временный буфер для сообщения // Выводим сообщение об ошибке if (pEx->GetErrorMessage(szErr, 1024)) cout << "Error: " << szErr << endl << endl; // Удаляем исключение pEx->Delete(); } // Завершаем сеанс связи m_InternetSession -> Close(); // Удаляем объект m_InternetSession delete(m_InternetSession); return 0; }
Постройте проект. Вы получите исполнимый файл приложения ConsoleFtp.exe. Запустите его на выполнение из командной строки MS-DOS Prompt (закрывать Windows не надо). В качестве параметра укажите URL адрес сервера FTP с которым вы желаете соединиться. Чтобы приложение правильно восприняло этот адрес, на забудьте указать в нем тип сервиса ftp.
Например, в ответ на следующую команду, приложение ConsoleFtp будет пытаться выполнить соединение с сервером FTP корпорации Microsoft:
ConsoleFtp.exe ftp://ftp.microsoft.com
Если указанный сервер FTP доступен, приложение выведет на экран сообщение об успешном соединении и завершит свою работу. Ниже мы привели сообщение приложения ConsoleFtp о соединении с сервером ftp.microsoft.com:
URL address: ftp://ftp.microsoft.com Connect to FTP server established
В некоторых случаях процесс соединения с сервером может занять значительное время, поэтому не торопитесь насильно прервать исполнение нашего приложения. Если сервер не будет обнаружен или произойдет какая-либо другая ошибка, приложение выведет на экран соответствующее сообщение:
URL address: ftp://failer Error: The server name or address could not be resolved
В данном случае мы попытались установить соединение с сервером FTP по адресу ftp://failer, но такой сервер не был обнаружен.
Если вы желаете запустить приложение ConsoleFtp непосредственно из среды Microsoft Visual C++ или под отладкой, вы должны указать параметры для него в диалоговой панели Project Settings. Чтобы открыть эту панель выберите из меню Build строку Settings. Выберите в панели Project Settings страницу Debug (рис. 2.1), а затем из списка Category строку General.
Рис. 2.1. Диалоговая панель Project Settings, страница Debug
Теперь вы можете ввести аргументы для приложения в строке Program arguments. Когда вы запустите приложение из среды Microsoft Visual C++, ему будут переданы именно эти параметры.
Для приложения ConsoleFtp мы использовали операторы потокового ввода/вывода << и >>, а также функции new и free. Поэтому нам понадобилось включить файлы iostream.h и stdlib.h:
#include <iostream.h> #include <stdlib.h>
Наряду со стандартными функциями библиотеки языка Си, наше приложение будет пользоваться классами WinInet, входящими в библиотеку MFC. Поэтому мы включили в исходный текст приложения файлы afx.h и afxinet.h:
#include <afx.h> #include <afxinet.h>
Определяя главную функцию приложения, мы указали для нее параметры argc и argv. Они позволяют получить параметры, указанные при запуске приложения в командной строке:
int main(int argc, char* argv[]) { }
Пользователь должен указать нашему приложению всего один параметр, содержащий URL адрес сервера FTP. Если приложение запущено без параметра или указано большее количество параметров, то мы отображаем на экране информацию о формате вызова приложения:
if (argc != 2) { cout << "Programm format: ConsoleFtp <URL>" << endl; cout << " <URL> - URL address of FTP" << endl << endl; return -1; }
Параметр, указанный при вызове приложения, записываем в текстовую строку sUrlAdress. Если параметр задан правильно он должен содержать URL адрес сервера FTP:
CString sUrlAdress; sUrlAdress = argv[1];
Отображаем адрес сервера FTP на экране, чтобы быть уверенным в том что он задан правильно:
cout << "URL address: " << sUrlAdress << endl << endl;
Далее мы объявляем временные переменные, которые будут использоваться при разборе адреса URL. Целью такого разбора будет выделение из адреса URL имени сервера FTP:
// Имя сервера CString sServer; // Имя объекта на который указывает адрес URL CString sObject; // Номер порта INTERNET_PORT nPort; // Тип сервиса или тип протокола DWORD dwServiceType;
Сам разбор адреса URL, записанного в строке sUrlAdress, выполняется с помощью функции AfxParseURL. Заметим, что это пожалуй единственная функция библиотеки MFC, относящаяся к WinInet, которая не требует предварительно создать сеанс связи - объект класса CInternetSession.
Исходный адрес URL передается функции AfxParseURL через параметр sUrlAdress, а результат записывается в переменные dwServiceType, sServer, sObject, nPort. Нас будут интересовать только две переменные dwServiceType и sServer. В переменную dwServiceType функция AfxParseURL записывает тип сервиса, указанный в адресе sUrlAdress, а в строку sServer - имя сервера:
if (!AfxParseURL(sUrlAdress, dwServiceType, sServer, sObject, nPort)) { cout << "AfxParseURL Error" << endl; return -1; } if(dwServiceType != AFX_INET_SERVICE_FTP) { cout << "URL Address not FTP server" << endl; return -1; }
Если вы указали неправильный адрес URL или тип сервиса, не соответствующий серверу FTP, то на экран выводится предупреждающее сообщение и работа приложения завершается. Если адрес сервера указан верно, приложение создает сеанс связи с Internet. Для этого мы объявляем указатель на объект класса CInternetSession, а затем создаем сам объект, используя функцию new:
// Указатель на объект класса CInternetSession CInternetSession* m_InternetSession = NULL; // Инициализируем сеанс работы с WinInet - создаем объект // класса CInternetSession catch m_InternetSession = new CInternetSession("Connecter");
Конструктор класса CInternetSession имеет много параметров, но мы все их используем по умолчанию, кроме первого параметра, через который мы задаем имя сеанса - Connecter. Если вы опустите и этот параметр конструктора, то в качестве имени сеанса будет взято имя приложения.
Теперь можно попытаться соединиться с сервером FTP. Для этого следует вызвать метод GetFtpConnection класса CInternetSession только что созданного объекта m_InternetSession. В случае ошибки этот метод может вызвать исключение CInternetException. Поэтому вызов метода GetFtpConnection мы помещаем в блок try и определяем для него соответствующий блок catch, обрабатывающий исключение CInternetException:
// Исключения, вызванные в этом блоке try обрабатываются // следующим блоком catch try { // . . . } catch (CInternetException* pEx) { // . . . }
Обработчик исключения CInternetException вызывает метод GetErrorMessage для полученного исключения. Он записывает во временный буфер szErr причину вызова исключения. Если текстовое описание исключения доступно, метод GetErrorMessage возвращает ненулевое значение. В этом случае мы отображаем полученный текст на экране:
// Обрабатываем исключение CInternetException TCHAR szErr[1024]; // временный буфер для сообщения // Выводим сообщение об ошибке if (pEx->GetErrorMessage(szErr, 1024)) cout << "Error: " << szErr << endl << endl;
Затем завершаем обработку исключения, вызывая для него метод Delete. Он удаляет объект, представляющий исключение, из оперативной памяти:
// Удаляем исключение pEx->Delete();
Вернемся к блоку try. В нем приложение исполняет свою главную задачу - пытается установить соединение с сервером FTP. Адрес сервера FTP передается методу GetFtpConnection класса CInternetSession. Если соединение с сервером будет установлено, то этот метод вернет указатель на объект класса CFtpConnection и мы запишем его во временную переменную m_FtpConnection:
// Определяем указатель на объект класса CFtpConnection CFtpConnection* m_FtpConnection = NULL; // Пытаемся соединиться с сервером sServer m_FtpConnection = m_InternetSession -> GetFtpConnection( sServer );
Если попытка соединиться с сервером окажется неудачной, то метод GetFtpConnection вызовет исключение CInternetException и оно будет обработано соответствующим блоком catch.
В случае успешного соединения с сервером FTP, приложение отображает на экране соответствующее сообщение:
// Выводим сообщение об успешном соединении cout << "Connect to FTP server established" << endl;
Больше приложение ConsoleFtp ничего не делает с сервером, поэтому закрываем соединение с сервером и удаляем соответствующий объект m_FtpConnection из памяти (этот объект создается методом GetFtpConnection):
// Закрываем соединение с сервером FTP m_FtpConnection -> Close(); // Удаляем объект m_FtpConnection delete( m_FtpConnection );
Перед окончанием работы приложения, завершаем сеанс связи, вызывая метод Close класса CInternetSession, а затем удаляем соответствующий объект из памяти, вызывая оператор delete:
// Завершаем сеанс связи m_InternetSession -> Close(); // Удаляем объект m_InternetSession delete(m_InternetSession);
Конечно, приложение ConsoleFtp демонстрирует только самые основные принципы использования классов WinInet. В следующем разделе мы приведем еще одно приложение для работы с серверами FTP. Приложение FtpView значительно сложнее чем ConsoleFtp. Оно позволяет пользователю просматривать структуру каталогов сервера FTP и названия записанных в них файлов. Несколько позже мы модифицируем приложение FtpView так, чтобы его можно было использовать для загрузки файлов и будем вносить в него небольшие изменения для демонстрации различных приемов работы с серверами FTP.
Навигатор Microsoft Internet Explorer можно использовать для просмотра серверов FTP и загрузки с них файлов на локальный диск компьютера. Если вы запустите навигатор и введете для просмотра адрес какого-нибудь сервера FTP, например сервера Microsoft, то в окне навигатора будет показан список файлов и каталогов сервера (рис. 2.2).
Рис. 2.2. Microsoft Internet Explorer просматривает корневой каталог сервера FTP по адресу ftp.microsoft.com
В этом разделе мы создадим свое приложение, которое подобно навигатору Microsoft Internet Explorer может просматривать структуру каталогов серверов FTP и даже загружать с них файлы на диск локального компьютера.
С помощью MFC AppWizard создайте проект FtpView, выбрав в качестве интерфейса пользователя диалоговую панель. Все установки оставьте по умолчанию, но отключите переключатель About box, который расположен во второй панели выбора свойств приложения - MFC AppWizard - Step 2 of 4. В результате приложение не будет иметь информационной диалоговой панели About и его исходные тексты будут более просты для понимания.
Приложение, сформированное MFC AppWizard, будет иметь единственную диалоговую панель, которая отображается сразу после ее запуска. О том, как устроено приложение FtpView, вы можете прочитать в 24 томе серии “Библиотека системного программиста” в разделе “Приложение с главной диалоговой панелью”.
Загрузите шаблон диалоговой панели IDD_FTPVIEW_DIALOG приложения в редактор ресурсов (рис. 2.3).
В верхней левой части панели создайте поле редактирования для ввода адреса сервера FTP, с идентификатором IDC_FTP_ADDRESS. С правой стороны от этого поля расположите кнопку Connect с идентификатором IDC_CONNECT, кнопку On top с идентификатором IDC_ON_TOP и кнопку OK.
В центре диалоговой панели разместите список (орган управления List Control) и присвойте ему идентификатор IDC_FTP_LIST. В этом списке будет отображаться содержимое каталогов сервера FTP.
Рис. 2.3. Редактирование диалоговой панели FtpView
Список List Control может отображать информацию в различных форматах. Это могут быть пиктограммы большого или маленького размера с подписями, отсортированные различным образом, или табличная информация разделенная на несколько колонок. Мы будем использовать последний из перечисленных форматов.
Откройте панель свойств списка List Control Properties. Мы показали ее на рисунке 2.4. Перейдите на страницу Styles. Из списка View выберите строку Report, которая определяет, что список будет отображать табличную информацию. Из списка Align выберите строку Left. Она задает выравнивание строк, отображаемых в списке, по левой границе. Вы также можете определить будут ли сортироваться строки, отображаемые в списке. Мы выбрали прямой порядок сортировки. Вы, в принципе, можете использовать обратный порядок сортировки или отказаться от сортировки совсем.
Рис. 2.4. Свойства списка IDC_FTP_LIST
В нижней части диалоговой панели разместите текстовую надпись Directory и поле редактирования для отображения названия текущего каталога с идентификатором IDI_DIRECTORY. Поле IDI_DIRECTORY будет использоваться только для вывода текста, поэтому откройте диалоговую панель свойств этого органа управления, перейдите на страницу Styles и включите переключатель Read-only (рис. 2.5).
Рис. 2.5. Свойства поля редактирования IDI_DIRECTORY
Кроме диалоговой панели IDD_FTPVIEW_DIALOG в ресурсы приложения FtpView входит пиктограмма IDR_MAINFRAME и ресурс, содержащий информацию о версии приложения VS_VERSION_INFO. Вы можете оставить ресурс VS_VERSION_INFO и пиктограмму IDR_MAINFRAME без изменения, но мы заменили стандартную пиктограмму приложений, созданных MFC AppWizard рисунком, который для нас нарисовал художник Алексей Абрамкин.
Вы должны добавить к ресурсам приложения еще две пиктограммы IDI_FILE и IDI_DIRECTORY, на которых изображены “лист бумаги” и “папка”. Эти пиктограммы будут использоваться для выделения файлов и каталогов в списке объектов каталога сервера FTP. Пиктограммы IDI_FILE и IDI_DIRECTORY обязательно должны иметь “маленькие” изображения размером 8 х 8 пикселов. Стандартные изображения 16 х 16 пикселов можно не рисовать - для нашего примера они не потребуются.
В следующей таблице мы привели изображения пиктограмм, которые используются в нашем приложении. В таблице все пиктограммы одинакового размера, но на самом деле пиктограммы IDI_FILE и IDI_DIRECTORY меньше чем пиктограмма IDR_MAINFRAME в два раза.
Пиктограмма |
Идентификатор пиктограммы |
Имя файла |
|
IDI_FILE |
file.ico |
|
IDI_DIRECTORY |
director.ico |
|
IDR_MAINFRAME |
russian.ico |
На рисунке 2.6 мы показали страницу ResourceView диалоговой панели Project Workspace. Так она будет выглядеть после того, как вы добавите в проект все необходимые нам ресурсы.
Ресурс VS_VERSION_INFO, который описывает версию приложения, нами не используется и мы оставляем его без изменения таким, каким он создан MFC AppWizard.
Рис. 2.6. Ресурсы приложения
Файл ресурсов приложения FtpView представлен в листинге 2.2. В нем вы найдете шаблон диалоговой панели IDD_FTPVIEW_DIALOG, команды для включения пиктограмм, информационный ресурс VS_VERSION_INFO и другие вспомогательные команды.
Листинг 2.2. Файл FtpView.rc
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Русские ресурсы #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) #ifdef _WIN32 LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT #pragma code_page(1251) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "#define _AFX_NO_SPLITTER_RESOURCES\r\n" "#define _AFX_NO_OLE_RESOURCES\r\n" "#define _AFX_NO_TRACKER_RESOURCES\r\n" "#define _AFX_NO_PROPERTY_RESOURCES\r\n" "\r\n" "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" "#ifdef _WIN32\r\n" "LANGUAGE 9, 1\r\n" "#pragma code_page(1252)\r\n" "#endif\r\n" "#include ""res\\FtpView.rc2"" \r\n" "#include ""afxres.rc"" \r\n" "#endif\0" END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Диалоговая панель IDD_FTPVIEW_DIALOG // IDD_FTPVIEW_DIALOG DIALOGEX 0, 0, 352, 194 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "FtpView" FONT 8, "MS Sans Serif" BEGIN DEFPUSHBUTTON "OK",IDOK,305,5,40,15 CONTROL "List1",IDC_FTP_LIST,"SysListView32", LVS_REPORT | LVS_SORTASCENDING | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,5,25,340,133 EDITTEXT IDC_FTP_ADDRESS,5,5,183,14,ES_AUTOHSCROLL PUSHBUTTON "On top",IDC_ON_TOP,253,5,40,15 PUSHBUTTON "Connect",IDC_CONNECT,196,5,45,15 LTEXT "Directory:",IDC_STATIC,7,171,33,8 EDITTEXT IDC_STATUS,44,169,301,15,ES_AUTOHSCROLL | ES_READONLY END #ifndef _MAC ////////////////////////////////////////////////////////////// // // Информация о приложении // VS_VERSION_INFO VERSIONINFO FILEVERSION 1,0,0,1 PRODUCTVERSION 1,0,0,1 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "CompanyName", "\0" VALUE "FileDescription", "FTPVIEW MFC Application\0" VALUE "FileVersion", "1, 0, 0, 1\0" VALUE "InternalName", "FTPVIEW\0" VALUE "LegalCopyright", "Copyright © 1997\0" VALUE "LegalTrademarks", "\0" VALUE "OriginalFilename", "FTPVIEW.EXE\0" VALUE "ProductName", "FTPVIEW Application\0" VALUE "ProductVersion", "1, 0, 0, 1\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1200 END END #endif // !_MAC ////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_FTPVIEW_DIALOG, DIALOG BEGIN LEFTMARGIN, 2 RIGHTMARGIN, 345 TOPMARGIN, 4 BOTTOMMARGIN, 187 END END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Пиктограммы приложения // IDR_MAINFRAME ICON DISCARDABLE "res\\Russian.ico" IDI_FILE ICON DISCARDABLE "res\\File.ico" IDI_DIRECTORY ICON DISCARDABLE "res\\director.ico" #endif ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // #define _AFX_NO_SPLITTER_RESOURCES #define _AFX_NO_OLE_RESOURCES #define _AFX_NO_TRACKER_RESOURCES #define _AFX_NO_PROPERTY_RESOURCES #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE 9, 1 #pragma code_page(1252) #endif #include "res\FtpView.rc2" #include "afxres.rc" #endif ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Идентификаторы ресурсов приложения FtpView определены в файле resource.h. Этот файл создается автоматически редактором ресурсов Microsoft Visual C++. Исходный текст файла resource.h представлен в листинге 2.3.
Листинг 2.3. Файл resource.h
//{{NO_DEPENDENCIES}} // Файл создан Microsoft Developer Studio // Используется в FtpView.rc // Идентификаторы органов управления #define IDD_FTPVIEW_DIALOG 102 #define IDR_MAINFRAME 128 #define IDI_DIRECTORY 130 #define IDI_FILE 131 #define IDC_FTP_LIST 1000 #define IDC_FTP_ADDRESS 1001 #define IDC_VIEW 1002 #define IDC_ON_TOP 1003 #define IDC_CONNECT 1004 #define IDC_STATUS 1005 // Следующие значения идентификаторов используются по // умолчанию для новых объектов #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 129 #define _APS_NEXT_COMMAND_VALUE 32771 #define _APS_NEXT_CONTROL_VALUE 1006 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Когда вы закончите создание и доработку ресурсов приложения FtpView, надо приступить к доработке программных кодов приложения. Список файлов, составляющих проект FtpView, вы можете просмотреть в окне Project Workspace на странице FileView (рис. 2.7). Вы можете непосредственно внести все необходимые изменения в исходные файлы проекта, однако значительно лучше переложить часть работы на MFC ClassWizard. Средствами MFC ClassWizard вы сможете легко добавить к классам новые элементы данных и новые методы.
Рис. 2.7. Файлы проекта FtpView
Главный класс приложения CFtpViewApp, определен в файле FtpView.h. Мы привели исходный текст этого файла в листинге 2.4. Класс CFtpViewApp наследуется от базового класса CWinApp. При этом переопределяется единственный виртуальный метод InitInstance XE "CWinApp:InitInstance" , который выполняет инициализацию приложения и отображает на экране главную диалоговою панель приложения.
Листинг 2.4. Файл FtpView.h
#ifndef __AFXWIN_H__ #error include 'stdafx.h' before including this file for PCH #endif #include "resource.h" // Включаемый файл содержащий // Идентификаторы ресурсов приложения //============================================================ // Определение класса CFtpViewApp // Методы класса CDialogApp определены в файле FtpView.cpp //============================================================ class CFtpViewApp : public CWinApp { public: CFtpViewApp(); // Overrides // В следующем блоке ClassWizard помещает описания // переопределенных виртуальных методов класса //{{AFX_VIRTUAL(CFtpViewApp) public: virtual BOOL InitInstance(); //}}AFX_VIRTUAL // Implementation //{{AFX_MSG(CFtpViewApp) // В этом блоке ClassWizard размещает описания методов // класса. Не редактируйте содержимое этого блока вручную //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Определение конструктора класса CFtpViewApp, метода InitInstance, таблицы сообщений, а также определение объекта данного класса вы найдете в файле FtpView.cpp. Исходный текст этого файла мы представили в листинге 2.5. Заметим, что файлы FtpView.h и FtpView.cpp мы оставляем без изменения какими их создал MFC AppWizard.
Весь программный код, который будет взаимодействовать с сервером FTP и обслуживать диалоговую панель IDD_FTPVIEW_DIALOG, мы добавим к классу CFtpViewDlg. По большей части мы будем использовать для этого средства MFC ClassWizard, так что вручную вам надо будет ввести только исходный текст добавленных методов.
Листинг 2.5. Файл FtpView.cpp
//============================================================ // Приложение для просмотра структуры каталогов // серверов FTP // // (C) Фролов Г.В., 1997 // E-mail: frolov@glas.apc.org // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov //============================================================ // Файл содержит определение методов и таблицы сообщений // главного класса приложения //============================================================ // Включаемые файлы #include "stdafx.h" #include "FtpView.h" #include "FtpViewDlg.h" // Для отладочной версии приложения включается дополнительные // определения #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif //============================================================ // Таблица сообщений класса CFtpViewApp //============================================================ BEGIN_MESSAGE_MAP(CFtpViewApp, CWinApp) //{{AFX_MSG_MAP(CFtpViewApp) //}}AFX_MSG ON_COMMAND(ID_HELP, CWinApp::OnHelp) END_MESSAGE_MAP() //============================================================ // Конструктор класса CFtpViewApp //============================================================ CFtpViewApp::CFtpViewApp() { // TODO: } ////////////////////////////////////////////////////////////// // Объект главного класса приложения CFtpViewApp theApp; //============================================================ // Метод InitInstance класса CFtpViewApp. // Выполняет инициализацию приложения //============================================================ BOOL CFtpViewApp::InitInstance() { #ifdef _AFXDLL Enable3dControls(); #else Enable3dControlsStatic(); #endif CFtpViewDlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Нажата кнопка OK } else if (nResponse == IDCANCEL) { // TODO: Нажата кнопка Cancel } // Так как диалоговая панель закрыта, возвращаем значение // FALSE чтобы завершить приложение return FALSE; }
Все изменения, которые мы будем вносить в наш проект затронут, в основном, только класса CFtpViewDlg. Это и понятно - именно к диалоговой панели IDD_FTPVIEW_DIALOG, которая управляется данным классом, мы добавили новые органы управления для взаимодействия с сервером FTP.
Приложения, которые используют для доступа к ресурсам сети Internet классы MFC WinInet, должны иметь как минимум один объект класса CInternetSession. Этот объект представляет собой “сеанс” связи с Internet.
В окне Project Workspace щелкните правой клавишей мыши по имени класса CFtpViewDlg. Откроется контекстное меню, из которого следует выбрать строку Add Variable. На экране появится диалоговая панель Add Member Variable (рис. 2.8).
Рис. 2.8. Диалоговая панель Add Member Variable
В поле Variable Type введите имя класса CInternetSession, а в поле Variable Declaration имя нового элемента класса CFtpViewDlg - m_InternetSession. Переключатель Access переведите в положение Protect. Нажмите кнопку OK. В класс CFtpViewDlg будет добавлен новый элемент - объект класса CInternetSession. Так как мы выбрали тип доступа Protect, то этот элемент будет расположен в секции protected и будет доступен только методам данного класса.
Повторите описанную процедуру и добавьте к классу CFtpViewDlg еще три элемента - строку sCurentDirectory класса CString, указатель m_ImageList на класс CImageList и указатель m_FtpConnection на класс CFtpConnection.
С помощью MFC ClassWizard привяжите переменные к органам управления диалоговой панели IDD_FTPVIEW_DIALOG. Для этого откройте MFC ClassWizard, перейдите на страницу Member Variables. В поле Class name выберите имя класса CFtpViewDlg. Затем последовательно выбирайте идентификаторы органов управления из списка Control IDs, нажимайте кнопку Add Variable и в открывающейся панели Add Member Variable вводите имя, категорию и тип переменной.
Добавляя переменные, пользуйтесь рисунком 2.9. На нем видно, какие переменные привязаны к органам управления диалоговой панели. Только кнопка OK не имеет собственной переменной, так как она нам не понадобится.
Рис. 2.9. Диалоговая панель MFC ClassWizard
Перейдите в диалоговой панели MFC ClassWizard на страницу Message Maps и добавьте несколько методов-обработчиков сообщений от органов управления диалоговой панели IDD_FTPVIEW_DIALOG. Для кнопки Connect, имеющей идентификатор IDC_CONNECT, и кнопки On Top с идентификатором IDC_ON_TOP добавьте обработчики командного сообщения BN_CLICKED - методы OnConnect и OnOnTop. Они будут вызываться при нажатии на эти кнопки. Для списка IDC_FTP_LIST добавьте обработчик сообщения NM_DBLCLK - метод OnDblclkFtpList. Он будет вызываться при двойном щелчке левой кнопкой мыши внутри списка.
Теперь остается совсем немного. Надо добавить метод DirectoryView и деструктор к классу CFtpViewDlg. MFC ClassWizard в этом вам не помощник. Откройте окно Project Workspase, если оно закрыто, и щелкните правой клавишей мыши по названию класса CFtpViewDlg. Откроется контекстное меню, из которого надо выбрать строку Add Function. На экране появится диалоговая панель Add Member Function (рис. 2.10).
Рис. 2.10. Диалоговая панель Add Member Function
Чтобы добавить метод DirectoryView, введите в поле Function Declaration его название, а в поле Function Type - тип значения, возвращаемого этим методом - BOOL. Повторите описанную операцию еще один раз и добавьте к классу CFtpViewDlg деструктор ~CFtpViewDlg. Для этого введите в поле Function Declaration строку ~CFtpViewDlg, а поле Function Type оставьте пустым.
MFC ClassWizard создает только шаблоны методов. Рабочий программный код вы должны добавить в них самостоятельно. Ниже мы привели исходные тексты файлов FtpViewDlg.h и FtpViewDlg.cpp, в соответствии с которыми вам надо доработать свой проект.
Класс CFtpViewDlg определен в файле FtpViewDlg.h. Мы привели исходный текст этого файла в листинге 2.6. Для этого файла вам надо добавить директивы define, определяющие четыре константы - COL_NUM, MIN_LEN_BUF, DIRECTORY и FILE. Эти константы будут использоваться методами класса FtpViewDlg.
Листинг 2.6. Файл FtpViewDlg.h
//============================================================ // Определение класса FtpViewDlg и некоторых констант //============================================================ // Определение констант #define COL_NUM 4 // Количество колонок таблицы #define MIN_LEN_BUF 30 // Минимальная длина буфера #define DIRECTORY 0 // Пиктограмма каталога #define FILE 1 // Пиктограмма файла ////////////////////////////////////////////////////////////// // Класс CFtpViewDlg class CFtpViewDlg : public CDialog { // Construction public: ~CFtpViewDlg(); CFtpViewDlg(CWnd* pParent = NULL); // Dialog Data //{{AFX_DATA(CFtpViewDlg) enum { IDD = IDD_FTPVIEW_DIALOG }; CEdit m_Status; // Поле Directory CButton m_Ok; // Кнопка OK CButton m_OnTop; // Кнопка On top CButton m_Connect; // Кнопка Connect CListCtrl m_FtpList; // Таблица с содержимым каталога CString m_FtpAddress; // Поле адреса сервера FTP //}}AFX_DATA //{{AFX_VIRTUAL(CFtpViewDlg) protected: // Обмен данными - DDX/DDV virtual void DoDataExchange(CDataExchange* pDX); //}}AFX_VIRTUAL // Implementation protected: CString sCurentDirectory; // Текущий каталог CImageList* m_ImageList; // Список изображений CFtpConnection* m_FtpConnection; // Сервер FTP CInternetSession* m_InternetSession; // Сеанс связи HICON m_hIcon; // Пиктограмма // приложения protected: // Метод, просматривающий содержимое каталога BOOL DirectoryView(); // Методы обрабатывающие сообщения //{{AFX_MSG(CFtpViewDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnOnTop(); afx_msg void OnDblclkFtpList(NMHDR* pNMHDR, LRESULT* pResult); afx_msg void OnConnect(); //}}AFX_MSG DECLARE_MESSAGE_MAP() };
Конструктор, деструктор, методы класса CFtpViewDlg, а также таблица сообщений определены в файле FtpViewDlg.cpp. Мы привели исходный текст файла FtpViewDlg.cpp в листинге 2.7. В соответствии с текстом этого файла вы должны доработать методы OnInitDialog, OnConnect, DirectoryView, OnDblclkFtpList, OnOnTop.
Листинг 2.7. Файл FtpViewDlg.cpp
#include "stdafx.h" #include "FtpView.h" #include "FtpViewDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif //============================================================ // Конструктор класса CFtpViewDlg //============================================================ CFtpViewDlg::CFtpViewDlg(CWnd* pParent /*=NULL*/) : CDialog(CFtpViewDlg::IDD, pParent) { //{{AFX_DATA_INIT(CFtpViewDlg) m_FtpAddress = _T(""); //}}AFX_DATA_INIT // При использовании метода LoadIcon нет необходимости // вызывать DestroyIcon m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } //============================================================ // Деструктор класса CFtpViewDlg //============================================================ CFtpViewDlg::~CFtpViewDlg() { // Если соединение с сервером установлено, закрываем его if (m_FtpConnection != NULL) { m_FtpConnection -> Close(); delete m_FtpConnection; } // Завершаем сеанс связи с Internet if (m_InternetSession != NULL) { m_InternetSession -> Close(); delete m_InternetSession; } // Удаляем список изображений delete m_ImageList; } //============================================================ // Метод DoDataExchange класса CFtpViewDlg // Выполняет привязку органов управления диалоговой панели // IDD_FTPVIEW_DIALOG и соответствующих элементов класса // CFtpViewDlg //============================================================ void CFtpViewDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CFtpViewDlg) DDX_Control(pDX, IDC_STATUS, m_Status); DDX_Control(pDX, IDOK, m_Ok); DDX_Control(pDX, IDC_ON_TOP, m_OnTop); DDX_Control(pDX, IDC_CONNECT, m_Connect); DDX_Control(pDX, IDC_FTP_LIST, m_FtpList); DDX_Text(pDX, IDC_FTP_ADDRESS, m_FtpAddress); //}}AFX_DATA_MAP } //============================================================ // Таблица сообщений класса CFtpViewDlg //============================================================ BEGIN_MESSAGE_MAP(CFtpViewDlg, CDialog) //{{AFX_MSG_MAP(CFtpViewDlg) ON_WM_PAINT() ON_WM_QUERYDRAGICON() // Сообщение от кнопки Connect ON_BN_CLICKED(IDC_CONNECT, OnConnect) // Сообщение от кнопки On Top ON_BN_CLICKED(IDC_ON_TOP, OnOnTop) // Сообщение с кодом извещения NM_DBLCLK от списка ON_NOTIFY(NM_DBLCLK, IDC_FTP_LIST, OnDblclkFtpList) //}}AFX_MSG_MAP END_MESSAGE_MAP() //============================================================ // Метод OnInitDialog класса CFtpViewDlg // Выполняет инициализацию диалоговой панели, а также сеанса // связи с Internet (создает объект класса CInternetSession) //============================================================ BOOL CFtpViewDlg::OnInitDialog() { // Вызываем метод OnInitDialog базового класса CDialog CDialog::OnInitDialog(); // Устанавливаем пиктограммы, которые будут отображаться // в случае минимизации диалоговой панели SetIcon(m_hIcon,TRUE); // Пиктограмма стандартного размера SetIcon(m_hIcon,FALSE); // Пиктограмма маленького размера //========================================================= // Выполняем инициализацию списка IDC_FTP_LIST //========================================================= // Структура, для описания характеристик колонок списка LV_COLUMN lv_column; // Переменная для определения размера списка CRect rectList; // Названия для колонок списка TCHAR szColHeader[COL_NUM][10] = { _T("Name"), _T("Length"), _T("Date"), _T("Time"), }; // Определяем размер области, которую занимает список // IDC_FTP_LIST в диалоговой панели m_FtpList.GetWindowRect(&rectList); // Указываем поля структуры lv_column, которые будут // использоваться lv_column.mask = LVCF_FMT | // Используется поле fmt LVCF_SUBITEM | // Используется поле iSubItem LVCF_TEXT | // Используется поле pszText LVCF_WIDTH; // Используется поле cx // Задаем выравнивание по левому краю lv_column.fmt = LVCFMT_LEFT; // Ширина колонки lv_column.cx = (rectList.Width() / COL_NUM) - 1; // Определяем характеристики колонок списка for (int i = 0; i < COL_NUM; i++) { // Номер колонки lv_column.iSubItem = i; // Заголовок колонки lv_column.pszText = szColHeader[i]; // Добавляем колонку с заданными свойствами к списку m_FtpList.InsertColumn(i, &lv_column); } // Создаем список из двух изображений размера 16 х 16 m_ImageList = new CImageList(); m_ImageList -> Create(16, 16, TRUE, 2, 2); // Добавляем в список две пиктограммы - // IDI_DIRECTORY (изображение каталога) и // IDI_FILE(изображение файла ) m_ImageList -> Add(AfxGetApp()->LoadIcon(IDI_DIRECTORY)); m_ImageList -> Add(AfxGetApp()->LoadIcon(IDI_FILE)); // Выбираем список изображений m_ImageList для // использования в списке IDC_FTP_LIST m_FtpList.SetImageList(m_ImageList, LVSIL_SMALL); //========================================================= // Выбираем адрес сервера FTP, который используется по // умолчанию //========================================================= m_FtpAddress = "dials.ccas.ru"; // Сервер FTP "ДиалогНаука" // Отображаем адрес на экране UpdateData(FALSE); //========================================================= // Инициализируем сеанс связи с Internet //========================================================= // Создаем сеанс связи с Internet, указываем в качестве // имени программы-клиента название приложения FtpView m_InternetSession = new CInternetSession("FtpView"); // В случае ошибки отображаем сообщение и завершаем // приложение if(!m_InternetSession) { AfxMessageBox("New Session Error", MB_OK); OnOK(); } // Инициализируем указатель m_FtpConnection m_FtpConnection = NULL; return TRUE; } //============================================================ // Метод OnPaint класса CFtpViewDlg // Отображает на экране пиктограмму в случае минимизации // главной диалоговой панели приложения //============================================================ void CFtpViewDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } //============================================================ // Метод OnQueryDragIcon класса CFtpViewDlg // Сообщает идентификатор пиктограммы, отображаемой в случае // минимизации приложения //============================================================ HCURSOR CFtpViewDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } //============================================================ // Метод OnOnTop класса CFtpViewDlg // Переходит в каталог верхнего уровня //============================================================ void CFtpViewDlg::OnOnTop() { // Блокируем список IDC_FTP_LIST m_FtpList.EnableWindow(FALSE); // Изменяем строку текущего каталога sCurentDirectory так, // чтобы она показывала на каталог верхнего уровня. // Ищем последнее вхождение символа / в строку с именем // каталога int iNum = sCurentDirectory.ReverseFind('/'); if(iNum == -1) { // Если символ / не обнаружен, значит мы находимся в // корневом каталоге AfxMessageBox("No top directory"); } else { // Удаляем из строки с именем текущего каталога названия // последнего каталога sCurentDirectory = sCurentDirectory.Left(iNum); // Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Отображаем содержимое каталога верхнего уровня DirectoryView(); } // Снимаем блокировку списка IDC_FTP_LIST m_FtpList.EnableWindow(TRUE); // Отображаем на диалоговой панели новый путь каталога m_Status.SetWindowText(sCurentDirectory); return; } //============================================================ // Метод OnDblclkFtpList класса CFtpViewDlg // Переходит в выбранный каталог //============================================================ void CFtpViewDlg::OnDblclkFtpList(NMHDR* pNMHDR, LRESULT* pResult) { int iTotalNumber; // Количество элементов списка CString sSelItem; // Название каталога CString sLength_Dir; // Длина файла или строка Dir // Блокируем список IDC_FTP_LIST m_FtpList.EnableWindow(FALSE); // Определяем количество элементов в списке IDC_FTP_LIST iTotalNumber = m_FtpList.GetItemCount(); // Определяем, какой объект списка выбран for(int i = 0; i < iTotalNumber; i++) { // Атрибут LVIS_SELECTED установлен // у выбранного элемента списка if(LVIS_SELECTED == m_FtpList.GetItemState( i, LVIS_SELECTED )) { // Определяем название выбранного элемента списка // (имя файла или каталога) sSelItem = m_FtpList.GetItemText( i, 0 ); // Считываем данные из колонки Length sLength_Dir = m_FtpList.GetItemText( i, 1 ); if(sLength_Dir == "Dir") // Выбран каталог // Переходим в выбранный каталог sCurentDirectory = sCurentDirectory + "/" + sSelItem; else // Выбран файл // Отображаем имя файла AfxMessageBox("You select file " + sSelItem); break; } } // Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Отображаем содержимое выбранного каталога DirectoryView(); // Отображаем в диалоговой панели новый путь каталога m_Status.SetWindowText(sCurentDirectory); // Снимаем блокировку списка IDC_FTP_LIST m_FtpList.EnableWindow(TRUE); *pResult = 0; } //============================================================ // Метод OnConnect класса CFtpViewDlg // Соединяется с указанным сервером FTP //============================================================ void CFtpViewDlg::OnConnect() { // Текущий каталог на сервере FTP CString sCurrentFtpDirectory = ""; // Блокируем кнопки Connect, OK и On Top m_Connect.EnableWindow(FALSE); m_Ok.EnableWindow(FALSE); m_OnTop.EnableWindow(FALSE); // Если вы ранее уже соединились с сервером FTP, разрываем // эту связь и удаляем объект m_FtpConnection if (m_FtpConnection != NULL) { m_FtpConnection -> Close(); delete m_FtpConnection; m_FtpConnection = NULL; } // Считываем из диалоговой панели адрес сервера FTP, так // как пользователь уже мог его изменить UpdateData(TRUE); // Пытаемся соединиться с сервером FTP try { // Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Соединяемся с сервером FTP. Эта операция // может вызвать исключение CInternetException m_FtpConnection = m_InternetSession->GetFtpConnection(m_FtpAddress); } catch (CInternetException* pEx) { // Обрабатываем исключение CInternetException TCHAR szErr[1024]; // временный буфер для сообщения // Выводим сообщение об ошибке if (pEx->GetErrorMessage(szErr, 1024)) AfxMessageBox(szErr); else AfxMessageBox("GetFtpConnection Error"); // Удаляем исключение pEx->Delete(); // Обнуляем указатель m_FtpConnection m_FtpConnection = NULL; } // Если соединение не установлено сообщяем об этом if( m_FtpConnection == NULL ) m_Status.SetWindowText("Connect not established"); // Если соединение установлено, определяем текущий каталог // и отображаем его содержимое на экране else { // Определяем текущий каталог сервера FTP BOOL fResult= m_FtpConnection -> GetCurrentDirectory(sCurrentFtpDirectory); if(fResult) sCurentDirectory = sCurrentFtpDirectory; else AfxMessageBox("GetCurrentDirectory Error"); // Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Отображаем содержимое выбранного каталога DirectoryView(); // Отображаем на диалоговой панели новый путь каталога m_Status.SetWindowText(sCurentDirectory); } // Снимаем блокировку кнопок Connect, OK и On Top m_Connect.EnableWindow(TRUE); m_Ok.EnableWindow(TRUE); m_OnTop.EnableWindow(TRUE); } //============================================================ // Метод DirectoryView класса CFtpViewDlg // Просматривает содержимое каталога и отображает его в // таблице m_FtpList //============================================================ BOOL CFtpViewDlg::DirectoryView() { // Переменная, сигнализирующая о получении последнего // элемента каталога BOOL fResult; // Временная переменная, определяющая тип объекта - // файл или каталог BOOL fDirectory; // Структура для добавления нового элемента к списку LV_ITEM lv_item; // Удалить все элементы из списка IDC_FTP_LIST m_FtpList.DeleteAllItems(); // Создаем объект класса CFtpFileFind, указывая // объект, представляющий уже установленное соединение // с сервером FTP CFtpFileFind m_FtpFileFind(m_FtpConnection); // Получаем имена всех объектов текущего каталога if(fResult = m_FtpFileFind.FindFile(_T(sCurentDirectory + "/*"))) { for(int n = 0;fResult; n++) { // Получаем очередной объект из каталога fResult = m_FtpFileFind.FindNextFile(); // Определяем что это - каталог или файл fDirectory = m_FtpFileFind.IsDirectory(); //============= Определяем имя объекта ============== // Временные строка для имени каталога или файла CString fileName; // Определяем имя объекта fileName = m_FtpFileFind.GetFileName(); // Заполняем структуру lv_item, сведениями об // очередном объекте каталога сервера FTP. Указываем, // что в список добавляется текст и изображение lv_item.mask = LVIF_TEXT | LVIF_IMAGE; // Указываем номер строки в списке lv_item.iItem = n; // Заполняем первую колонку lv_item.iSubItem = 0; // Выбираем изображение для нового элемента списка в // зависимости от типа объекта lv_item.iImage = (fDirectory) ? DIRECTORY : FILE; // Указываем имя каталога или файла lv_item.pszText = fileName.GetBuffer(MIN_LEN_BUF); // Добавляем новый элемент к списку IDC_FTP_LIST m_FtpList.InsertItem(&lv_item); //============= Определяем длину файла ============= // Длинна файла DWORD dwLength = 0; // Временные строка для формирования текстового // представления длинны файла CString sLength; // Заполняем колонку Length для новой записи и // записываем в нее длинну файла или строку Dir, если // новый объект - каталог. // Добавляется только текст без пиктограммы lv_item.mask = LVIF_TEXT; // Указываем номер строки в списке lv_item.iItem = n; // Заполняем вторую колонку lv_item.iSubItem = 1; // Если очередной объект является каталогом, то // вместо в колонке Length записываем строку Dir if(fDirectory) { lv_item.pszText = "Dir"; } // Если очередной объект является файлом, то // записываем его длину в колонку Length else { // Определяем длину файла dwLength = m_FtpFileFind.GetLength(); // Формируем текстовое представление длины файла sLength.Format("%d", dwLength); lv_item.pszText = sLength.GetBuffer(MIN_LEN_BUF); } // Добавляем запись во второй колонке списка (колонка // Length) m_FtpList.SetItem(&lv_item); //======== Определяем дату и время создания ========= // Дата и время создания каталога или файла CTime mTime; // Временные строки для формирования текстового // представления даты и времени CString sDate; CString sTime; // Определяем время изменения файла или каталога if(!m_FtpFileFind.GetLastWriteTime(mTime)) break; // Добавляется только текст без пиктограммы lv_item.mask = LVIF_TEXT; // Указываем номер строки в списке lv_item.iItem = n; // Заполняем третью колонку lv_item.iSubItem = 2; // Выделяем из объекта mTime день, месяц и год sDate = mTime.Format("%d.%m.%y"); // Записываем сформированную дату в структуру lv_item lv_item.pszText = sDate.GetBuffer(MIN_LEN_BUF); // Добавляем запись во второй колонке списка (колонка // Date) m_FtpList.SetItem(&lv_item); // Заполняем четвертую колонку, записываем в нее // время последнего изменения файла (каталога) lv_item.iSubItem = 3; // Выделяем из объекта mTime часы, минуты и секунды sTime = mTime.Format("%H:%M:%S"); // Записываем сформированную строку, содержащую время lv_item.pszText = sTime.GetBuffer(MIN_LEN_BUF); // Добавляем запись во второй колонке списка (колонка // Time) m_FtpList.SetItem(&lv_item); } // Заканчиваем поиск объектов в каталоге, закрываем // объект m_FtpFileFind m_FtpFileFind.Close(); } // Если каталог не содержит других объектов - каталогов и // файлов, выводин соответствующее сообщение else AfxMessageBox("File's not found or error"); return TRUE; }
Кроме уже описанных нами файлов в проект FtpView входят еще два исходных файла, содержащих программный код. Это файлы stdafx.cpp и включаемый файл stdafx.h. Исходный текст файла stdafx.cpp содержится в листинге 2.8. Как видите, он состоит из единственной директивы #include, включающей файл stdafx.h.
Листинг 2.8. Файл stdafx.cpp
// Включаем файл stdafx.h, определенный в нашем приложении #include "stdafx.h"
Файл stdafx.h задействует часто используемые включаемые системные файлы - afxwin.h, afxext.h, afxcmn.h и afxinet.h. Эти файлы не изменяются. Поэтому Microsoft Visual C++ компилирует их только один раз. За счет этого значительно сокращается время, затрачиваемое на повторное построение проекта.
Последний включаемый файл afxinet.h содержит описание классов, структур и констант библиотеки MFC, относящихся к программному интерфейсу WinInet (листинг 2.9). MFC AppWizard не включает этот файл по умолчанию. Вы должны добавить соответствующую директиву #include самостоятельно.
#include "stdafx.h"
Листинг 2.9. Файл stdafx.h
// Исключает редко используемые определения при обработке // файлов заголовков #define VC_EXTRALEAN // Основные компоненты библиотеки MFC #include <afxwin.h> // Расширения MFC #include <afxext.h> #ifndef _AFX_NO_AFXCMN_SUPPORT // Используется для органов управления Windows #include <afxcmn.h> #endif // _AFX_NO_AFXCMN_SUPPORT // Включаемый файл для библиотеки WinInet. // MFC AppWizard не включает этот файл, вы должны сделать это // самостоятельно #include "afxinet.h"
Запустите приложение FtpView. На экране появится главная диалоговая панель приложения (рис. 2.11). В поле редактирования, расположенном в верхнем левом углу окна отображается адрес сервера FTP. В качестве примера мы привели адрес сервера АО “ДиалогНаука”. Вы можете изменить этот адрес по вашему усмотрению и ввести адрес известного вам сервера FTP.
Заметим, что вы должны указать только имя сервера без префикса ftp://, имен подкаталогов и имен файлов. Приложение FtpView не выполняет каких-либо проверок введенной строки и передает ее непосредственно методу FtpConnect класса CInternetSession.
Нажмите кнопку Connect. Приложение FtpView попытается установить связь с указанным сервером FTP. Если такая попытка закончится успешно, приложение выведет в диалоговой панели содержимое текущего каталога сервера FTP. Путь текущего каталога вы можете просмотреть в поле Directory. В зависимости от настройки сервера это может быть корневой каталог или один из его подкаталогов.
Обратите внимание, что соединяясь с сервером, вы не указываете имя пользователя и пароль. Поэтому по умолчанию предполагается, что вы входите на сервер под именем anonimys и с паролем, соответствующим имени приложения.
Если сервер FTP, с которым вы соединяетесь, не позволяет с ним работать незарегистрированным пользователям, то на экране появится соответствующее сообщение об ошибке. Вы можете доработать приложение FtpView так, чтобы оно позволяло задавать имя пользователя и пароль. Для этого достаточно передать их методу GetFtpConnection через параметры pstrUserName и pstrPassword.
Рис. 2.11. Приложение FtpView
Список содержимого текущего каталога сервера отображается в таблице, состоящей из четырех столбцов - Name, Length, Date и Time. В столбце Name отображаются имена каталогов и файлов, расположенных в данном каталоге. Остальные три столбца - Length, Date и Time включают информацию о размере, дате и времени создания или изменения соответствующего каталога или файла. Каталоги выделяются пиктограммой , расположенной перед его именем и строкой Dir в столбце Length, а файлы только пиктограммой .
Чтобы войти в каталог, надо сделать двойной щелчок мышью по его названию. Приложение попытается просмотреть содержимое указанного вами каталога и выведет его в таблице диалоговой панели приложения. При этом содержимое старого каталога будет скрыто. Чтобы вернуться в каталог верхнего уровня, надо нажать кнопку On top. Таким образом вы можете просматривать структуру каталогов сервера FTP.
Если выберать из списка не каталог, а файл, то на экране появится предупреждающее сообщение и вы останетесь в текущем каталоге. На рисунке 2.12 показано сообщения, которое появляется при выборе файла. В данном случае мы выбрали файл с именем README.DOC.
Рис. 2.12. Выбран файл README.DOC
Основу приложения FtpView составляют два класса CFtpViewApp и CFtpViewDlg. Класс CFtpViewApp является основным классом приложения. Он выполняет инициализацию и отображает диалоговую панель приложения. Класс CFtpViewDlg управляет этой диалоговой панелью, а также соединяется с Internet для получения списка объектов на сервере FTP.
На рисунке 2.13 представлена страница ClassView окна проекта Project Workspace. На ней вы можете просмотреть структуру классов нашего приложения.
Рис. 2.13. Классы приложения FtpView
Класс CFtpViewApp является главным классом приложения и наследуется от базового класса CWinApp. Этот класс самый маленький. В состав класса CFtpViewApp входят только конструктор и метод InitInstance. Конструктор класса CFtpViewApp не выполняет никаких действий. Он создается MFC AppWizard и мы оставляем его без изменений.
Метод InitInstance также создается MFC AppWizard. Он переопределяет виртуальный метод InitInstance базового класса CWinApp. Виртуальный метод InitInstance выполняет инициализацию приложения и отображает на экране главную диалоговою панель приложения, которая имеет идентификатор IDD_FTPVIEW_DIALOG и управляется классом CFtpViewDlg. Более подробно этот метод описан в 24 томе серии “Библиотека системного программиста”.
Класс CFtpViewDlg управляет диалоговой панелью приложения и выполняет всю работу по связи с Internet и сервером FTP. Поэтому основное внимание мы уделим именно этому классу.
Кроме конструктора и деструктора в состав класса CFtpViewDlg входит еще восемь методов - OnInitDialog, OnPaint, OnQueryDragIcon, DoDataExchange, OnOnTop, OnDblclkFtpList, OnConnect и DirectoryView. Только первые четыре метода создаются MFC AppWizard по умолчанию, остальные мы добавили с помощью MFC ClassWizard.
Класс CFtpViewDlg также включает несколько элементов данных, предназначенных для управления органами управления диалоговой панели приложения и для установления связи с сервером FTP. Они также были добавлены нами с помощью MFC ClassWizard.
Когда вы с помощью MFC ClassWizard привязывали переменные к органам управления диалоговой панели, в класс были добавлены несколько элементов данных. Все они были занесены с специальный блок, отмеченный комментариями вида //}}AFX_DATA:
//{{AFX_DATA(CFtpViewDlg) CEdit m_Status; // Поле Directory CButton m_Ok; // Кнопка OK CButton m_OnTop; // Кнопка On top CButton m_Connect; // Кнопка Connect CListCtrl m_FtpList; // Таблица с содержимым каталога CString m_FtpAddress; // Поле адреса сервера FTP //}}AFX_DATA
Эти элементы данных представляют собой объекты классов MFC, управляющие соответствующими им органами управления диалоговой панели. Например, объект m_Connect класса CButton, привязан к кнопке Connect. Вызывая различные методы для этого объекта вы можете управлять кнопкой Connect (блокировать ее, снимать блокировку и т. д.).
Элементы данных, определенные внутри блока AFX_DATA тесно соотносятся с методом DoDataExchange, который, собственно, и осуществляет их связь с органами управления диалоговой панели.
Другая группа элементов данных класса CFtpViewDlg определена после ключевого слова protected содержит пять объектов. Доступ к этим объектам открыт только для методов самого класса CFtpViewDlg:
protected: HICON m_hIcon; // Пиктограмма приложения CString sCurentDirectory; // Текущий каталог CImageList* m_ImageList; // Список изображений CFtpConnection* m_FtpConnection; // FTP сервер CInternetSession* m_InternetSession; // Сеанс связи
Элемент m_hIcon используется для хранения идентификатора пиктограммы приложения. Этот элемент создан MFC AppWizard и не представляет для нас особого интереса. Остальные элементы данных класса добавлены нами через окно Project Workspace и мы рассмотрим их более подробно.
Полный путь каталога, содержимое которого отображается в списке на диалоговой панели, хранится в строке sCurentDirectory. Этот путь устанавливается, когда приложение соединяется с сервером FTP, а потом изменяется, когда вы входите в каталог, отображаемый в списке или выходите в каталог верхнего уровня, нажимая кнопку On top.
В списке IDC_FTP_LIST отображаются названия каталогов и файлов. Для наглядности мы выделяем их различными пиктограммами. Список изображений формируется во время инициализации диалоговой панели методом OnInitDialog и указатель на него записывается в элемент данных m_ImageList класса CFtpViewDlg.
Два последних элемента данных, входящих в класс CFtpViewDlg, наиболее интересны, так как используются для доступа к сети Internet и серверу FTP.
Элемент m_InternetSession является указателем на объект класса CInternetSession. Этот объект создается во время инициализации диалоговой панели с помощью метода OnInitDialog и требуется для дальнейшей работы с классами WinInet.
Элемент m_FtpConnection представляет собой указатель на объект класса CFtpConnection. Этот объект создается при установлении соединения с сервером FTP, которое выполняется методом OnConnect, когда пользователь нажимает кнопку Connect.
Таблица сообщений класса CFtpViewDlg состоит из макрокоманд BEGIN_MESSAGE_MAP и END_MESSAGE_MAP. Между ними расположены макрокоманды, определяющие сообщения обрабатываемые данным классом. Как видите, они расположены в блоке AFX_MSG_MAP, поэтому для управления ими используется ClassWizard.
Необработанные сообщения передаются базовому классу CDialog, так как он указан во втором параметре макрокоманды BEGIN_MESSAGE_MAP:
//============================================================ // Таблица сообщений класса CFtpViewDlg //============================================================ BEGIN_MESSAGE_MAP(CFtpViewDlg, CDialog) //{{AFX_MSG_MAP(CFtpViewDlg) ON_WM_PAINT() ON_WM_QUERYDRAGICON() // Сообщение от кнопки Connect ON_BN_CLICKED(IDC_CONNECT, OnConnect) // Сообщение от кнопки On Top ON_BN_CLICKED(IDC_ON_TOP, OnOnTop) // Сообщение с кодом извещения NM_DBLCLK от списка ON_NOTIFY(NM_DBLCLK, IDC_FTP_LIST, OnDblclkFtpList) //}}AFX_MSG_MAP END_MESSAGE_MAP()
Две первые макрокоманды, расположенные в данной таблице сообщений - ON_WM_PAINT и ON_WM_QUERYDRAGICON. При помощи ClassWizard вы можете обнаружить, что данные макрокоманды выполняют обработку сообщений WM_PAINT и WM_QUERYDRAGICON, вызывая для этого методы OnPaint и OnQueryDragIcon.
Для обработки сообщений от кнопок Connect с идентификатором IDC_CONNECT и On Top с идентификатором IDC_ON_TOP макрокоманды ON_BN_CLICKED вызывают методы OnOnTop и OnConnect, определенные в классе CFtpViewDlg. Таблица сообщений класса CFtpViewDlg не содержит макрокоманд для обработки сообщений от кнопки OK, которая имеет идентификатор IDOK, и поэтому для нее вызывается метод OnOK базового класса CDialog.
Последняя макрокоманда таблицы сообщений - ON_NOTIFY. Она вызывает метод OnDblclkFtpList для обработки сообщений с кодом извещения NM_DBLCLK от списка IDC_FTP_LIST. Сообщение с таким кодом извещения вырабатывается списком, когда пользователь делает в нем двойной щелчок левой клавишей мыши.
Конструктор класса CFtpViewDlg вызывается при создании объекта соответствующего класса, которое выполняется методом InitInstance главного класса приложения CFtpViewApp:
CFtpViewDlg dlg;
Конструктор класса CFtpViewDlg вызывает конструктор своего базового класса CDialog. При этом ему передается идентификатор диалоговой панели IDD и идентификатор главного окна приложения pParent. Диалоговая панель нашего приложения имеет идентификатор IDD_FTPVIEW_DIALOG, но в определении класса CFtpViewDlg указано, что IDD соответствует IDD_FTPVIEW_DIALOG:
enum { IDD = IDD_FTPVIEW_DIALOG };
В теле конструктора расположен блок AFX_DATA_INIT. В нем ClassWizard поместил код инициализации элемента данных класса m_FtpAddress. Конструктор также инициализирует m_hIcon, записывая в него идентификатор пиктограммы IDR_MAINFRAME:
//============================================================ // Конструктор класса CFtpViewDlg //============================================================ CFtpViewDlg::CFtpViewDlg(CWnd* pParent /*=NULL*/) : CDialog(CFtpViewDlg::IDD, pParent) { //{{AFX_DATA_INIT(CFtpViewDlg) m_FtpAddress = _T(""); //}}AFX_DATA_INIT // При использовании метода LoadIcon нет необходимости // вызывать DestroyIcon m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
С помощью MFC ClassWizard мы привязали к органам управления диалоговой панели несколько переменных. Всю работу по связыванию этих переменных и органов управления выполняет метод DoDataExchange XE "CWnd:DoDataExchange" . В блоке AFX_DATA_MAP размещены вызовы соответствующих методов DDX XE "DDE" XE "DDV" :
//============================================================ // Метод DoDataExchange класса CFtpViewDlg //============================================================ void CFtpViewDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CFtpViewDlg) DDX_Control(pDX, IDC_STATUS, m_Status); DDX_Control(pDX, IDOK, m_Ok); DDX_Control(pDX, IDC_ON_TOP, m_OnTop); DDX_Control(pDX, IDC_CONNECT, m_Connect); DDX_Control(pDX, IDC_FTP_LIST, m_FtpList); DDX_Text(pDX, IDC_FTP_ADDRESS, m_FtpAddress); //}}AFX_DATA_MAP }
Обратите внимание, что для большей части органов управления диалоговой панели используются обращения к методу DDX_Control. Он осуществляет привязку к органу диалоговой панели управляющего объекта соответствующего класса. Вызывая методы этого объекта можно выполнять над органом управления различные действия.
Только последний метод в блоке AFX_DATA_MAP отличается от остальных. Это метод DDX_Text, который используется для обмена данными между полем редактирования IDC_FTP_ADDRESS и строкой m_FtpAddress. Обмен выполняется при обращении к методу UpdateData.
Метод DoDataExchange класса CFtpViewDlg создается и модифицируется средствами MFC AppWizard и MFC ClassWizard. В большинстве случаев от вас не потребуется изменять этот метод вручную.
После того, как метод InitInstance главного класса приложения CFtpViewApp создает объект dlg класса CFtpViewDlg, представляющий диалоговую панель, для него вызывается метод DoModal:
int nResponse = dlg.DoModal();
Этот метод кроме прочего передает диалоговой панели сообщение WM_INITDIALOG. В ответ на сообщение WM_INITDIALOG вызывается метод OnInitDialog, объявленный как виртуальный метод класса CDialog. Обратите внимание, что таблица сообщений класса CFtpViewDlg не содержит макрокоманд для обработки сообщения WM_INITDIALOG. Метод OnInitDialog вызывается непосредственно MFC.
Первоначально метод OnInitDialog был переопределен MFC AppWizard во время создания приложения. Затем мы его изменили, добавив программный код для инициализации органов управления диалоговой панели и инициализации сеанса работы с WinInet. Рассмотрим метод OnInitDialog более подробно.
Сначала вызывается метод OnInitDialog базового класса CDialog. Он выполняет основную работу по инициализации диалоговой панели - загружает соответствующий шаблон диалоговой панели из ресурсов приложения и т. д.:
CDialog::OnInitDialog();
Затем два раза вызывается метод SetIcon, определенный в базовом классе CWnd. Этот метод устанавливает пиктограммы стандартного и уменьшенного размера, которые будут отображаться в случае минимизации диалоговой панели:
SetIcon(m_hIcon,TRUE); // Пиктограмма стандартного размера SetIcon(m_hIcon,FALSE); // Пиктограмма маленького размера
Первый параметр m_hIcon, передаваемый методам SetIcon, представляет собой индекс пиктограммы IDR_MAINFRAME. Эта пиктограмма загружается конструктором класса CFtpViewDlg, описанным выше.
Далее метод OnInitDialog выполняет инициализацию списка IDC_FTP_LIST, в котором будет отображаться содержимое каталогов сервера FTP.
Для временного использования объявляются несколько переменных - объект rectList класса CRect, структура lv_column типа LV_COLUMN и список строк szColHeader:
// Структура, для описания характеристик колонок списка LV_COLUMN lv_column; // Переменная для определения размера списка CRect rectList; // Названия для колонок списка TCHAR szColHeader[COL_NUM][10] = { _T("Name"), _T("Length"), _T("Date"), _T("Time"), };
В переменную rectList записывается размер области диалоговой панели, которую занимает список IDC_FTP_LIST. Для этого мы вызываем метод GetWindowRect объекта, управляющего списком. Размеры списка нам потребуются, когда мы будем определять ширину колонок, входящих в список:
m_FtpList.GetWindowRect(&rectList);
Затем мы заполняем поля структуры lv_column. Эта структура определяет характеристики колонки списка. Мы будем ее использовать при создании колонок.
Сначала заполняется поле mask. В него заносится комбинация нескольких констант, определяющих какие поля данной структуры будут иметь значение. Мы указали, что используются четыре поля структуры lv_column - fmt, iSubItem, pszText и cx:
lv_column.mask = LVCF_FMT | // Используется поле fmt LVCF_SUBITEM | // Используется поле iSubItem LVCF_TEXT | // Используется поле pszText LVCF_WIDTH; // Используется поле cx
Поле fmt задает выравнивание по левому краю колонки ее заголовка и текста элементов. Мы выбрали выравнивание по левому краю, записав в это поле значение LVCFMT_LEFT:
lv_column.fmt = LVCFMT_LEFT;
В поле cx заносится ширина колонки. Мы указываем для всех колонок списка одинаковую ширину, разделив на равные части ширину всего списка:
lv_column.cx = (rectList.Width() / COL_NUM) - 1;
Остальные поля структуры lv_column - iSubItem и pszText различаются для каждой колонки. Поэтому мы заполняем их отдельно. Поле iSubItem означает номер колонки списка, а поле pszText - текст его заголовка.
В следующем цикле заполняются последние два поля структуры и метод InsertColumn включает колонку в список. Номера колонок изменяются от 0 до 3, а названия полей берутся из списка строк szColHeader:
// Определяем характеристики колонок списка for (int i = 0; i < COL_NUM; i++) { // Номер колонки lv_column.iSubItem = i; // Зааголовок колонки lv_column.pszText = szColHeader[i]; // Добавляем колонку с заданными свойствами к списку m_FtpList.InsertColumn(i, &lv_column); }
Новые колонки добавляются в список с помощью метода InsertColumn, определенного в классе CListCtrl. Этому методу передается номер колонки и заполненная структура lv_column, описывающая добавляемую колонку.
В списке сервера FTP могут фигурировать два типа объектов - каталоги и файлы. Для выделения их в списке им присваиваются различные пиктограммы.
Чтобы в списке можно было отмечать отдельные элементы при помощи пиктограмм, необходимо сформировать список изображений. Для этого в состав библиотеки MFC включен класс CImageList. Метод OnInitDialog создает объект этого класса, представляющий список из двух изображений размером 16 х 16 пикселов и включает в него пиктограммы с изображением каталога (пиктограмма с идентификатором IDI_DIRECTORY) и файла (пиктограмма с идентификатором IDI_FILE):
// Создаем список из двух изображений размера 16 х 16 m_ImageList = new CImageList(); m_ImageList -> Create(16, 16, TRUE, 2, 2); // Добавляем в список две пиктограммы - // IDI_DIRECTORY (изображение каталога) и // IDI_FILE(изображение файла ) m_ImageList -> Add(AfxGetApp()->LoadIcon(IDI_DIRECTORY)); m_ImageList -> Add(AfxGetApp()->LoadIcon(IDI_FILE));
Полученный список изображений выбирается для использования в списке IDC_FTP_LIST с помощью метода SetImageList класса CListCtrl. Первый параметр метода SetImageList содержит указатель на список изображений, а второй определяет их размер. Константа LVSIL_SMALL означает, что данный список изображений будет использоваться при отображении пиктограмм маленького размера:
m_FtpList.SetImageList(m_ImageList, LVSIL_SMALL);
По завершении инициализации списка IDC_FTP_LIST, метод OnInitDialog выводит в поле редактирования IDC_FTP_ADDRESS, адрес сервера FTP. Адрес сервера FTP, который будет использоваться по умолчанию записывается в элемент данных m_FtpAddress класса CFtpViewDlg:
m_FtpAddress = "dials.ccas.ru"; // Сервер FTP “ДиалогНаука”
Для нашего примера мы использовали сервер FTP АО “ДиалогНаука”, который имеет адрес ftp://dials.ccas.ru. Вы можете заменить этот адрес на адрес любого другого сервера FTP по своему усмотрению.
Поле редактирования, предназначенное для ввода адреса сервера, мы связали со строкой m_FtpAddress. Поэтому чтобы вывести ее на экран вызывается метод UpdateData и в качестве параметра ему указывается значение FALSE:
UpdateData(FALSE);
Далее метод OnInitDialog приступает к инициализации сеанса связи с Internet (программного интерфейса WinInet). Создается новый объект класса CInternetSession. В качестве параметров конструктору класса CInternetSession указывается только имя приложения - строка FtpView. Вы можете указать здесь любую другую строку или не использовать этот параметр совсем. Указатель на созданный объект заносится в элемент данных m_InternetSession, принадлежащий классу CFtpViewDlg:
// Создаем сеанс связи с Internet, указываем в качестве // имени программы-клиента название приложения FtpView m_InternetSession = new CInternetSession("FtpView");
В случае возникновения ошибки при создании объекта класса CInternetSession мы отображаем на экране соответствующее сообщение и завершаем работу приложения:
if(!m_InternetSession) { AfxMessageBox("New Session Error", MB_OK); OnOK(); }
Если объект класса CInternetSession создан, инициализируем указатель m_FtpConnection, также принадлежащий классу CFtpViewDlg:
m_FtpConnection = NULL;
После того как приложение установит связь с сервером FTP, адрес объекта класса CFtpConnection, представляющего сеанс связи с эти сервером, будет занесен в элемент данных m_FtpConnection.
Метод OnInitDialog возвращает значение TRUE. Это означает, что фокус ввода будет установлен на первый орган управления диалоговой панели (первый орган диалоговой панели можно выбрать в редакторе диалоговой панели, выбрав из меню Layout строку Tab Order).
По завершении работы диалоговая панель отображается на экране и пользователь может с ней работать - изменять адрес сервера FTP, соединятся с ним и просматривать содержимое его каталогов.
Методы OnPaint и OnQueryDragIcon используются приложением FtpView в случае его минимизации чтобы отображать на экране пиктограмму. Эти методы мы оставляем без изменения и рассматривать их не будем. Вы можете узнать о методах OnPaint и OnQueryDragIcon более подробно из 24 тома серии “Библиотека системного программиста”.
Метод OnConnect класса CFtpViewDlg вызывается, когда вы нажимаете кнопку Connect чтобы соединится с сервером FTP. Адрес сервера FTP, с которым устанавливается соединение, должен быть введен в поле редактирования IDC_FTP_ADDRESS. Это поле располагается на диалоговой панели с левой стороны от кнопки Connect.
Перед тем как устанавливать соединение с сервером FTP, метод OnConnect блокирует кнопки управления, расположенные на диалоговой панели - Connect, OK и On top. Блокировка выполняется с помощью метода EnableWindow, определенного в классе CWnd. Этот метод вызывается для объектов m_Connect, m_Ok и m_OnTop класса CButton, представляющих эти кнопки:
// Блокируем кнопки Connect, OK и On Top m_Connect.EnableWindow(FALSE); m_Ok.EnableWindow(FALSE); m_OnTop.EnableWindow(FALSE);
Если вы ранее уже соединились с сервером FTP и указатель m_FtpConnection не равен значению NULL, разрываем эту связь и удаляем объект m_FtpConnection. Затем присваиваем m_FtpConnection значение NULL:
if (m_FtpConnection != NULL) { m_FtpConnection -> Close(); delete m_FtpConnection; m_FtpConnection = NULL; }
Пользователь мог изменить адрес сервера, с которым надо установить соединение, поэтому обновляем строку m_FtpAddress, считывая значение из поля редактирования IDC_FTP_ADDRESS. Для этого опять (раньше в методе OnInitDialog) вызываем метод UpdateData, но теперь указываем ему параметр TRUE. Он означает, что состояние органов управления диалоговой панели должно быть записано в привязанные к ним переменные:
UpdateData(TRUE);
Теперь, когда адрес сервера известен, пытаемся с ним соединиться. Для этого вызываем метод GetFtpConnection класса CInternetSession объекта. В случае ошибки метод GetFtpConnection может вызвать исключение CInternetException.
Чтобы организовать обработку этого исключения помещаем вызов метода GetFtpConnection в блок try и создаем соответствующий блок catch:
// Пытаемся соединиться с сервером FTP try { // Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Соединяемся с сервером FTP. Эта операция // может вызвать исключение CInternetException m_FtpConnection = m_InternetSession->GetFtpConnection(m_FtpAddress); }
Так как процесс соединения с сервером может занять достаточно много времени, изменяем форму курсора. Для этого достаточно создать объект wait класса CWaitCursor. Курсор примет свою прежнюю форму автоматически, когда объект wait будет удален. В данном случае это произойдет при выходе из блока try.
В случае если работа метода GetFtpConnection вызовет исключение CInternetException, вызывается соответствующий блок catch. Такое может произойти, например, если сервер FTP с заданным адресом не существует или он не работает в данное время.
Обработчик исключения вызывает для него метод GetErrorMessage. Он возвращает текстовое описание причины исключения, которое затем отображается на экране с помощью функции AfxMessageBox. Если метод GetErrorMessage не может вернуть текстового описания, то на экране отображается сообщение GetFtpConnection Error.
Чтобы завершить обработку исключения удаляем его, вызывая для исключения метод Delete. Так как исключение означает, что установить соединение с FTP сервером не удалось, присваиваем указателю m_FtpConnection значение NULL:
catch (CInternetException* pEx) { // Обрабатываем исключение CInternetException TCHAR szErr[1024]; // временный буфер для сообщения // Выводим сообщение об ошибке if (pEx->GetErrorMessage(szErr, 1024)) AfxMessageBox(szErr); else AfxMessageBox("GetFtpConnection Error"); // Удаляем иссключение pEx->Delete(); // Обнуляем указатель m_FtpConnection m_FtpConnection = NULL; }
Если соединение не установлено, тогда выводим соответствующую надпись в поле IDC_STATUS, снимаем блокировку кнопок Connect, OK и On Top и завершаем работу метода. Чтобы снять блокировку кнопок, вызываем для управляющих ими объектов метод EnableWindow с параметром TRUE:
if( m_FtpConnection == NULL ) m_Status.SetWindowText("Connect not established"); ... m_Connect.EnableWindow(TRUE); m_Ok.EnableWindow(TRUE); m_OnTop.EnableWindow(TRUE);
Если соединение с сервером FTP установлено успешно, определяем текущий каталог. Для этого вызываем метод GetCurrentDirectory, передавая ему в качестве параметра строку sCurrentFtpDirectory. Эта строка определена в методе OnConnect как объект класса CString:
// Определяем текущий каталог сервера FTP BOOL fResult= m_FtpConnection -> GetCurrentDirectory(sCurrentFtpDirectory); if(fResult) sCurentDirectory = sCurrentFtpDirectory; else AfxMessageBox("GetCurrentDirectory Error");
В случае успешного завершения метод GetCurrentDirectory запишет в строку sCurrentFtpDirectory полный путь текущего каталога и мы скопируем его в строку sCurentDirectory, являющуюся элементом класса CFtpViewDlg.
Узнав текущий каталог, вызываем метод DirectoryView, определенный в классе CFtpViewDlg, который считывает имена файлов и каталогов сервера FTP и отображает их в списке на диалоговой панели. Перед вызовом метода DirectoryView мы создаем объект класса CWaitCursor, поэтому во время длительного процесса опроса текущего каталога сервера курсор изменит свою форму. Заметим, что после заполнения списка при выходе управления из блока в котором определен объект wait класса CWaitCursor, форма курсора автоматически восстанавливается:
// Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Отображаем содержимое выбранного каталога DirectoryView();
После того, как содержимое каталога выведено в списке на диалоговой панели, отображаем в поле IDC_STATUS путь каталога и снимаем блокировку с кнопок Connect, OK и On Top:
// Отображаем на диалоговой панели новый путь каталога m_Status.SetWindowText(sCurentDirectory);
Метод DirectoryView класса CFtpViewDlg выполняет львиную долю работы всего нашего приложения. Именно этот метод после соединения с сервером FTP определяет названия и основные характеристики объектов в заданном каталоге сервера, а затем отображает их в списке IDC_FTP_LIST на главной диалоговой панели приложения:
BOOL CFtpViewDlg::DirectoryView() { // Переменная, сигнализирующая о получении последнего // элемента каталога BOOL fResult; // Временная переменная, определяющая тип объекта - // файл или каталог BOOL fDirectory;
Метод DirectoryView заполняет список IDC_FTP_LIST именами каталогов и файлов сервера FTP. Чтобы удалить из списка элементы, занесенные в него ранее, вызываем метод DeleteAllItems для объекта m_FtpList, управляющего этим списком (см. метод DoDataExchange):
// Удалить все элементы из списка IDC_FTP_LIST m_FtpList.DeleteAllItems();
Для поиска файлов и каталогов на сервере FTP предназначен класс CFtpFileFind, входящий в состав библиотеки классов MFC. Метод DirectoryView создает объект этого класса. В качестве параметра конструктору класса CFtpFileFind передается указатель m_FtpConnection на объект, представляющий соединение с сервером FTP:
CFtpFileFind m_FtpFileFind(m_FtpConnection);
Далее мы вызываем для только что созданного объекта m_FtpFileFind метод FindFile. Он выполняет поиск каталогов и файлов в каталоге, путь которого указан в качестве параметра:
// Получаем имена всех объектов текущего каталога if(fResult = m_FtpFileFind.FindFile(_T(sCurentDirectory + "/*"))) { . . . }
Если каталог не содержит других объектов - каталогов и файлов, или в случае возникновения ошибки, метод FindFile возвращает ненулевое значение. Тогда на экран выводится сообщение File's not found or error и работа метода DirectoryView завершается.
В случае, если метод FindFile завершается успешно, это означает, что указанный каталог содержит другие каталоги или файлы. Тогда мы начинаем в цикле вызывать метод FindNextFile. Он получает имя очередного файла или каталога и мы отображаем его вместе с некоторой дополнительной информацией в списке объектов каталога. Когда метод FindNextFile вернет значение FALSE, это значит, что мы считали последний объект каталога. В этом случае мы добавляем этот последний объект к списку, завершаем цикл и выходим из метода DirectoryView:
for(int n = 0;fResult; n++) { // Получаем очередной объект из каталога fResult = m_FtpFileFind.FindNextFile(); . . . }
После обращения к методу FindNextFile можно вызывать другие методы класса CFtpFileFind. Они позволяют определить различные характеристики обнаруженных объектов. Например, вы можете узнать являются ли они каталогами или файлами, получить их имя, размер, дату создания и т. д.
Так, сначала мы определяем, является ли очередной объект, полученный методом FindNextFile файлом или каталогом и записываем полученный результат в fDirectory. Если мы получили каталог, то метод IsDirectory возвращает значение 1, а если файл, то - 0. Мы будем использовать значение fDirectory, сравнивая его с константой DIRECTORY, определенной как 0 и FILE, определенной как 1 в файле FtpViewDlg.h.
Далее с помощью метода GetFileName определяется имя объекта (файла или каталога) и записывается во временную строку fileName класса CString:
CString fileName; // Определяем имя объекта fileName = m_FtpFileFind.GetFileName();
Теперь, когда мы узнали тип объекта и его имя можно добавить элемент к первой колонке в списке. Для этого необходимо заполнить поля структуры lv_item и передать ее методу InsertItem объекта, управляющего списком:
// Структура для добавления нового элемента к списку LV_ITEM lv_item; . . . // Заполняем структуру lv_item, сведениями об // очередном объекте каталога сервера FTP. Указываем, // что в список добавляется текст и изображение lv_item.mask = LVIF_TEXT | LVIF_IMAGE; // Указываем номер строки в списке lv_item.iItem = n; // Заполняем первую колонку lv_item.iSubItem = 0; // Выбираем изображение для нового элемента списка в // зависимости от типа объекта lv_item.iImage = (fDirectory) ? DIRECTORY : FILE; // Указываем имя каталога или файла lv_item.pszText = fileName.GetBuffer(MIN_LEN_BUF); // Добавляем новый элемент к списку IDC_FTP_LIST m_FtpList.InsertItem(&lv_item);
После заполнения первой колонки списка необходимо заполнить вторую, записав в нее длину файла или, если данный элемент списка описывает каталог, то строку Dir. Точно также как в случае с именем объекта, сначала заполняется структура lv_item, а затем она передается методу InsertItem.
Обратите внимание, что в отличие от первой колонки, во второй колонки не используется пиктограмма, поэтому в поле mask структуры lv_item заносится только константа LVIF_TEXT. Другое отличие состоит в том, что полю iSubItem структуры lv_item присваивается значение 1, указывающее, что мы будем добавлять элемент во вторую колонку (нумерация iSubItem начинается с нуля):
//============= Определяем длину файла ============= // Длинна файла DWORD dwLength = 0; // Временные строка для формирования текстового // представления длинны файла CString sLength; // Заполняем колонку Length для новой записи и // записываем в нее длину файла или строку Dir, если // новый объект - каталог. // Добавляется только текст без пиктограммы lv_item.mask = LVIF_TEXT; // Указываем номер строки в списке lv_item.iItem = n; // Заполняем вторую колонку lv_item.iSubItem = 1; // Если очередной объект является каталогом, то // вместо в колонке Length записываем строку Dir if(fDirectory) { lv_item.pszText = "Dir"; } // Если очередной объект является файлом, то // записываем его длину в колонку Length else { // Определяем длину файла dwLength = m_FtpFileFind.GetLength(); // Формируем текстовое представление длины файла sLength.Format("%d", dwLength); lv_item.pszText = sLength.GetBuffer(MIN_LEN_BUF); } // Добавляем запись во второй колонке списка (колонка // Length) m_FtpList.SetItem(&lv_item);
Теперь остается заполнить только третью и четвертую колонки списка, в которых надо записать дату и время создания или изменения файла (каталога). Для определения этих данных вызывается метод GetLastWriteTime класса CFtpFileFind, который записывает полученную информацию в объект mTime класса CTime:
// Дата и время создания каталога или файла CTime mTime; // Временные строки для формирования текстового // представления даты и времени CString sDate; CString sTime; // Определяем время изменения файла или каталога if(!m_FtpFileFind.GetLastWriteTime(mTime)) break;
Затем мы подготавливаем структуру lv_item, заполняя ее поля соответствующими значениями:
// Добавляется только текст без пиктограммы lv_item.mask = LVIF_TEXT; // Указываем номер строки в списке lv_item.iItem = n; // Заполняем третью колонку lv_item.iSubItem = 2; // Выделяем из объекта mTime день, месяц и год sDate = mTime.Format("%d.%m.%y"); // Записываем сформированную дату в структуру lv_item lv_item.pszText = sDate.GetBuffer(MIN_LEN_BUF);
Чтобы добавить запись в колонке Date обращаемся к методу SetItem и передаем ему указатель на структуру lv_item:
// Добавляем запись во второй колонке списка (колонка Date) m_FtpList.SetItem(&lv_item);
Затем мы немного модифицируем только что заполненную структуру lv_item, изменяя в ней только поля iSubItem и pszText:
// Заполняем четвертую колонку, записываем в нее // время последнего изменения файла (каталога) lv_item.iSubItem = 3; // Выделяем из объекта mTime часы, минуты и секунды sTime = mTime.Format("%H:%M:%S"); // Записываем сформированную строку, содержащую время lv_item.pszText = sTime.GetBuffer(MIN_LEN_BUF);
Опять вызываем метод SetItem, передавая ему модифицированную структуру lv_item. Этот вызов добавит информацию о времени последнего изменения файла в колонке Time:
// Добавляем запись во второй колонке списка (колонка Time) m_FtpList.SetItem(&lv_item);
Когда при очередном вызове метод FindNextFile возвращает нулевое значение, значит найден последний объект из каталога. В список добавляется последняя строка и на этом работа цикла заполнения списка завершается. Заканчиваем поиск объектов в данном каталоге вызывая метод Close и возвращаем управление с помощью оператора return:
// Заканчиваем поиск объектов в каталоге, закрываем // объект m_FtpFileFind m_FtpFileFind.Close();
В списке IDC_FTP_LIST показываются имена файлов и каталогов сервера FTP. Файлы выделяются пиктограммой , а каталоги пиктограммой и строкой Dir в столбце Length. Чтобы вы имели возможность просмотра не только того каталога, с которым вас по умолчанию соединил сервер FTP, но также и других вложенных каталогов, мы предусмотрели метод OnDblclkFtpList.
Метод OnDblclkFtpList вызывается когда пользователь выполняет двойной щелчок левой клавишей мыши по имени каталога или имени файла. В качестве параметров методу OnDblclkFtpList передаются указатель pNMHDR на структуру типа NMHDR и указатель pResult на переменную типа LRESULT.
Рассмотрим метод OnDblclkFtpList более подробно. Сначала он блокирует список IDC_FTP_LIST, вызывая метод EnableWindow с параметром FALSE:
CString sSelItem; // Название каталога // Блокируем список IDC_FTP_LIST m_FtpList.EnableWindow(FALSE);
Затем с помощью метода GetItemCount мы определяем количество элементов в списке IDC_FTP_LIST и записываем его во временную переменную iTotalNumber типа int:
iTotalNumber = m_FtpList.GetItemCount();
Далее мы пытаемся определить элемент, который выбран из списка. К сожалению мы не обнаружили простого способа выполнить эту задачу. Поэтому последовательно проверяем все элементы списка до тех пор, пока не найдем элемент, у которого установлен атрибут LVIS_SELECTED, означающий, что он выбран.
Для определения атрибутов элемента списка вызываем метод GetItemState. В качестве первого параметра методу GetItemState указывается индекс элемента, атрибуты которого надо определить, а в качестве второго - комбинацию атрибутов состояние которых нужно узнать. В нашем случае в качестве второго параметра фигурирует только константа LVIS_SELECTED, так как нас интересует только выбран данный элемент или нет:
// Определяем, какой объект списка выбран for(int i = 0; i < iTotalNumber; i++) { if(LVIS_SELECTED == m_FtpList.GetItemState(i,LVIS_SELECTED)) { . . . break; } }
Если метод m_FtpList.GetItemState( i, LVIS_SELECTED )) возвращает значение LVIS_SELECTED, значит элемент с индексом i выбран из списка. В этом случае определяем имя соответствующего объекта и его размер, считывая их непосредственно из первой и второй колонки списка:
// Определяем название выбранного элемента списка // (имя файла или каталога) sSelItem = m_FtpList.GetItemText( i, 0 ); // Считываем данные из колонки Length sLength_Dir = m_FtpList.GetItemText( i, 1 );
Если вместо длины файла во второй колонке записана строка Dir, значит из списка выбран каталог. Тогда мы добавляем название этого каталога к концу строки, содержащей текущий каталог сервера FTP.
В противном случае вы выбрали из списка не каталог, а файл и на экран выводится предупреждающее сообщение, о том что вы выбрали файл с определенным именем:
if(sLength_Dir == "Dir") // Выбран каталог sCurentDirectory = sCurentDirectory + "/" + sSelItem; else // Выбран файл AfxMessageBox("You select file " + sSelItem);
После этого изменяем форму курсора и вызываем метод DirectoryView, который считывает имена объектов, расположенных в каталоге sCurentDirectory и отображает их в списке диалоговой панели приложения.
Чтобы изменить форму курсора мы создаем объект wait класса CWaitCursor. Когда при выходе из метода OnDblclkFtpList этот объект удаляется, курсор автоматически восстанавливает свою форму:
. . . // Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Отображаем содержимое выбранного каталога DirectoryView(); }
Затем в поле IDC_STATUS отображается новый путь каталога и снимается блокировка со списка, так что мы опять можем выбирать из него файлы и каталоги:
// Отображаем на диалоговой панели новый путь каталога m_Status.SetWindowText(sCurentDirectory); // Снимаем блокировку списка IDC_FTP_LIST m_FtpList.EnableWindow(TRUE);
В завершении метода OnDblclkFtpList мы записываем по адресу pResult нулевое значение, которое означает, что метод успешно завершен:
*pResult = 0;
Метод OnOnTop во многом похож на уже описанный нами метод OnDblclkFtpLis, но используется не для входа, а для выхода из каталогов. Если вы вошли в каталог и желаете выйти из него на верхний уровень, вы должны нажать кнопку On top. В ответ на сообщение от этой кнопки вызывается метод OnOnTop. Он блокирует список IDC_FTP_LIST, вызывая метод EnableWindow для объекта m_FtpList, управляющего этим списком:
// Блокируем список IDC_FTP_LIST m_FtpList.EnableWindow(FALSE);
Затем с помощью метода ReverseFind класса CString мы ищем последнее вхождение символа / в строке sCurentDirectory, содержащей имя текущего каталога сервера, содержимое которого показывается на экране.
Если метод ReverseFind не обнаруживает в строке с путем каталога символов /, он возвращает значение -1. Это означает что мы уже находимся в корневом каталоге сервера:
int iNum = sCurentDirectory.ReverseFind('/'); if(iNum == -1) { // Если символ / не обнаружен, значит мы находимся в // корневом каталоге AfxMessageBox("No top directory"); }
Если же символ / найден, то обращаясь к методу Left мы удаляем все символы, расположенные справа от него (включительно). Таким образом, в строке sCurentDirectory теперь будет записан путь каталога верхнего уровня.
Так как мы изменили текущий каталог, содержимое которого показывается на экране, вызываем метод DirectoryView. Он просмотрит имена каталогов и файлов, расположенных по новому пути, и заполнит список:
else { // Удаляем из строки с именем текущего каталога названия // последнего каталога sCurentDirectory = sCurentDirectory.Left(iNum); // Меняем форму курсора (курсор "ожидание") CWaitCursor wait; // Отображаем содержимое каталога верхнего уровня DirectoryView(); }
Перед тем как вызывать метод DirectoryView, мы создаем объект wait класса CWaitCursor. При этом автоматически изменится внешний вид курсора приложения. Как только метод DirectoryView закончит свою работу и управление выйдет из блока else, объект wait будет удален, а внешний вид курсора восстановится.
После этого снимаем блокировку со списка IDC_FTP_LIST и отображаем новый текущий путь в поле IDC_STATUS:
// Снимаем блокировку списка IDC_FTP_LIST m_FtpList.EnableWindow(TRUE); // Отображаем на диалоговой панели новый путь каталога m_Status.SetWindowText(sCurentDirectory);
Когда пользователь нажимает кнопку OK и закрывает диалоговую панель, вызывается деструктор класса CFtpViewDlg. Он последовательно закрывает соединение с сервером FTP и завершает сеанс связи с Internet.
Деструктор проверяет установлено ли соединение с сервером FTP - при этом указатель m_FtpConnection должен быть не равен значению NULL. Если это так, сначала вызывается метод Close, закрывающий соединение, а затем удаляется сам объект m_FtpConnection:
if (m_FtpConnection != NULL) { m_FtpConnection -> Close(); delete m_FtpConnection; }
После того, как соединение с сервером FTP закрыто, завершаем сеанс связи с Internet. Для этого вызываем метод Close, а затем удаляем объект m_InternetSession:
if (m_InternetSession != NULL) { m_InternetSession -> Close(); delete m_InternetSession; }
В завершение деструктор класса CFtpViewDlg удаляет список изображений m_ImageList:
delete m_ImageList;
Приложение FtpView, рассмотренное нами в предыдущем разделе, позволяет только просматривать структуру каталогов сервера FTP. С его помощью вы не сможете загрузить файл с сервера на диск локального компьютера или наоборот, записать файл на сервер FTP. В этом разделе мы расширим возможности приложения FtpView, так чтобы его можно было использовать для загрузки файлов с сервера.
Приложение FtpView построено таким образом, что при выборе из списка файла, метод OnDblclkFtpList отображает на экране предупреждающее сообщение и больше ничего не делает. Добавим к классу CFtpViewDlg новый метод FtpFileDownLoad, которое будет выполнять загрузку файла с данным именем. Тогда нам останется немного исправить метод OnDblclkFtpList и приложение для загрузки файлов готово.
Чтобы добавить к классу CFtpViewDlg метод FtpFileDownload, воспользуйтесь средствами MFC ClassWizard. В определении класса CFtpViewDlg появится объявление нового метода (листинг 2.10). Соответствующий фрагмент кода мы привели в листинге 3.3. Определение метода FtpFileDownload будет добавлено в файле FtpViewDlg.cpp. Исправьте этот метод так, как это показано в листинге 2.11.
Листинг 2.10. Файл FtpViewDlg.h, фрагмент определения класса CFtpViewDlg
////////////////////////////////////////////////////////////// // Класс CFtpViewDlg class CFtpViewDlg : public CDialog { . . . DECLARE_MESSAGE_MAP() private: BOOL FtpFileDownload( CString sFileName ); };
Метод FtpFileDownload запрашивает у пользователя имя и расположение файла на локальном диске компьютера, в который будет загружен файл с сервера, выбранный из списка в главной диалоговой панели приложения (листинг 2.11). Когда имя файла определено, метод FtpFileDownload выполняет загрузку файла, считывая его непосредственно с сервера FTP.
Листинг 2.11. Файл FtpViewDlg.cpp, метод FtpFileDownload
//============================================================ // Метод FtpFileDownload класса CFtpViewDlg // Загружает файл с сервера FTP //============================================================ BOOL CFtpViewDlg::FtpFileDownload( CString sFileName ) { BOOL fResult; CString sLocalFileName; // Определяем объект класса CFileDialog, представляющий // стандартную диалоговую панель Save As, в которой // по умолчанию выбрано имя файла sFileName CFileDialog mFileOpen(FALSE, NULL, sFileName); // Отображаем диалоговую панель Open и позволяем // пользователю выбрать с помощью нее один файл int result = mFileOpen.DoModal(); // Проверяем как была закрыта диалоговая панель Open - // по нажатию кнопки OK или Cancel if(result == IDCANCEL) { // Если пользователь отказался от выбора файла и // нажал кнопку Cancel отображаем соответствующее // сообщение и возвращаем значение FALSE AfxMessageBox("File not selected"); return FALSE; } else if(result == IDOK) { // Если пользователь нажал кнопку OK, определяем // имя выбранного файла sLocalFileName = mFileOpen.GetPathName(); } sFileName = sCurentDirectory + "/" + sFileName; fResult = m_FtpConnection -> GetFile(sFileName.GetBuffer(MIN_LEN_BUF), sLocalFileName.GetBuffer(MIN_LEN_BUF), FALSE ); return fResult; }
Остается только исправить метод OnDblclkFtpList так, чтобы когда пользователь выберет файл из списка в главной диалоговой панели приложения, то выполнялся вызов метода FtpFileDownload класса CFtpViewDlg. На листинге 2.12 мы привели фрагмент файла FtpViewDlg.cpp, который содержит новый вариант метода OnDblclkFtpList.
Листинг 2.12. Файл FtpViewDlg.cpp, метод OnDblclkFtpList
//============================================================ // Метод OnDblclkFtpList класса CFtpViewDlg // Переходит в выбранный каталог //============================================================ void CFtpViewDlg::OnDblclkFtpList(NMHDR* pNMHDR, LRESULT* pResult) { int iTotalNumber; // Количество элементов списка CString sSelItem; // Название каталога CString sLength_Dir; // Длина файла или строка Dir // Блокируем список IDC_FTP_LIST m_FtpList.EnableWindow(FALSE); // Определяем количество элементов в списке IDC_FTP_LIST iTotalNumber = m_FtpList.GetItemCount(); // Определяем, какой объект списка выбран for(int i = 0; i < iTotalNumber; i++) { // Атрибут LVIS_SELECTED установлен // у выбранного элемента списка if(LVIS_SELECTED == m_FtpList.GetItemState( i, LVIS_SELECTED )) { // Определяем название выбранного элемента списка // (имя файла или каталога) sSelItem = m_FtpList.GetItemText( i, 0 ); // Считываем данные из колонки Length sLength_Dir = m_FtpList.GetItemText( i, 1 ); if(sLength_Dir == "Dir") // Выбран каталог { // Переходим в выбранный каталог sCurentDirectory = sCurentDirectory + "/" + sSelItem; // Меняем форму курсора CWaitCursor wait; // Отображаем содержимое выбранного каталога DirectoryView(); // Отображаем новый путь каталога m_Status.SetWindowText(sCurentDirectory); } else // Выбран файл { // Меняем форму курсора CWaitCursor wait; FtpFileDownload(sSelItem); } break; } } // Снимаем блокировку списка IDC_FTP_LIST m_FtpList.EnableWindow(TRUE); *pResult = 0; }
Заново постройте проект и запустите приложение. На экране появится главная диалоговая панель, с помощью которой вы можете соединиться с сервером FTP и просмотреть содержимое его каталогов.
Однако теперь, когда вы делаете двойной щелчок левой клавишей мыши по имени файла, вместо предупреждающего сообщения на экране появляется стандартная диалоговая панель Save As с предложением сохранить выбранный файл на локальном диске компьютера.
На рисунке 2.14 мы показали предложение приложения FtpView сохранить файл с именем readme.txt, расположенный на сервере FTP корпорации Microsoft (адрес ftp.microsoft.com) в каталоге developer, в файле readme.txt на локальном диске компьютера в каталоге Library.
Рис. 2.14. Выбор имени файла на локальном компьютере
По умолчанию в панели Save As вам предлагается имя файла, соответствующее имени исходного файла на сервере FTP. Вы можете оставить его без изменения или поменять по своему усмотрению. Если вы выберите на локальном компьютере уже существующий файл, когда приложение запросит подтверждение на его замену (рис. 2.15).
Рис. 2.15. Файл уже существует
Рассмотрим как работают методы OnDblclkFtpList и FtpFileDownload.
Когда из списка файлов и каталогов сервера FTP вы выбираете файл и делаете по его имени двойной щелчок левой клавишей мыши, вызывается метод OnDblclkFtpList.
Он блокирует список IDC_FTP_LIST и определяет сколько элементов в нем содержится. Далее в цикле находится индекс выбранного элемента списка и считываются его название (колонка Name) и длина (колонка Length). До этого момента программный код метода OnDblclkFtpList совпадает с первым вариантом метода приложения FtpView. Далее начинаются отличия:
// Определяем название выбранного элемента списка // (имя файла или каталога) sSelItem = m_FtpList.GetItemText( i, 0 ); // Считываем данные из колонки Length sLength_Dir = m_FtpList.GetItemText( i, 1 );
Если в колонке Length записана строка Dir, значит данный элемент описывает каталог. В этом случае мы переходим в выбранный каталог, для чего добавляем к строке sCurentDirectory имя выбранного каталога.
Затем изменяем форму курсора и вызываем метод DirectoryView. Он обновляет информацию в списке, заполняя ее именами каталогов и файлов из каталога sCurentDirectory сервера FTP:
if(sLength_Dir == "Dir") // Выбран каталог { // Переходим в выбранный каталог sCurentDirectory = sCurentDirectory + "/" + sSelItem; // Меняем форму курсора CWaitCursor wait; // Отображаем содержимое выбранного каталога DirectoryView(); // Отображаем новый путь каталога m_Status.SetWindowText(sCurentDirectory); }
Если же из списка выбран не каталог, а файл, то порядок действий другой. В этом случае мы также сначала изменяем форму курсора, а затем вызываем метод FtpFileDownload класса CFtpViewDlg, передавая ему в качестве параметра имя выбранного файла:
else // Выбран файл { // Меняем форму курсора CWaitCursor wait; FtpFileDownload(sSelItem); }
Таким образом, всю основную работу выполняет метод FtpFileDownload, который мы рассмотрим ниже.
Метод FtpFileDownload имеет единственный параметр, через который передается имя файла, выбранного пользователем из списка объектов сервера FTP:
BOOL CFtpViewDlg::FtpFileDownload( CString sFileName ) { . . . }
Для начала метод FtpFileDownload запрашивает у пользователя имя файла, под которым файл с сервера FTP будет записан на диске локального компьютера.
Для облегчения этой задачи мы воспользовались классом CFileDialog, который входит в состав библиотеки классов MFC и предназначен для управления стандартной диалоговой панелью Save As. Объявляем объект mFileOpen этого класса:
CFileDialog mFileOpen(FALSE, NULL, sFileName);
Конструктору класса CFileDialog мы указали три параметра. Первый параметр имеет значение FALSE и определяет, что объект mFileOpen будет управлять диалоговой панелью Save As. Если бы мы указали вместо FALSE значение TRUE, то была бы создана диалоговая панель Open.
Второй параметр конструктора может задавать расширение для имен файлов, которое будет выбрано по умолчанию, но мы не используем эту возможность, так как наше приложение способно загружать с сервера FTP файлы различных типов с разными расширениями.
Третий параметр конструктора sFileName содержит имя файла, которое мы выбрали из списка. Это имя будет предложено в диалоговой панели Save As по умолчанию.
Конструктор класса CFileDialog не открывает диалоговой панели Save As. Он только создает управляющий объект mFileOpen. Чтобы диалоговая панель Save As появилась на экране вызывается метод DoModal класса CFileDialog:
result = mFileOpen.DoModal();
Метод DoModal создает модальную диалоговую панель. Поэтому он возвращает управление только тогда, когда пользователь выберет файл или откажется от выбора и нажмет кнопку Cancel.
Если пользователь откажется от выбора и нажмет кнопку Cancel, то метод DoModal вернет значение IDCANCEL. Тогда мы выводим на экран соответствующее сообщение и завершаем метод FtpFileDownload, возвращая значение FALSE:
// Проверяем как была закрыта диалоговая панель Open - // по нажатию кнопки OK или Cancel if(result == IDCANCEL) { // Если пользователь отказался от выбора файлов и // нажал кнопку Cancel отображаем соответствующее // сообщение и возвращаем значение FALSE AfxMessageBox("File not selected"); return FALSE; }
Если пользователь выбрал файл из диалоговой панели Save As, то метод DoModal возвращает значение IDOK. В этом случае мы определяем полный путь файла, включая имя диска, путь каталога и имя файла и записываем его во временную строку sLocalFileName. Эта строка определена в данном метода как объект класса CString:
else if(result == IDOK) { sLocalFileName = mFileOpen.GetPathName(); }
Через параметр sFileName методу FtpFileDownload было передано имя файла, выбранного пользователем из списка файлов и каталогов сервера FTP. Мы не стали передавать полный путь файла целиком, так как имя файла можно использовать для инициализации диалоговой панели Save As. Текущий путь каталога сервера FTP записан в строке sCurentDirectory, поэтому чтобы сформировать полный путь файла, выбранного на сервере, достаточно выполнить следующую операцию:
sFileName = sCurentDirectory + "/" + sFileName;
Теперь в строке sFileName записан полный путь файла, расположенного на сервере FTP, а в строке sLocalFileName - полный путь файла на диске локального компьютера.
Остается загрузить файл с сервера FTP. Для этого мы вызываем метод GetFile, входящий в класс CFtpConnection. Метод GetFile имеет много различных параметров, но в нашем приложении мы используем только три. Первый параметр задает полный путь файла, который надо получить с сервера FTP, второй - полный путь файла на локальном компьютере в который полученный файл будет записан, а третий - определяет поведение метода в том случае если второй параметр задает файл, уже существующий на диске. В данном случае в качестве третьего параметра используется значение FALSE. Оно означает, что если на диске локального компьютера уже есть файл с таким именем, он перезаписывается (предварительно у пользователя запрашивается подтверждение на перезапись файла). Вы можете использовать в качестве третьего параметра значение TRUE или вовсе его не указывать. Тогда метод GetFile не будет перезаписывать уже существующий файл, а просто завершится с ошибкой:
fResult = m_FtpConnection -> GetFile( sFileName.GetBuffer(MIN_LEN_BUF), sLocalFileName.GetBuffer(MIN_LEN_BUF), FALSE );
В случае успешного завершения метод GetFile возвращает ненулевое значение, а если произошла какая-нибудь ошибка - нуль. Мы используем значение, возвращаемое методом GetFile в качестве результата работы самого метода FtpFileDownload:
return fResult;
У приложения FtpView, рассмотренном в предыдущем разделе, есть как минимум один большой недостаток. В то время когда идет загрузка файла с сервера FTP, ход этого процесса никак не отображается и приложение создает впечатление зависшей программы. Только те, кто использует внешний модем, могут догадаться о работе приложения по интенсивному или не очень интенсивному миганию индикаторов на его лицевой панели. Если компьютер оборудован внутренним модемом, вы в принципе также можете следить за его состоянием, если разместите в панели управления Windows 95 (или Windows NT версии 4.0) индикатор работы модема.
Этот недостаток нашего приложения обусловлен устройством метода GetFile класса CFtpConnection, так как он не предусматривает возможности извещения приложения о загрузке части файла. Поэтому вместо использования метода GetFile мы предлагаем воспользоваться методом OpenFile класса CFtpConnection и методом Read класса CInternetFile. Метод OpenFile открывает файл, расположенный на сервере, а метод Read позволяет считывать открытый файл по частям.
Кроме того, метод GetFile сразу записывает файл, полученный с сервера, в файл на локальном диске компьютера. Метод Read записывает полученный фрагмент файла с сервера во временный буфер. Это позволяет, например, обеспечить дополнительную обработку данных по мере их получения.
Доработаем приложение FtpView так, чтобы оно показывало ход загрузки файлов с сервера FTP. Для этого сначала загрузите в редактор ресурсов диалоговую панель IDD_FTPVIEW_DIALOG. Немного увеличьте вертикальный размер панели и добавьте внизу панели линейный индикатор progress bar, выбрав для него идентификатор IDC_PROGRESS, и текстовую строку Progress (рис. 2.13). Фрагмент файла FtpView.rc, содержащий шаблон диалоговой панели мы привели в листинге 4.4.
Листинг 2.13. Фрагмент файла FtpView.rc
////////////////////////////////////////////////////////////// // // Dialog // IDD_FTPVIEW_DIALOG DIALOGEX 0, 0, 352, 215 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW CAPTION "FtpView" FONT 8, "MS Sans Serif" BEGIN PUSHBUTTON "Connect",IDC_CONNECT,196,5,45,15 PUSHBUTTON "On top",IDC_ON_TOP,253,5,40,15 DEFPUSHBUTTON "OK",IDOK,305,5,40,15 CONTROL "List1",IDC_FTP_LIST,"SysListView32",LVS_REPORT | LVS_SORTASCENDING | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,5,25,340,133 EDITTEXT IDC_FTP_ADDRESS,5,5,183,14,ES_AUTOHSCROLL LTEXT "Directory:",IDC_STATIC,7,171,33,8 EDITTEXT IDC_STATUS,44,169,301,15,ES_AUTOHSCROLL | ES_READONLY CONTROL "Progress1",IDC_PROGRESS, "msctls_progress32", WS_BORDER, 43,193,302,14 LTEXT "Progress:",IDC_STATIC,7,196,30,8 END
Запустите MFC ClassWizard, перейдите на страницу Member Variables, выберите класс CFtpViewDlg и добавьте к нему управляющий объект для линейного индикатора. Для этого выберите из списка Control IDs идентификатор IDC_PROGRESS и нажмите кнопку Add Variable. Вам будет предложено привязать к линейному индикатору объект класса CProgressCtrl. Введите имя этого объекта. В нашем случае мы использовали имя m_LoadProgress. Нажмите кнопку OK и закройте MFC ClassWizard.
В классе CFtpViewDlg появится новый элемент m_LoadProgress. Он будет добавлен в блоке AFX_DATA, которым управляет MFC ClassWizard:
class CFtpViewDlg : public CDialog { // Construction public: ~CFtpViewDlg(); CFtpViewDlg(CWnd* pParent = NULL); // Dialog Data //{{AFX_DATA(CFtpViewDlg) enum { IDD = IDD_FTPVIEW_DIALOG }; CProgressCtrl m_LoadProgress; . . . //}}AFX_DATA . . . }
Кроме того, вносятся изменения в метод DoDataExchange класса CFtpViewDlg. В блоке AFX_DATA_MAP, в котором MFC ClassWizard размещает методы для обмена данными, добавляется новая строка. Она устанавливает связь между линейным индикатором IDC_PROGRESS и объектом m_LoadProgress:
DDX_Control(pDX, IDC_PROGRESS, m_LoadProgress);
Перед тем как использовать линейный индикатор, следует выполнить его настройку - установить границы значений в которых он может меняться и шаг приращения. В принципе, вы можете оставить эти значения по умолчанию. В этом случае линейный индикатор будет отображать значения в интервале от 0 до 100 с шагом 10. Однако мы все же выполним инициализацию линейного индикатора, чтобы программный код приложения был более нагляден.
Загрузите в текстовый редактор Microsoft Visual C++ метод OnInitDialog класса CFtpViewDlg и добавьте к нему вызовы методов SetRange и SetStep, как это показано на листинге 2.14. Обратите внимание, что эти методы вызываются для объекта m_LoadProgress, представляющего линейный индикатор IDC_PROGRESS.
Листинг 2.14. Фрагмент файла FtpViewDlg.cpp, метод OnInitDialog класса CFtpViewDlg
BOOL CFtpViewDlg::OnInitDialog() { // Вызываем метод OnInitDialog базового класса CDialog CDialog::OnInitDialog(); // Устанавливаем пиктограммы, которые будут отображаться // в случае минимизации диалоговой панели SetIcon(m_hIcon,TRUE); // Пиктограмма стандартного размера SetIcon(m_hIcon,FALSE); // Пиктограмма маленького размера //========================================================= // Устанавливаем границы для линейного индикатора m_LoadProgress.SetRange(0, 100); // Устанавливаем шаг приращения для линейного индикатора m_LoadProgress.SetStep(10); //========================================================= // Выполняем инициализацию списка IDC_FTP_LIST //========================================================= . . . }
Наибольшие изменения надо внести в метод FtpFileDownload класса CFtpViewDlg. Мы должны изменить способ загрузки файла с сервера - отказаться от использования метода GetFile и принимать файл по частям, используя методы OpenFile и Read. Так как в этом случае принимаемые данные не записываются в файл, а помещаются во временный буфер, необходимо организовать запись данных из буфера в файл на локальном диске. Для этого мы будем использовать возможности класса CFile. И наконец, по мере загрузки файла с сервера мы будем изменять состояние линейного индикатора IDC_PROGRESS.
Модифицированный метод FtpFileDownload класса CFtpViewDlg, выполняющий все описанные выше действия представлен в листинге 2.15.
Листинг 2.15. Фрагмент файла FtpViewDlg.cpp, метод FtpFileDownload класса CFtpViewDlg
BOOL CFtpViewDlg::FtpFileDownload( CString sFileName ) { BOOL fResult; CString sLocalFileName; // Определяем объект класса CFileDialog, представляющий // стандартную диалоговую панель Save As, в которой // по умолчанию выбрано имя файла sFileName CFileDialog mFileOpen(FALSE, NULL, sFileName); // Отображаем диалоговую панель Open и позволяем // пользователю выбрать с помощью нее один файл int result = mFileOpen.DoModal(); // Проверяем как была закрыта диалоговая панель Open - // по нажатию кнопки OK или Cancel if(result == IDCANCEL) { // Если пользователь отказался от выбора файлов и // нажал кнопку Cancel отображаем соответствующее // сообщение и возвращаем значение FALSE AfxMessageBox("File not selected"); return FALSE; } else if(result == IDOK) { // Если пользователь нажал кнопку OK, определяем // имя выбранного файла sLocalFileName = mFileOpen.GetPathName(); } //========================================================= // Формируем полное имя файла для загрузки его с // сервера FTP sFileName = sCurentDirectory + "/" + sFileName; //========================================================= // Чтобы узнать размер файла, записанного на сервере FTP, // создаем объект класса CFtpFileFind CFtpFileFind m_FtpFileFind(m_FtpConnection); // Выполняем поиск выбранного нами файла if(fResult = m_FtpFileFind.FindFile(_T(sFileName))) { // Если поиск закончился успешно, получаем его // характеристики fResult = m_FtpFileFind.FindNextFile(); // Временная переменная для хранения размера файла DWORD dwLength = 0; // Определяем длину файла dwLength = m_FtpFileFind.GetLength(); // В соответствии с размером файла устанавливаем новые // границы для линейного индикатора m_LoadProgress.SetRange(0, (int)(dwLength/READ_BLOCK) ); // Устанавливаем шаг приращения для линейного индикатора m_LoadProgress.SetStep(1); } // Так как мы искали только один файл, заканчиваем поиск m_FtpFileFind.Close(); // Определяем указатель на файл сервера CInternetFile* iFile; // Открываем выбранный нами файл на сервере FTP iFile = m_FtpConnection -> OpenFile( sFileName.GetBuffer(MIN_LEN_BUF), GENERIC_READ, FTP_TRANSFER_TYPE_BINARY ); //========================================================= // Создаем и открываем файл на локальном диске компьютера CFile fLocalFile( sLocalFileName, CFile::modeCreate | CFile::modeWrite ); //========================================================= // Временная переменная. В нее заносится количество байт, // считанное с файла на сервере UINT nReadCount = 0; // Создаем буфер для чтения файла с сервера FTP void* ptrBuffer; ptrBuffer = malloc( READ_BLOCK ); // Считываем файл с сервера, записываем его в локальный // файл и изменяем текущее положение линейного индикатора do { // Если во время операции чтения возникнет исключение // CInternetException, то оно будет обработано // соответствующим блоком catch try { // Читаем из файла на сервере READ_BLOCK байт nReadCount = iFile -> Read( ptrBuffer, READ_BLOCK ); } catch (CInternetException* pEx) { // Обрабатываем исключение CInternetException TCHAR szErr[1024]; // Временный буфер для сообщения // Выводим сообщение об ошибке if (pEx->GetErrorMessage(szErr, 1024)) AfxMessageBox(szErr); else AfxMessageBox("GetFtpConnection Error"); // Так как возникла ошибка, значит данные не считаны nReadCount = 0; // Удаляем исключение pEx->Delete(); } // Записываем информацию, считанную из файла на сервере, // которая находится в буфере в локальный файл fLocalFile.Write( ptrBuffer, nReadCount ); // Увеличиваем значение на линейном индикаторе m_LoadProgress.StepIt(); // Продолжаем чтение файла с сервера до тех пор, пока не // будет достигнут конец файла } while (nReadCount == READ_BLOCK); // После окончания загрузки файла сбрасываем // линейный индикатор m_LoadProgress.SetPos( 0 ); // Закрываем файл на сервере iFile -> Close(); // Удаляем объект iFile delete iFile; // Закрываем файл на локальном компьютере fLocalFile.Close(); // Освобождаем буфер ptrBuffer free( ptrBuffer ); return fResult; }
Постройте проект и запустите полученное приложение. Введите адрес сервера FTP и нажмите кнопку Connect. Далее выберите из списка файлов на сервере, файл для загрузке и сделайте по нему двойной щелчок левой кнопкой мыши. Как и ранее, вам будет предложено указать имя файла на локальном компьютере, в который будет записан файл с сервера FTP. Затем начнется процесс загрузки файла, ход которого будет отображаться линейным индикатором в нижней части диалоговой панели приложения (рис. 2.16).
Рис. 2.16. Загрузка файла с сервера FTP
Как вы уже могли заметить, основные изменения в исходных текстах приложения FtpView коснулись только метода FtpFileDownload класса CFtpViewDlg, который собственно и осуществляет загрузку файла с сервера FTP. Рассмотрим подробнее как работает этот метод.
Метод FtpFileDownload класса CFtpViewDlg имеет единственный параметр sFileName, через который ему передается строка, содержащая имя файла на сервере FTP. Этот файл надо загрузить на локальный компьютер:
BOOL CFtpViewDlg::FtpFileDownload( CString sFileName ) { }
Первая часть метода FtpFileDownload практически полностью соответствует более ранним версиям этого метода. В ней создается и отображается на экране стандартная диалоговая панель Save As. Эта панель позволяет пользователю указать имя файла и каталог на локальном компьютере в который записывается файл, загружаемый с сервера FTP. По умолчанию в качестве имени файла предлагается использовать имя загружаемого файла. Это имя передается конструктору класса CFileDialog через параметр sFileName:
// Создаем объект, управляющий панелью Save As CFileDialog mFileOpen(FALSE, NULL, sFileName); // Отображаем панель Save As на экране int result = mFileOpen.DoModal();
После того как пользователь выберет файл и нажмет кнопку OK, или откажется от выбора и нажмет кнопку Cancel, диалоговая панель Save As закрывается и метод DoModal возвращает соответствующий код завершения.
Если пользователь отказался от выбора файла и нажал кнопку Cancel, то метод DoModal возвращает значение IDCANCEL. В этом случае мы только выводим соответствующее предупреждающее сообщение и завершаем работу метода FtpFileDownload не выполняя загрузки файла:
if(result == IDCANCEL) { AfxMessageBox("File not selected"); return FALSE; }
Если пользователь выбрал файл и нажал кнопку OK (или выбрал файл, выполнив по его имени двойной щелчок левой кнопкой мыши), то метод DoModal возвращает значение IDOK. В этом случае мы определяем полный путь выбранного пользователем файла и записываем его в строку sLocalFileName:
CString sLocalFileName; . . . else if(result == IDOK) { sLocalFileName = mFileOpen.GetPathName(); }
Через единственный параметр метода FtpFileDownload ему передается строка sFileName, содержащая имя файла выбранного пользователем для загрузки с сервера. Чтобы получить полный путь этого файла на сервере мы добавляем имя файла к текущему каталогу. Результат записываем обратно в строку sFileName:
// Формируем полное имя файла для загрузки его с сервера FTP sFileName = sCurentDirectory + "/" + sFileName;
Чтобы мы смогли определить во время загрузки файла какая его часть загружена, необходимо узнать размер файла. Для этой цели мы создаем объект класса CFtpFileFind и выполняем поиск данного файла на сервере.
Сначала мы вызываем метод FindFile, передавая ему в качестве параметра полный путь файла, который надо найти на сервере, а затем, в случае успеха, вызываем метод FindNextFile который позволит нам определить характеристики, и в том числе длину, обнаруженного файла.
Надо заметить, что файл sFileName обязательно будет обнаружен, так как мы уже нашли его раньше (см. метод DirectoryView). Конечно мы не учитываем в этом случае возможность разрыва связи, удаление файла администратором и сервера и т. д.:
// Создаем объект класса CFtpFileFind CFtpFileFind m_FtpFileFind(m_FtpConnection); // Выполняем поиск выбранного нами файла if(fResult = m_FtpFileFind.FindFile(_T(sFileName))) { // Если поиск закончился успешно, получаем его // характеристики fResult = m_FtpFileFind.FindNextFile(); . . . }
Повторный поиск файла мы выполняем исключительно потому, что так наиболее просто узнать его размер. Для этого достаточно вызвать метод GetLength:
// Временная переменная для хранения размера файла DWORD dwLength = 0; // Определяем длину файла dwLength = m_FtpFileFind.GetLength();
В соответствии с длинной загружаемого файла устанавливаются новые границы изменения значений для линейного индикатора и новый шаг приращения.
Мы будем загружать файл с сервера FTP блоками по READ_BLOCK байт в каждом (последний блок может иметь меньшую длину). Поэтому мы сможем изменить состояние линейного индикатора dwLength / READ_BLOCK раз:
// Устанавливаем новые границы для линейного индикатора m_LoadProgress.SetRange(0, (int)(dwLength/READ_BLOCK) ); // Устанавливаем шаг приращения равный единице m_LoadProgress.SetStep(1);
Заметим, что чем меньше загружаемый файл, тем более резко будет меняться значение линейного индикатора, отражающего процесс загрузки. Если размер загружаемого файла будет меньше, чем размер буфера (READ_BLOCK), то линейный индикатор вообще не будет задействован. В принципе, вы можете сделать линейный индикатор более чувствительным, если будете загружать файл меньшими порциями - уменьшите значение READ_BLOCK.
Перед тем как приступить к самому интересному - загрузке файла с сервера FTP, заканчиваем поиск и вызываем метод Close для объекта m_FtpFileFind:
// Так как мы искали только один файл, заканчиваем поиск m_FtpFileFind.Close();
Перед тем как приступить к загрузке файла с сервера мы должны его открыть. Для этого следует воспользоваться методом OpenFile, входящим в состав класса CFtpConnection. Он возвращает указатель на объект класса CInternetFile:
// Определяем указатель на файл Internet CInternetFile* iFile; // Открываем выбранный нами файл на сервере FTP iFile = m_FtpConnection -> OpenFile( sFileName.GetBuffer(MIN_LEN_BUF), GENERIC_READ, FTP_TRANSFER_TYPE_BINARY );
Первый параметр метода OpenFile задает имя файла, который надо открыть. Второй параметр выбирает режим в котором открывается файл. Константа GENERIC_READ означает, что файл открывается для чтения. Третий и последний параметр метода OpenFile устанавливает режим передачи двоичных данных. Мы выбрали этот режим чтобы иметь возможность загружать не только текстовые, но и двоичные файлы.
Файл, получаемый с сервера FTP, мы должны сохранить на локальном компьютере в файле под именем sLocalFileName. Создаем файл sLocalFileName, используя возможности класса CFile:
// Создаем и открываем файл на локальном диске компьютера CFile fLocalFile( sLocalFileName, CFile::modeCreate | CFile::modeWrite );
Файл создается с атрибутами CFile::modeCreate и CFile::modeWrite, поэтому если файла с именем sLocalFileName нет - он будет создан, а если такой файл уже есть, то он перезаписывается. У пользователя при этом запрашивается подтверждение на перезапись файла.
Для чтения информации из файла на сервере мы будем обращаться к методу Read класса CInternetFile. Этот метод считывает порцию данных из файла и записывает их во временный буфер. Буфер мы предварительно создаем, при помощи функции malloc:
// Создаем буфер для чтения файла с сервера FTP void* ptrBuffer; ptrBuffer = malloc( READ_BLOCK );
Затем в цикле мы приступаем к чтению данных из файла на сервере и записи их в файл на локальном компьютере. Цикл будет продолжаться до тех пор, пока не будет достигнут конец файла.
Операция чтения файла с сервера выполняется методом Read, определенным в классе CInternetFile. Каждый вызов метода Read считывает READ_BLOCK байт и записывает их в буфер ptrBuffer. Количество байт, которое действительно было считано из файла на сервере, возвращается методом Read и записывается во временную переменную nReadCount.
Как только после вызова метода Read значение nReadCount станет меньше READ_BLOCK значит достигнут конец файла и можно выходить из цикла чтения:
UINT nReadCount = 0; do { . . . // Читаем из файла на сервере READ_BLOCK байт nReadCount = iFile -> Read( ptrBuffer, READ_BLOCK ); . . . } while (nReadCount == READ_BLOCK);
Если во время чтения файла с сервера возникнет ошибка, например разорвется связь, то метод Read вызовет соответствующее исключение CInternetException. Мы организуем обработку такой ситуации и помещаем вызов метода Read в блок try, а ниже определяем блок catch:
try { // Читаем из файла на сервере READ_BLOCK байт nReadCount = iFile -> Read( ptrBuffer, READ_BLOCK ); }
Блок catch выполняет обработку исключения CInternetException. Обращаясь к методу GetErrorMessage мы получаем текстовое описание причины, вызвавшей исключение и отображаем ее на экране. Если метод GetErrorMessage не может определить причину исключения, он возвращает нулевое значение и мы отображаем на экране строку GetFtpConnection Error:
catch (CInternetException* pEx) { // Обрабатываем исключение CInternetException TCHAR szErr[1024]; // временный буфер для сообщения // Выводим сообщение об ошибке if (pEx->GetErrorMessage(szErr, 1024)) AfxMessageBox(szErr); else AfxMessageBox("GetFtpConnection Error"); // Так как возникла ошибка, значит данные не считаны nReadCount = 0; // Удаляем исключение pEx->Delete(); }
Чтобы прервать дальнейшее выполнение цикла загрузки файла мы записываем в переменную nReadCount нулевое значение. Затем удаляем исключение, вызывая для него метод Delete.
Данные, которые считаны из файла на сервере и находятся в буфере ptrBuffer, надо записать в файл fLocalFile на локальном диске компьютера. Для этого передаем буфер ptrBuffer методу Write и указываем, что надо записать в файл nReadCount байт. Напомним, что при достижении конца файла на сервере метод Read считывает остаток файла, размер которого может быть меньше размера буфера:
fLocalFile.Write( ptrBuffer, nReadCount );
Когда очередной блок из файла считан, увеличиваем значение линейного индикатора на одну позицию. За время загрузки файла линейный индикатор изменит свое значение от крайне левого до крайне правого:
// Увеличиваем значение на линейном индикаторе m_LoadProgress.StepIt();
После того, как файл полностью получен, переводим линейный индикатор загрузки в начальное положение. Вызываем для этого метод SetPos с нулевым значением в качестве параметра:
// После окончания загрузки файла сбрасываем // линейный индикатор m_LoadProgress.SetPos( 0 );
Так как открытый файл на сервере FTP более нам не нужен, закрываем его, а затем удаляем из памяти соответствующий управляющий объект:
// Закрываем файл на сервере iFile -> Close(); // Удаляем объект iFile delete iFile;
Также закрываем и локальный файл, в который скопирован файл с сервера FTP:
// Закрываем файл на локальном компьютере fLocalFile.Close();
Освобождаем буфер ptrBuffer, вызывая функцию free:
// Освобождаем буфер ptrBuffer free( ptrBuffer );
Исходный вариант приложения FtpView и его модификация обладают еще одним очень неприятным недостатком. Во время загрузки файлов с сервера FTP интерфейс приложения блокируется практически полностью. Только по линейному индикатору, который используется в доработанном варианте приложения мы можем судить о том, что оно все же работает.
В остальном складывается такое впечатление, что приложение “зависло”. Оно даже не перерисовывает свое окно и если временно переключится на другое приложение, окно которого перекроет окно FtpView, то изображение в окне FtpView будет испорчено. Только по окончании загрузки файла наше приложение спохватится и восстановит свое окно.
Как мы уже говорили ранее, эффекта зависания можно избежать, если использовать WinInet в асинхронном режиме или выполнять наиболее длительные операции по загрузке файлов в отдельной задаче.
Сейчас мы предлагаем вам испробовать второй путь и создать для загрузки файлов с сервера отдельную задачу, которая будет выполняться параллельно с основной задачей приложения. Таким образом, загрузка файла будет осуществляться в фоновом режиме и не будет блокировать пользовательский интерфейс приложения.
За основу мы взяли базовый вариант приложения FtpView, выполняющий загрузку файлов с сервера FTP при помощи метода GetFile. Мы постарались свести все изменения в исходных текстах приложения к минимуму. Вы должны будете только добавить три глобальные переменные, изменить метод FtpFileDownload класса CFtpViewDlg, и добавить функцию ThreadFileLoadProc.
Определение глобальных переменных добавьте в самом начале файла FtpViewDlg.cpp непосредственно после директив препроцессора. Все три новых глобальных переменных global_sFtpAddress, global_sFileName и global_sLocalFileName являются объектами класса CString. Эти переменные используются для передачи задаче, осуществляющей загрузку файла, адреса сервера FTP, пути каталога и имени исходного файла на сервере FTP, а также полного пути файла на диске локального компьютера в который будет записан файл с сервера:
//================== Глобальные переменные =================== // Адрес сервера FTP CString global_sFtpAddress = ""; // Полный путь файла на диске локального компьютера CString global_sFileName = ""; // Путь каталога и имя файла на сервере FTP CString global_sLocalFileName = "";
Задача, осуществляющая загрузку файла с сервера FTP, определяется функцией ThreadFileLoadProc. Как видите, мы не стали ее особенно усложнять и просто создали еще один сеанс связи с Internet, а затем в его рамках осуществили соединение с сервером FTP и получили с него заданный файл.
Адрес сервера FTP мы берем из глобальной переменной global_sFtpAddress, путь файла, который надо загрузить с сервера и его название - из переменной global_sFileName, а полный путь файла на диске локального компьютера, куда полученный файл должен быть записан - из переменной global_sLocalFileName:
//============================================================ // Задача, выполняющая загрузку файла с сервера FTP //============================================================ UINT ThreadFileLoadProc(LPVOID param) { //========================================================= // Инициализируем сеанс связи с Internet //========================================================= CFtpConnection* m_FtpConnection; // Сервер FTP CInternetSession* m_InternetSession; // Сеанс связи // Создаем сеанс связи с Internet, указываем в качестве // имени программы-клиента строку FtpProcess m_InternetSession = new CInternetSession("FtpProcess"); // В случае ошибки отображаем сообщение и завершаем // задачу с кодом завершения 1 if(!m_InternetSession) { AfxMessageBox("New Session Error", MB_OK); return 1; } // Инициализируем указатель m_FtpConnection m_FtpConnection = NULL; // Пытаемся соединиться с сервером FTP try { // Соединяемся с сервером FTP. Эта операция // может вызвать исключение CInternetException m_FtpConnection = m_InternetSession -> GetFtpConnection(global_sFtpAddress); } catch (CInternetException* pEx) { // Выводим сообщение об ошибке AfxMessageBox("GetFtpConnection Error"); // Удаляем исключение pEx->Delete(); // Обнуляем указатель m_FtpConnection m_FtpConnection = NULL; } if(m_FtpConnection != NULL) { BOOL fResult; // Загружаем файл с сервера fResult = m_FtpConnection -> GetFile(global_sFileName.GetBuffer(MIN_LEN_BUF), global_sLocalFileName.GetBuffer(MIN_LEN_BUF), FALSE ); // Закрываем соединение с сервером m_FtpConnection -> Close(); delete m_FtpConnection; // После успешного завершения загрузки выводим сообщение if( fResult ) AfxMessageBox("File load complited:" + global_sFileName); } // Завершаем сеанс связи с Internet m_InternetSession -> Close(); delete m_InternetSession; return 0; }
Функция ThreadFileLoadProc выполняет загрузку файла с сервера FTP в файл на диске локального компьютера. Надо только заполнить глобальные переменные global_sFtpAddress, global_sFileName и global_sLocalFileName, а затем запустить функцию ThreadFileLoadProc как отдельную задачу. Для этого измените метод FtpFileDownload класса CFtpViewDlg как это показано ниже:
BOOL CFtpViewDlg::FtpFileDownload( CString sFileName ) { BOOL fResult; CString sLocalFileName; // Определяем объект класса CFileDialog, представляющий // стандартную диалоговую панель Save As, в которой // по умолчанию выбрано имя файла sFileName CFileDialog mFileOpen(FALSE, NULL, sFileName); // Отображаем диалоговую панель Open и позволяем // пользователю выбрать с помощью нее один файл int result = mFileOpen.DoModal(); // Проверяем как была закрыта диалоговая панель Open - // по нажатию кнопки OK или Cancel if(result == IDCANCEL) { // Если пользователь отказался от выбора файлов и // нажал кнопку Cancel отображаем соответствующее // сообщение и возвращаем значение FALSE AfxMessageBox("File not selected"); return FALSE; } // Если пользователь нажал кнопку OK else if(result == IDOK) { // Записываем полный путь файла на диске локального // компьютера global_sLocalFileName = mFileOpen.GetPathName(); // Определяем путь и имя файла, который надо загрузить // с сервера FTP global_sFileName = sCurentDirectory + "/" + sFileName; // Запоминаем адрес сервера FTP global_sFtpAddress = m_FtpAddress; // Запускаем новую задачу, определенную функцией // ThreadFileLoadProc AfxBeginThread( ThreadFileLoadProc, NULL, THREAD_PRIORITY_NORMAL); } return fResult; }
Мы не будем подробно описывать функцию ThreadFileLoadProc и новый вариант метода FtpFileDownload класса CFtpViewDlg. В них используются те же приемы, что продемонстрированы в методе FtpFileDownload класса CFtpViewDlg базового варианта приложения FtpView.