Внедрение Notification Bar

Сложность 100

В прошлый раз я провел общий обзор компонента Notification Bar, рассказал как он работает, возможности, планы по развитию. Немного коснулся того, как модифицировать код, чтобы можно было внедрить Notification Bar (NB), насколько мало надо действий для этого. В этом посте я хочу детально разобрать о том, как внедрить систему уведомлений в приложение, и, я надеюсь, вы согласитесь с тем, что это сделать весьма просто.

Установка

Начнем с установки компонента. Это можно сделать с помощью NuGet пакета следующей командой:

Install-Package VioletTape.NotificationBar

При установке будут подгружены так же все зависимые компоненты и пакеты:

  • PostSharp Free Community Edition
  • PostSharp Threading Toolkit
  • PostSharp Domain Toolkit
  • Rx Framework
  • StructureMap
  • MakeMeAsync

Данный пакет необходимо будет установить для сборки с UI компонентами и для сборки с моделями.

UI настройка

Естественно, что для любой технологии и для любого компонента всегда есть некоторые ограничения и необходимые начальные условия. Так что предлагаю рассмотреть некоторые необходимые начальные условия для Notification Bar. На данный момент такими естественными ограничениями являются технологии:

  • .Net Framework 4.5
  • Windows Presentation Framework

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

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

В этом месте стоит остановится поподробнее и рассказать об общей системе приложений, о системе UI.

Если мы говорим о сколько-нибудь серьезном приложении, для которого собственно и понадобится система уведомлений, то скорее всего схематически оно выглядит так:

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

В тестовом примере у меня ViewManager выглядит следующим образом:

На самом деле достаточно примитивная реализация. Я заранее создаю словари с сопоставлением интерфейсов и представлений, интерфейсов и моделей. В тестовом примере этого достаточно.

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

Далее следует упомянуть о том, как Notification Bar выглядит в xaml записи, и в этой области есть небольшой, но очень важный нюанс. Схематически уведомления будут выглядеть так:

Важный нюанс написан красным ))

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

В противном случае вы получите что-то в духе:

Могу привести пример из тестового приложения реального xaml, который чрезвычайно прост:

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

Инструментализация моделей

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

Сначала стоит так же сказать об ограничениях на модели, которые накладываются в целях использования Notification Bar. Этим обязательным условием является реализация интерфейса IDisposable. Хотя, честно сказать, я рассматриваю это не как ограничение, а как форсирование лучших практик и как напоминание о том, что стоит освобождать ресурсы.

Кроме этого есть фундаментальное условие на то, как будут реализованы сами асинхронные методы. Для большинства систем это не является проблемой пока что, но с использованием async\await уже ничего не сработает, так как необходимо 2 метода, которые требуется инструментализировать. Конечно, можно будет потом постараться инструментализировать и переписывать код в момент разбора и переписи компилятором методов с маркером async.

Теперь можно перейти к самой инструментализации с помощью атрибутов.

Основные атрибуты для работы:

SupportNotificationBar – используется, чтобы указать, что модель должна отслеживаться системой уведомлений. Применяется только на класс. Никаких дополнительных параметров и настроек не имеет. Работает как простой маркер. Обязательным условием является реализация интерфейса IDisposable.

TriggerNotifyAction – используется, чтобы указать на метод начала асинхронной операции, конец которой надо будет отслеживать. В качестве опционального строкового параметра принимает Id, по нему будет происходить совмещение методов начала и конца логической операции. Если в модели только одна операция для отслеживания, то указывать не обязательно. Используется только для методов.

NotifyActionEnds ­– используется, чтобы указать об окончании логической операции. Id операции так же является опциональным параметром. Кроме этого возможно указать иконку для уведомления с помощью параметра Icon. Заголовок для сообщения – Title, и тело сообщения Message. Используется только для методов. Возможны разные способы задания сообщений. Подробности позже.

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

SupportNotificationBar

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

Пример:

 

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

TriggerNotifyAction

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

В сложном случае это выглядит следующим образом:

Если не указывать идентификатор вручную, то будет использован идентификатор Default. Помните об этом во избежание коллизий. Хотя стоит наверно придумать какой-нибудь очень сложный идентификатор, который не будет случайно повторен.

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

NotifyActionEnds

Используется, чтобы указать об окончании логической операции. С этим атрибутом все гораздо интереснее, так как в этом моменте собственно будет формироваться сообщение в Notification Bar и мы можем влиять на составление этого сообщения.

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

  • Icon – изображение, которое будет использовано в сообщении.
  • Title – заголовок сообщения.
  • Message – собственно само сообщение.

Существует несколько способов задания этих сообщений:

  • Оставить значения по умолчанию
  • Задать значения явно
  • Задать значения через переменные

Значения по умолчанию

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

В самой программе результат будет следующим:

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

Явные значения

При задании явных значений используются свойства атрибута. Например:

Визуально будет следующий результат:

Как можно увидеть из кода, значение для иконки, сообщение и заголовок задаются явным образом. Для иконок есть специальный класс NotifyIcon, куда внесены несколько вариантов иконок. Можете поэкспериментировать на досуге. Само значение является строкой, которая указывает путь до ресурса. Например для Refresh используется значение @»Icons\128\refresh_128.png».

Значения через переменные

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

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

Восстановление контекстных переменных

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

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

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

Конечно стоит помнить о том, что это не бесплатно и помечать все переменные так не стоит, так как оно все хранится в памяти, пока вы не перейдете на целевой экран и переменные не освободятся.

Итого

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

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

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

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

 

 

Hard’n’Heavy!

 

3 комментарий на “Внедрение Notification Bar

  1. Отличная идея! Прекрасная реализация!
    Мне тоже очень нравятся уведомления в Android, но в голову как-то не приходило использовать этот подход в своих приложениях.
    Хотелось бы взглянуть на исходный код примера, который ты используешь, чтобы поглубже погрузится в этот процесс.

    Ты не планируешь опубликовать исходники компонента на github?

    • О, спасибо! )) Стараюсь делать так, чтобы все было максимально просто использовать в конечном итоге.
      Исходники доступны на Assembla https://subversion.assembla.com/svn/notificationbar/, о том как это все писалось планирую скоро написать подробный пост. Но исходный код можно посмотреть уже сейчас, он открыт. Использование Git чуть более проблематичное, чем SVN на мой взгляд, поэтому использую его. Так исторически сложилось из-за первой работы, где были не иллюзорные сложности с прокидыванием портов, использованием протоколов и тд. а так как для SVN нужен только http, то и использование его стало закономерным. Хотя потом я все же нашел способ как использовать Git, все настроил успешно, но обучить коллег Git не удалось, что опять же сыграло свою роль.

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