52628.fb2 Программирование КПК и смартфонов на .NET Compact Framework - читать онлайн бесплатно полную версию книги . Страница 8

Программирование КПК и смартфонов на .NET Compact Framework - читать онлайн бесплатно полную версию книги . Страница 8

Глава 7Разработка приложений

Активация и деактивация формы

Модель выполнения программ на карманном компьютере отличается от поведения программ, работающих на обычном персональном компьютере. Например, на мобильных компьютерах используется один экземпляр запущенной программы. Аналогом подобного поведения на настольных компьютерах является почтовая программа Outlook Express, которая всегда запускается в одном экземпляре. При попытке запуска программы она просто активируется (если уже была запущена). При этом вторая копия программы не запускается.

При создании приложения для КПК разработчику не придется прилагать никаких усилий для реализации подобного поведения. Среда выполнения .NET Compact Framework сама позаботится о том, чтобы запускался только один экземпляр программы. Следует помнить, что пользователь не должен сам закрывать программу. При запуске новой программы ее окно просто загораживает предыдущую программу.

Учитывая подобное поведение, нужно писать программы, которые не занимают много ресурсов системы. Однажды запущенное приложение может находиться в памяти несколько дней, пока пользователь не перезагрузит компьютер или не закроет программу самостоятельно. Деактивированная программа закроется автоматически, если система обнаружит уменьшение свободной памяти при разрядке батареи. Но, тем не менее, иногда надо проследить, чтобы при закрытии программа освободила ресурсы, которые она использовала. Бывают ситуации, когда приложение поддерживает соединение с базой данных или осуществляет связь с СОМ-портами. В этом случае система может не освободить занимаемые программой ресурсы. Для отслеживания состояния формы используются события Form.Deactivate и Form.Activated. В листинге 7.1 приведен пример работы с этими событиями.

Листинг 7.1

private void Form1_Activated(object sender, EventArgs e) {

 // Здесь ваш код для восстановления связей с портами и т.д.

 lblInfo.Text = "Приложение активировано";

}

private void Form1_Deactivate(object sender, EventArgs e) {

 // Здесь ваш код для освобождения ресурсов

 lblInfo.Text = "Приложение деактивировано";

}

Так как приложение в неактивном состоянии может быть закрыто системой, то важно блокировать возможную потерю данных. Для этого нужно использовать событие Deactivate.

Закрыть или свернуть окно

Закрыть или свернуть — вот в чем вопрос. Компания Microsoft предложила для мобильных приложений модель поведения программ, отличающую от принятой в настольных компьютерах. Когда пользователь щелкает на кнопке закрытия, то на самом деле окно программы не закрывается, а сворачивается. Для пользователей подобное поведение приложений кажется странным, поэтому некоторые разработчики создавали программы, которые позволяли закрывать приложения одним нажатием стилуса. Популярность таких программ говорит о том, что не всем пользователям понравилось поведение приложений, которые отнимают ресурсы у системы. Но сейчас не нужно обсуждать целесообразность такого подхода к закрытию программ. Разработчик может создать приложение, которое позволит выбрать вариант закрытия приложения. Пользователь может нажать кнопку закрытия, чтобы просто свернуть окно, либо выполнить команду меню Выход, чтобы действительно закрыть приложение.

Но бывают ли такие ситуации, когда действительно требуется принудительно закрывать программу? Такая необходимость возникает при отладке и тестировании программы в эмуляторе. При стандартной модели поведения довольно утомительно каждый раз вручную останавливать программу, запущенную в эмуляторе. Конечно, можно временно присвоить свойству MinimizeBox при отладке значение False, что поможет избавиться от этой проблемы. Но перед окончательным релизом программы надо все же поставить значение True. Однако полагаться на свою память не стоит. Гораздо проще воспользоваться условной компиляцией.

При создании приложения надо использовать несколько строчек кода в конструкторе формы сразу после вызова процедуры InitializeComponent(), как показано в листинге 7.2.

Листинг 7.2

#if DEBUG

 MinimizeBox = false;

#else

 MinimizeBox = true;

#endif

Этот код стоит вынести на панель инструментов (рис. 7.1), что позволит быстро добавлять эту конструкцию в создаваемые приложения. Отныне все примеры в данной книге будут снабжаться этим кодом.

Рис. 7.1. Код условной компиляции на панели инструментов

Пиктограмма приложения

Любая серьезная программа должна иметь собственную пиктограмму. Чтобы указать используемую пиктограмму, надо при помощи команды меню Project►Properties открыть диалоговое окно Property Pages, выбрать раздел Application и указать путь к файлу с пиктограммой в свойстве Icon (рис. 7.2).

Рис. 7.2. Добавление пиктограммы для приложения

Создание собственных диалоговых окон

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

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

Предположим, что нужно создать специальное окно авторизации пользователя для доступа к программе. В состав проекта нужно включить новую форму, которая будет реализована как диалоговое окно проверки имени пользователя LogonForm. Это будет маленькое окно без четко очерченной границы. В нем надо разместить текстовое поле и две кнопки. Затем надо задать значения свойств FormBorderStyle, Size и Location. При загрузке основной формы и обработке события Load создается новый экземпляр объекта LogonForm и вызывается как диалоговое окно. Данное окно может вернуть значения DialogResult.OK, если пользователь ввел имя, или DialogResult.Cancel, если он просто закрыл форму. Если было введено правильное имя, то главная форма продолжает свою работу. В противном случае приложение следует закрыть. Соответствующий код приведен в листинге 7.3.

ВНИМАНИЕ

Чтобы элементы управления диалогового окна были доступны вызывающей форме, их надо объявить с модификатором public. По умолчанию используется модификатор private.

Листинг 7.3

private void Form1_Load(object sender, EventArgs e) {

 LogonForm LogonFrm = new LogonForm();

 if (LogonFrm.ShowDialog() == DialogResult.Cancel) {

  LogonFrm.Dispose();

  this.Close();

 } else {

  this.Text += " - " + LogonFrm.txtCheck.Text;

  LogonFrm.Dispose();

 }

}

После того как форма авторизации будет отображена на экране, нужно обработать события Click для нажатия кнопки проверки введенного имени пользователя или кнопки отмены. Первая кнопка проверяет правильность ввода имени. Если проверка завершилась успешно, то возвращается значение DialogResult.OK. Это иллюстрирует код, приведенный в листинге 7.4.

