Разработка приложений, использующих в своей работе сетевое взаимодействие или доступ к ресурсам Internet – это довольно популярная штука в наши дни. И в этот раз мы рассмотрим такую часто используемую задачу как загрузка файлов из Internet.

Для того чтобы скачать файл в C++ приложении с wxWinCE надо совсем немного кода. В простейшем случае для реализации однопоточной загрузки файла мы можем использовать класс wxURL, скормив ему адрес загружаемого ресурса.

void MobileDownloaderMainFrame::OnDOWNLOADClick( wxCommandEvent& event )
{
	do
	{
		wxString address = wxGetTextFromUser(_("Input URL"), 
			wxGetTextFromUserPromptStr, 
			wxT("http://wxwidgets.info"));
		if(address.IsEmpty()) break;
		wxURL url(address);
		if(!url.IsOk()) break;
		wxInputStream * stream = url.GetInputStream();
		if(!stream) break;
		wxString result;
		wxStringOutputStream out(&result);
		out.Write(*stream);
		delete stream;
		m_SingleThreadedResultTextCtrl->SetValue(result);
	}
	while(false);
}

Приведенный пример хорош всем кроме того, что пока выполняется загрузка, приложение не отвечает на действия пользователя, т.к. загрузка происходит в основном потоке и пока выполнение функции не закончится, приложение не может продолжить работу.

Чтобы таких ситуаций не случалось можно использовать библиотеку wxDownloadFile, которая позволяет загружать каждый файл в отдельном потоке и получать уведомления о состоянии загрузки.

Наш следующий пример демонстрирует как можно реализовать многопоточную загрузку файлов с минимальными усилиями.

Для начала создадим класс, который будет содержать информацию о загрузке:

#ifndef _DOWNLOAD_INFO_H
#define _DOWNLOAD_INFO_H

#include <wx/wx.h>
#include <DownloadFile.h>
#include <DownloadEvent.h>

class DownloadInfo
{
private:
	wxInt64 m_DownloadedSize;
	wxString m_URL;
	wxDownloadEvent::DownloadSTATUS m_Status;
	wxDownloadFile * m_Downloader;
public:
	DownloadInfo(const wxString & url)
		: m_DownloadedSize(0), m_URL(url), 
		m_Status(wxDownloadEvent::DOWNLOAD_NONE), m_Downloader(NULL) {}

	const wxString & GetURL() {return m_URL;}

	wxInt64 GetDownloadedSize() {return m_DownloadedSize;}
	void SetDownloadedSize(wxInt64 value) {m_DownloadedSize = value;}

	wxDownloadEvent::DownloadSTATUS GetStatus() {return m_Status;}
	void SetStatus(wxDownloadEvent::DownloadSTATUS value) {m_Status = value;}

	wxDownloadFile * GetDownloader() {return m_Downloader;}
	void SetDownloader(wxDownloadFile * value) {m_Downloader = value;}
};

WX_DECLARE_STRING_HASH_MAP(DownloadInfo *, DownloadsHash);

#endif

Кроме самого класса с информацией о загрузке нам необходима еще хеш-таблица, которая будет содержать информацию об активных загрузках в приложении. Доступ к элементам хеш-таблицы будет производиться по строковому ключу, в качестве которого мы будем использовать URL.

Для отображения информации о загрузках мы будем использовать компонент wxSimpleHtmlListBox. Компонент будет содержать указатель на хеш-таблицу с загрузками.

Чтобы в списке загрузок отображалась актуальная информация, нам необходимо переопределить метод OnGetItem().

#include "DownloadInfo.h"
...
class wxDownloadListBox: public wxSimpleHtmlListBox
{
...
    DownloadsHash * GetDownloads() const { return m_Downloads ; }
    void SetDownloads(DownloadsHash * value) { m_Downloads = value ; }

    virtual wxString OnGetItem(size_t n) const;

    void AddNew() {SetItemCount(GetItemCount()+1);}

    DownloadsHash * m_Downloads;
};

wxString wxDownloadListBox::OnGetItem(size_t n) const
{
	wxString value;
	do 
	{
		DownloadsHash::iterator i = m_Downloads->begin();
		for(size_t j = 0; (j < n) && (i != m_Downloads->end()); j++)
		{
			i++;
		}
		if(i == m_Downloads->end()) break;
		value = wxString::Format(wxT("<b>%s</b>"), i->first.GetData());
		DownloadInfo * download = i->second;
		if(!download) break;
		switch(download->GetStatus())
		{
		case wxDownloadEvent::DOWNLOAD_INPROGRESS:
			value += wxString::Format(
				_(" - <i>downloaded %d bytes</i>"), 
				download->GetDownloadedSize());
			break;
		case wxDownloadEvent::DOWNLOAD_COMPLETE:
			value += _(" (finished)");
			break;
		case wxDownloadEvent::DOWNLOAD_FAIL:
			value += _(" (failed)");
			break;
		}
	}
	while (false);
	return value;
}

