Идея этой статьи состоит в том, чтобы познакомить вас с основными концепциями UWP и опробовать их, создав таймер Pomodoro. Если вы уже знаете, что такое UWP, можете сразу перейти к разделу Создание нашего первого приложения.

Представляем UWP

Универсальная платформа Windows (на данный момент UWP) — это API Microsoft, представленный в ОС Windows 10. Этот API предоставляет общую платформу, доступную для любого совместимого с Windows устройства. Это означает, что вы можете создать пакет приложения и установить его на широкий спектр устройств.

UWP совместим с несколькими языками программирования, включая C, C++, C#, Visual Basic и JS. Он также предоставляет множество способов продолжать взаимодействовать с пользователем, даже когда приложение неактивно.

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

Одно приложение для всех устройств

UWP совместим со всеми устройствами Windows 10. Это потому, что у него есть элементы управления и входы, которые реагируют на размер и DPI с любого экрана. Эта функция позволяет им подстраиваться под каждый размер экрана, помимо компоновки и/или масштабирования.

Также в UWP есть множество расширений SDK с эксклюзивными функциями для некоторых устройств или их семейства. Подробнее о расширениях SDK читайте в официальной документации.

Держите пользователей вовлеченными

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

Распространение вашего приложения

Вы можете распространять свои приложения на самые разные устройства с помощью Магазина Windows. Магазин предоставляет унифицированный носитель распространения и единое управление с помощью Панели инструментов Windows Dev Center. Есть несколько способов получения прибыли с помощью приложений, наиболее популярными являются Платная загрузка, Бесплатные пробные версии, Продажи, Покупки в приложении и Реклама.

Что такое Помидор?

Это техника тайм-менеджмента, созданная Франческо Чирилло. Он заключается в том, чтобы за 25 минут выполнить задание, сосредоточившись только на нем.

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

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

Создание вашего первого приложения

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

Настройка среды

Чтобы следовать этому руководству, вам необходимо установить Visual Studio и включить ваше устройство для разработки.

Для получения дополнительной информации о том, как настроить Visual Studio для разработки UWP, вы можете прочитать руководство по настройке и попытаться создать свое первое приложение.

Добавление таймера

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

DispatcherTimer — это класс .NET Core из System.Windows.Threading.DispatcherTimer для обработки времени. У него есть функция тика, которую можно настроить с помощью обратного вызова, и ее можно инициализировать с помощью классов int или TimeSpan.

В нашем основном классе мы собираемся инициализировать таймер диспетчера и настроить метод тика. Мы инициализируем DispatcherTimer, используя метод initializeTimer. Также мы определили переменные _restsTaken, _currentTime и _tickInterval. _restsTaken подсчитывает количество взятых остатков. _currentTime устанавливает номер текущего интервала в минутах. И the_tickInterval устанавливает значение для каждого тика.

public MainPage()
{
    this.InitializeComponent();
    _takenTicks = 0;
    _currentTime = 0;
    _timeInterval = TimeSpan.FromSeconds(1);
    this.initializeTimer(_timeInterval.Seconds);
    this.initializeDisplayTimer(0);
}

Мы используем класс enum с именем Interval, чтобы объявить количество минут каждого интервала. У нас есть интервал для текущей задачи, один для короткого отдыха и один для длительного отдыха.

private enum Interval
{
    Task = 2,
    ShortRest = 1,
    LongRest = 6
};

Метод InitializeTimer

private void initializeTimer(int tickInterval)
{
    _timer = new DispatcherTimer();
    _timer.Interval = TimeSpan.FromSeconds(tickInterval);
    _timer.Tick += interval_Tick;
}

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

Метод InitDisplayTimer

private void initializeDisplayTimer(int intervalTime)
{
    _intervalRemainingTime = TimeSpan.FromMinutes(intervalTime);
    timerLabel.Text = _intervalRemainingTime.ToString();
}

