Мне нравится создавать приложения для Windows Phone. Мне также нравится разрабатывать примеры кода и писать о платформе Windows Phone (это часть моей ежедневной работы), хотя без ухабов на этом пути не обошлось. На протяжении всей работы я спотыкался и поднимался, проваливался в норы и выбирался из них. Цель моей статьи — сгладить эту полосу препятствий для вас и дать ряд практических советов. Я буду говорить об элементах управления, UDP (User Datagram Protocol) Multicast, Ad Control, изолированном хранилище, инструментарии и устройствах с 256 Мб памяти. Так что откиньтесь на спинку кресла, расслабьтесь и наслаждайтесь.
Указывайте TargetName при использовании HyperlinkButton
HyperlinkButton — это элемент управления «кнопка», отображаемый в виде гиперссылки. Я постоянно пользуюсь им, чтобы упростить пользователям навигацию по моим приложения или по Интернету.
В одном из приложений я использовал HyperlinkButton для вывода краткой справочной информации и хотел, чтобы гиперссылка отправляла пользователей в Интернет за более подробными сведениями. Поэтому я сделал, как показано ниже (детали опущены):
<HyperlinkButton NavigateUri="http://www.msdn.com">
Help</HyperlinkButton>
Элементарно, да? Я запустил свое приложение, перешел на страницу, содержащую кнопку справки, и щелкнул ссылку. И ничего не произошло. Тогда я перешел в режим отладки приложения и снова щелкнул кнопку. На этот раз я получил исключение NavigationFailed, которое сообщает: «Navigation is only supported to relative URIs that are fragments, or begin with '/,' or which contain ';component/.' Parameter name: uri.» («Навигация поддерживается только к относительным URI, которые являются фрагментами или начинаются с '/', или содержат ';component/'. Имя параметра: uri.»).
В недоумении я почесал затылок. Чтение документации пролило немного света на эту проблему. Я не задал свойство TargetName элемента управления HyperlinkButton. Этот атрибут указывает целевое окно для гиперссылки. Пропустив это свойство, я использовал значение по умолчанию, равное «», т. е. пустую строку. Это приводит к тому, что HyperlinkButton пытается загрузить содержимое в страницу, на которой вы щелкнули ссылку. Такой вариант неработоспособен, потому что страница, где это происходит, не является браузером. Вот как мне следовало бы поступить на самом деле:
<HyperlinkButton NavigateUri="http://www.msdn.com"
TargetName="_blank">Help</HyperlinkButton>
Этот код загружает связанный документ в новый экземпляр браузера. Теперь щелчок ссылки работает.
Элемент управления RichTextBox также может показывать гиперссылки, используя встраиваемый элемент HyperLink. С ним будет та же проблема. Поэтому задайте TargetName элемента HyperLink, а не то и вам придется чесать затылок! Подробнее об элементах управления, доступных для Windows Phone, см. по ссылке wpdev.ms/windowsphonecontrols.
Используйте DataContext для ссылки на связанный объект в ListBox
Иногда нужно выполнять какое-то действие, когда пользователь касается какого-то элемента и удерживает палец на нем. Например, пользователь может коснуться и держать палец на каком-либо элементу в списке. Определить, на каком именно элементе пользователь держит палец, может оказаться не так-то просто. Я проиллюстрирую это на примере использования упрощенного ViewModel, как показано на рис. 1.
Рис. 1. Пример ViewModel
public class SimpleViewModel : INotifyPropertyChanged
{
private string _myText = "Initial Value";
public string MyText
{
get
{
return _myText;
}
set
{
if (value != _myText)
{
_myText = value;
NotifyPropertyChanged("MyText");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string PropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(PropertyName));
}
}
}
Мне нужно предоставить набор этих элементов следующим образом:
public ObservableCollection<SimpleViewModel> Items { get; private set; }
В конструкторе страницы я создаю экземпляр этого набора и добавляю некоторые тестовые данные, как показано на рис. 2.
Рис. 2. Создание экземпляра набора и добавление тестовых данных в конструкторе страницы
// Конструктор
public MainPage()
{
InitializeComponent();
// Создаем тестовые данные
Items = new ObservableCollection<SimpleViewModel>();
Items.Add(new SimpleViewModel
{
MyText = "My first item",
});
Items.Add(new SimpleViewModel
{
MyText = "My second item",
});
Items.Add(new SimpleViewModel
{
MyText = "My third item",
});
this.DataContext = Items;
}
На рис. 2 я вывожу данные в списке на странице и добавляю немного графики к каждому из перечисленных элементов. При этом я использую стандартную XAML-привязку к элементу управления ListBox:
<ListBox ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="duck.png" Height="60" Width="60"/>
<TextBlock Text="{Binding MyText}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
В итоге появляется список элементов с симпатичным изображением и значением MyText для каждого элемента, как показано на рис. 3.
Рис. 3. Список элементов с изображением
Далее я хочу выводить контекстное меню, когда пользователь касается элемента в списке и удерживает палец на нем. Это не выбор элемента, а просто касание и удержание. Контекстное меню должно варьироваться в зависимости от текущего контекста, т. е. от нижележащих данных, связанных с конкретным элементом.
Для этого мне пришлось изменить XAML-код ListBox, чтобы добавить event¬Handler для события Hold:
<ListBox ItemsSource="{Binding}" Hold="ListBox_Hold">
Как я узнаю, каким является контекст данных? Весь фокус в обработчике события Hold:
private void ListBox_Hold(object sender, GestureEventArgs e)
{
SimpleViewModel data = ((FrameworkElement)
e.OriginalSource).DataContext as SimpleViewModel;
MessageBox.Show(data.MyText);
}
Что же здесь происходит? Хотя ListBox — элемент управления, обрабатывающий событие Hold, на самом деле оно срабатывает в одном из UI-элементов ListBoxItem, например в Image или TextBlock. Поскольку я не задавал DataContext в каждом ListBoxItem, они наследуют DataContext от своего родительского UI-элемента. Поэтому, приводя OriginalSource к FrameworkElement, мы можем обращаться к DataContext, который является нижележащим объектом данных или SimpleViewModel, как в данном случае.
Использовать многоадресную рассылку при сотовом соединении нельзя
UDP Multicast не поддерживается для сотовых соединений. Что это означает? BeginJoinGroup начинает операцию присоединения (join operation) к группе Multicast. Если вы вызываете BeginJoinGroup, когда смартфон подключен только к сотовой сети, генерируется SocketException. Его можно захватывать, как показано на рис. 4.
Рис. 4. Попытка присоединения к группе UDP Multicast
try
{
// Выдаем запрос на присоединение к группе
_client.BeginJoinGroup(
result =>
{
// Завершаем присоединение
_client.EndJoinGroup(result);
// Send или Receive в группе многоадресной рассылки
}, null);
}
catch (SocketException socketException)
{
if (socketException.SocketErrorCode ==
SocketError.NetworkDown)
{
MessageBox.Show(
"UDP Multicast works only over WiFi/Ethernet");
}
}
Очевидно, что на практике следовало бы обрабатывать это исключение более изящно, а не просто выводить сообщение через MessageBox. Вы улучшите ситуацию использованием метода расширения SetNetworkRequirement. Тогда вы по крайней мере заранее заявите, что знаете, что делаете, тем не менее в этом случае все равно будет получено исключение NetworkDown. Все детали работы с сетями в Windows Phone см. по ссылке wpdev.ms/windowsphonenetworking.
Контролируйте время запуска вашего приложения
Мое первое приложение было платным, поэтому я предусмотрел в нем режим пробного использования, чтобы его потенциальные покупатели могли опробовать его ключевые возможности до приобретения. Я был рад количеству загрузок, да и количество активаций было весьма неплохим. Все, что я хотел, — больших объемов продаж! Ожидая, когда откроются шлюзы, я подумал, а не предложить ли рынку бесплатное приложение, но с заложенной в него рекламой, чтобы понять, можно ли таким образом получить какую-то отдачу от своих вложений в разработку.
Модель получения дохода за счет рекламы — популярный способ среди разработчиков, позволяющий окупать их затраты на разработку. Изначально я сомневался, стоит ли идти таким путем, — главным образом из-за того, что мое первое приложение было ориентировано на детей и я не верил в целесообразность рекламы в таких приложениях. Они и так получают достаточно рекламы, глядя в телевизор. Другой причиной моих колебаний было незнание всего, что связано с рекламой. Я не знал, как рекламировать и как реализовать рекламу в приложении. Теперь я понимаю, что на самом деле у меня не было причин для таких сомнений. Перевести свое приложение на модель получения дохода за счет рекламы, изучить, как определяются контакты с рекламой (impressions), эффективная цена за тысячу показов (effective cost per thousand impressions, eCPM) и рекламные блоки (ad units), совсем нетрудно, и Microsoft Advertising SDK for Windows Phone — отличный пример того, насколько доступным может быть мир рекламы.
Microsoft Ad Control for Windows Phone встроен непосредственно в Windows Phone SDK, и вы можете воспользоваться им безо всяких усилий. Вам нужно лишь создать идентификатор приложения и рекламный блок в Pub Center, поместить Ad Control на свою страницу и заполнить свойства ApplicationId и AdUnitId этого элемента значениями, полученными из Pub Center, — вот и все. Более подробные сведения см. по ссылке wpdev.ms/adsdk. Хотя Advertising SDK поставляется с Windows Phone SDK, обязательно проверяйте обновления Advertising SDK на wpdev.ms/adsdkupdates, потому что они поступают отдельно от других частей Windows Phone SDK.
Однако все эти широкие возможности требуют ответственного подхода. Вы должны убедиться, что, как и в случае всех UI-элементов, загрузка Ad Control не влияет на отзывчивость вашего приложения и особенно на скорость его запуска. Когда кто-то выбирает тайл вашего приложения, вы захотите, чтобы он оказался в нем до того, как передумает его запускать.
Простой способ проверки времени запуска приложения — использование Marketplace Test Kit, встроенного в Visual Studio IDE. Применяя тесты с мониторингом из этого набора, вы запускаете свое приложение на смартфоне, играете с ним и закрываете. После этого инструментарий анализирует показатели, собранные в процессе выполнения вашего приложения и выдает вам отчет о времени запуска, пиковом использовании памяти и др. Провал любого из этих тестов при передаче приложения в Marketplace не позволит ему пройти сертификацию. В Technical Certification Requirements for Windows Phone включено и требование к времени запуска приложения. На момент написания этой статьи требовалось, чтобы первый экран приложения появлялся в пределах пяти секунд после запуска. Чтобы посмотреть самый свежий список требований, зайдите по ссылке wpdev.ms/techcert.
Всегда контролируйте время запуска, чтобы убедиться, что вы не достигли предела в пять секунд. Добавление Ad Control на главную страницу приложения также может повлиять на время запуска. Это относится ко всему, что вы делаете на этой странице, но на примере Ad Control можно хорошо представить себе данную проблему. К счастью, есть способ избежать влияния на время запуска таких элементов управления, как Ad Control, при условии, что вы не против небольшой задержки в первом появлении рекламы на странице. Лично у меня таких возражений не возникло, потому что скорость реакции и удобство в использовании приложения для меня важнее, чем несколько дополнительных показов рекламы в день. Я использовал шаблон, позволяющий отложить загрузку Ad Control. Попутно, замечу, что я также сделал свое приложение более надежным, позаботившись об обработке ошибок, возможных в случае неудачи загрузки рекламы из-за сетевых проблем или по другим причинам.
Вместо размещения Ad Control на странице в XAML я включил в нее элемент управления Grid, в котором будет содержаться Ad Control:
<!-- Ad Control добавляется динамически из отделенного кода -->
<Grid Grid.Row="0" Height="90" x:Name="AdGrid"/>
В отделенном коде страницы я добавляю переменную-член в класс для Ad Control (не забудьте добавить в свой проект ссылку на Microsoft.Advertising.Mobile.UI):
// Ad Control динамически добавляется в AdGrid
private AdControl adControl = null;
В конструкторе страницы я подключаю событие Loaded:
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
В метод MainPage_Loaded я добавляю следующее:
// Загружаем Ad Control в обработчике события Loaded,
// поэтому он не повлияет на время запуска приложения
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
LoadAdControl();
}
Для добавления нового Ad Control в AdGrid, созданного мной в XAML, используется метод LoadAdControl, как показано на рис. 5.
Рис. 5. Добавление Ad Control в Grid программным способом
private void LoadAdControl(){
// Создаем Ad Control, если его еще нет
if (adControl == null ||
!AdGrid.Children.Contains(adControl))
{
adControl = new AdControl(APPID, ADUNITID, true)
{
Width = 480,
Height = 80,
VerticalAlignment = System.Windows.VerticalAlignment.Top
};
// Подключаем интересующие нас события
adControl.AdRefreshed +=
new EventHandler(adControl_AdRefreshed);
adControl.ErrorOccurred +=
new EventHandler<Microsoft.Advertising.AdErrorEventArgs>(
adControl_ErrorOccurred);
// Добавляем в AdGrid
AdGrid.Children.Add(adControl);
}
}
Обратите внимание на то, что APPID и ADUNITID на рис. 5 следует заменить на идентификаторы, которые вы получите после создания рекламного блока (ad unit) в Pub Center.
Я также подключил в этом методе события AdRefreshed и Error¬Occurred. Обработка этих событий — хороший стиль. Дело в том, что при получении рекламы по сети возможны ошибки и важно обрабатывать такие ситуации. В данном случае я просто отображаю кое-какой текст, например название своего приложения. Другие разработчики более предприимчивы и выводят в таких случаях рекламу других своих приложений в Windows Phone Marketplace или используют рекламу от альтернативного провайдера. На рис. 6 показано, как я обрабатываю ошибку Ad Control.
Рис. 6. Обработка ошибки Ad Control
// Если произошла ошибка, отображаем какой-нибудь текст
void adControl_ErrorOccurred(object sender,
Microsoft.Advertising.AdErrorEventArgs e)
{
AdControl ad = (AdControl)sender;
Dispatcher.BeginInvoke(() =>
{
// Скрываем Ad Control
ad.Visibility = System.Windows.Visibility.Collapsed;
// Помещаем что-то взамен. Я предпочел добавить TextBlock,
// содержащий название моего приложения. Вы могли бы
// вместо этого создавать экземпляр Ad Control от другого
// провайдера, показывать статическую рекламу других
// своих приложений или просто ничего не делать.
if (tbBrand == null)
{
tbBrand = new TextBlock()
{
Text = "My Application",
Foreground = (Brush)Resources["PhoneForegroundBrush"],
FontSize = (double)Resources["PhoneFontSizeMedium"],
HorizontalAlignment =
System.Windows.HorizontalAlignment.Center,
VerticalAlignment =
System.Windows.VerticalAlignment.Center,
Margin = new Thickness(10)
};
}
If (!AdGrid.Children.Contains(tbBrand))
{
AdGrid.Children.Add(tbBrand);
}
tbBrand.Visibility = System.Windows.Visibility.Visible;
});
}
Я добавил обработчик AdRefreshed, потому что хотел быть уверенным, что Ad Control становится видимым, как только он восстанавливается после ошибки (рис. 7).
Рис. 7. Добавление обработчика AdRefreshed
// Обеспечиваем видимость Ad Control при обновлении рекламы
void adControl_AdRefreshed(object sender, EventArgs e)
{
AdControl ad = (AdControl)sender;
Dispatcher.BeginInvoke(() =>
{
ad.Visibility = System.Windows.Visibility.Visible;
if (tbBrand != null)
{
tbBrand.Visibility = System.Windows.Visibility.Collapsed;
}
});
}
Это хорошая практика, и Ad Control работает надежнее, но как это влияет на время запуска?
Для демонстрации я создал новый проект, используя File | New Project | Windows Phone Application. Сначала я поместил Ad Control непосредственно в XAML. Затем запустил Marketplace Test Kit, предварительно установив приложение на смартфон. Я многократно запускал свое крошечное приложение и смотрел на время запуска. В среднем оно составляло 2,7 секунды. Это все еще приемлемое время. Далее я применил шаблон отложенной загрузки, о котором упоминал ранее. Я снова воспользовался Marketplace Test Kit и прогнал тесты столько же раз, сколько и раньше. Среднее время запуска составило теперь всего 1,9 секунды. Говорить тут больше не о чем.
Используйте IsolatedStorageSettings для хранения простых параметров
Я предпочитаю для хранения параметров приложения IsolatedStorageSettings. Учитывая его структуру из пар «ключ-значение», это хранилище следует использовать только для простейших параметров. Как правило, я храню в нем информацию о том, включен или отключен звук в моем приложении, количество объектов, отображаемых на странице, время последнего обновления данных и т. д. Простые данные должны помещаться в это хранилище, и им действительно легко пользоваться. Прочитайте в документации MSDN Library, как создать собственную страницу параметров (Settings Page), по ссылке wpdev.ms/settingspage.
Когда дело доходит до хранения больших объемов данных, вам никуда не деться от записи файлов в изолированное хранилище. Все время помня о времени запуска, загружайте данные асинхронно (BackgroundWorker, асинхронные веб-вызовы или что угодно другое, но только асинхронное). Я также применяю шаблон, при котором на диск записываются лишь изменения, и делаю это более-менее по мере их появления. Подробнее о состоянии в Windows Phone читайте по ссылке bit.ly/oR96Ux.
Не используйте FileMode.Append для несуществующего файла
Как уже упоминалось, нужно уделять много внимания оптимизации передач данных между вашей программой и изолированным хранилищем. На первом месте должна стоять отзывчивость вашего приложения — иначе вы обнаружите, что приложение, над которым вы так трудились, почти никто не скачивает. В одной из своих разработок я смог сохранять и загружать данные довольно быстро, но радовался этому ровно до тех пор, пока не оказалось, что извлекаемые данные не всегда соответствуют сохраняемым. Я где-то терял данные! И не сразу обнаружил это по нескольким причинам. Одной из причин было давно забытое выражение Debug.WriteLine, которое однажды поздно ночью я поместил в блок catch. Вот и получилось так, что я думал, будто записываю все в изолированное хранилище, а на самом деле все это время у меня сходил с ума Error List. Я считал, что дописываю данные в один из текстовых файлов, но в Error List появлялось сообщение: «Operation not permitted on IsolatedStorageFileStream» («Операция не разрешена в IsolatedStorageFileStream»).
Я не большой любитель показывать неправильный код в таких статьях, как эта. Я научился этому от своего преподавателя математики в средней школе в те времена, когда носили гетры, «семейные галстуки», а Commodore 64 был пределом мечтаний. Да, он никогда не стал бы показывать неправильный ответ для задачи из страха, что этот компьютер зависнет. (Забавно, что инициалы моего преподавателя были D. O. S. — сомневаюсь, что в то время я мог бы их с чем-то ассоциировать!) Итак, вот что я должен был бы здесь сделать.
Никогда не пытайтесь вызывать FileMode.Append для несуществующего файла. Создайте его, запишите в него что-то, а затем дописывайте при последующих операциях, как показано на рис. 8.
Рис. 8. Запись в файл в изолированном хранилище
if (!_store.FileExists("mydata.data"))
{
// Открываем для записи
using (var textFile = _store.OpenFile("mydata.data",
FileMode.CreateNew))
{
WriteRecordsToFile(textFile, dataString);
}
}
else
{
using (var textFile = _store.OpenFile("mydata.data",
FileMode.Append))
{
WriteRecordsToFile(textFile,dataString);
}
}
private void WriteRecordsToFile(
IsolatedStorageFileStream textFile, string data)
{
using (var writer = new StreamWriter(textFile))
{
writer.WriteLine(data);
}
writer.Flush();
writer.Close()
}
Используйте Isolated Storage Explorer для проверки хранилища в своем приложении
Предлагаемый для Windows Phone инструментарий постоянно расширяется, и я просто потрясен, какими высококачественными инструментами заполняются пробелы в предложении Microsoft. Впрочем, эти пробелы не столь уж велики — примерно такие же, как и в других инструментальных наборах на других мобильных платформах. Нет, это пробелы, до заполнения которых у Microsoft просто не дошли еще руки. Например, Microsoft предлагает Isolated Storage Explorer — утилиту командной строки. Но для этого инструмента вы найдете удобные оболочки, которые сильно облегчают жизнь. Одно из таких средств — Windows Phone 7 Isolated Storage Explorer, который вы найдете на CodePlex (wp7explorer.codeplex.com). Посмотрите там и подберите себе подходящий вариант либо используйте исходную утилиту командной строки. Но так или иначе позаботьтесь о просмотре изолированного хранилища и используйте подходящие средства, например, для записи в изолированное хранилище вашего приложения каких-либо тестовых данных. Это куда проще, чем потом искать ошибки, связанные с этим хранилищем.
Используйте Marketplace Test Kit
Marketplace Test Kit — отличный инструментарий для контроля показателей приложения в процессе работы над ним. Запускайте Marketplace Test Kit прямо из Visual Studio IDE; он предоставляет наборы тестов, которые проверяют корректность значков для приложения, распознают, какие аппаратные средства смартфона реально используются вашим приложением, сообщают время запуска программы и т. д. Есть даже ручные тесты, дающие хорошие представление о том, что именно будет анализироваться в процессе сертификации.
Кроме одной неудачи из-за непонимания того, как группа тестирования в Windows Phone Marketplace проверяет приложения, я больше никогда не проваливал процесс сертификации. Я мог бы заявить, что это я такой умный, но на самом деле мне просто помогал Marketplace Test Kit, и вы тоже должны им пользоваться.
Есть один подвох, которого вам следует остерегаться: тесты с мониторингом проваливаются безо всяких уведомлений при попытке запуска их в эмуляторе. Эта неудача просто приводит к тому, что после экрана, показанного на рис. 9, вы видите экран, как на рис. 10, сразу после щелчка Start Application, если в качестве целевого устройства выбран эмулятор.
Рис. 9. До выполнения тестов с мониторингом
Рис. 10. Неудача при прохождении теста с мониторингом
Мне кажется, что здесь не слишком внятно сообщается о том, что эти тесты можно проводить только на реальном устройстве. На мой взгляд, было бы неплохо выводить четкое уведомление. Подробнее о Marketplace Test Kit см. по ссылке wpdev.ms/toHcRb.
Тестируйте медиа-приложения с применением WPConnect
Когда я впервые решил повозиться с медиа-приложениями для расширения кругозора, я пару часов ломал голову и в конце концов сдался, как только первый же тест сообщил, что просматривать медиа-библиотеку нельзя, пока смартфон подключен к ПК. После этого я махнул рукой и занялся более понятными мне областями разработки для этой платформы, просто подивившись тому, насколько должны быть терпеливы разработчики медиа-приложений. Так дело обстояло до тех пор, пока я не нашел Windows Phone Connect Tool (WPConnect). Да, его освоение потребовало некоторого времени, но оно того стоило. Что в нем такого особенного? Попросту говоря, он позволяет отлаживать медиа-приложения на смартфоне, подключенном к ПК. Инструкции по его использованию очень четкие, поэтому советую проверить данный инструмент по ссылке wpdev.ms/wpconnect.
Проверяйте скорость работы приложения на устройствах с 256 Мб памяти
Смартфоны с 256 Мб памяти существуют. Из-за малого объема памяти на таких устройствах вы должны следить за количеством используемой памяти и решить, что делать, если ваша программа окажется на одном из таких смартфонов. Вы могли бы определять, подпадает ли данное устройство под указанную категорию, и просто отключать все прибамбасы. Вы также могли бы прийти к выводу, что ваша программа не рассчитана на эти устройства, и не рекомендовать ее скачивание для владельцев устройств с 256 Мб памяти, чтобы не рисковать получением рейтинга с одной звездочкой из-за плохой производительности.
С появлением Windows Phone SDK 7.1.1 Microsoft помогает вам принимать обоснованные решения в этой области. Вы можете проверять ApplicationWorkingSetLimit для устройства, чтобы выяснить, относится ли оно к классу устройств с 256 Мб памяти. Вы даже можете выбрать вариант, при котором ваша программа не будет видна в Windows Phone Marketplace для владельцев таких смартфонов. Детали см. по ссылке wpdev.ms/256devices.
Заключение
В этой статье мы с вами прошлись по широкому спектру проблем, с которыми я сталкивался при создании приложений. Я также выделил некоторые инструменты, которые здорово помогли мне в работе. Должно быть очевидно, что отзывчивость и скорость работы — это базисные качества приложения, за которыми нужно следить в процессе его создания. Надеюсь, вы нашли в этой статье что-то полезное для своих разработок под Windows Phone. Это мощная платформа, и мне нравится создавать для нее программы и писать о ней. Мне также нравится ежедневно заглядывать в Windows Phone Marketplace, чтобы узнавать обо всех новинках. Если я еще не опробовал ваше приложение, это лишь дело времени.