Метод AddNew() указывает компоненту, что у него добавился новый элемент.

Теперь рассмотрим код, создающий новую загрузку и добавляющий ее в список:

void MobileDownloaderMainFrame::OnADDClick( wxCommandEvent& event )
{
	do 
	{
		DownloadParametersDialog * dlg = new DownloadParametersDialog(this);
		int result = dlg->ShowModal();
		wxString url = dlg->m_URLTextCtrl->GetValue();
		wxString fileName = dlg->m_FileNamePicker->GetPath();
		dlg->Destroy();
		if(result != wxID_OK) break;
		DownloadInfo * info = m_Downloads[url];
		bool needAddNew(true);
		if(info != NULL)
		{
			info->GetDownloader()->CancelDownload();
			needAddNew = false;
		}
		else
		{
			info = new DownloadInfo(url);
		}
		wxDownloadFile * download = new wxDownloadFile(this, url, fileName, true);
		
		info->SetDownloader(download);
		m_Downloads[url] = info;

		download->Run();
		if(needAddNew)m_DownloadsListBox->AddNew();

		m_DownloadsListBox->RefreshLines(
			m_DownloadsListBox->GetFirstVisibleLine(),
			m_DownloadsListBox->GetLastVisibleLine());
	} 
	while (false);
}

Собственно, что у нас здесь происходит:

  • отображается диалог создания новой загрузки. В нем необходимо ввести URL и путь к результирующему файлу
  • В случае если диалог отработал успешно, мы проверяем наличие загрузки с указанным URL в хеш-таблице
  • Если загрузка присутствует, то она останавливается
  • Если загрузки с указанным URL в таблице нет, то создается новый объект DownloadInfo
  • Затем создается объект wxDownloadFile и запускается
  • После этого происходит обновление списка загрузок

Как уже было сказано ранее, класс wxDownloadFile позволяет получать уведомления о состоянии загрузки. Для этого используется обработчик события EVT_DOWNLOAD():

BEGIN_EVENT_TABLE( MobileDownloaderMainFrame, wxFrame )
...
EVT_DOWNLOAD(MobileDownloaderMainFrame::OnDownloadStatus)
END_EVENT_TABLE()

void MobileDownloaderMainFrame::OnDownloadStatus(wxDownloadEvent & event)
{
	do
	{
		wxString url = event.GetDownLoadURL();
		DownloadInfo * downloadInfo = m_Downloads[url];
		wxDownloadFile * download = downloadInfo->GetDownloader();
		if(!download) break;
		downloadInfo->SetStatus((wxDownloadEvent::DownloadSTATUS)event.GetDownLoadStatus());
		switch(event.GetDownLoadStatus())
		{
		case wxDownloadEvent::DOWNLOAD_FAIL:
			wxLogDebug(wxT("DOWNLOAD_FAIL"));
			downloadInfo->SetDownloader(NULL);
			break;
		case wxDownloadEvent::DOWNLOAD_COMPLETE:
			wxLogDebug(wxT("DOWNLOAD_COMPLETE"));
			downloadInfo->SetDownloader(NULL);
			break;
		case wxDownloadEvent::DOWNLOAD_INPROGRESS:
			wxLogDebug(wxT("DOWNLOAD_INPROGRESS"));
			downloadInfo->SetDownloadedSize(event.GetDownLoadedBytesCount());
			break;
		default:
			break;
		}
		m_DownloadsListBox->RefreshLines(
			m_DownloadsListBox->GetFirstVisibleLine(),
			m_DownloadsListBox->GetLastVisibleLine());
	}
	while(false);
}

В обработчике события мы получаем состояние загрузки из объекта wxDownloadEvent и, в зависимости от этого значения, выполняем обновление объекта DownloadInfo в хеш-таблице загрузок.

О чем еще важно помнить? Приложение остается висеть в памяти пока не будут завершены все его потоки. wxDownloadFile создает поток на каждую загрузку и если мы попытаемся закрыть приложение во время скачивания файлов, то приложение будет выгружено только по завершению всех потоков. Чтобы такой ситуации у нас не было, нам необходимо принудительно завершить все потоки при закрытии главной формы приложения. Для этого мы будем использовать обработчик EVT_CLOSE():