Листинг 7.4

private void butOK_Click(object sender, EventArgs e) {

 if (txtCheck.Text == "Alex") {

  this.DialogResult = DialogResult.OK;

 } else {

  MessageBox.Show("В доступе отказано. Попробуйте еще раз",

   "Вход в программу");

 }

}

Если пользователь не знает имени для доступа к программе, то ему придется нажать кнопку Отмена. В этом случае обработчик события butCancel_Click, код которого приведен в листинге 7.5, возвращает значение DialogResult.Cancel в главную форму, которая закрывает приложение.

Листинг 7.5

private void butCancel_Click(object sender, System.EventArgs e) {

 this.DialogResult = DialogResult.Cancel;

}

Создание заставки Splash Screen

Многие программы имеют так называемые заставки (splash screen). При загрузке формы сначала отображается окно с логотипом компании, названием продукта и дополнительной информацией. Следует реализовать приложение с подобным экраном, чтобы научиться использовать эту технологию.

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

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

Итак, надо создать дополнительную форму AboutForm и задать значения всех необходимых свойств окна. На форме надо расположить таймер, интервал срабатывания которого будет равен 3 с. Код, реализующий подобное поведение программы, приведен в листинге 7.6.

Листинг 7.6

protected override void OnPaint(PaintEventArgs e) {

 StringFormat sf = new StringFormat();

 sf.Alignment = StringAlignment.Center;

 sf.LineAlignment = StringAlignment.Center;

 Graphics g = e.Graphics;

 g.DrawString(".NET Compact Framework", this.Font,

 new SolidBrush(Color.Blue), Screen.PrimaryScreen.Bounds, sf);

}

private void timer1_Tick(object sender, EventArgs e) {

 this.Close();

}

В событии OnPaint формы AboutForm нужно установить свойства для вывода текста. При желании можно добавить отображение логотипа. Через заданный интервал таймер просто закроет это окно. Код для основной формы MainForm приведен в листинге 7.7.

Листинг 7.7

public MainForm() {

 InitializeComponent();

#if DEBUG

 MinimizeBox = false;

#else

 MinimizeBox = true;

#endif

 AboutForm about = new AboutForm();

 about.ShowDialog();

}

private void mnuAbout_Click(object sender, EventArgs e) {

 AboutForm about = new AboutForm();

 about.ShowDialog();

}

Теперь при запуске приложения на экране сначала будет отображаться заставка. После истечения трех секунд она исчезнет, и пользователь увидит основную форму.

Поворот экрана

Устройства с операционной системой Pocket PC 2003 Second Edition и старше обрели долгожданную возможность поворачивать содержимое экрана. Раньше пользователям приходилось устанавливать дополнительные программы для достижения такого эффекта. А разработчики получили возможность управлять поворотами экрана управляемыми методами только в .NET Compact Framework 2.0. Но зато теперь это можно сделать буквально одной строкой кода. Тем, кто по ряду причин должен по-прежнему использовать .NET Compact Framework 1.0, придется задействовать сложный код с вызовами функций API, который приведен в листинге 7.8. Сначала надо установить ссылку на пространство имен Microsoft.WindowsCE.Forms. После этого следует просто использовать нужные свойства класса SystemSettings.

Листинг 7.8.

using Microsoft.WindowsCE.Forms;

// запоминаем настройки экрана

ScreenOrientation initialOrientation = SystemSettings.ScreenOrientation;

private void butRot90_Click(object sender, EventArgs e) {

 // поворачиваем экран на 90 градусов

 SystemSettings.ScreenOrientation = ScreenOrientation.Angle90;

}

private void butRestore_Click(object sender, EventArgs e) {

 // восстанавливаем старую ориентацию

 if (SystemSettings.ScreenOrientation != initialOrientation) {

  try {

   SystemSettings.ScreenOrientation = initialOrientation;

  } catch (Exception) {

   // Невозможно вернуться к старым настройкам

   MessageBox.Show("He могу восстановить " +

    "предыдущую ориентацию экрана.");

  }

 }

}

Рекомендации по дизайну форм

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

Рекомендуемые размеры кнопок для Pocket PC составляют 21×21 пикселов, если пользователь применяет стилус, и 38×38 пикселов, если он предпочитает нажимать кнопки пальцами. Также надо предусмотреть свободное пространство между элементами, чтобы избежать ошибочных нажатий. Если маленькие кнопки для копирования и удаления файлов находятся рядом, то пользователи не раз вспомнят вас недобрым словом при неаккуратном нажатии на кнопки.

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

Готовые приложения

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

Файловый менеджер для смартфона

Смартфоны под управлением Windows Mobile 2005 не имеют в составе системы приложения, которое позволяет просматривать содержимое папок и файлов. В этом разделе будет рассматриваться аналог Проводника для смартфона. Основой данного примера послужил великолепный проект с открытым исходным кодом, который находится на сайте www.businessanyplace.net/?p=spfileman. Автор проекта Кристиан Форсберг (Christian Forsberg) любезно разрешил использовать его программу в качестве учебного пособия для этой книги.

Интересна история создания этого приложения. Сам автор оригинальной версии писал программу еще на Visual Studio .NET 2003 для смартфонов под управлением системы Smartphone 2003. Когда я скачал исходный код и попытался запустить его, то среда разработки Visual Studio .NET 2005 предложила конвертировать проект. Я согласился, в результате получил новую версию проекта. Теперь, после переделки, проект запускался в Visual Studio 2005 и использовал эмулятор Smartphone 2003. Но мне захотелось использовать пример для смартфонов Windows Mobile 2005. Для этого достаточно было в свойствах проекта выбрать другую платформу. Снова несколько минут раздумий, и Visual Studio выдает еще раз переделанный проект. Но и этого мне мало. Проект по-прежнему использует .NET Compact Framework 1.0. После выбора нужных значений свойств проекта, он стал использовать .NET Compact Framework 2.0. Осталось лишь перевести некоторые команды на русский язык и ввести новые возможности .NET Compact Framework 2.0 вместо старых конструкций, которые применялись для .NET Compact Framework 1.0.

Зачем нужен файловый менеджер

