Сообщения с таймером

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

Задумка

Я думаю, что если подумать, то все вспомнят о статусной строке в приложении, где часто пишется состояние программы, уведомления о завершении каких-либо фоновых задач, другая интересная информация о работе программы. Еще можно вспомнить о программах, которые показывают информационное сообщение пользователю в каком-либо специальном месте на интерфейсе, а через какое-то время (секунд 5) надпись исчезает. Учитывая последние тенденции к тому, чтобы избавлять пользователя от popup-окон, в которых написано что-то в духе: Данная операция не может быть совершена, так как она в процессе выполнения, — и на всплывшем окне только одна кнопка OK. Раздражает такое поведение неимоверно, так как приводит к лишним действиям! В общем, сегодня я покажу, как можно реализовать набор классов для реализации такого поведения и использовать его в дальнейшем без существенных модификаций.

Неискушенный читатель наверно может воскликнуть: «Что за бредятина, какие еще  наборы классов для того, чтобы сделать два set’a строки? Надо показать сообщение, так присвоил переменной сообщения нужный текст, когда не надо – присвоил пустую строку. Any problem?»

Если кратко, то проблем много! Сейчас попробую перечислить их, как они придут в голову:

  • Много инфраструктурного кода получится, ведь надо будет вводить таймеры, ответы и наверняка что-то еще. И это каждый раз при попытке сменить текст.
  • Надо запоминать предыдущий текст в сообщении, если он был.
  • Легко запутаться что, где и зачем реализовано. Скорее всего, будет много копи-пасты, а следовательно больше мест для ошибок.
  • Некрасиво!

Лично для меня хватило только первого пункта из-за моей профессиональной лени.

Подготовка

Демонстрационный проект будет на базе WPF с использованием Rx.  О да, мне очень понравился Rx и он идеально подходит для нашей цели в силу своей компактности при записи и общем удобстве использования. Честно сказать, я сразу сделал проект на Rx, и хотя была мысль сделать то же самое без использования этого фреймворка, но ужаснувшись дебрям кода который надо будет написать, я решил так не делать. Мне кажется, можно будет и так оценить краткость решения.

Итак, у нас будет настольное приложение на WPF. Для того чтобы не заморачиваться с INotifyPropertyChanged я использую Kind of Magic, так как его использование было описано ранее, то подробно останавливаться на этом я не буду.

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

Визуально, наше приложение будет выглядеть так:

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

Из установленных NuGet пакетов:

  • Rx-Main
  • Rx-WPF (install-package rx-wpf)

Устанавливать можно только последний, так как он автоматом подтянет и поставит основную сборку.

По организации взаимодействия интерфейса и всего остального – используем MVVM.

Основные задачи

Еще раз четко проговорим, какие задачи должны быть решены:

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

Техническое решение

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

Далее, следуя традициям ООП, временное сообщение должно самостоятельно отслеживать время собственной жизни. К тому же у меня есть ощущение, что такая организация ответственности будет проще.

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

Класс сообщений

Остановимся поподробнее на классе сообщения. Он должен знать как минимум:

  • Текст
  • Время жизни в секундах
  • Закончилось ли время жизни

Вообще, один класс можно использовать как для временных, так и для постоянных сообщений. Для постоянных сообщений мы не будем запускать таймер и оно всегда будет «живым», актуальным.

Так же класс должен уметь отсчитывать время и сообщать об истечении этого времени. Для этого используем Timer из пространства System.Timers (другие не очень подойдут)

Сообщение создается один раз и не модифицируется в течение жизни, так что все параметры задаем в конструкторе.

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

На данный момент есть класс, который через заданное количество секунд уведомляет управляющего, о том, что надо показать что-то другое. Отлично!

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

Класс управления сообщениями

Управлять публикацией сообщений будем с помощью Rx. Основными методами класса являются:

  • Выставление потока сообщений
  • Установка новых сообщений

Начнем с выставления потока сообщений. При работе с Rx, важно для себя понять, что это работа с потоком данных, а не со статическим набором данных.

Используем BehaviorSubject, он запоминает только последнее свое значение, чего нам достаточно будет. При использовании, я покажу, как мы будем использовать этот метод для подписки на новые сообщения. Мне кажется, идеологически более верно представить это методом, нежели свойством, в виду «долгосрочности» его работы. Есть такое ощущение.

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

Если нет временных сообщений, то сразу публикуем новое значение в потоке subject. Сохранение последнего постоянного сообщения будет нужно далее.

Важный момент: в качестве метода для отработки по срабатыванию таймера идет проверка наличия следующего сообщения. Сейчас мы этот метод и рассмотрим CheckNextMessage.

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

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

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

Использование

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

Итак, объявление и подписка на появление новых сообщений от управляющего класса. Подписываться будем в конструкторе. Обратите внимание, что подписка на поток событий идет для потока GUI.

И пара методов для облегчения использования:

Как я уже сказал, при работе с Rx, стоит мыслить о его использовании как о непрерывном потоке данных. С помощью одного метода мы помещаем данные в поток, там они каким-либо образом обрабатываются (задерживаются, агрегируются, суммируются и так далее) и помощью другого метода  принимаем результат. Может быть сначала непривычно, но стоит чуть поэкспериментировать и все встает на свои места и становится понятно. Работать с Rx легче чем с событиями (event), компактнее.

И теперь как это использовать, например, при нажатии на кнопки о важных сообщениях на 2 и 6 секунд:

Ну не красота ли?!

В программе попробуйте запустить долгий процесс и вызвать сообщение на 2 секунды. Или вызвать сообщение на 6 секунд, а потом на 2 и посмотреть, как меняются сообщения, как ведут себя классы.

Я думаю, что использование класса StatusMessageManager предельно простое и цена внедрения в проект низка.

Развитие

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

При создании постоянной записи вы получаете GUID записи. Постоянные записи тоже укладываются в стек, и показываются только самые свежие, пока не придет код (GUID) отмены, тогда показывается предыдущее значение. Так до какого-нибудь базового значения в духе «Готово к работе». На мой взгляд, это сильно поможет в плане информативности в программах, которые оперируют большими объемами данных, загружаемыми асинхронно в фоновом режиме. В этом случае возможен следующий сценарий:

  • Готово
  • Загрузка абонентов…
  • Загрузка товаров…
  • Загрузка складов… (заканчивается загрузка товаров быстрее и код отмены приходит, затем заканчивается загрузка складов)
  • Загрузка абонентов…
  • Готово

 

Или еще можно получать GUID и для временных записей, чтобы продлевать их жизнь, если они до сих пор живы.

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

Заключение

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

Source code.

 

Hard’n’heavy!

 

Violet Tape

Оставить комментарий