void MobileDownloaderMainFrame::OnCloseWindow( wxCloseEvent& event )
{
	for(DownloadsHash::iterator i = m_Downloads.begin(); i != m_Downloads.end(); i++)
	{
		DownloadInfo * download = i->second;
		if(!download) continue;
		if(download->GetDownloader()) download->GetDownloader()->CancelDownload();
		wxDELETE(download);
		i->second = NULL;
	}
    event.Skip();
}

Вот теперь все.
wxMobileDownloader - Пример использования wxDownloadFile
Скачать исходный код приложения и проекты для Win32 и Windows Mobile.

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

Leave a Reply

Your email address will not be published. Required fields are marked *

w.

wxYahooMaps – Библиотека для работы с Yahoo! Maps Image API

Странная вещь получается. А ведь нету нормальных библиотек для С++, работающих с online-сервисами карт. Все популярные сервисы предоставляют API для .NET, а С++, как мне кажется, незаслуженно обделяют своим вниманием.
И вот пару дней назад решил восполнить этот пробел. Выбор пал на сервис Yahoo! Maps, а если быть точным, то Yahoo! Map Image API, которое позволяет получить изображение карты, сформировав определенным образом REST (Representational State Transfer) запрос.

Ознакомиться с механизмом работы сервиса можно здесь. Сильно подробно описывать не буду, скажу только что смысл заключается в формировании URL, открыв который мы получим XML-файл с адресом изображения карты.

Необходимым условием для работы с Yahoo! Map Image API является наличие у приложения уникального идентификатора (Application ID), получить который можно здесь (кстати, я уже писал ранее что Maps API для Google Android тоже требует Application ID).

Итак, wxYahooMaps. Что это такое и как это работает: это wxWidgets-based библиотека, которая позволяет по указанным координатам (широта + долгота) или названию местности (улица/город/штат/почтовый код) получить изображение карты, в центре которого будет искомая точка. Библиотека загружает карты асинхронно и поддерживает многопоточную загрузку, что иногда бывает очень удобным.

Для того чтобы начать пользоваться wxYahooMaps достаточно выполнить несколько простых действий:

Создать объект класса wxYahooMap (желательно чтобы это был член класса формы или приложения, т.е. был доступен на протяжении всей работы программы):


wxYahooMap * m_YahooMap;

...

m_YahooMap = new wxYahooMap(this);

К форме, которая должна получать уведомления о состоянии процесса загрузки карт добавить обработчик события EVT_YAHOO_MAP_STATUS:


BEGIN_EVENT_TABLE( wxYahooMapsTestMainFrame, wxFrame )
...
EVT_YAHOO_MAP_STATUS(wxID_ANY, wxYahooMapsTestMainFrame::OnYahooMapStatus)
END_EVENT_TABLE()

void wxYahooMapsTestMainFrame::OnYahooMapStatus(wxYahooMapStatusEvent & event)
{
	if(event.GetInt() == 0)
	{
		wxBitmap * bmp = event.GetBitmap();
		if(bmp)
		{
			// Здесь работаем с изображением карты
			// После этого изображение надо удалить
			wxDELETE(bmp);
		}
	}
	else
	{
		// Обработать ошибку
	}
}

Теперь для того чтобы загрузить изображение карты необходимо указать необходимые параметры поиска и вызвать метод wxYahooMap::StartDownload()

void wxYahooMapsTestMainFrame::OnGOBUTTONClick( wxCommandEvent & event )
{
	TransferDataFromWindow();
	m_YahooMap->SetApplicationID(m_AppID);
	m_YahooMap->SetUsePosition(m_UsePosition);
	m_YahooMap->SetPosition(wxRealPoint(m_Longitude, m_Latitude));
	m_YahooMap->SetStreet(m_Street);
	m_YahooMap->SetCity(m_City);
	m_YahooMap->SetState(m_State);
	m_YahooMap->SetZIPCode(m_ZIPCode);
	m_YahooMap->SetImageType(wxYahooMap::IntToYahooMapImageType(m_ImageType));
	m_YahooMap->SetImageSize(wxSize(m_ImageWidth, m_ImageHeight));
	m_YahooMap->SetZoomLevel(m_ZoomLevel);
	m_YahooMap->StartDownloading();
}

После окончания загрузки карты будет вызван обработчик события wxEVT_YAHOO_MAP_STATUS, в котором можно произвести необходимые действия с изображением карты. После этого обязательно необходимо очистить память, занятую объектом изображения.
В результате у нас должно получиться что-то вроде этого:

