Главная » Разработка для Windows Phone 7 » Реализация секундомера Windows Phone 7

0

Очень полезным приложением в телефоне является секундомер. Также это идеальный пример использования как ToggleButton, так и класса Stopwatch (Секундомер), который описан в пространстве имен System.Diagnostics.

Я намеренно использовал прописные буквы в написании имени проекта StopWatch (Секундомер), чтобы избежать путаницы с .NET-классом Stopwatch. Я решил сделать приложение более интересным и реализовал отображение истекшего времени в трех разных форматах соответственно членам следующего перечисления:

Проект Silverlight: StopWatch Файл: ElapsedTimeFormat.cs

namespace StopWatch {

public enum ElapsedTimeFormat

HourMinuteSecond,

Seconds,

Milliseconds

}

}

В StopWatch формат отображения истекшего времени является параметром приложения, поэтому он предоставляется как открытое свойство в классе App. Как это обычно бывает с параметрами приложения, формат отображения истекшего времени сохраняется в изолированном хранилище, когда приложение деактивируется или закрывается, и извлекается при запуске или активации приложения:

Проект Silverlight: StopWatch Файл: App.xaml.cs (фрагмент)

public partial class App : Application {

// Параметры приложения

public ElapsedTimeFormat ElapsedTimeFormat { set; get; }

private void Application_Launching(object sender, LaunchingEventArgs e) LoadSettings();

private void Application_Activated(object sender, ActivatedEventArgs e) LoadSettings();

private void Application Deactivated(object sender, DeactivatedEventArgs e) SaveSettings();

private void Application_Closing(object sender, ClosingEventArgs e) SaveSettings();

void LoadSettings()

IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;

if (settings.Contains("elapsedTimeFormat"))

ElapsedTimeFormat = (ElapsedTimeFormat)settings["elapsedTimeFormat"];

else

ElapsedTimeFormat = ElapsedTimeFormat.HourMinuteSecond;

}

void SaveSettings() {

IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;

settings["elapsedTimeFormat"] = ElapsedTimeFormat; settings.Save();

}

}

Описание области содержимого в XAML-файле немного более пространное, чем можно ожидать, потому что включает тип «диалогового окна», используемого пользователем для

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

Проект Silverlight: StopWatch Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

<!– Экран секундомера –> <Grid VerticalAlignment="Center" Margin="25 0"> <Grid.RowDefinitions>

<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions>

<TextBlock Name="elapsedText" Text="0" Grid.Row="0" FontFamily="Arial"

FontSize="{StaticResource PhoneFontSizeExtraLarge}"

TextAlignment="Center"

Margin="0 0 0 50"/>

<ToggleButton Name="startStopToggle" Content="Start" Grid.Row="1"

Checked="OnToggleButtonChecked" Unchecked="OnToggleButtonChecked" />

</Grid>

<!– Прямоугольник для имитации неактивного состояния –> <!– "Диалоговое окно" для выбора формата TimeSpan –> </Grid>

Файл выделенного кода описывает всего три поля и должен обязательно включать директивы using для System.Diagnostics и System.Globaliztion.

Проект Silverlight: StopWatch Файл: MainPage.xaml.cs (фрагмент)

public partial class MainPage : PhoneApplicationPage {

Stopwatch stopwatch = new Stopwatch(); TimeSpan suspensionAdjustment = new TimeSpan();

string decimalSeparator = NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;

public MainPage() {

InitializeComponent(); DisplayTime();

}

void DisplayTime() {

TimeSpan elapsedTime = stopwatch.Elapsed + suspensionAdjustment; string str = null;

switch ((Application.Current as App).ElapsedTimeFormat) {

case ElapsedTimeFormat.HourMinuteSecond:

str = String.Format("{0:D2} {1:D2} {2:D2}{3}{4:D2}",

elapsedTime.Hours, elapsedTime.Minutes, elapsedTime.Seconds, decimalSeparator, elapsedTime.Milliseconds / 10);

break;

case ElapsedTimeFormat.Seconds:

str = String.Format("{0:F2} sec", elapsedTime.TotalSeconds); break;

case ElapsedTimeFormat.Milliseconds:

str = String.Format("{0:F0} msec", elapsedTime.TotalMilliseconds); break;

}

elapsedText.Text = str;

}

}

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

Рассмотрим, как используется поле suspensionAdjustment (Настройка задержки) в связи с захоронением.

.NET-объект Stopwatch предоставляет истекшее время в форме объекта TimeSpan. У меня не получилось «уговорить» объект TimeSpan отображать истекшее время именно в том формате, в котором мне хотелось бы, поэтому пришлось выполнять форматирование самостоятельно. Поле decimalSeparator (Десятичный разделитель) является красивым жестом для локализации.

