Недостатки приложения FtpView
У приложения 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. Рассмотрим подробнее как работает этот метод.