На данный момент библиотека wxYahooMaps оттестирована под:

  • Windows Vista
  • Windows XP
  • Windows Mobile 2003 (PPC)
  • Windows Mobile 6.0

Проводится тестирование под Mac OS и Linux.
Исходный код можно загрузить из SVN-репозитория на Google Code. Там же можно загрузить исполняемый файл примера для Windows XP/Vista.

PS: Если кто-либо из читателей блога желает помочь в развитии проекта, можно оставлять свои контакты в комментариях. На данный момент требуется помощь в реализации работы с геокодером Yahoo!.

К.

Как программно сменить имя устройства в Windows Mobile

Имя устройства в Windows Mobile хранится в реестре. Это параметр Name ключа HKEY_LOCAL_MACHINE\Ident. Имя устройства используется для отображения устройства при синхронизации с настольным компьютером и, например, при обзоре Wi-Fi или Bluetooth устройств.

В этом посте я расскажу как получить и изменить имя устройства с помощью Native API и с помощью .NET Compact Framework.

Change Mobile Device Name

На имена устройств накладываются определенные ограничения:

  • имя должно содержать от 1 до 15 символов.
  • Первый символ должен быть из диапазона ‘a’-‘z’ или ‘A’-‘Z.’
  • Остальные символы ‘a’-‘z’, ‘A’-‘Z’, ‘0’-‘9’, или ‘-.’

Более подробно об именовании мобильных устройств можно узнать в MSDN.

А вот и примеры:

Native API (C++)

int _tmain(int argc, _TCHAR* argv[])
{
	HKEY hKey = NULL;
	DWORD dataSize(0);
	BYTE * data = NULL;
	do
	{
		if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Ident"), 0,  KEY_READ, &hKey)
			!= ERROR_SUCCESS) break;
		
		if(RegQueryValueEx(hKey, _T("Name"), NULL, NULL, NULL, &dataSize)
			!= ERROR_SUCCESS) break;

		int allocatedSize = max((int)(dataSize+1), (int)256);
		data = new BYTE[allocatedSize];
		ZeroMemory(data, allocatedSize);
		if(RegQueryValueEx(hKey, _T("Name"), NULL, NULL, data, &dataSize)
			!= ERROR_SUCCESS) break;
		MessageBox(0, (LPCTSTR)data, _T("Device Name"), MB_OK);
#if defined _UNICODE
		wsprintf((LPTSTR)data, _T("SampleName"));
#else
		sprintf((LPTSTR)data, _T("SampleName"));
#endif
		if(RegSetValueEx(hKey, _T("Name"), NULL, REG_SZ, data, 
#if defined _UNICODE
			wcslen((wchar_t*)data)*sizeof(wchar_t)
#else
			strlen((char*)data)
#endif
			)
			!= ERROR_SUCCESS) break;
	}
	while(false);
	if(data) delete [] data;
	if(hKey) RegCloseKey(hKey);
	return 0;
}

Указанный выше способ работает как для ANSI так и для UNICODE-сборки проекта.

wxWidgets (wxWinCE)

bool MobileDeviceNameMainFrame::SetDeviceName(const wxString & newDeviceName)
{
	do
	{
		if(newDeviceName.IsEmpty())
		{
			wxLogError(_("Device name can't be empty"));
			break;
		}
		wxRegKey key(wxRegKey::HKLM, wxT("Ident"));
		if(!key.Open())
		{
			wxLogError(_("Unable to open registry key"));
			break;
		}
		key.SetValue(wxT("Name"), newDeviceName);
		key.Close();
		return true;
	}
	while(false);
	return false;
}

wxString MobileDeviceNameMainFrame::GetDeviceName()
{
	do 
	{
		wxRegKey key(wxRegKey::HKLM, wxT("Ident"));
		if(!key.Open())
		{
			wxLogError(_("Unable to open registry key"));
			break;
		}
		wxString result;
		if(!key.QueryValue(wxT("Name"), result)) break;
		return result;
	} 
	while (false);
	return wxEmptyString;
}

.NET Compact Framework

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Ident", true))
            {
                deviceNameTextCtrl.Text = key.GetValue("Name").ToString();
            }
        }

        private void btnChangeDeviceName_Click(object sender, EventArgs e)
        {
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey("Ident", true))
            {
                key.SetValue("Name", deviceNameTextCtrl.Text);
            }
        }
    }

Скачать исходный код к статье.