Вступление
Многие Windows Mobile разработчики, пишущие на .NET, слышали или читали замечательный цикл постов Криса Крафта «30 Days of .NET [Windows Mobile Applications]». Я решил начать цикл переводов этих постов, но чтобы было интереснее, в статьях будет представлен перевод не только оригинального поста из блога Криса с примерами на С#, но также и перевод статей от Кристофера Фэрбейрна — энтузиаста, который решил портировать все примеры Криса на C++! На текущий момент портировано 8 из 30 приложений, но это тоже очень неплохо.
При переводе я постараюсь свести к минимуму неотносящиеся к делу лирические отступления, потому что «вода» в переводе становится ещё более жидкой и читать становится невозможно :)
Итак, приступим — первое приложение, обратный отсчёт до полуночи.
Крис Крафт. C#
Оригинал находится здесь.
Одна из целей, которые я ставлю перед собой в этом цикле статей — это делать приложение в день публикации. Это не оставляет мне выбора, я не уверен, что буду успевать, но это только добавляет веселья. У меня есть жизнь, а между семьёй, друзьями, карьерой, хобби и мечтами, остаётся не так уж и много времени, как мне бы иногда хотелось. Так что разрабатывая по одному приложению в день, я заодно выясню, сколько у меня обычно остаётся свободного времени.
Сколько минут до полуночи
Простое приложение, но я бы всё-таки рискнул назвать его полезным. Сначала я полагал, что буду отображать данные в формате, например, «2 часа 45 минут 38 секунд», но решил, что это слишком уж просто, поэтому я буду использовать прогресс-бары, потому что они добавят приложению, ну, скажем, немного «веса».
Но даже после использования прогресс-баров, приложение выглядело слишком стерильным. Воспользовавшись одной из палитр с сайта http://www.colourlovers.com/, я немного раскрасил приложение.
Очень быстро я понял, что есть небольшая проблема. С моей точки зрения, возможно субъективной, прогресс-бар был неправильным. progressBarHours.Value = timeSpan.Hours; должен был быть
progressBarHours.Value = 24 — timeSpan.Hours;. Как только я внес данное изменение, всё сразу встало на свои места.
Верхний прогресс-бар меня не радовал. Он должен был показывать общее время до конца дня, но не хватало места ещё для одного заголовка. Есть поговорка «лучшее — враг хорошего». Я её понимаю так, что если всегда пытаться стремиться достичь идеала, из этого никогда не выйдет ничего хорошего. В итоге я разбил форму на логические секции путём выставления разного цвета фона у нижних панелей.
Всё, что оставалось сделать — это общее количество оставшихся минут. В зависимости от используемых вычислений, иногда получалось нечто типа "X.666666666 of 1440 total minutes left". К счастью есть простое решение — пользовательское форматирование числа: timeSpan.TotalMinutes.ToString("#.0").
Прим. переводчика: В оригинале нет вставки кода, однако я его помещаю (всё остальное делается мышкой в студии):
private void timer_Tick(object sender, EventArgs e)
{
TimeSpan timeSpan = DateTime.Now.Date.AddDays(1) - DateTime.Now;
labelHours.Text = string.Format("{0} of 24 hours left", timeSpan.Hours);
labelMinutes.Text = string.Format("{0} of 60 minutes left", timeSpan.Minutes);
labelSeconds.Text = string.Format("{0} of 60 seconds left", timeSpan.Seconds);
labelTotalMinutes.Text = string.Format("{0} of 1440 total minutes left",
timeSpan.TotalMinutes.ToString("#.0"));
labelTotalSeconds.Text = string.Format("{0} of 86400 total seconds left",
timeSpan.TotalSeconds);
progressBarTotal.Value = 86400 - (int) timeSpan.TotalSeconds;
progressBarHours.Value = 24 - timeSpan.Hours;
progressBarMinutes.Value = 60 - timeSpan.Minutes;
progressBarSeconds.Value = 60 - timeSpan.Seconds;
progressBarTotalMinutes.Value = 1440 - (int) timeSpan.TotalMinutes;
progressBarTotalSeconds.Value = 86400 - (int) timeSpan.TotalSeconds;
}
* This source code was highlighted with Source Code Highlighter.
Исходный код C#: minutes2Midnight.zip.
Кристофер Фэрбейрн. С++
Оригинал находится здесь.Разработка интерфейса
Самый простой способ создать небольшое приложение на С++ — воспользоваться интерфейсом на базе диалогов.
Как и в приложениях на основе System.Windows.Forms, в C++ существует четкое разделение между оформлением и кодом. Оформление одного и более диалогов находится в файле ресурсов (*.rc), в то время как код находится в *.cpp файлах. Каждый диалоговый ресурс получает свой ID, например IDD_MYDIALOG, что позволяет обращаться к нему из кода.
Для отображения диалога воспользуемся DialogBox API:
DialogBox(hInstance, (LPCSTR)IDD_MYDIALOG, NULL, MyDialogProc);
Данный вызов отобразит диалог с идентификатором IDD_MYDIALOG и будет ожидать его закрытия. Последний параметр — это имя диалоговой процедуры. Написанная вами процедура будет обрабатывать сообщения, посылаемые окну диалога. Эти сообщения аналогичны событиям и виртуальным методам таких классов как
System.Windows.Forms.Control.
Базовая структура диалоговой процедуры будет выглядеть следующим образом:
INT_PTR CALLBACK MyDialogProc(HWND hDlg,
UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
// this is similar to the Load event so we
// can perform dialog initialisation code here
break;
case WM_CLOSE:
// this is similar to the Close event so we
// can perform dialog shutdown logic here
break;
...
}
}
* This source code was highlighted with Source Code Highlighter.
Заголовки
Эквивалентом для System.Windows.Forms.Label является static контрол.
Для того, чтобы взаимодействовать с контролом, размещенным в диалоге, необходимо получить его дескриптор. В рамках Win32 приложения, контролы являются специальной формой окна, а контролами их делает факт, что у них есть родительское окно. Для получения дескриптора мы воспользуемся методом GetDlgItem, передав ему в качестве параметров дескриптор нашего диалога и идентификтор контрола, который мы назначили в редакторе ресурсов.
// Get the window handle for the control
// with an ID of IDC_MESSAGE
HWND hwndCtrl = GetDlgItem(hWnd, IDC_MESSAGE);
* This source code was highlighted with Source Code Highlighter.
Получив дескриптор, мы можем посылать сообщения. Это эквивалентно присваиванию свойств у .net контролов. В некоторых случаях у нас есть даже вспомогательные функции, которые слегка упрощают манипуляции. Например, мы можем изменить текст заголовка с помощью функции SetWindowText:
// Change the label to display "Hello World"
SetWindowText(hwndCtrl, L"Hello World");
* This source code was highlighted with Source Code Highlighter.
Прогресс-бары
Взаимодействие с прогресс-барами почти ничем не отличается от взаимодействия со статическими контролами, с той только разницей, что прогресс-бары понимают другой набор сообщений. Например, чтобы задать минимум и максимум мы можем послать сообщение PBM_SETRANGE или PBM_SETRANGE32 следующим образом:
// Set the progress bar referenced by ‘hWndCtrl’
// to have the range 25 to 75
SendMessage(hWndCtrl, PBM_SETRANGE, 0, MAKELPARAM(25, 75));
* This source code was highlighted with Source Code Highlighter.
Можно ли, глядя в документацию по PBM_SETRANGE и PBM_SETRANGE32, понять, почему существует два способа выставления минимума и максимума? Вот один из ярких примеров того, от чего Compact Framework абстрагируется.
Для выставления текущего значения прогресса воспользуемся PBM_SETPOS сообщением:
// Set the progress bar referenced by ‘hWndCtrl’
// to the value 45
SendMessage(hwndCtrl, PBM_SETPOS, 45, 0);
* This source code was highlighted with Source Code Highlighter.
Таймеры
Вместо того, чтобы просто перетащить таймер на форму, С++ программист должен создать таймер самостоятельно:
// Create a timer with ID 1234 and
// an interval of 1000 milliseconds (1 second)
SetTimer(hWnd, 1234, 1000, NULL);
* This source code was highlighted with Source Code Highlighter.
Когда таймер больше не нужен, воспользуемся соответствующей функцией KillTimer:
// Stop the timer with ID 1234
KillTimer(hWnd, 1234);
* This source code was highlighted with Source Code Highlighter.
Как видно из примера, таймеры ассоциированы с окном, поэтому каждый раз, когда срабатывает таймер, окно получает сообщение WM_TIMER, которое по сути эквивалентно Timer.Tick событию в .NET.
case WM_TIMER:
if (wParam == 1234)
{
// timer 1234's interval has occurred so
// we can do something here...
}
break;
* This source code was highlighted with Source Code Highlighter.
Каждому таймеру назначается свой идентификатор, чтобы была возможность работать с более чем одним таймером. Для этих целей сообщение WM_TIMER передаёт параметром идентификатор таймера.
Раскрашивание фона
Для логического разделения интерфейса Крис раскрасил участки формы разными цветами. Самый простой способ добиться этого же — обрабатывать сообщение WM_PAINT, которое посылается системой, когда пришла пора отрисовывать содержимое. Нарисуем 3 прямоугольника, воспользовавшись методом FillRect:
// Define a rectangle at x=40, y=10 with size 40x10
RECT rcBounds;
rcBounds.top = 10;
rcBounds.bottom = 20;
rcBounds.left = 40;
rcBounds.right = 80;
// Create a red brush and fill the
// area of the rectangle
HBRUSH hbrRed = CreateSolidBrush(RGB(255, 0, 0));
FillRect(hdc, &rcBounds, hbrRed);
DeleteObject(hbrRed);
* This source code was highlighted with Source Code Highlighter.
Графические методы в целом называются GDI (Graphical Drawing Interface) и вы заметите, что у них очень много общего с пространством имен System.Drawing. Сравните CreateSolidBrush и System.Drawing.SolidBrush.
Вычисление времени
В силу отсутствия эквивалента структуры System.DateTime, время придётся вычислять более сложными путями.
Для получения текущего времени воспользуемся функцией GetLocalTime, которая вернёт структуру SYSTEMTIME. Эта структура не в состоянии ничего вычислять самостоятельно, поэтому воспользуемся функцией SystemTimeToFileTime, которая преобразует данные в структуру FILETIME, что есть ни что иное, как 64-битное целое число, хранящее количество 100-наносекундных интервалов, прошедших с 1 января 1601 года.
Имея это число, мы можем проводить простые математические операции. Например, мы можем получить, сколько прошло от начала дня, либо сколько осталось до его конца:
__int64 amount_of_today_past = current_time % ONE_DAY;
__int64 amount_of_today_left = ONE_DAY - amount_of_today_past;
* This source code was highlighted with Source Code Highlighter.
Из полученного значения уже совсем просто получить часы, минуты и секунды. Например, следующее вычисление вернёт то же, что и TimeSpan.Minutes:
__int64 minutes = (amount_of_today_left / ONE_MINUTE) % 60
* This source code was highlighted with Source Code Highlighter.
C++ пример можно скачать здесь: minutes2midnight.zip.