В этом методе мы отображаем таймер на холсте. TimerLabel покажет только начальное состояние.

Метод Interval_Tick

private void interval_Tick(object sender, object e)
{
    _intervalRemainingTime = _intervalRemainingTime.Subtract(_timeInterval);
    timerLabel.Text = _intervalRemainingTime.ToString();
}

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

Теперь таймер есть в нашем приложении, но он все еще не работает.

Быстрый способ проверить это — запустить таймер в основном методе. Мы можем сделать это timer.start() в конце инициализации. Когда он закончит обратный отсчет, остановите часы вот так.

if(TimeSpan.Equals(DisplayTimer,TimeSpan.Zero))
{
    timer.stop()
}

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

Привязка таймера к UI

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

Сначала мы собираемся нарисовать наш пользовательский интерфейс в XAML.

<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width="auto" Height="146">
    <TextBlock x:Name="timerLabel" HorizontalAlignment="Center" Text="00:00:00" TextWrapping="Wrap" VerticalAlignment="Top"  FontSize="60" FontWeight="Normal"  TextAlignment="Center" Width="328" Margin="0,0,0,0"/>
    <Button Name="startButton" Content="Start" HorizontalAlignment="Left" Click="Button_Click_Start"  VerticalAlignment="Top" Margin="44,101,0,0"/>
    <Button Name="stopButton" Content="Stop" HorizontalAlignment="Left" Click="Button_Click_Stop" VerticalAlignment="Top" Margin="104,101,0,0" RenderTransformOrigin="1.373,0.57"/>
    <Button Name="resetButton" Content="Reset" HorizontalAlignment="Left" Click="Button_Click_Reset" VerticalAlignment="Top" Margin="164,101,0,0" RenderTransformOrigin="1.373,0.57"/>
    <Button Name="restButton" Content="Rest" HorizontalAlignment="Left" Click="Button_Click_Rest" VerticalAlignment="Top" Margin="230,101,0,0" RenderTransformOrigin="1.373,0.57"/>
</Grid>

Теперь это должно выглядеть так

Отображение таймера. В нашем методе interval_tick мы собираемся показать нашу метку с обновленным таймером. Мы добавили строку timerLabel.Text = _intervalRemainingTime.ToString(); под вычитанием минут. Метод должен выглядеть так.

private void interval_Tick(object sender, object e)
{
    TimeSpan previousTime = _intervalRemainingTime;
    _intervalRemainingTime = _intervalRemainingTime.Subtract(_timeInterval);
    timerLabel.Text = _intervalRemainingTime.ToString();
}

Кроме того, нам нужна метка, которая уведомляет нас, когда время истекло. Итак, мы собираемся добавить проверку, которая останавливает таймер и добавляет метку «Время вышло», когда время истекло. Нравится.

if (TimeSpan.Equals(_intervalRemainingTime, TimeSpan.Zero))
{
    _timer.Stop();
    timerLabel.Text = "Time´s up";
    UpdateTile("Time´s up");
}

Итак, окончательный метод должен быть:

private void interval_Tick(object sender, object e)
{
    TimeSpan previousTime = _intervalRemainingTime;
    _intervalRemainingTime = _intervalRemainingTime.Subtract(_timeInterval);
    timerLabel.Text = _intervalRemainingTime.ToString();
    if (TimeSpan.Equals(_intervalRemainingTime, TimeSpan.Zero))
    {
        _timer.Stop();
        timerLabel.Text = "Time´s up";
        UpdateTile("Time´s up");
        
    }
}

Кнопка "Пуск"

Кнопка «Пуск» включает таймер после кнопки «Стоп» или «Отдых». Таймер начинается в 00:00:00. Когда вы нажмете кнопку «Пуск», начнется их обратный отсчет с интервала, установленного ранее.

Мы собираемся сосредоточиться на начале нашего мероприятия. Это только вызовет таймер.

private void Button_Click_Start(object sender, RoutedEventArgs e)
{
    _timer.Start();
}

Кнопка "Стоп"

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

private void Button_Click_Start(object sender, RoutedEventArgs e)
{
    _timer.Start();
}

Кнопка сброса

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

private void Button_Click_Reset(object sender, RoutedEventArgs e)
{
    _timer.Stop();
    this.initializeDisplayTimer(_currentTime);  
}

Кнопка отдыха

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

private void Button_Click_Rest(object sender, RoutedEventArgs e)
{
    _takenTicks++;
    int longRestTime = 0;
    if (_takenTicks == 3)
    {
        longRestTime = (int)Interval.LongRest;
        _takenTicks = 0;
    }
    else
    {
        longRestTime = (int)Interval.ShortRest;
    }
    _isRestTime = true;
    _currentTime = longRestTime;
    _timer.Start();
}

Прикрепление событий к соответствующим кнопкам

Обратите внимание, что в теге XAML есть свойство Click. Просто мы будем ссылаться на наше событие в этом свойстве.

Управление жизненным циклом приложения

С реализацией этого таймера все еще есть проблема. Когда вы сворачиваете приложение, таймер останавливается до тех пор, пока вы его не возобновите. Чтобы избежать этого, мы собираемся показать вам, как обращаться с методом App_Resuming.

Метод App_Resuming обрабатывает событие возобновления работы окна.

private void App_Resuming(Object sender, Object e)
{
    if (_isRunning) 
    {
        if (_intervalEnd <= DateTime.Now)
        {
            _timer.Stop();
            timerLabel.Text = "Time`s up";
        }
        else
        {
            TimeSpan diff = (_intervalEnd - DateTime.Now);
            _intervalRemainingTime = new TimeSpan(0, diff.Minutes, diff.Seconds);
            timerLabel.Text = _intervalRemainingTime.ToString();
        }
    }
}

Переменная _intervalEnd — это время начала обратного отсчета плюс временной интервал. Когда время таймера больше или равно этому _intervalEnd, приложение вычтет из нашего интервала время DateTime.Now времени. Таким образом, это будет новое время, отображаемое в пользовательском интерфейсе. Также обратите внимание, что флаг isRunning всегда проверяет, действительно ли работает таймер, перед обработкой логики события. Это было сделано для того, чтобы логика событий не активировалась, пока таймер остановлен и свернут.

Важно подписаться на это событие при инициализации приложения, иначе оно не будет работать.

Application.Current.Resuming += new EventHandler<Object>(App_Resuming);

Уведомление, когда время истекло

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

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

Настройка времени уведомления

Для этого установим переменную _intervalEnd при запуске таймера на любой интервал, будь то задача или отдых. Чтобы рассчитать точное время, мы создадим DateTime с текущим временем, используя DateTime.Now. Затем мы добавим оставшееся время, полученное из _intervalRemainingTime.

В обработчике события нажатия кнопки «Пуск» мы присвоим значение _intervalEnd, используя логику, которую мы объяснили, сразу после кода для сброса таймера.

private void Button_Click_Start(object sender, RoutedEventArgs e)
{
    if (TimeSpan.Equals(_intervalRemainingTime, TimeSpan.Zero))
    {
        _currentTime = (int)Interval.Task;
        this.initializeDisplayTimer(_currentTime);
    }
    
    _intervalEnd = DateTime.Now.Add(_intervalRemainingTime);
}

В обработчике события нажатия кнопки «Отдых» мы добавим ту же логику после того, как определим, какой отдых будет следующим (длинный или короткий).

private void Button_Click_Rest(object sender, RoutedEventArgs e)
{
    _takenTicks++;
    int longRestTime = 0;
    if (_takenTicks == 3)
    {
        longRestTime = (int)Interval.LongRest;
        _takenTicks = 0;
    }
    else
    {
        longRestTime = (int)Interval.ShortRest;
    }
    _intervalEnd = DateTime.Now.Add(TimeSpan.FromMinutes(_currentTime));
}