Метод DisplayTime (Вывести время) задает значение свойства Text объекта TextBlock. Он выполняет доступ к свойству Elapsed (Истекший) объекта Stopwatch и добавляет значение suspensionAdjustment. Полученное значение форматируется одним из трех способов в зависимости от значения свойства ElapsedTimeFormat (Формат истекшего времени) класса App.

При нажатии объект ToggleButton формирует события Checked и Unchecked. Они оба обрабатываются методом OnToggleButtonChecked (При выборе переключателя). Этот метод использует значение свойства IsChecked объекта ToggleButton для запуска или остановки объекта Stopwatch и также для изменения текста, отображаемого кнопкой. Чтобы текст обновлялся оперативно, событие CompositionTarget.Rendering просто вызывает DisplayTime:

Проект Silverlight: StopWatch Файл: MainPage.xaml.cs (фрагмент)

void 0nToggleButtonChecked(object sender, RoutedEventArgs e) {

if ((bool)startStopToggle.IsChecked) {

stopwatch.Start(); startStopToggle.Content = "Stop";

CompositionTarget.Rendering += 0nCompositionTargetRendering;

}

else {

stopwatch.Stop();

startStopToggle.Content = "Start";

CompositionTarget.Rendering -= 0nCompositionTargetRendering;

}

Как видите, приложение также включает ApplicationBar. Его две кнопки обозначены как «format» (форматировать) и «reset» (сброс). Рассмотрим описание ApplicationBar в XAML- файле:

Проект Silverlight: StopWatch Файл: MainPage.xaml (фрагмент)

<phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar>

<shell:ApplicationBarIconButton IconUri="/Images/appbar.feature.settings.rest.png"

Text="format"

Click="OnAppbarFormatClick" />

<shell:ApplicationBarIconButton IconUri="/Images/appbar.refresh.rest.png"

Text="reset"

Click="OnAppbarResetClick" />

</shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>

Более простым из двух методов Click является тот, который обеспечивает сброс секундомера. Сброс .NET-объекта Stopwatch также останавливает отсчет времени, поэтому выполняется явная отмена выбора ToggleButton и suspensionAdjustment устанавливается равным нулю:

Проект Silverlight: StopWatch Файл: MainPage.xaml.cs (фрагмент)

void OnAppbarResetClick(object sender, EventArgs args) {

stopwatch.Reset();

void OnCompositionTargetRendering(object sender, EventArgs args) {

DisplayTime();

}

Посмотрим, что получилось:

startStopToggle.IsChecked = false;

suspensionAdjustment = new TimeSpan(); DisplayTime();

Выбрать формат отображения истекшего времени несколько сложнее. Я решил реализовать эту функциональность не с помощью пунктов меню ApplicationBar, а с помощью небольшого диалогового окна. Этот диалог описан прямо в XAML-файле в той же ячейке Grid, что и главное окно:

Проект Silverlight: StopWatch Файл: MainPage.xaml.cs (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <!– Экран секундомера –>

<!– Прямоугольник для имитации неактивного состояния –> <Rectangle Name="disableRect" Fill="#80000000" Visibility="Collapsed" />

<!– "Диалоговое окно" для выбора формата TimeSpan –> <Border Name="formatDialog"

Background="{StaticResource PhoneChromeBrush}" BorderBrush="{StaticResource PhoneForegroundBrush}" BorderThickness="3" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed"> <Grid>

<Grid.RowDefinitions>

<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions>

<StackPanel Name="radioButtonPanel" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center">

<RadioButton Content="Hour/Minute/Seconds" Tag="HourMinuteSecond" />

<RadioButton Content="Seconds" Tag="Seconds" />

<RadioButton Content="Milliseconds" Tag="Milliseconds" />

</StackPanel>

<Button Grid.Row="1" Grid.Column="0" Content="ok"

Click="0n0kButtonClick" />

<Button Grid.Row="1" Grid.Column="1" Content="cancel" Click="0nCancelButtonClick" />

</Grid> </Border> </Grid>

Обратите внимание, что свойству Visibility обоих объектов, Rectangle и Border, задано значение Collapsed, поэтому в обычном состоянии на экране их не видно. Rectangle покрывает всю область содержимого и используется исключительно для обеспечения визуального представления неактивного состояния. Border структурирован во многом аналогично традиционному диалоговому окну и включает три элемента управления RadioButton и два Button с надписями «ok» и «cancel».

Обратите внимание, что в элементах управления RadioButton не заданы обработчики для событий Checked, но у них есть текстовые строки для задания свойств Tag (Тег). Свойство Tag определено классом FrameworkElement и позволяет прикреплять данные произвольного характера к элементам и элементам управления. И это не просто совпадение, что текстовые строки, заданные мною как значения свойств Tag, точно повторяют члены перечисления ElapsedTimeFormat.

Когда пользователь нажимает кнопку ApplicationBar с надписью «format», вызывается метод OnAppbarFormatClick (По щелчку кнопки форматировать панели приложения) и делает элементы disableRect (Прямоугольник неактивного состояния) и formatDialog (Диалоговое окно форматирования) видимыми:

Проект Silverlight: StopWatch Файл: MainPage.xaml.cs (фрагмент)

void OnAppbarFormatClick(object sender, EventArgs args) {

disableRect.Visibility = Visibility.Visible; formatDialog.Visibility = Visibility.Visible;

// Инициализируем переключатели

ElapsedTimeFormat currentFormat = (Application.Current as App).ElapsedTimeFormat;

foreach (UIElement child in radioButtonPanel.Children) {

RadioButton radio = child as RadioButton; ElapsedTimeFormat radioFormat =

(ElapsedTimeFormat)Enum.Parse(typeof(ElapsedTimeFormat),

radio.Tag as string, true); radio.IsChecked = currentFormat == radioFormat;

}

}

Согласно логике задание свойства IsChecked, определенного RadioButton, выполняется если значение его свойства Tag (после преобразования в член перечисления ElapsedTimeFormat) равно значению ElapsedTimeFormat, хранящемуся как параметр приложения. (Более простая логика была бы возможна, если бы значениями свойства Tag были просто 0,1 и 2 для целочисленных значений членов перечисления.)

Получаем такое диалоговое окно:

С элементами управления RadioButton не ассоциирован ни один обработчик событий. Следующим событием, полученным приложением после вывода диалогового окна на экран, будет сигнал о нажатии пользователем кнопки «ok» или «cancel»:

Проект Silverlight: StopWatch Файл: MainPage.xaml.cs (фрагмент)

void 0n0kButtonClick(object sender, RoutedEventArgs args) {

foreach (UIElement child in radioButtonPanel.Children) {

RadioButton radio = child as RadioButton; if ((bool)radio.IsChecked)

(Application.Current as App).ElapsedTimeFormat =

(ElapsedTimeFormat)Enum.Parse(typeof(ElapsedTimeFormat),

radio.Tag as string, true);

}

0nCancelButtonClick(sender, args);

}

void 0nCancelButtonClick(object sender, RoutedEventArgs args) {

disableRect.Visibility = Visibility.Collapsed; formatDialog.Visibility = Visibility.Collapsed; DisplayTime();

}

Процедура обработки нажатия кнопки «ok» проверяет, какой объект RadioButton выбран и задает параметру приложения соответствующее значение. Также вызывается обработчик нажатия кнопки «cancel», который «удаляет» диалоговое окно, возвращая свойству Visibility объектов disableRect и formatDialog значение Collapsed.

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

В идеале секундомер должен продолжать отсчет времени, даже если пользователь переходит к другому приложению. Конечно, это невозможно, потому что приложение завершает выполнение.

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

Проект Silverlight: StopWatch Файл: MainPage.xaml.cs (фрагмент)

protected override void OnNavigatedFrom(NavigationEventArgs args) {

PhoneApplicationService service = PhoneApplicationService.Current; service.State["stopWatchRunning"] = (bool)startStopToggle.IsChecked; service.State["suspensionAdjustment"] = suspensionAdjustment + stopwatch.Elapsed;

service.State["tombstoneBeginTime"] = DateTime.Now;

base.OnNavigatedFrom(args);

}

protected override void OnNavigatedTo(NavigationEventArgs args) {

PhoneApplicationService service = PhoneApplicationService.Current;

if (service.State.ContainsKey("stopWatchRunning")) {

suspensionAdjustment = (TimeSpan)service.State["suspensionAdjustment"];

if ((bool)service.State["stopWatchRunning"]) {

suspensionAdjustment += DateTime.Now -

(DateTime)service.State["tombstoneBeginTime"];

startStopToggle.IsChecked = true;

}

else {

DisplayTime();

}

}

base.OnNavigatedTo(args);

}

При любом повторном запуске приложения исходное значение истекшего времени всегда равно нулю. Настроить .NET-объект Stopwatch напрямую нельзя. Для этого используется поле suspensionAdjustment, которое представляет время, прошедшее с момента захоронения приложения, плюс значение истекшего времени, зафиксированное Stopwatch на момент захоронения. Пользователь может покидать приложение несколько раз при включенном секундомере, поэтому это поле может содержать данные за несколько периодов захоронения.

Самый простой вариант OnNavigatedTo – возвращение в приложение при выключенном секундомере. В этом случае требуется лишь задать suspensionAdjustment сохраненное значение. Но если секундомер «был включен» все это время, значение suspensionAdjustment должно быть увеличено на отрезок времени, прошедший с момента захоронения, на основании значения, возвращаемого DateTime.Now.

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

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии