Pull to refresh

Перевод: 30 дней Windows Mobile — день первый (С# vs WinAPI)

Development of mobile applications *
Translation
Original author: Chris Craft и Christopher Fairbairn

Вступление


Многие 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.
Tags:
Hubs:
Total votes 57: ↑43 and ↓14 +29
Views 2.2K
Comments Comments 32