Файловый менеджер необходим как для разработчиков, так и для пользователей. С его помощью можно просматривать содержимое папок, удалять лишние файлы, создавать новые папки и выполнять другие операции с файлами. Трудно сказать, почему Microsoft решила не включать программу подобного рода в состав системы Windows Mobile. Кстати, производители смартфонов самостоятельно добавляют файловые менеджеры собственной разработки в состав стандартных программ. Но мы с вами напишем свою программу, что гораздо интереснее.

Графический интерфейс программы

У создаваемого приложения будет своя пиктограмма. При запуске программа будет отображать содержимое папки My Documents. Сам графический интерфейс программы очень прост и понятен. Навигация по папкам осуществляется простым выделением нужной папки и нажатием кнопки Enter. Для перехода на один уровень вверх нужно выделить папку, обозначенную двумя точками. Пункт меню Выход закрывает файловый менеджер, а пункт Меню позволяет выбрать выполняемую операцию (рис. 7.3).

Рис. 7.3. Общий вид файлового менеджера

Меню содержит команды для всех стандартных файловых операций. Пользователь может удалять, копировать, добавлять и переименовывать файлы. Также имеется возможность работы с ярлыками. Чтобы использовать эту возможность, нужно сначала выбрать файл, выполнить команду Копировать, затем перейти в нужную папку и выполнить команду Вставить ярлык. При выборе команды Свойства появляется соответствующее окно (рис. 7.4).

Рис. 7.4. Окно свойств

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

Код программы

Теперь можно приступить к написанию кода. При запуске программы выполняется обработчик события Form_Load. При загрузке основной формы MainForm работает код, приведенный в листинге 7.9.

Листинг 7.9

ListViewHelper.SetGradient(listView);

string bs = Path.DirectorySeparatorChar.ToString();

// Устанавливаем начальную папку

this.path = bs + "My Documents" + bs;

// Заполняем список папок и файлов

fillList();

Сначала устанавливается внешний вид элемента listView с градиентной закраской фона. Затем устанавливается папка по умолчанию My Documents, которую программа открывает при загрузке. Метод fillList заполняет список ListView содержимым открываемой папки. Сам код метода приведен в листинге 7.10.

Листинг 7.10

/// <summary>

/// Заполнение ListView списком папок и файлов

/// </summary>

private void fillList() {

 Cursor.Current = Cursors.WaitCursor;

 // Заполняем ListView списком папок и файлов

 // в выбранной папке

 ListViewItem lvi;

 listView.BeginUpdate();

 listView.Items.Clear();

 // Если не корневая папка

 if (path.Length > 1) {

  // Добавляем папку "Вверх"

  lvi = new ListViewItem(UPDIR);

  lvi.ImageIndex = 0;

  listView.Items.Add(lvi);

 }

 // Добавляем папки

 string[] dirs = Directory.GetDirectories(path);

 ArrayList list = new ArrayList(dirs.Length);

 for(int i = 0; i < dirs.Length; i++)

  list.Add(dirs[i]);

 list.Sort(new SimpleComparer());

 foreach(string dir in list) {

  lvi = new ListViewItem(Path.GetFileName(dir));

  lvi.ImageIndex = 0;

  listView.Items.Add(lvi);

 }

 // Добавляем файлы

 string[] files = Directory.GetFiles(path);

 list = new ArrayList(files.Length);

 for(int i = 0; i < files.Length; i++)

  list.Add(files[i]);

 list.Sort(new SimpleComparer());

 foreach(string file in list) {

  lvi = new ListViewItem(Path.GetFileName(file));

  lvi.ImageIndex = 1;

  listView.Items.Add(lvi);

 }

 listView.EndUpdate();

 if (listView.Items.Count > 0) {

  // выделяем первый элемент

  listView.Items[0].Selected = true;

  listView.Items[0].Focused = true;

 }

 Cursor.Current = Cursors.Default;

}

Итак, посмотрим, что делает метод fillList. Перед заполнением элемента списком файлов надо очистить его содержимое от предыдущих записей при помощи метода Clear. После очистки списка надо проверить, является ли папка корневой. Если папка не корневая, то в список добавляется специальная папка «На один уровень выше». Затем в список добавляются все папки в отсортированном порядке.

После этого наступает очередь файлов. Они сортируются и заносятся в список. Наконец, первый элемент списка выделяется другим цветом. Заодно на первом элементе устанавливается фокус ввода. Навигация по папкам и файлам осуществляется с помощью кнопок и дополняется кнопкой Action. Код навигации приведен в листинге 7.11.

Листинг 7.11

/// <summary>

/// Навигация по папкам и файлам

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void listView_ItemActivate(object sender, System.EventArgs e) {

 Cursor.Current = Cursors.WaitCursor;

 ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 bool isFolder = lvi.ImageIndex == 0;

 if (lvi.Text == UPDIR) {

  path = path.Substring(0,

   path.Substring(0,

   path.Length - 1).LastIndexOf(Path.DirectorySeparatorChar) + 1);

  fillList();

 } else if (isFolder) {

  path += lvi.Text + Path.DirectorySeparatorChar;

  fillList();

 } else

  ShellExecute.Start(path + lvi.Text);

 Cursor.Current = Cursors.Default;

}

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

Теперь разберем код для команд меню. Для команды Вырезать код приведен в листинге 7.12.

Листинг 7.12

private void cutMenuItem_Click(object sender, System.EventArgs e) {

 ListViewItem lvi =

  listView.Items[listView.SelectedIndices[0]];

 clipboardFileName = this.path + lvi.Text;

 clipboardAction = ClipboardAction.Cut;

}

Путь к текущему выбранному файлу сопоставляется с производимым действием. Код, выполняющийся после выбора команды Копировать, приведен в листинге 7.13.

Листинг 7.13

private void copyMenuItem_Click(object sender, System.EventArgs e) {

 ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 clipboardFileName = path + lvi.Text;

 clipboardAction = ClipboardAction.Copy;

}

Для команды меню Вставить код немного усложняется. Он приведен в листинге 7.14.

Листинг 7.14

