Текст книги "Программирование мобильных устройств на платформе .NET Compact Framework"
Автор книги: Иво Салмре
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 32 (всего у книги 69 страниц)
Стратегии проектирования высокопроизводительных пользовательских интерфейсов
Сложность написания кода пользовательского интерфейса объясняется тем, что он представляет собой пограничную область, в которой ваш код взаимодействует как с операционной системой, так и с конечным пользователем. Пользовательский интерфейс находится в буквальном смысле слова между ними и играет роль посредника при обмене запросами, связывающими между собой пользователя и операционную систему. Как проектировщик приложения, вы должны ответить на тысячи вопросов:
■ Какие стандартные или пользовательские элементы управления следует применять для отображения данных и организации взаимодействия с пользователем?
■ Имеет ли смысл создать собственные нестандартные элементы управления, или же лучше расширить свойства уже существующих элементов управления с тем, чтобы они соответствовали вашим конкретным нуждам?
■ Какие стратегии следует использовать при заполнении элементов интерфейса, которые должны содержать многочисленные данные? В этом отношении особого внимания заслуживают элементы управления, содержащие списки деревьев данных.
■ На какие управляющие события необходимо реагировать? В каких случаях реакция на эти события должна подавляться?
■ Можно ли уменьшить количество событий, запускаемых при выполнении обычных или часто повторяющихся задач?
■ Существует ли необходимость в форсировании немедленного обновления или перерисовки элементов управления ради поддержания в пользователе уверенности в том, что его команды воспринимаются и исполняются, если при этом страдает общая производительность приложения?
С учетом того, что перечень этих вопросов довольно большой, число возможных вариантов реализации кода, обеспечивающего удовлетворение тех или потребностей пользовательского интерфейса, оказывается практически неограниченным. Наряду с предоставлением вам широкой свободы действий, эта ситуация порождает риск принятия случайных или неэффективных решений. Представьте, что вы пришли в мебельный магазин покупать диван, а вам выдали несколько упаковок с деталями, количество которых явно превышает необходимое, и предложили самому заняться сборкой дивана, не удосужившись снабдить вас никакими дополнительными инструкциями. Возможно, собранная вами конструкция окажется прочной, и будет выглядеть вполне прилично, но не исключено, что результат вашего труда окажется менее привлекательным в отношении дизайна и будет походить скорее на настил, сооруженный первобытным человеком, чем на современный диван. Не имея инструкций, стратегии и хорошего технического руководства, вы закончите, вероятнее всего, вторым вариантом. При наличии же понятной схемы сборки исход окажется благополучным. Как и в случае с нашим вымышленным диваном, после того как вы соберете воедино все части приложения, вам будет очень трудно вновь отделить их одну от другой для внесения возможных изменений, связанных с пересмотром проекта. Если вы не хотите, чтобы конечный результат оказался посредственным, и не желаете начинать все заново с самого начала, вы должны продумать стратегии своих действий, которыми вы будете руководствоваться и которые будут гарантировать, что процесс разработки пользовательского интерфейса приложения все время развивается в правильном русле
Ниже приведены некоторые из основных стратегий проектирования, ориентированных на создание высокопроизводительных пользовательских интерфейсов.
Использование встроенных средств повышения производительностиЕсли среда выполнения мобильного приложения, которую вы используете, предлагает для создания высокопроизводительных кодов пользовательских интерфейсов специальные возможности, вы обязательно должны ими воспользоваться. Более того, вы должны очень внимательно изучить перечень событий, свойств и методов, относящихся к используемым вами элементам управления, и попытаться найти среди них те, которые особенно подходят для повышения производительности интерфейса. Лучшим способом узнать как можно больше об этих возможностях каркаса пользовательского интерфейса является исследование членов его классов окон, форм и элементов управления. Кроме того, полезно внимательно изучить перегруженные версии методов, предназначенных для решения наиболее часто возникающих задач программирования, и выяснить, не найдутся ли методы, позволяющие выполнять операции в пакетном режиме вместо того, чтобы повторно вызывать один и тот же метод в цикле. Как несложно догадаться, выполнение операций в пакетном режиме обычно происходит гораздо быстрее. Для такого исследования вам, как правило, потребуется каких– нибудь несколько минут, но вы сами удивитесь, как много нового для себя вы сможетe обнаружить; очень часто плохое функционирование кода пользовательского интерфейса объясняется просто тем, что для достижения тех или иных целей используются далеко не самые эффективные свойства или методы, или тем, что вызываются методы, приостанавливающие операции с пользовательским интерфейсом на время выполнения его обновлений.
Использование элементов управления TreeView и ListView среды .NET Compact Framework для повышения производительности приложений
Элементы управления TreeView и ListView используются для отображения наборов взаимосвязанных данных в пользовательских интерфейсах. Поскольку эти элементы управления работают с наборами данных, часто возникает необходимость в добавлении к ним или удалении из них сразу целых групп элементов данных. Для обоих элементов управления предусмотрены высокопроизводительные методы, позволяющие эффективно решать подобные задачи.
• .BeginUpdate()/EndUpdate(). Оба эти метода присутствуют как в TreeView, так и в ListView, и предназначены для приостановки и возобновления автоматической перерисовки элемента управления на экране. Вызов метода BeginUpdate() указывает на то, что элемент управления не должен автоматически перерисовываться всякий раз, когда в него добавляются или из него удаляются элементы данных, тогда как вызов метода EndUpdate() восстанавливает режим автоматической перерисовки элемента управления. Выполнение необязательных операций перерисовки экрана может отрицательно сказываться на производительности приложения. Если в процессе работы вашего приложения возникает необходимость в помещении в элемент управления или исключении из него многочисленных данных, то соответствующий участок кода целесообразно окружить парой вызовов BeginUpdate() и EndUpdate().
• .AddRange(). Для коллекции узлов элемента управления TreeView предусмотрен метод AddRange() (например, treeView1.Nodes.AddRange()), обеспечивающий групповое добавление узлов в TreeView. Такой "пакетный" режим обработки является гораздо более предпочтительным по сравнению с простым итеративным добавлением каждого узла по отдельности.
Используя эти эффективные встроенные механизмы, вы можете добиться существенного выигрыша в отношении как производительности, так и бесперебойности работы пользовательского интерфейса. Если с каким-либо элементом управления приходится работать особенно часто, то стоит просмотреть список его свойств и методов, чтобы выяснить, не найдутся ли среди них такие, использование которых позволит увеличить производительность.
Пример: различия в производительности, обусловленные использованием различных подходов при работе с элементами управления TreeView
Приведенный в листинге 11.1 пример предназначен для количественной оценки эффективности трех различных методик работы с элементами управления TreeView среды .NET Compact Framework. Используя Visual Studio .NET, создайте новый проект C# для мобильного приложения, выбрав в качестве целевой платформы устройство Pocket PC. Убедившись в том, что вы находитесь в режиме конструктора, добавьте в пустую форму элемент управления TreeView и пять кнопок, как показано на рис. 11.1.
Visual Studio .NET автоматически создаст и свяжет с кнопкой пустой обработчик событий
Все, что вы должны для этого сделать – это дважды щелкнуть на кнопке формы. Имя добавленной функции будет состоять из имени элемента управления (например, button1) и суффикса _Click. Visual Studio выполнит следующее: 1) создаст для вас функцию обработчика событий, 2) запишет код в функцию InitializeComponent() формы, предназначенную для подключения только что созданного обработчика события щелчка, и 3) откроет окно редактора кода, чтобы вы могли ввести в нем код для обработчика события. При желании вы можете назвать кнопку по-другому, изменив свойство Name в окне Properties (окно справа на рис. 11.1). Целесообразно сделать это до двойного щелчка на кнопке с целью создания и подключения обработчика события, поскольку функция обработчика создается с использованием текущего имени элемента управления. Если имя элемента управления будет изменено уже после создания этой функции, обработчик по-прежнему останется связанным с ним должным образом, но его имя не будет согласовываться с новым именем элемента управления. Обеспечить совпадение имен в этом случае вам придется вручную; сделать это не составляет особого труда, но для этого вам придется выполнить лишнюю работу.
Представленный в листинге 11.1 код состоит из набора обработчиков событий для различных кнопок, которые имеются на вашей форме. Фактические имена используемых функций будут происходить от имен, присвоенных соответствующим кнопкам. В своем коде я использовал для кнопок следующие имена: UnOptimizedFill, UnOptimizedClear, UseBeginEndUpdateForFill, UseBeginEndUpdateForClear и FillArrayBeforeAttachingToTree. Если вы используете заданные по умолчанию имена, которые предложит вам Visual Studio .NET, то у вас будут кнопки с именами button1, button2, button3, button4 и button5 и функции обработчиков событий с другими именами, которые надо будет соответственно изменить.