Создание уведомления

Метод ScheduleNotification позволяет нам настроить время, в течение которого будет отображаться всплывающее уведомление.

private void ScheduleNotification(string title, string description)
{
    var notificationDictionary = new Dictionary<string, string>()
    {
        { "{TITLE}", title },
        { "{DESCRIPTION}", description }
    };
    var notificationXML = LoadAndPopulateXMLFile("/Resources/Notification.xml", notificationDictionary);
    var toastScheduled = new ScheduledToastNotification(notificationXML, _intervalEnd);
    ToastNotificationManager.CreateToastNotifier().AddToSchedule(toastScheduled);
}

У нас есть шаблон XML для установки стиля уведомлений. В нем мы заменим строки {TITLE} и {DESCRIPTION} текущей информацией для отображения в нашем уведомлении. Затем мы запланируем уведомление на время окончания нашего интервала и добавим его в очередь уведомлений. Этот XML-шаблон обрабатывается с помощью метода LoadAndPopulateXMLFile.

private XmlDocument LoadAndPopulateXMLFile(string fileNameWithExtension, Dictionary<string, string> dictionary)
{
    var xmlDocument = new XmlDocument();
    var xmlFile = File.ReadAllText(Directory.GetCurrentDirectory() + fileNameWithExtension);
    foreach (var item in dictionary)
    {
        xmlFile = xmlFile.Replace(item.Key, item.Value);
    }
    xmlDocument.LoadXml(xmlFile);
    return xmlDocument;
}

Этот метод позволяет пользователю загрузить свой собственный xml-файл и создать шаблон уведомления, задав путь к ресурсу и словарь со всеми строками ключей, которые будут заменены.

Настройка живой плитки

Еще одна замечательная особенность UWP — интеграция с ОС. Чтобы продемонстрировать эту функцию, мы установим живую плитку в меню «Пуск». Это покажет метку с количеством минут, оставшихся до истечения времени. Вам нужно будет заранее создать живую плитку в меню «Пуск», чтобы протестировать эту функцию.

Обновить плитку

Реализация живой плитки аналогична реализации всплывающих уведомлений. Мы устанавливаем текстовый тайл методом UpdateTile. Он использует XML-шаблон для создания плитки, а затем мы запускаем его в методе interval_tick.

private void UpdateTile(string tileSign)
{
    var tileDictionary = new Dictionary<string, string>()
    {
        { "Please Start!", tileSign }
    };
    var tile = new TileNotification(LoadAndPopulateXMLFile("/Resources/Tile.xml", tileDictionary));
    TileUpdateManager.CreateTileUpdaterForApplication().Clear();
    TileUpdateManager.CreateTileUpdaterForApplication().Update(tile);
}

LoadAndPopulateXMLFile заполняет XML и создает уведомление о живой плитке. Важно очистить живую плитку перед ее обновлением.

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

private void interval_Tick(object sender, object e)
{ 
    timerLabel.Text = _intervalRemainingTime.ToString(); if (!TimeSpan.Equals(previousTimeInMinutes.Minutes, _intervalRemainingTime.Minutes))
    {
        string timeIndicator = _intervalRemainingTime.Minutes == 0 ? "1 >" : _intervalRemainingTime.Minutes.ToString();
        UpdateTile(timeIndicator + " minute(s) left");
    } 
}

Подведение итогов

В этом посте вы увидели краткое объяснение того, что такое UWP, что мы можем с ним делать, и создали с нуля приложение-таймер Pomodoro, чтобы показать вам некоторые из наиболее часто используемых функций платформы на практике. Однако некоторые функции, такие как живая плитка и реализация всплывающих уведомлений, можно улучшить, используя утилиты, предоставляемые Windows Community Toolkit, вместо использования простого шаблона XML.

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

Увидимся!

использованная литература

Проект Pomodoro Timer

Официальная страница таймера Pomodoro

Официальный сайт УВП