private void pasteMenuItem_Click(object sender, System.EventArgs e) {

 // Если файл существует

 string dest = path + Path.GetFileName(clipboardFileName);

 if (File.Exists(dest)) {

  if (MessageBox.Show("Файл уже существует, перезаписать?", this.Text,

   MessageBoxButtons.YesNo, MessageBoxIcon.Question,

   MessageBoxDefaultButton.Button2) == DialogResult.Yes)

   File.Delete(dest);

  else return;

 }

 // Перемещаем или копируем

 string s = path.Substring(0, path.Length - 1);

 switch(clipboardAction) {

 case ClipboardAction.Cut:

  File.Move(clipboardFileName, dest);

  break;

 case ClipboardAction.Copy:

  File.Copy(clipboardFileName, dest, false);

  break;

 }

 clipboardAction = ClipboardAction.None;

 clipboardFileName = string.Empty;

 fillList();

}

Перед тем как вставить файл в другую папку, нужно удостовериться, что в ней нет файла с таким именем. Если же такой файл существует, то надо предупредить пользователя и узнать, что он хочет сделать. Код для команды Вставить ярлык приведен в листинге 7.15.

Листинг 7.15

private void pasteShortcutMenuItem_Click(object sender, System.EventArgs e) {

 int i = 2;

 string s = string.Empty;

 string dest;

 while(true) {

  dest = path + "Shortcut" + s + " to " +

   Path.GetFileName(Path.GetFileNameWithoutExtension(clipboardFileName) +

   ".lnk");

  if (!File.Exists(dest)) break;

  s = " (" + i.ToString() + ")";

  i++;

 }

 StreamWriter sw = new StreamWriter(dest);

 s = clipboardFileName;

 if(s.IndexOf(" ") > 0)

  s = "\"" + s + "\"";

 s = s. Length.ToString() + "#" + s;

 sw.WriteLine(s);

 sw.Close();

 fillList();

}

В этом коде создается уникальное имя ярлыка, которое затем записывается в виде файла с добавлением. К имени ярлыка добавляется расширение .LNK.

Код для команды Переименовать приведен в листинге 7.16.

Листинг 7.16

private void renameMenuItem_Click(object sender, System.EventArgs e) {

 Cursor.Current = Cursors.WaitCursor;

 istViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 bool isFolder = lvi.ImageIndex = 0;

 string s;

 if (isFolder)

  s = "папку";

 else s = "файл";

 NameForm nameForm =

  new NameForm(this, "Переименовать " + s, lvi.Text,

   new SetNameDelegate(SetRename));

 if (nameForm.ShowDialog() = DialogResult.OK) fillList();

 listView.Focus();

}

Сначала обрабатывается текущий выделенный элемент. Если пользователь выделил папку, то для формы nameForm задается соответствующий заголовок Переименовать папку. Также из этой формы передается в основную форму новое имя папки или файла с помощью метода Set Rename, как это показано в листинге 7.17.

Листинг 7.17

/// <summary>

/// Метод для переименования папки или файла

/// </summary>

/// <param name="name">Имя папки или файла</param>

public void SetRename(string name) {

 ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 bool isFolder = lvi.ImageIndex == 0;

 string itemName = path + lvi.Text;

 string destName =

  Path.GetDirectoryName(itemName) +

  Path.DirectorySeparatorChar.ToString() + name;

 if (isFolder)

  Directory.Move(itemName, destName);

 else

  File.Move(itemName, destName);

}

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

Листинг 7.18