Рис. 11.1. Конструктор форм среды Visual Studio .NET с размещенными на форме элементами управления TreeView и Button
В любом случае, проще всего сначала создать и связать с кнопками пустые обработчики событий, выполняя для этого двойные щелчки на каждой из кнопок в конструкторе форм Visual Studio .NET, а затем вставить приведенный в листинге код обработчиков в созданные для вас определения функций.
Листинг 11.1. Заполнение данными и очистка от них элементов управления TreeView с использованием альтернативных стратегий
//–
//Примечание #1: В этом примере используется класс PerformanceSampling,
// определенный ранее в данной книге. Убедитесь в том, что
// вы включили этот класс в свой проект.
//Примечание #2: Этот код необходимо включить в класс Form, содержащий
// элемент управления TreeView и кнопки Button, к которым
// подключены приведенные ниже функции xxx_Click.
//–
//Количество элементов, которые необходимо поместить в элемент
//управления TreeView
const int NUMBER_ITEMS = 800;
//–
//Код для кнопки "Fill: Baseline"
//
//Использование неоптимизированного подхода для заполнения данными элемента
//управления TreeView
//–
private void UnOptimizedFill_Click(object sender, System.EventArgs e) {
//Очистить массив для создания одинаковых условий тестирования
if (treeView1.Nodes.Count > 0) {
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
treeView1.Update();
}
//Для повышения корректности тестирования предварительно выполнить
//операцию сборки мусора
System.GC.Collect();
//Запустить таймер
PerformanceSampling.StartSample(0, "TreeViewPopulate");
//Заполнить данными элемент управления TreeView
for (int i = 0; i < NUMBER_ITEMS; i++) {
treeView1.Nodes.Add("TreeItem" + i.ToString());
}
//Остановить таймер и отобразить результат
PerformanceSampling.StopSample(0);
System.Windows.Forms.MessageBox.Show(PerformanceSampling.GetSampleDurationText(0));
}
//–
//Код для кнопки "Clear: Baseline"
//
//Использование неоптимизированного подхода для заполнения данными элемента
//управления TreeView
//–
private void UnOptimizedClear_Click(object sender, System.EventArgs e) {
//Для повышения корректности тестирования предварительно выполнить
//операцию сборки мусора
System.GC.Collect();
//Запустить таймер
PerformanceSampling.StartSample(1, "TreeViewClear");
treeView1.Nodes.Clear();
PerformanceSampling.StopSample(1);
System.Windows.Forms.MessageBox.Show(PerformanceSampling.GetSampleDurationText(1));
}
//–
//Код для кнопки "Fill: BeginUpdate"
//
//Подход, в котором используется метод BeginUpdate()
//–
private void UseBeginEndUpdateForFill_Click(object sender, System.EventArgs e) {
//Очистить массив для создания одинаковых условий тестирования
if (treeViewl.Nodes.Count > 0) {
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
treeView1.Update();
}
//Для повышения корректности тестирования предварительно выполнить
//операцию сборки мусора
System.GC.Collect();
//Запустить таймер
PerformanceSampling.StartSample(2, "Populate – Use BeginUpdate");
//Заполнить данными элемент управления
TreeView treeView1.BeginUpdate();
for (int i = 0; i < NUMBER_ITEMS; i++) {
treeView1.Nodes.Add("TreeItem" + i.ToString());
}
treeView1.EndUpdate();
//Остановить таймер и отобразить результат
PerformanceSampling.StopSample(2);
System.Windows.Forms.MessageBox.Show(PerformanceSampling.GetSampleDurationText(2));
}
//–
//Код для кнопки "Clear: BeginUpdate"
//Подход, в котором используется метод BeginUpdate()
//–
private void UseBeginEndUpdateForClear_Click(object sender, System.EventArgs e) {
//Для повышения корректности тестирования предварительно выполнить
//операцию сборки мусора
System.GC.Collect();
//Запустить таймер
PerformanceSampling.StartSample(3, "Clear – Use BeginUpdate");
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
//Остановить таймер и отобразить результат
PerformanceSampling.StopSample(3);
System.Windows.Forms.MessageBox.Show(PerformanceSampling.GetSampleDurationText(3));
}
//–
//Код для кнопки "Fill: Use Array"
//
//Подход, в котором используется массив
//–
private void FillArrayBeforeAttachingToTree_Click(object sender, System.EventArgs e) {
//Очистить массив для создания одинаковых условий тестирования
if (treeView1.Nodes.Count > 0) {
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
treeView1.Update();
}
//Для повышения корректности тестирования предварительно выполнить
//операцию сборки мусора
System.GC.Collect();
//Запустить таймер
PerformanceSampling.StartSample(4, "Populate – Use Array");
//Распределить память для нашего массива узлов дерева
System.Windows.Forms.TreeNode [] newTreeNodes = new System.Windows.Forms.TreeNode[NUMBER_ITEMS];
//Заполнить массив
for(int i = 0; i < NUMBER_ITEMS; i++) {
newTreeNodes[i] = newSystem.Windows.Forms.TreeNode("TreeItem" + i.ToString());
}
//Связать массив с элементом управления
TreeView treeView1.BeginUpdate();
treeView1.Nodes.AddRange(newTreeNodes);
treeView1.EndUpdate();
//Остановить таймер и отобразить результат
PerformanceSampling.StopSample(4);
System.Windows.Forms.MessageBox.Show(PerformanceSampling.GetSampleDurationText(4));
}
Результаты, полученные с использованием различных методик добавления данных в элемент управления TreeView и их исключения из него, приведены в таблицах 11.1 и 11.2.
Таблица 11.1. Физическое устройство Pocket PC: добавление 800 элементов данных (время в секундах)
| 1 | 40,785 | 12,484 | 10,388 |
| 2 | 40,533 | 12,322 | 10,419 |
| 3 | 40,878 | 13,343 | 11,686 |
| Среднее | 40,732 | 12,716 | 10,831 |
| Экономия времени по сравнению с базовым значением | Базовое значение (0%) | 68,78% | 73,41% |
Из табл. 11.1 видно, что экономия времени, достигаемая за счет окружения кода, предназначенного для добавления в элемент управления TreeView и исключения из него данных, вызовами методов BeginUpdate() и EndUpdate(), составила примерно две трети (68,78%). Наряду с этим, благодаря тому, что мерцание элементов управления вследствие их обновления происходит реже, повышается и привлекательность интерфейса, оцениваемая с позиций конечного пользователя. Использование метода AddRange() (столбец "Использование массива") для заполнения данными элемента управления TreeView позволило уменьшить накладные расходы еще на 5%; это улучшение также является ощутимым.
Было довольно-таки неожиданно обнаружить, что использование пары вызовов BeginUpdate() и EndUpdate() привело не только к значительному увеличению скорости добавления данных в элемент управления TreeView, но и оказало достаточно сильное влияние на скорость их удаления. Результаты, полученные с использованием двух различных подходов для удаления данных из элемента управления TreeView, сравниваются в табл. 11.2.
Таблица 11.2. Очистка 800 элементов данных (время в секундах)
| 1 | 18,791 | 8,656 |
| 2 | 15,910 | 8,964 |
| 3 | 16,821 | 8,516 |
| Среднее | 17,174 | 8,712 |
| Экономия времени по сравнению с базовым значением | Базовое значение (0 %) | 49,27 % |
Как видно из табл. 11.2, одного лишь окружения кода, предназначенного для удаления данных из элемента управления TreeView, вызовами методов BeginUpdate() и EndUpdate() оказалось достаточным для того, чтобы достигнуть 50%-ной экономии времени.
На основании полученных результатов можно сделать следующие выводы:
1. Очень важно внимательно исследовать возможности применяемого каркаса пользовательского интерфейса с целью отыскания в нем встроенных механизмов, которые могут быть использованы для повышения производительности
2. Было бы неправильно предполагать, будто такие концептуально простые операции, как, например, очистка элемента управления от данных, будут сами по себе выполняться быстро и поэтому не следует прилагать никаких усилий к их ускорению.
3. Всегда имеет смысл затратить время на количественную оценку быстродействия различных подходов, чтобы выбрать наиболее оптимальный способ решения задач, возлагаемых на пользовательский интерфейс.
Выполняйте тесты с использованием реальных объемов данных, которые будут отображаться в вашем приложенииОбычная ошибка, которую допускают при написании кодов пользовательских интерфейсов. заключается в том, что в процессе проектирования и тестирования интерфейса используются данные меньшего объема, чем тот, с которым приходится сталкиваться при развертывании мобильного приложения. Алгоритм, который прекрасно справляется с извлечением из базы данных 20 элементов данных и их передачей пользовательскому интерфейсу, вовсе не обязательно сделает хорошо то же самое для 200 элементов данных. Если вы тестируете интерфейс, используя лишь небольшие наборы данных, то тем самым оставляете открытыми двери для множества неприятных сюрпризов, ожидающих вас на последующих стадиях разработки или при развертывании приложения, когда настанет черед работать с реальными объемами данных. Гораздо лучше выявлять все проблемы производительности еще на ранней стадии, когда для их преодоления могут быть найдены наиболее конструктивные решения; вносить изменения в проект впоследствии всегда труднее, это может нарушать стабильность работы приложения и почти всегда приводит к менее удовлетворительным результатам.
Еще одной распространенной ошибкой является использование других источников данных в процессе проектирования приложения, например, использование локальной базы данных или текстового файла в качестве временной замены до тех, пока не станут доступными данные на удаленном сервере, к которому при реальной работе будет получать доступ мобильное приложение. Чаще всего подобные проблемы возникают в тех случаях, когда либо реальные базы данных или данные недоступны для приложения на стадии проектирования, либо фактическая природа данных имеет критическое значение, но использовать в целях тестирования актуальные данные проектировщики приложения возможности не имеют. Резкие скачки производительности могут наблюдаться и при переходе от одного источника данных к другому. Как уже отмечалось, лучше всего, если тестирование приложения еще на ранних стадиях его разработки осуществляется в условиях, близких к реальным.
В любом случае очень важно точно представлять себе емкость и местоположение источников данных, которые ваше приложение должно будет отображать в пользовательском интерфейсе, а также механизмы доступа к этим источникам. Определите эти характеристики источников данных уже на ранних стадиях процесса проектирования приложения и выполняйте тестирование приложения с использованием источников данных, удовлетворяющих этим требованиям. Если приложение предназначено для работы с данными различного объема, тестируйте его на максимальном объеме данных. Настоятельно рекомендуется организовать буфер данных и тестировать приложение при объеме данных, который в разумной степени превышает расчетный, чтобы проверить, как это повлияет на производительность. Если данные могут поступать из различных источников, тестируйте производительность с использованием каждого из возможных механизмов доступа к данным. Если для доступа к данным будет использоваться коммутируемая линия или линия с низкой пропускной способностью, тестируйте приложение в таких же условиях. Важно не только то, чтобы приложение в подобных ситуациях работало корректно, но и то, чтобы оно имело привлекательный внешний вид и не теряло своей интерактивности на время длительных периодов обновления пользовательского интерфейса. При соблюдении всех этих рекомендаций весь ваш процесс проектирования будет нацелен на достижение производительности, обеспечивающей прекрасные условия для работы в реальных условиях. Для этого не существует лучшего способа, чем работа с теми же данными и с использованием тех же методов доступа к ним, что и те, с которыми будут сталкиваться в реальной работе конечные пользователи.