private void deleteMenuItem_Click(object sender,

 System.EventArgs e) {

 ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 bool isFolder = lvi.ImageIndex == 0;

 string s = "Are you sure you want to delete " + lvi.Text;

 if (isFolder)

  s += " and all its content";

 s += "?";

 if (MessageBox.Show(s, this.Text, MessageBoxButtons.YesNo,

  MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) ==

  DialogResult.Yes) {

 if (isFolder)

  Directory.Delete(path + lvi.Text, true);

 else

  File.Delete(path + lvi.Text);

 fillList();

}

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

Листинг 7.19

private void newFolderMenuItem_Click(object sender, System.EventArgs e) {

 Cursor.Current = Cursors.WaitCursor;

 ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 NameForm nameForm = new NameForm(this, "Новая папка", "",

  new SetNameDelegate(SetNewName));

 if (nameForm.ShowDialog() == DialogResult.OK) fillList();

 listView.Focus();

}

В результате действия этой функции отображается форма NameForm с заголовком Новая папка. Эта форма также передает информацию в главную форму при помощи метода SetNewName, который приведен в листинге 7.20.

Листинг 7.20

/// <summary>

/// Устанавливает новое имя для папки

/// </summary>

/// <param name="name">Имя для папки</name>

public void SetNewName(string name) {

 Directory.CreateDirectory(path + name);

}

Метод создает папку с заданным именем. Как видно, код его чрезвычайно прост.

Код для выполнения команды Свойства приведен в листинге 7.21.

Листинг 7.21

private void propertiesMenuItem_Click(object sender, System.EventArgs e) {

 Cursor.Current = Cursors.WaitCursor;

 ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 FileInfo fi = new FileInfo(path + lvi.Text);

 PropertiesForm propertiesForm =

  new PropertiesForm(this, fi, new SetNameDelegate(SetRename),

  new SetAttributesDelegate(SetAttributes));

 if (propertiesForm.ShowDialog() == DialogResult.OK) fillList();

 listView.Focus();

}

Этот код вызывает форму PropertiesForm, которая отображает атрибуты выбранного файла или папки. Также в этой форме пользователь может изменять атрибуты файла при помощи метода SetAttributes, код которого приведен в листинге 7.22.

Листинг 7.22

public void SetAttributes(FileAttributes fileAttributes) {

 ListViewItem lvi = listView.Items[listView.SelectedIndices[0]];

 bool isFolder = lvi.ImageIndex = 0;

 if (isFolder) {

  DirectoryInfo di = new DirectoryInfo(path + lvi.Text);

  di.Attributes = fileAttributes;

 } else {

  FileInfo fi = new FileInfo(path + lvi.Text);

  fi.Attributes = fileAttributes;

 }

}

Для создания градиентной заливки соответствующего элемента интерфейса применяется метод, код которого приведен в листинге 7.23.

Листинг 7.23

public static void SetGradient(System.Windows.Forms.ListView listView) {

 // Новый вариант

 // Для .NET Compact Framework 2.0

 SendMessage(listView.Handle, LVM_SETEXTENDEDLISTVIEWSTYLE,

  LVS_EX_GRADIENT);

 listView.Refresh();

}

Итак, основные трудности реализации программы рассмотрены. Кроме того, в примере присутствуют вызовы функций Windows API для работы с табличным списком ListView. Эти примеры рассматривались в главе 4, поэтому не стоит повторять их разбор. На самом деле эту программу можно улучшать до бесконечности, добавляя новые функциональные возможности. Надеюсь, у вас это получится. Буду рад, если вы пришлете свои варианты примеров, которые, на ваш взгляд, украсят программу.

Диспетчер задач

Но мы с вами не расстаемся с программами, написанными Кристианом Форсбергом. На его сайте можно найти еще одну полезную программу, необходимую как разработчику, так и пользователю. Это Диспетчер задач (Task Manager). Программа подобного рода тоже отсутствует в стандартной поставке Windows Mobile. А ведь эта программа очень полезна в работе. Владелец смартфона под управлением системы Windows Mobile может узнать много нового о своей системе после запуска этой утилиты. Диспетчер задач покажет все программы, которые размещаются в памяти смартфона, отбирая системные ресурсы. Диспетчер задач также позволяет удалять из памяти ненужные программы и процессы. Не случайно, многие производители сами снабжают свои устройства программами подобного типа. Если вам не повезло и у вас нет такой программы, то вы можете сами написать Диспетчер задач.

Как и предыдущий пример, оригинальная версия программы была написана на Visual Studio 2003 для смартфонов под управлением Windows Mobile 2003 на платформе .NET Compact Framework 1.0. Следуя нашей традиции, я с согласия автора конвертировал проект для Visual Studio 2005 для целевой системы Windows Mobile 5.0 и с применением .NET Compact Framework 2.0.

Графический интерфейс программы

Диспетчер задач при запуске показывает список запущенных программ (рис. 7.5).

Рис. 7.5. Внешний вид программы

С помощью меню, размещенного в левой части окна, можно активировать выбранное приложение. При этом сам менеджер задач закрывается. Меню, расположенное в правой части окна, предоставляет пользователю несколько больше возможностей. Команды этого меню приведены в следующем списке:

□ Обновить — обновляет список запущенных программ;

□ Процессы — показывает список запущенных процессов;

□ Остановить — останавливает выбранную программу;

□ Остановить все — останавливает все запущенные программы;

□ Вид — показывает информацию о процессе;

□ Убить — закрывает процесс;

□ О программе — выводит информацию об авторе программы;

□ Готово — закрывает программу. Внешний вид этого меню показан на рис. 7.6.

Рис. 7.6. Команды меню для правой кнопки

Код программы

При активации основной формы MainForm программа получает список запущенных программ при помощи процедуры fillTaskList, код которой приведен в листинге 7.24.

Листинг 7.24

private void fillTaskList() {

 Cursor.Current = Cursors.WaitCursor;

 // Получим список запущенных приложений

 windows = WindowHelper.EnumerateTopWindows();

 // Заполняем ListView

 ListViewItem lvi;

 listView.BeginUpdate();

 listView.Items.Clear();

 foreach(Window w in windows) {

  lvi = new ListViewItem(w.ToString());

  listView.Items.Add(lvi);

 }

 listView.EndUpdate();

 if (listView.Items.Count > 0) {

  listView.Items[0].Selected = true;

  listView.Items[0].Focused = true;

 }

 Cursor.Current = Cursors.Default;

}

Данная процедура использует класс WindowHelper, который позволяет получить информацию о запущенных приложениях. В листинге 7.25 приведен код метода EnumerateTopWindows, который находит все окна запущенных в системе приложений.

Листинг 7.25

public static Window[] EnumerateTopWindows() {

 ArrayList windowList = new ArrayList();

 IntPtr hWnd = IntPtr.Zero;

 Window window = null;

 // Получим первое окно

 hWnd = GetActiveWindow();

 hWnd = GetWindow(hWnd, GW_HWNDFIRST);

 while(hWnd != IntPtr.Zero) {

  if (IsWindow(hWnd) && IsWindowVisible(hWnd)) {

   IntPtr parentWin = GetParent(hWnd);

   if ((parentWin == IntPtr.Zero)) {

    int length = GetWindowTextLength(hWnd);

    if (length > 0) {

     string s = new string('\0', length + 1);

     GetWindowText(hWnd, s.length + 1);

     s = s.Substring(0, s.IndexOf('\0'));

     if (s != "Tray" && s != "Start" && s != "Task Manager") {

      window = new Window();

      window.Handle = hWnd;

      window.Text = s;

      windowList.Add(window);

     }

    }

   }

  }

  hWnd = GetWindow(hWnd, GW_HWNDNEXT);

 }

 return (Window[])windowList.ToArray(typeof(Window));

}

В этом методе вызываются функции Windows API, с помощью которых можно получить список всех открытых окон. Все обнаруженные окна добавляются в список, если они удовлетворяют некоторым условиям. Добавляемые окна не должны иметь родительских окон, они должны быть видимыми и иметь заголовок. При этом сам Диспетчер задач не должен попасть в этот список. Все остальные окна записываются в массив.

Активация и закрытие приложения

Для активации запущенного приложения вызывается функция Windows API SetForegroundWindow, которая использует дескриптор окна. Для закрытия приложения используется функция SendMessage с соответствующим сообщением закрытия WM_CLOSE. Для закрытия сразу всех окон можно использовать функцию Windows API SHCloseApps, которая закрывает все запущенные программы, кроме самого Диспетчера задач. Код, выполняющий эти действия, приведен в листинге 7.26.

Листинг 7.26

public static void ActivateWindow(IntPtr hWnd) {

 // Активируем приложение

 SetForegroundWindow(hWnd);

}

public static void CloseWindow(IntPtr hWnd) {

 // Закрываем приложение

 SendMessage(hWnd, WM_CLOSE, 0, 0);

}

public static void CloseApps() {

 // Закрываем все приложения

 SHCloseApps(int.MaxValue);

}

Перечисление процессов

Для отображения списка процессов используется функция, код которой приведен в листинге 7.27.

Листинг 7.27

private void fillProcessList() {

 Cursor.Current = Cursors.WaitCursor;

 // Получаем список запущенных процессов

 processes = Process.GetProcesses();

 // Заполняем ListView

 ListViewItem lvi;

 listView.BeginUpdate();

 listView.Items.Clear();

 foreach (Process p in processes) {

  lvi = new ListViewItem(p.ProcessName);

  //lvi.SubItems.Add("ID");

  listView.Items.Add(lvi);

 }

 listView.EndUpdate();

 if (listView.Items.Count > 0) {

  listView.Items[0].Selected = true;

  listView.Items[0].Focused = true;

 }

 Cursor.Current = Cursors.Default;

}

Список активных процессов извлекается при помощи класса Process. Основой класса является метод GetProcesses, приведенный в листинге 7.28.

Листинг 7.28

public static Process[] GetProcesses() {

 ArrayList procList = new ArrayList();

 IntPtr handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

 if ((int)handle > 0) {

  try {

   PROCESSENTRY32 peCurrent;

   PROCESSENTRY32 pe32 = new PROCESSENTRY32();

   byte[] peBytes = pe32.ToByteArray();

   int retval = Process32First(handle, peBytes);

   while(retval == 1) {

    peCurrent = new PROCESSENTRY32(peBytes);

    Process proc =

     new Process(new IntPtr((int)peCurrent.PID), peCurrent.Name,

     (int)peCurrent.ThreadCount, (int)peCurrent.BaseAddress);

     procList.Add(proc);

    retval = Process32Next(handle, peBytes);

   }

  } catch(Exception ex) {

   throw new Exception("Exception: " + ex.Message);

  }

  CloseToolhelp32Snapshot(handle);

  return (Process[])procList.ToArray(typeof(Process));

 } else {

  throw new Exception("Unable to get processes!");

 }

}

С помощью данного метода можно узнать детальную информацию о каждом процессе.

Закрытие процесса

Чтобы закрыть процесс, используется метод Kill, код которого приведен в листинге 7.29.

Листинг 7.29

public void Kill() {

 IntPtr hProcess;

 hProcess = OpenProcess(PROCESS_TERMINATE, false, (int) processId);

 if (hProcess != (IntPtr) INVALID_HANDLE_VALUE) {

  bool bRet;

  bRet = TerminateProcess(hProcess, 0);

  CloseHandle(hProcess);

 }

}

Данный метод также использует вызовы функций Windows API. Функция OpenProcess получает дескриптор процесса, который затем передается функции TerminateProcess для уничтожения процесса.

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

Маленький блокнот

Однажды мой друг, далекий от программирования, попросил меня написать простенький текстовый редактор для карманного компьютера. Его не совсем устраивало приложение Word Mobile, которое используется для работы с текстовыми файлами в операционной системе Windows Mobile. Заказчик хотел получить только основные функции стандартного Блокнота из Windows XP, то есть копирование, вырезание, вставку и удаление текста. Также он хотел обойтись без установки .NET Compact Framework 2.0, так как устаревшая модель его карманного компьютера обладала малой емкостью памяти.

В рамках решения поставленной задачи и была написана программа Блокнотик, которая и будет рассматриваться в этом разделе главы.

Единственная сложность при написании данного текстового редактора состояла в том, что библиотека .NET Compact Framework 1.0 не поддерживает работу с буфером обмена на уровне управляемого кода. Поэтому пришлось прибегать к вызовам функций Windows API.

Данный пример можно использовать в качестве основы для тех, кто хочет написать свой текстовый редактор для .NET Compact Framework 1.0. Надо заметить, что если бы я стал писать свой пример с использованием .NET Compact Framework 2.0, то справиться с задачей было бы гораздо легче, так как вторая версия библиотеки поддерживает буфер обмена, который так необходим при операциях с текстом.

Первые шаги

После запуска Visual Studio .NET 2005 надо создать новый проект. При выборе типа проекта надо указать, что будет использоваться .NET Compact Framework 1.0. Для начала на форме следует разместить текстовое поле с именем txtEditor. Для свойства Multiline надо задать значение True, а свойство ScrollBars получит значение Both.

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

Также на первом этапе разработки надо указать позицию текстового поля и установить в нем фокус. Соответствующий код был добавлен в обработчик события Form_Load, что иллюстрирует листинг 7.30.

Листинг 7.30

private void MainForm_Load(object sender, EventArgs e) {

 // устанавливаем позицию текстового поля

 txtEditor.Location = new Point(0, 0);

 // Приравниваем размеры текстового поля к размерам формы

 txtEditor.Width = this.Width;

 txtEditor.Height = this.Height;

 // Устанавливаем фокус

 txtEditor.Focus();

}

Если бы программа создавалась для настольного компьютера, то написанный код не вызывал бы никаких сомнений. Но у КПК нет внешней клавиатуры, и для ввода текста используется панель ввода SIP. Поэтому на форму надо добавить элемент inputPanel. Так как при активации панель ввода закроет часть формы, то надо написать код для вычисления высоты текстового поля для этого случая и соответствующим образом изменить обработчик события Form_Load, как показано в листинге 7.31.

Листинг 7.31

private void MainForm_Load(object sender, EventArgs e) {

 ...

 // Высоту текстового поля устанавливаем в зависимости от SIP

 //txtEditor.Height = this.Height;

 SetTextBoxHeight();

}

// устанавливаем размеры текстового поля в зависимости от

// активности SIP

private void SetTextBoxHeight() {

 if (SIP.Enabled)

  txtEditor.Height = SIP.VisibleDesktop.Height + 2;

 else

  txtEditor.Height = this.Height;

}

private void SIP_EnabledChanged(object sender, EventArgs e) {

 SetTextBoxHeight();

}

Стандартные операции с текстом

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

Пользователь должен иметь возможность манипулировать текстом, то есть копировать часть текста, вырезать его, удалять и вставлять в нужную позицию. Для этого надо создать меню при помощи элемента mainMenu. В подменю Правка надо добавить стандартные команды редактирования текста — Отменить, Вырезать, Копировать, Вставить. Если бы приложение создавалось для среды исполнения .NET Compact Framework 2.0, то можно было бы использовать класс Clipboard. Но так как используется .NET Compact Framework 1.0, то придется обратиться к функциям Windows API и использовать неуправляемый код, что иллюстрирует листинг 7.32.

Листинг 7.32

// сообщения буфера обмена

public const int WM_CUT = 0x0300;

public const int WM_COPY = 0x0301;

public const int WM_PASTE = 0x0302;

public const int WM_CLEAR = 0x0303;

public const int WM_UNDO = 0x0304;

// функции API

[DllImport("coredll.dll", CharSet = CharSet.Unicode)]

public static extern IntPtr GetFocus();

[DllImport("coredll.dll")]

public static extern int SendMessage(IntPtr hWnd, uint Message,

 uint wParam, uint lParam);

private void mnuCut_Click(object sender, EventArgs e) {

 // Вырезаем текст

 SendMessage(hwndEditor, WM_CUT, 0, 0);

}

private void mnuUndo_Click(object sender, EventArgs e) {

 // Отменяем последнее действие

 SendMessage(hwndEditor, WM_UNDO, 0, 0);

}

private void mnuCopy_Click(object sender, EventArgs e) {

 // Копируем выделенный текст

 SendMessage(hwndEditor, WM_COPY, 0, 0);

}

private void mnuPaste_Click(object sender, EventArgs e) {

 // Вставляем текст из буфера обмена

 SendMessage(hwndEditor, WM_PASTE, 0, 0);

}

private void mnuDelete_Click(object sender, EventArgs e) {

 // Удаляем выделенный текст

 SendMessage(hwndEditor, WM_CLEAR, 0, 0);

}

Теперь необходимо добавить в создаваемое приложение поддержку контекстного меню. Использование контекстного меню избавит пользователя от необходимости постоянно переводить стилус в нижнюю часть экрана для доступа к командам меню. В программу нужно добавить элемент управления ContextMenu и сделать список команд меню, который будет дублировать подпункт основного меню Правка. Созданное контекстное меню надо связать с текстовым полем при помощи свойства ContextMenu. Осталось только скопировать код из команд основного меню в соответствующие места для команд контекстного меню. Например, для команды контекстного меню Копировать надо использовать код, приведенный в листинге 7.33.

Листинг 7.33

private void cmenuCopy_Click(object sender, EventArgs e) {

 // Копируем выделенный текст

 SendMessage(hwndEditor, WM_COPY, 0, 0);

}

Мы сделали еще один шаг вперед. Теперь наш маленький блокнот умеет работать с текстом. Но приложение нужно еще немного доработать. Например, пользователь может во время работы с блокнотом переключиться на другую программу и скопировать в буфер обмена картинку, а затем вернуться обратно к текстовому редактору. Конечно, картинку нельзя вставить в текстовое поле. Поэтому надо проверить тип содержимого в буфере обмена, и если там содержатся не текстовые данные, то нужно заблокировать пункт меню Вставить. Для этого можно использовать функцию IsClipboardFormatAvailable, а проверку данных в буфере обмена выполнять в событии Popup, как показано в листинге 7.34.

Листинг 7.34

[DllImport("Coredll.dll")]

private static extern bool IsClipboardFormatAvailable(uint uFormat);

// константа для буфера обмена

private const uint CF_UNICODETEXT = 13;

public static bool IsText() {

 try {

  return IsClipboardFormatAvailable(CF_UNICODETEXT);

 } catch (Exception ex) {

  MessageBox.Show("He могу понять, что содержится в буфере обмена!");

  return false;

 }

}

private void mnuEdit_Popup(object sender, EventArgs e) {

 if (IsText())

  mnuPaste.Enabled = true;

 else

  mnuPaste.Enabled = false;

}

Подобные изменения надо сделать и для пунктов меню. Если пользователь не выделил часть текста, то пункты Вырезать, Копировать и Удалить также должны быть заблокированы. Код, реализующий эту функциональность, приведен в листинге 7.35.

Листинг 7.35

//Если текст выделен

if (txtEditor.SelectionLength > 0) {

 mnuCut.Enabled = true;

 mnuCopy.Enabled = true;

 mnuDelete.Enabled = true;

} else {

 mnuCut.Enabled = false;

 mnuCopy.Enabled = false;

 mnuDelete.Enabled = false;

}

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

Листинг 7.36

private void mnuOpen_Click(object sender, EventArgs e) {

 dlgOpenFile.Filter = "Текстовые документы (*.txt)|*.txt|Все файлы |*.*";

 dlgOpenFile.ShowDialog();

 if (File.Exists(dlgOpenFile.FileName)) {

  fname = dlgOpenFile.FileName;

  StreamReader sr =

   new StreamReader(fname, System.Text.Encoding.GetEncoding("Windows-1251"), false);

  txtEditor.Text = sr.ReadToEnd();

  flag = false;

  sr.Close();

 }

}

private void mnuSaveAs_Click(object sender, EventArgs e) {

 SaveFileDialog dlgSaveFile = new SaveFileDialog();

 dlgSaveFile.Filter = "Текстовые документы (*.txt)|*.txt|Все файлы |*.*";

 dlgSaveFile.ShowDialog(); fname = dlgSaveFile.FileName;

 savedata();

}

private void savedata() {

 if (fname == "") {

  SaveFileDialog dlgSaveFile = new SaveFileDialog();

  dlgSaveFile.Filter = "Текстовые документы (*.txt)|*.txt|Все файлы|*.*";

  DialogResult res = dlgSaveFile.ShowDialog();

  if (res == DialogResult.Cancel) {

   return;

  }

  fname = dlgSaveFile.FileName;

  MessageBox.Show(fname);

 }

 StreamWriter sw =

  new StreamWriter(fname, false, System.Text.Encoding.GetEncoding("Windows-1251"));

 sw.WriteLine(txtEditor.Text);

 sw.Flush();

 sw.Close();

 flag = false;

}

private void mnuSave_Click(object sender, EventArgs e) {

 savedata();

}

private void txtEditor_TextChanged(object sender, EventArgs e) {

 flag = true;

}

Работа с файлами в .NET Compact Framework не отличается от методов работы с файлами в полной версии .NET Framework, поэтому заострять внимание на этом коде не нужно. Осталось только добавить в программу некоторые детали, которые придают программе профессиональный вид. Нужно присоединить собственную пиктограмму приложения, а также добавить диалоговое окно О программе с упоминанием автора программы и логотипом фирмы. Безусловно, вы можете наделить текстовый редактор новыми возможностями или расширить его функциональность. Например, для сохранения и открытия файлов я использовал стандартные диалоговые окна, которые работают с файлами в пределах папки Мои документы. Но используя код ранее созданного файлового менеджера, можно научить приложение сохранять и открывать файлы в любом месте файловой системы. Также можно доработать меню Формат, позволяющее работать с различными кодировками текста.

Распространение приложений

Даже если вы написали очень полезную программу, она не сможет обрести всемирную известность, пока вы держите ее на своем компьютере. Нужно все же распространить программу и, если она не бесплатная, то и немного заработать на отпуск. Программы для настольных компьютеров распространять довольно просто. Нужно лишь создать специальный проект для создания установочного пакета, который сгенерирует специальный файл установки Microsoft Installer (MSI). К сожалению, для мобильных устройств процесс создания установочных файлов немного отличается. В процессе распространения программы участвуют три составляющие: настольный компьютер, программа синхронизации Microsoft ActiveSync и программа wceload.exe для извлечения файлов из cab-файлов.

Для пользователя процесс установки программы не сильно отличается от привычной схемы. Сначала он скачивает программу или находит ее на компакт-диске. Затем запускает установочный msi-файл. Программа Microsoft Installer с помощью специального мастера установки помогает пользователю установить программу с нужными настройками. После этого программа считается установленной, и пользователь может запускать ее.

Создание cab-файла

Прежде чем установочный пакет попадет в руки пользователя, нужно хорошенько поработать над его созданием. Устройства под управлением Windows Mobile не могут напрямую работать с файлами .msi. Вместо этого используются кабинетные файлы с расширением .cab. Таким образом, задача программиста заключается в том, чтобы составить список команд для программы синхронизации ActiveSync, которые позволят скопировать cab-файлы на устройство с учетом необходимых установок. Для создания удобного установочного пакета с интуитивно понятным интерфейсом вам необходимо выполнить нехитрую последовательность действий.

1. Создать cab-файл для устройства.

2. Добавить в cab-файл дополнительные файлы, используемые программой, например изображения или файлы с данными

3. Добавить в cab-файл инструкции для записи в реестр.

4. Зарегистрировать cab-файл с помощью ActiveSync, чтобы пользователь мог установить приложение с настольного компьютера.

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

6. Упаковать все необходимые файлы в один специальный файл установки с расширением .msi.

Вы, вероятно, знаете, что кабинетный файл является специальным файлом упаковки и компрессии, с помощью которого можно сжимать файлы, что приведет к уменьшению их размеров. Также в этом файле могут содержаться инструкции для внесения изменений в реестр системы. За обработку cab-файлов на устройстве отвечает утилита wceload.exe, входящая в состав Windows Mobile.

Создание проекта

Приступим к разработке проекта для создания установочного пакета. Прежде всего нужно запустить уже существующий проект, который планируется подготовить для распространения. В качестве примера будет использоваться проект SmallNotepad. Затем нужно выполнить команду меню File►Add►New Project. В открывшемся диалоговом окне надо перейти в раздел Other Project Types, выбрать тип Smart Device Cab Project и задать имя нового проекта DeployNotepadCab (рис. 7.7).

Рис. 7.7. Выбор нового проекта для распространения приложения

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

Затем надо запустить редактор File System Editor, нажав соответствующую кнопку в окне свойств. Нужно выбрать пункт Application Folder и в контекстном меню выбрать пункт Add►Project Output (рис. 7.8).

Рис. 7.8. Выбор параметров проекта

В результате этого будет открыто диалоговое окно Add Project Output Group (рис. 7.9).

Рис. 7.9. Диалоговое окно Add Project Output Group

С помощью данного окна можно выбрать различные типы файлов, необходимые для программы, такие как файлы документации или, например, локализированные ресурсы. Нужно выбрать пункт Primary Output и нажать кнопке OK. В правой части окна следует щелкнуть правой кнопкой мыши на единственном пункте Primary output from SmallNotepad_CS и в контекстном меню выбрать пункт Create Shortcut to Primary output from SmallNotepad_CS (рис. 7.10). Это позволит включить пиктограмму в список файлов для распространения.

Рис. 7.10. Создание пиктограммы приложения

Созданный ярлык надо переместить мышью в папку Program Files Folder. Теперь можно приступать к созданию установочного файла.

В меню надо выполнить пункт Build►Build DeployNotepadCab. После этого среда разработки создаст специальный файл с расширением .CAB. При помощи файлового менеджера его нужно найти и запомнить его расположение.

Теперь надо установить созданный файл на эмуляторе. Для этого выполняется команда меню Tools►Device Emulator Manager. В диалоговом окне надо выбрать эмулятор. Например, Pocket PC 2003 SE Emulator. В этом же окне следует выполнить команду меню Actions►Connect. При этом выбранный эмулятор будет активирован.

В окне эмулятора надо выполнить команду меню File►Configure. После этого откроется окно настроек эмулятора, в котором следует перейти в раздел Shared Folder. В этом разделе надо выбрать папку, в которой находится созданный cab-файл (рис. 7.11). Эмулятор будет считать, что данная папка является карточкой памяти.

Рис. 7.11. Активация и настройка эмулятора

Если открыть в эмуляторе программу File Explorer (Start►Programs►File Explorer) и найти папку Storage Card, то в ней можно будет увидеть ранее созданные установочные файлы.

Нужно выбрать файл DeployNotepadCab и запустить его. В результате начнется процесс установки программы на устройство. При установке автоматически будет создан файл деинсталляции. Он поможет корректно удалить приложение. Для этого в окне эмулятора надо выполнить команду меню Start►Settings►System►Remove Program. В списке установленных программ надо найти ранее установленное приложение, выделить его и нажать кнопку Remove (рис. 7.12).

Рис. 7.12. Деинсталляция приложения

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

Дополнительные материалы

На сайте MSDN есть очень подробная статья «Deploying .NET Compact Framework 2.0 Applications with .cab and .msi Files», в которой приведены дополнительные сведения о создании и распространении установочных файлов. Стоит ознакомиться с данным материалом, чтобы использовать все возможности установочных файлов.