Сообщение об ошибке для пользователя

Здесь вы не увидите ни строчки кода. Мы поговорим об обычных людях — о наших пользователях, точнее о том, как сообщать им, если в системе возникла какая-то непредвиденная ситуация.

В основе статьи доклад Антонины Хисаметдиновой с Heisenbug 2017 Moscow, которая занимается проектировкой пользовательских интерфейсов в компании Собака Павлова.

Кроме того, на Медиуме есть цикл статей «Руководство по проектированию ошибок». Цикл еще не дописан до конца, но дает более полную и цельную картину по теме статьи.

Ошибочный сценарий

Раз за разом мы проектируем основные сценарии самых разнообразных сервисов. В случае интернет-магазина основной будет таким:

Человек заходит на сайт, выбирает товар, заказывает его доставку; оплачивает и получает заказ.

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

Всё это — ошибочные сценарии, возникающие, когда что-то идет не так.

Продуктовые команды часто не уделяют достаточно внимания таким сценариям. Например, очень типичная история: «Что-то пошло не так. У нас проблемы, поэтому просто закройте это сообщение».

Еще пример: «У нас ошибка. Повторите вашу попытку позже»:

И еще одна категория ошибок — моя любимая: неизвестные ошибки.

Зачем работать над ошибочными сценариями?

Обосновать бизнесу необходимость проработки ошибочных сценариев бывает очень сложно. Зачем нам возвращаться назад и что-то исправлять, когда впереди у нас новые фичи? Но у меня есть четыре железных аргумента, которые помогут продемонстрировать вашему product owner’у или бизнесу необходимость такой работы.

Хорошее сообщение об ошибке снижает нагрузку на техническую поддержку и персонал

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

Обратите внимание, 400 человек в месяц звонят просто из-за того, что не могут войти или корректно ввести логин / пароль в соответствующей форме на сайте.

Хорошее сообщение об ошибке помогает пользователю не потеряться в воронке конверсии

Если сообщение об ошибке составлено грамотно, оно возвращает его к основному сценарию, даже если произошел разрыв сессии.

Хорошее сообщение об ошибке обучает работе с сервисом

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

Хорошее сообщение об ошибке позволяет сохранить доверие к сервису в трудную минуту

Это последний, но немаловажный аргумент.

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

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

Из-за чего возникают ошибки

Я несколько раз упомянула «хорошее сообщение об ошибке». Настала пора поговорить о том, что это значит. И для начала разберемся, из-за чего в принципе возникают ошибки.

  • первое, что приходит в голову, это какие-то глобальные сбои или технические работы на сервисе;
  • специфические баги;
  • ошибки пользователя.

Но это далеко не всё. Еще есть:

  • проблемы на стороне подключенных сервисов;
  • внешние проблемы;
  • крайне необычное поведение пользователей или сервиса.

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

Глобальные сбои

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

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

Хороший вопрос: что в такой ситуации делать?

Пока разработчики спешно чинят какие-то инструменты, несчастные пользователи получают странные сообщения об ошибках и достают вашу техподдержку, пишут неприятные посты в твиттере:

Давайте посмотрим на сообщения, которые в этот момент выводятся:

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

Как им помочь?

Подумайте о последствиях

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

Многие в таких ситуациях ограничиваются сообщением: да, у нас есть проблема и мы скоро ее поправим:

Но «скоро» — это когда?

Пользователю не нужно знать, когда вы всё поправите, с точностью до минуты. Но им нужно понимать какие-то значимые временные ориентиры, например, 15 минут, один час, пять часов или даже сутки. Это поможет им сориентироваться в пространстве и спланировать управление своими деньгами.

Еще один резонный вопрос (в ракурсе финансового сервиса): работают ли карточки?

И хорошее сообщение об ошибке сможет на него ответить. Даже если карточки не работают, лучше всё равно об этом сказать, потому что это очень важная информация.

Еще одна история — тут зарплата или перевод должны быть; а когда придут эти деньги?

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

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

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

Предупредите заранее

Не все пользователи готовы зайти в личный кабинет прямо сейчас, и далеко не все пользователи в принципе зайдут и заметят ошибку. Но если вы предупредите их заранее (например, постом в Twitter, SMS-сообщением или по электронной почте), то когда они столкнутся с сообщением об ошибке, будут готовы.

Отдельно стоит сказать про профессиональные сервисы, от которых ежедневно зависит работа пользователей. Например, сервис Антиплагиат иногда выводит такое сообщение о проведении технических работ:

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

Тема предупреждений об ошибках косвенно связана с сохранением доверия. Может показаться, что очередное предупреждение об ошибке заставит часть пользователей усомниться в надежности сервиса (возможно, они бы в этот момент и не воспользовались сервисом, т.е. в принципе не узнали бы об ошибке). Но восприятие предупреждения как заботы или как лишнего камня в огород сервиса зависит в том числе и от того, как часто вы говорите, что у вас проблемы. Плюс есть совершенно разные сервисы. Интернет-банк — это одно. Но, к примеру, если у вас интернет-магазин, не нужно каждый раз писать пользователю о проблемах, потому что он заходит к вам не так часто.

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

Специфические баги

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

Для нас баги — это знакомая история. Мы четко классифицируем их по разным параметрам: степени опасности, необходимости исправления и т.п.

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

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

  • раздел «Контакты» и обратная связь;
  • онлайн-консультант и звонок в техподдержку;
  • социальные сети и чаты компании;
  • отзывы (App Store и Play Market)!!!
  • блоги и форумы.

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

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

Или могут вообще перестать пользоваться вашим сервисом, как неработающим.
Поэтому в багтрекере ВКонтакте висит такой вот тикет, который называется «отсутствие кнопки «Сообщить о баге»»:

Действительно, это проблема очень многих сервисов.

Создайте специальные окна для сбора обратной связи

Но есть и позитивные примеры, например, Semrush. Почти по всему сервису размещены специальные окна, которые нацелены на то, чтобы забирать фидбэк от человека.

В такой ситуации пользователю стоит меньших усилий написать вам о какой-то ошибке или о фидбеке. Особенно это актуально для бета-тестирования.

Если нельзя исправить баг быстро, предупредите о нем

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

В качестве примера здесь приведен скриншот, когда с помощью совершенно обычных окошек разработчики иконочного шрифта material design предупреждают пользователей, что есть проблема совместимости, и приносят свои извинения:

Обратите внимание, что они приводят ссылку для тех, у кого возникли эти проблемы. По ссылке инструкция, как всё исправить.

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

Ошибки пользователей

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

Первый пример узкого места многих сервисов — это, конечно, вход / регистрация:

Например, поле входа в InVision. Маленькая красная полосочка — это, в принципе, всё сообщение об ошибке. Наверное, когда дизайнер его рисовал, думал, что пользователь без труда прочитает сообщение: «Упс, комбинация email и пароля не верна». Проверит сначала email, затем пароль, и снова нажмет кнопочку войти. Но статистика подсказывает, что пользователь делает несколько попыток входа и ввода пароля, прежде чем догадывается, что проблема в email-адресе.

Это происходит, потому что внимание пользователя в момент входа сосредоточено в одной очень узкой области — она называется фокусом внимания:

Как вы видите, сообщение об ошибке, достаточно высоко и пользователь может его просто не заметить при обновлении страницы. К тому же InVision стирает пароль (надо же помочь пользователю…). И шевеление в области пароля еще больше фокусирует внимание пользователя; он думает, что ошибка именно там.

Фишка 1. Разместите сообщение в фокусе внимания

Те же ребята из InVision в другой части продукта предоставили информацию об ошибке немного по-другому. Во-первых, они подсветили оба поля. Во-вторых, не стирают пароль, потому что он может быть правильный (они предполагают, что пользователь заметит, где именно ошибка, и сам примет решение):

Фишка 2. Показывайте, где именно ошибка

Подсвечивание обоих полей — это и есть вторая фишка.
Но и это не всегда помогает.

Например, дизайнеры компании Adobe считают, что пользователи действительно это всё читают:

Еще один классический пример предлагает Xiaomi:

Или, например, сайт Госуслуги (как и многие другие) просто дублирует название поля заголовка в ошибку:

Фишка 3. Используйте понятные и короткие формулировки

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

Но в окружении интерфейса и текущих задач у пользователей это выглядит вот так:

И они не дочитывают до конца. Когда пользователь читает строку, он фокусируется на начале строки. А чтобы прочитать дальше, ему надо приложить усилия:

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

Фишка 4. Подскажите, как исправить ошибку

Кто сталкивался с кассами самообслуживания?

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

В чем была проблема? Пользователи забывают положить маленькие товары на другую сторону. Поэтому разработчики добавили звуковой сигнал, после чего в 90% таких ситуаций покупатели стали обходиться без помощника. Сигнал заставлял человека поднимать глаза на экран кассы и выходить из состояния, когда он сканирует свою огромную корзину покупок: «Точно, я не положил воду».

У этого сообщения об ошибке есть две из перечисленных «фишек»: оно подсказывает, где именно ошибка, и обучает работе с сервисом.

Фишка 5. Сохраняйте работу пользователя

Последнее, но самое интересное.

Давайте сразу на примере. Это кусочек пути регистрации (в очередной раз напоминаю, что регистрация — достаточно слабое место у очень многих сервисов):

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

Дальше я должна ввести свое имя и фамилию. Ну раз они определили мою страну, то я начинаю вводить автоматически по-русски, и когда я уже нажимаю «Далее», заполнив всю форму, сервис мне говорит: «Пожалуйста, используйте латинские буквы». Автоматическая валидация уже давно всем известна, и ее нужно обязательно применять! Но на этом дело не заканчивается. Мне нужно заполнить адресную информацию, причем, обратите внимание, страна уже подставлена автоматически и написана кириллицей.

Но меня не обманешь — я ввожу адрес латиницей, нажимаю Continue. И, как вы думаете, что происходит?

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

Поэтому не заставляйте пользователя вводить какие-то поля заново, используйте как можно больше автоматизации.

Проблемы подключенного сервиса

Тестируйте API подключенных сервисов

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

Однажды этот мопс-партнер позвонил в стоковую компанию и пожаловался на поломку сервиса.

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

Учите их различать проблемы

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

И в интерфейсе эту проблему не решить.

Поэтому очень важно потратить усилия, чтобы научить ваш сервис различать причины проблем с API.

Предусмотрите в интерфейсе оповещение о проблемах

Очень хороший пример — сервис-автоматизатор ifthisthenthat. С помощью связок API различных сервисов (например, умного дома или социальных сетей) они заставляют сторонние сервисы делать определенные вещи. Например, если я опубликовала пост в Instagram, он автоматически уходит в мой Facebook. Или, если я вышла из дома, сервис определяет по моей геопозиции, что я нахожусь в офисе, и проверяет, выключила ли я все свои смарт-утюги. А если не выключила, то выключает.

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

Во-первых, они выделяют отдельную вкладку для ошибок. Все неудавшиеся операции собираются в этот лог.

Они определяют разные типы ошибок:

В первом случае — сервис Instagram офлайн, и мы понимаем, в чем проблема. Возможно, мы временно вышли из зоны действия сети.

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

Внешние проблемы

Что такое внешние проблемы в моем пользовательском понимании?

Весь software завязан на аппаратуру, на датчики и т.п. Всё это тоже создано людьми и может не работать. Поэтому очень важно сообщать об этом пользователю. Хороший сервис может сообщать о таких ошибках, как о своих.

Дайте понять, какие действия в вашем сервисе недоступны из-за внешних проблем

Хороший пример — отсутствие интернет-соединения в коммуникаторе Slack. Если во время работы у меня отвалился интернет, я вижу вот такое сообщение сверху:

Как мы помним про сообщения об ошибках пользователей, в момент ввода какого-то текста пользователь сконцентрирован в этой области:

Slack об этом не забывает и подсвечивает поле желтеньким.

При этом он не блокирует мне набор сообщения. Я могу продолжить писать его дальше, но при попытке отправить Slack-бот отправляет мне вот такое сообщение:

И в принципе очень доступно объясняет, с чем именно проблема. Такую ошибку я замечу достаточно быстро.

Большая проблема с внешними ошибками, которая пришла к нам еще из «древних» времен, когда продукты создавались инженерами для инженеров, — это содержание текстов об ошибках:

Они написаны таким языком, как будто мы сейчас до сих пор подразумеваем, что пользователь знает, что такое firewall, ftp, dll, ядро, kernel и так далее.

Четко разделите уровни компетенции

Техническому специалисту мы показываем одну информацию, а пользователю — другую.

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

Для многих это действительно большой стресс. Большая часть сообщений об ошибках совершенно не подразумевает, что они должны быть поняты. Человек, который даже не знает английского языка, пытается как-то объяснить: у меня там что-то сломалось. Он испытывает очень сильный дискомфорт. И всё это влияет в целом на его опыт общения с вашим сервисом. Поэтому старайтесь создавать такие сообщения, которые пользователь сможет осознать и своими словами передать техподдержке.

Например, это фото 3D-принтера, который четко и ясно (с помощью маленького экранчика) говорит, что температурный сенсор испортился — произошла ошибка, поэтому он остановился. Свяжитесь с техподдержкой. Пользователь легко может понять, в чем дело, и для него не сложно описать эту проблему своими словами без технических терминов:

Помогите пользователю оценить приоритет проблемы

Что это значит?

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

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

А есть еще такая категория: «У меня там до зарплаты неделька… ничего же не случится?»
Поэтому очень важно дать возможность пользователю оценить опасность этой проблемы. Пользователь в этот момент не хочет лезть в какие-то сложные инструкции. Если действительно произошло что-то страшное, важно указать одно —  серьезность проблемы. Иногда «эксплуатацию продолжать нельзя», а иногда и правда можно подождать до зарплаты.

Крайне необычное поведение пользователей или сервисов.

Бывает ситуация, как на графике. Что вызвало такой резкий скачок? К примеру, это температура в двигателе повысилась? Или это просто датчик какой-то забарахлил?

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

В качестве примера — хорошая длинная история. В сентябре этого года видеоблогер PewDiePie во время стрима на несколько сотен тыс. человек обозвал своего чернокожего противника словом, которое в англоязычном мире называть в принципе не стоит. Он, конечно, потом извинился, но все равно произошел скандал. Производители разных игр, в том числе, Шон Ванаман, подали жалобу в YouTube с просьбой удалить все видеозаписи того, как PewDiePie играл в их игры.

Но за PewDiePie тоже стояла большая армия поддержки. И на игры Шона Ванамана в Steam (сервис, который продает эти игры) посыпались сотни негативных отзывов. Эти отзывы не отражали качество игры, но могли негативно сказаться на ее продажах. И Steam проделал просто потрясающую работу: они обратили внимание пользователя, что произошло, что замечен нетипичный объем отрицательных отзывов с 11 сентября:

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

Дополнительные возможности — скрытый потенциал

Не все ошибки — просто баги. У многих есть скрытый потенциал. Давайте про это немного поговорим.

Обучайте через ошибки

Во-первых, как я уже говорила ранее, через ошибки можно и нужно обучать.
Например, сервис Skyeng — это онлайн школа английского языка, которая работает только через браузер Google Chrome. Этот браузер автоматически (по дефолту) иногда блокирует входящие видеозвонки или аудиозаписи. И в такой ситуации Skyeng вешает кнопочку, которая ведет на вполне подробную инструкцию:

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

Еще один пример — SEMrush. Это окно входа в сервис:

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

Выводите из тупика

Еще один потенциал сообщений об ошибках — это вывод из тупика.

Часто ошибки являются абсолютно тупиковыми сценариями. Пользователю нужно вспоминать контекст и возвращаться по сценарию выше.

Например, возьмем сервис Avito. Там есть вкладка «Сохраненные поиски»:

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

А можно было сделать вот так:

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

Доступность

Есть еще одна важная тема, которую я хотела обсудить, это доступность интерфейсов.

Меня очень радует, что в последнее время об этом стали много говорить, и много в этом направлении стали делать. Например, недавно UsabilityLab проводили тестирование доступности интернет-банков для людей с нарушениями зрения и слуха.

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

Например, многие используют только цветовую индикацию ошибки. Так делать не стоит, потому что есть дальтоники:

Многие дизайнеры скажут: «Я всё проверил в специальном сервисе, который показывает, как видит дальтоник». Но на самом деле эти сервисы никогда не покажут точной картины, потому что все дальтоники видят по-разному. И даже если вы подберете яркость / контрастность, всё равно существует риск, что пользователь-дальтоник эту ошибку не распознает.

Например, поле регистрации во Wrike содержит как раз такую ошибку:

У них реализована чисто цветовая дифференциация — при ошибке красным подсвечивается обводка и текст внутри поля. Лучше всего добавить какое-то текстовое сообщение или символ.

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

Человек просто сломает глаза при попытке прочитать такой текст.

Проводите Accessibility testing для сценариев с ошибками

Бизнес-ценность

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

Что нужно делать? Мой коллега выстроил работу в своем коллективе следующим образом. Все ошибки, которые возникают, сначала собираются в какой-то один большой мешок (log). Оттуда вычленяются только те ошибки, которые повторяются.

Повторяющиеся ошибки уже имеют бизнес-ценность. Это те ошибки, на которые стоит потратить время.

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

Я понимаю, что интерфейс — это не всегда часть вашей работы. И даже далеко не все product owner’ы горят желанием выстраивать работу с ошибками в своей команде, потому что это не всегда выгодно (выгода, если и есть, иногда не видна сразу). Но моя цель — немного расширить ваш образ мышления и задать вопрос: вы делаете только свою работу или вы делаете классный продукт?

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

Резюме

Что я предлагаю вам делать со всей этой информацией?

  1. Когда вы придете на работу, обсудите доклад с командой и владельцем продукта. Особенно полезно зайти к UX’ерам или к дизайнерам.
  2. Проверьте, насколько ваши сообщения об ошибках полезны пользователям.
  3. После этого вы сможете комплексно посмотреть на свой продукт, найти его слабые места, которых раньше, возможно, не замечали, и улучшить ошибочные сценарии.
  4. И еще один очень важный пункт в контексте тестирования — ошибочные сценарии тоже нужно тестировать и часто на равных правах с остальными.

Что почитать?

Здесь есть несколько ссылок:

  1. «Release It!: Design and Deploy Production-Ready Software», Michael T. Nygard
  2. «How to write a great error message», Thomas Fuchs, https://goo.gl/4L8YWo
  3. Architecting Your Software Errors For Better Error Reporting, Nick Harley, https://goo.gl/7em6cQ

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


Если тема тестирования и обработки ошибок вам так же близка, как и нам, наверняка вас заинтересуют вот эти доклады на нашей майской конференции Heisenbug 2018 Piter:

  • Пишем UI тесты для Web, iOS и Android одновременно # python (Игорь Балагуров, Uptick)
  • Web Security Testing Starter Kit (Андрей Леонов, SEMrush)
  • Бета-тестирование ВКонтакте (Анастасия Семенюк, ВКонтакте)

О чем эта статья

Статья продолжает цикл статей «Первые шаги в разработке на 1С».

В ней мы рассмотрим способы информирования пользователя, которые присутствуют в платформе «1С:Предприятие» 8, а также акцентируем ваше внимание на некоторых особенностях работы этих механизмов, эти особенности связаны с режимом использования модальности.

Применимость

В статье рассматривается функциональность:

  • Интерфейса в варианте «Версии 8.2» для конфигурации, разработанной на платформе «1С:Предприятие» 8.2.19.130
  • Интерфейса «Такси» для конфигурации, разработанной на платформе «1С:Предприятие» 8.3.4.496 до 8.3.9+
  • Интерфейса «Такси» для конфигурации, разработанной на платформе «1С:Предприятие» 8.3.10-8.3.11

Как в 1С вывести сообщение пользователю

  1. Ознакомительные сообщения
  2. Механизм оповещений
  3. Терминирующие сообщения
  4. Особенности использования модальных окон в Платформе 8.3
  5. Класс СообщениеПользователю
  6. Уведомление о состоянии процесса

Вывод сообщений в пользовательском режиме решает ряд задач:

  • отражение хода выполнения текущего процесса (показ стадии выполнения процесса; показ расчетных значений, полученных в ходе работы алгоритма);
  • выдача ошибок пользователю для возможного их исправления;
  • выдача рекомендаций;

Типы сообщений:

  • терминирующие, которые останавливают выполнение программы и не дают продолжить ее, пока пользователь не ознакомится с этим сообщением и не выполнит определенные действия. Например, на экран пользователю будет выдан вопрос, на который нужно будет ответить Да или Нет. Пока пользователь не ответит – программа не выполняет дальнейшие действия;
  • ознакомительные сообщения, которые просто выводятся для пользователя и позволяют работать дальше (т.е. используются в режиме оповещения).

Терминирующими сообщениями должны быть сообщения об ошибках, а ознакомительными: рекомендации, сообщения о текущем этапе процесса и показ расчетных значений (отладочная печать).

Ознакомительные сообщения

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

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

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

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

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

Открепить от формы окно с сообщением нельзя.

Синтаксис функции:

Сообщить (<Текст сообщения>, <Статус>)

Т.е. первым параметром является сам текст.

Второй параметр (статус сообщения) является необязательным. Для статуса можно указывать значения: Обычное, Важное, ОченьВажное и т.д.

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

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

Ознакомительные сообщения

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

Пользователь нажимает на кнопку Записать и закрыть, в этом случае сообщение выводится в соответствующее окно (справа формы).

Но форма моментально закрывается, и пользователь не увидит, что для него выводилась какая-то информация.

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

Неправильное использование функции Сообщить

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

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

Функция Сообщить полностью поддерживается в Платформе 8.3. Ее можно использовать, и она будет работать (и в файловом варианте, и в клиент-серверном).

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

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

Так, программный код в Платформе 8.3 может быть исполнен как на стороне Клиента, так и на стороне Сервера.

При этом клиентский программный код отвечает за взаимодействие с пользователем, т.е. на стороне клиента открываются формы, выводятся отчеты.

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

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

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

В этот момент система запросит данные из буфера и выведет их на экран.

Эта же особенность касается и класса СообщениеПользователю. На рисунке приведен пример использования метода Сообщить на стороне Сервера.

НаСервере Сообщить

В результате использования метода Сообщить на стороне Сервера вывелись сообщения на экран на стороне Клиента.

Сообщения на Клиенте

Механизм оповещений

Механизм оповещений нужен, чтобы информировать пользователя о том, что в системе “что-то” произошло и это “что-то” требует внимания пользователя. Оповещения создаются двумя сценариями:

  1. Самой платформой при интерактивной записи или изменении объекта
  2. Разработчиком при вызове в коде метода ПоказатьОповещениеПользователя().

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

Механизм оповещений

Кроме того, к оповещениям можно обратиться в соответствующей области информационной панели (кнопка “История” слева внизу формы приложения в варианте интерфейса «Версии 8.2»).

Чтобы создавать свои собственные оповещения, необходимо использовать метод глобального контекста ПоказатьОповещениеПользователя(). Его синтаксис до редакции 8.3.10 представлен ниже:

ПоказатьОповещениеПользователя (<Текст>, <НавигационнаяССылка>, <Пояснение>, <Картинка>)

В первом параметре передается текст, который будет выводиться в оповещении.

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

С помощью третьего параметра можно передать пояснение для сообщения, т.е. какое-то расширенное описание.

Также можно присвоить картинку, отображающую статус оповещения.

Следует отметить, что все эти параметры являются необязательными для заполнения. Ниже приведен пример использования данного метода (в конфигураторе и в пользовательском режиме в варианте интерфейса «Версии 8.2»).

Механизм оповещений
Механизм оповещений

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

ПоказатьОповещениеПользователя(<Текст>, <ДействиеПриНажатии>, <Пояснение>, <Картинка>, <СтатусОповещенияПользователя>, <КлючУникальности>)

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

Механизм оповещений

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

Следующий параметр СтатусОповещенияПользователя появился впервые. В нем указывается статус оповещения (Информация или Важное).

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

Механизм оповещений

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

Механизм оповещений

В панели инструментов появилась кнопка с пиктограммой звонка, по которой вызывается упомянутый выше Центр оповещений. В нем накапливаются новые важные оповещения, на которые пользователь пока никак не отреагировал.

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

Механизм оповещений

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

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

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

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

Также к новым возможностям относится и одновременное отображение на экране до трех оповещений.

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

Давайте представим такую простую ситуацию: пользователь установил фильтр в каком-то списке для удобства. Допустим, он сделал это в форме списка справочника Номенклатуры. Потом, через какое-то время, решил ввести новый элемент с наименованием “Стул”, который не соответствует установленному ранее фильтру. Вводит его, записывает и…? И не видит его в списке. Что будет делать среднестатистический пользователь? Конечно, введет его второй раз, но опять не увидит. Дальше может последовать третий, четвертый, пятый раз. Когда ему надоест вводить одно и тоже, он, наконец, спросит у вас: а куда все пропадает?

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

Механизм оповещений

Терминирующие сообщения

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

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

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

В вопросе могут определяться разные наборы вариантов ответов (ДаНет, ДаНетОтмена, ОК, ОКОтмена, ПовторитьОтмена, ПрерватьПовторитьПропустить), которые задаются с помощью параметра.

Выведем какое-нибудь предупреждение с помощью строки (например, в модуле управляемого приложения):

Предупреждение(“Сейчас будет открыта база”);

Чтобы открыть модуль управляемого приложения, следует в дереве конфигурации выбрать объект Конфигурация, вызвать контекстное меню и выбрать пункт Открыть модуль управляемого приложения.

Открытие модуля управляемого приложения

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

Модальное окно

Аналогичным образом работает и функция Вопрос.

Синтаксис:
Вопрос(<ТекстВопроса>,<Кнопки>,<Таймаут>,<КнопкаПоУмолчанию>,<Заголовок>,
<КнопкаТаймаута>);

Обязательными являются только первые два параметра. Для второго параметра тип данных составной (РежимДиалогаВопрос или СписокЗначений). Третий параметр (<Таймаут>) характеризует интервал времени в секундах, в течение которого система будет ожидать ответа пользователя.

По истечении интервала окно вопроса будет закрыто. Аналогичный параметр(<Таймаут>) есть и у функции Предупреждение.

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

Модуль управляемого приложения

Использование функции Вопрос

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

Особенности использования модальных окон в Платформе 8.3

В платформе 8.3 существуют режимы работы с использованием и без использования модальности. По умолчанию стоит настройка Не использовать режим модальности.

В этом случае использование терминирующих сообщений невозможно. В случае необходимости использования терминирующих сообщений (функции Предупреждение и Вопрос) следует изменить значение свойства конфигурации Режим использования модальности на Использовать.

Режим использования модальности

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

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

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

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

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

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

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

Синтаксис функции ПоказатьПредупреждение:

ПоказатьПредупреждение(<ОписаниеОповещенияОЗавершении>, <ТекстПредупреждения>, <Таймаут>, <Заголовок>)

Параметр <ОписаниеОповещенияОЗавершении> (необязательный)

Тип данных: ОписаниеОповещения.

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

Синтаксис функции ПоказатьВопрос:

ПоказатьВопрос(<ОписаниеОповещенияОЗавершении>, <ТекстВопроса>, <Кнопки>, <Таймаут>, <КнопкаПоУмолчанию>, <Заголовок>, <КнопкаТаймаута>)

Обязательными являются первые три параметра.

Ниже приведен пример использования функции.

Процедура ОписаниеОповещения

Пример функции оповещения в 1С

Класс СообщениеПользователю

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

Сообщения могут быть привязаны к конкретному экранному элементу. Этот объект доступен и на Сервере.

Следует обратить внимание, что, во-первых, данный объект нужно создавать. Например: Сообщение = Новый СообщениеПользователю;

Таким образом мы создаем экземпляр данного объекта.

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

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

Внимание! Для привязки к нужному полю формы обратите внимание на инициализацию свойств ПутьКДанным и КлючДанных. Применительно для документа при размещении кода в модуле объекта можно писать:

Сообщение.ПутьКДанным = “Объект”;
Сообщение.КлючДанных = ЭтотОбъект.Ссылка;

Чтобы открыть модуль документа, следует в окне редактирования объекта (документа) на закладке Прочее нажать на кнопку Модуль объекта.

Для эксперимента в модуле объекта какого-либо документа разместим код.

Сообщение пользователю

Ниже представлен полученный в пользовательском режиме результат для Платформы 8.3.

Сообщение пользователю в пользовательском режиме 1С

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

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

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

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

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

Уведомление о состоянии процесса

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

Синтаксис: Состояние(<ТекстСообщения>, <Прогресс>, <Пояснение>, <Картинка>)
Параметры: <ТекстСообщения> и <Пояснение> – не обязательные, тип – Строка.
Текст выводится на специальную панель состояния.
<Прогресс> параметр тоже необязательный, но наглядный.
Тип: Число. Значение индикатора прогресса (от 1 до 100).
<Картинка> тоже необязательный параметр.
При обработке какого-либо события могут использоваться периодические вызовы функции типа:

Вызов функции

При этом могут меняться надписи, а могут изменяться значения параметра Прогресс.

Функция может вызываться как из одной процедуры (функции), так и из нескольких. Таким образом можно отслеживать состояние выполнения процесса.

Состояние выполнения процесса

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

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

Хочется еще раз акцентировать ваше внимание на том факте, что если ваша конфигурация (версии 8.3.3+) предполагает работу с помощью веб-клиента, то:

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

Более подробно об отказе от использования модальных окон в платформе 1С:Предприятие 8.3 можно почитать в финальной статье цикла. А мы идем дальше и, наконец, приступаем к изучению долгожданного интерфейса «Такси», который уже не раз упоминался в наших материалах.

PDF-версия статьи для участников группы ВКонтакте

Мы ведем группу ВКонтакте – http://vk.com/kursypo1c.

Если Вы еще не вступили в группу – сделайте это сейчас и в блоке ниже (на этой странице) появятся ссылка на скачивание материалов.

Статья по программированию - в PDF-формате

Полезные ссылки:

Ссылка доступна для зарегистрированных пользователей)
Ссылка доступна для зарегистрированных пользователей)
Ссылка доступна для зарегистрированных пользователей)

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


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

Предыстория.

В свое время, работая с системой контроля версий Perforce, а именно с ее графическими клиентами, я обратил внимание на удобный элемент интерфейса – многострочное поле вывода сообщений внизу главного окна программы. После того, как пользователь выполнял какие-то обращения к серверу системы, в это поле добавлялись текстовые сообщения, описывающие процесс взаимодействия клиента с сервером (какие конкретно действия выполнялись, и результат их выполнения). Не знаю, почему именно Perforce произвел на меня такое впечатление, хотя абсолютно ту же систему я наблюдал до и после в различных графических IDE (Delphi, C++ и т.д.) – окно вывода сообщений той консольной программы (компилятор или клиент системы контроля версий), в качестве оболочки которой выступает графическая программа. Но впрочем, статья вовсе не про Perforce, просто я считаю удобным способ многострочного вывода сообщений в процессе выполнения какой-то длительной работы. Получить большой список сообщений гораздо лучше (информативнее), чем одиночный MessageBox в самом конце этой работы. Подобный подход мы, кстати, видим и в антивирусных программах.

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

В рассматриваемом примере таблица вывода сообщения (элемент интерфейса ТабличноеПоле) выглядит примерно так:

Описание реализации.

В общем, опишу здесь то, что получилось. Для реализации буду опять использовать свою технологию «ООП в 1С» (метод подробно описан здесь). Соответственно будет создан «класс»: «СообщенияПрограммы».
В дальнейшем при объяснениях будем считать, что делаем внешнюю обработку.

Нам понадобятся иконки для отображения статусов сообщений. Загрузим из соответствующих BMP-файлов (прилагаются к данной статье) в ресурсы (макеты типа «Двоичные данные»)

Изображение Название исходного файла Название макета
statok.bmp КартинкаСообщениеСтатусОК
statwarning.bmp КартинкаСообщениеСтатусПредупреждение
staterror.bmp КартинкаСообщениеСтатусОшибка

Затем нужно будет как-то эти картинки загрузить для использования – превратим их в ресурсы.
Определим в главном модуле глобальную переменную «КоллекцияКартинок» в виде структуры из объектов стандартного класса «Картинка». Напишем подпрограмму «СформироватьКоллекциюКартинок», которая будет инициализировать коллецию. Будем вызывать эту подпрограмму в инициализирующей секции модуля.

Также наши сообщения будут иметь некоторые числовые типы – уровни. Номер уровня будет влиять на количество отступов из пробельных символов слева при выводе сообщения в интерфейс или файл (как бы наглядно показываем уровень вложенности сообщений друг в друга).

Название Описание Числовое значение
ТИПСООБЩ_НЕОПРЕД Неопределенное значение 0
ТИПСООБЩ_УРОВЕНЬ1 Уровень вложенности 1 1
ТИПСООБЩ_УРОВЕНЬ2 Уровень вложенности 2 2
ТИПСООБЩ_УРОВЕНЬ3 Уровень вложенности 3 3

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

Название Описание Числовое значение
СТАТСООБЩ_НЕОПРЕД Неопределенное значение 0
СТАТСООБЩ_ОК Уровень вложенности 1 1
СТАТСООБЩ_ПРЕДУПР Уровень вложенности 2 2
СТАТСООБЩ_ОШИБКА Уровень вложенности 3 3

Представим типы и статусы в виде числовых констант, как это делается в С/С++ (#define) или Pascal (const). В 1С будем имитировать константы вставкой их в глобальную переменную-структуру (ключ элемента структуры – это символьное имя константы, а значение элемента структуры – это численное значение константы).

Функция ИнициализацияПрочихКонстант()
	
	Проч = Новый Структура;
	
	// Типы сообщений
	Проч.Вставить("ТИПСООБЩ_НЕОПРЕД", 0);   // Тип неопределен
	Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ1", 1);  // Сообщение уровня 1
	Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ2", 2);  // Сообщение уровня 2
	Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ3", 3);  // Сообщение уровня 3

	// Статусы сообщений
	Проч.Вставить("СТАТСООБЩ_НЕОПРЕД", 0);  // Статус неопределен
	Проч.Вставить("СТАТСООБЩ_ОК", 1);       // Статус "ОК"
	Проч.Вставить("СТАТСООБЩ_ПРЕДУПР", 2);  // Статус "Предупреждение"
	Проч.Вставить("СТАТСООБЩ_ОШИБКА", 3);   // Статус "Ошибка"
	
	Возврат Проч
	
КонецФункции

Еще нам нужно будет получать текущее значение времени в миллисекундах. Используем для этого машину JavaScript в виде глобальной переменной COM-объекта.

Вот что получилось в главном модуле:

Исходный текст

////////////////////////////////////////////////////////////////////////////////
// ПРОЧИЕ КОНСТАНТЫ

Перем Проч Экспорт;                       // Структура с константами

Перем КоллекцияКартинок Экспорт;          // Набор иконок интерфейса


////////////////////////////////////////////////////////////////////////////////
// ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ

Перем JavaScript;                         // Машина для работы с JavaScript


///////////////////////////////////////////////////////////////////////////////////////////////////
//
// П О Д П Р О Г Р А М М Ы    И Н И Ц И А Л И З А Ц И И    К О Н С Т А Н Т
//
///////////////////////////////////////////////////////////////////////////////////////////////////

// Инициализирует набор констант общего назначения
//
// Параметры:
//  НЕТ.
//
// Возврат:
//  Структура с полями - константами общего назначения
//
Функция ИнициализацияПрочихКонстант()
	
	Проч = Новый Структура;
	
	// Типы сообщений
	Проч.Вставить("ТИПСООБЩ_НЕОПРЕД", 0);   // Тип неопределен
	Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ1", 1);  // Сообщение уровня 1
	Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ2", 2);  // Сообщение уровня 2
	Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ3", 3);  // Сообщение уровня 3

	// Статусы сообщений
	Проч.Вставить("СТАТСООБЩ_НЕОПРЕД", 0);  // Статус неопределен
	Проч.Вставить("СТАТСООБЩ_ОК", 1);       // Статус "ОК"
	Проч.Вставить("СТАТСООБЩ_ПРЕДУПР", 2);  // Статус "Предупреждение"
	Проч.Вставить("СТАТСООБЩ_ОШИБКА", 3);   // Статус "Ошибка"
	
	Возврат Проч
	
КонецФункции


// Формирует набор картинок, на основании изображений, сохраненных в двоичных
// макетах обработки.
//
// Параметры:
//  НЕТ.
//
// Возврат:
//  Структура с полями - объектами типа Картинка с загруженными картинками
//
Функция СформироватьКоллекциюКартинок()
	
	КоллекцияКартинок = Новый Структура;
	
	КоллекцияКартинок.Вставить("СообщениеСтатусОК", Новый Картинка(ПолучитьМакет("КартинкаСообщениеСтатусОК")));
	КоллекцияКартинок.Вставить("СообщениеСтатусПредупреждение", Новый Картинка(ПолучитьМакет("КартинкаСообщениеСтатусПредупреждение")));
	КоллекцияКартинок.Вставить("СообщениеСтатусОшибка", Новый Картинка(ПолучитьМакет("КартинкаСообщениеСтатусОшибка")));
	
	Возврат КоллекцияКартинок
	
КонецФункции


///////////////////////////////////////////////////////////////////////////////////////////////////
//
// П О Д П Р О Г Р А М М Ы    И Н И Ц И А Л И З А Ц И И    Г Л О Б А Л Ь Н Ы Х    П Е Р Е М Е Н Н Ы Х
//
///////////////////////////////////////////////////////////////////////////////////////////////////

// Создает объект JavaScript и возвращает его в качестве значения
// Формирует набор картинок, на основании изображений, сохраненных в двоичных
// макетах обработки.
//
// Параметры:
//  НЕТ.
//
// Возврат:
//  Ссылка на машину JavaScript или Неопределено, если машина не создана
//
Функция СоздатьОбъектJavaScript()
	
	Рез = Неопределено;
	Попытка
		Рез = Новый COMОбъект("MSScriptControl.ScriptControl");
	    Рез.Language = "javascript";	
	Исключение
	КонецПопытки;

    Возврат Рез

КонецФункции


///////////////////////////////////////////////////////////////////////////////////////////////////
//
// П О Д П Р О Г Р А М М Ы    О Б Щ Е Г О    Н А З Н А Ч Е Н И Я
//
///////////////////////////////////////////////////////////////////////////////////////////////////

// Расширяет строку до заданной ширины пробелами, добавляя их слева
//
// Параметры:
//  Стр - исходная строка
//  Ширина - кол-во символов до которого нужно расширить строку
// Возврат:
//  Полученная строка
//
Функция РасширСтрЛев(Стр, Ширина)
	
	Рез = Стр;
	Инд = Ширина - СтрДлина(Рез);
	Пока Инд > 0 Цикл
		Рез = " " + Рез;
		Инд = Инд - 1
	КонецЦикла;
	
	Возврат Рез
	
КонецФункции

// Расширяет строку до заданной ширины пробелами, добавляя их справа
//
// Параметры:
//  Стр - исходная строка
//  Ширина - кол-во символов до которого нужно расширить строку
// Возврат:
//  Полученная строка
//
Функция РасширСтрПрав(Стр, Ширина)
	
	Рез = Стр;
	Инд = Ширина - СтрДлина(Рез);
	Пока Инд > 0 Цикл
		Рез = Рез + " ";
		Инд = Инд - 1
	КонецЦикла;
	
	Возврат Рез
	
КонецФункции


// Возвращает текущую дату и время в миллисекундах
//
// Параметры:
//  НЕТ
// Возврат:
//  Числовое значение или 0, если значение определить не удалось
//
Функция ПолучитьТекущееВремяВМиллисекундах() Экспорт
	
	Время = 0;
	Если JavaScript <> Неопределено Тогда
		Время = JavaScript.Eval("new Date().getTime()");
	КонецЕсли;
	
	Возврат Время;
	
КонецФункции




////////////////////////////////////////////////////////////////////////////////
// ОПЕРАТОРЫ ОСНОВНОЙ ПРОГРАММЫ - НАЧАЛЬНАЯ ИНИЦИАЛИЗАЦИЯ



// ИНИЦИАЛИЗАЦИЯ КОНСТАНТ

// Константы общего назначения
Проч = ИнициализацияПрочихКонстант();

// Коллекция картинок
КоллекцияКартинок = СформироватьКоллекциюКартинок();



// ИНИЦИАЛИЗАЦИЯ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ

// Создать машину JavaScript
JavaScript = СоздатьОбъектJavaScript();

Набор сообщений существует в виде таблицы значений (стандартный класс ТаблицаЗначений) со следующими колонками:

Название колонки Тип данных Назначение
ТипСообщ Число Тип сообщения (уровень) – одна из констант ТИПСООБЩ_
СтатусСообщ Число Статус сообщения – одна из констант СТАТСООБЩ_
ТекстСообщ Строка Основной текст сообщения. Здесь обычно указываем, какое действие выполняется длительным процессом в данный момент.
КомментарийСообщ Строка Дополнительный текст сообщения. Здесь обычно указываем результат выполнения действия, описанного в колонке ТекстСообщ. Если действие завершилось ошибкой, то сообщение об ошибке приводим в этой же колонке

Эта таблица значений уже может отображаться в каких-то элементах интерфейса пользователя (например в объекте класса TaбличноеПоле).

Набор сообщений будет реализован в виде класса, имеющего следующие методы:

Заголовок метода Описание
Функция Сообщения_Конструктор() Начальное создание объекта. Возвращает объект класса «Сообщения».
Процедура Сообщения_УстАтриб(Сообщ, Форма, ТабПоле, ТабЗнач) Установка атрибутов объекта. Нужно передать:
— ссылку на форму (Форма), на которой будет отображаться список сообщений;
— элемент формы типа ТабличноеПоле (ТабПоле), в который будут выводится сообщения;
— ТаблицаЗначений (ТабЗнач), в которой будет храниться собственно список сообщений.
Процедура Сообщения_Инициализация(Сообщ) Будет проинициализирована ТаблицаЗначений (ранее установленная процедурой Сообщения_УстАтриб)
Процедура Сообщения_Обновить(Сообщ) Выполняет принудительную отрисовку списка сообщений на форме (ранее установленная процедурой Сообщения_УстАтриб)
Функция Сообщения_Добавить(Сообщ, ТипСообщ, ТекстСообщ, СтатусСообщ=Неопределено, КомментСообщ=Неопределено) Добавляет в набор новое сообщение с соответствующими атрибутами. Возвращает идентификатор сообщения с помощью которого, к данному сообщению можно будет обращаться в дальнейшем.
Процедура Сообщения_Изменить(Сообщ, ИдСообщ, ТипСообщ=Неопределено, ТекстСообщ=Неопределено, СтатусСообщ=Неопределено, КомментСообщ=Неопределено) Изменяет атрибуты уже существующего сообщения с указанным идентификатором.
Процедура Сообщения_Удалить(Сообщ, ИдСообщ) Удалить ранее добавленное сообщение с соответствующим идентификатором.
Процедура Сообщения_Очистить(Сообщ) Удалить все сообщения набора.
Функция Сообщения_ПолучитьТип(Сообщ, ИдСообщ) Получить атрибут «Тип» для существующего сообщения с указанным идентификатором.
Процедура Сообщения_УстановитьТип(Сообщ, ИдСообщ, ТипСообщ) Установить атрибут «Тип» для существующего сообщения с указанным идентификатором.
Функция Сообщения_ПолучитьСтатус(Сообщ, ИдСообщ) Получить атрибут «Статус» для существующего сообщения с указанным идентификатором.
Процедура Сообщения_УстановитьСтатус(Сообщ, ИдСообщ, СтатусСообщ) Установить атрибут «Статус» для существующего сообщения с указанным идентификатором.
Функция Сообщения_ПолучитьТекст(Сообщ, ИдСообщ) Получить атрибут «Текст сообщения» для существующего сообщения с указанным идентификатором.
Процедура Сообщения_УстановитьТекст(Сообщ, ИдСообщ, ТекстСообщ) Установить атрибут «Текст сообщения» для существующего сообщения с указанным идентификатором.
Функция Сообщения_ПолучитьКоммент(Сообщ, ИдСообщ) Получить атрибут «Комментарий» для существующего сообщения с указанным идентификатором.
Процедура Сообщения_УстановитьКоммент(Сообщ, ИдСообщ, КомментСообщ) Установить атрибут «Комментарий» для существующего сообщения с указанным идентификатором.
Функция Сообщения_ЦветТекста(ТипСообщ, СтатусСообщ) Определить цвет текста сообщения для указанного статуса. Используется при раскраске сообщений в интерфейсе.
Функция Сообщения_ЦветКомментария(ТипСообщ, СтатусСообщ) Определить цвет дополнительного текста (комментария) сообщения для указанного статуса. Используется при раскраске сообщений в интерфейсе.
Функция Сообщения_Картинка(ТипСообщ, СтатусСообщ) Определить иконку для сообщения с указанным статусом. Используется при выводе сообщений в интерфейсе.
Функция Сообщения_СохранитьВФайл(Сообщ) Сохранить набор сообщений в текстовый файл (имя файла запрашивает у пользователя).

Итак, полный текст главного модуля обработки:

Текст модуля

//////////////////////////////////////////////////////////////////////////////// // ПРОЧИЕ КОНСТАНТЫ Перем Проч Экспорт; // Структура с константами Перем КоллекцияКартинок Экспорт; // Набор иконок интерфейса //////////////////////////////////////////////////////////////////////////////// // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ Перем JavaScript; // Машина для работы с JavaScript /////////////////////////////////////////////////////////////////////////////////////////////////// // // П О Д П Р О Г Р А М М Ы И Н И Ц И А Л И З А Ц И И К О Н С Т А Н Т // /////////////////////////////////////////////////////////////////////////////////////////////////// // Инициализирует набор констант общего назначения // // Параметры: // НЕТ. // // Возврат: // Структура с полями - константами общего назначения // Функция ИнициализацияПрочихКонстант() Проч = Новый Структура; // Типы сообщений Проч.Вставить("ТИПСООБЩ_НЕОПРЕД", 0); // Тип неопределен Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ1", 1); // Сообщение уровня 1 Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ2", 2); // Сообщение уровня 2 Проч.Вставить("ТИПСООБЩ_УРОВЕНЬ3", 3); // Сообщение уровня 3 // Статусы сообщений Проч.Вставить("СТАТСООБЩ_НЕОПРЕД", 0); // Статус неопределен Проч.Вставить("СТАТСООБЩ_ОК", 1); // Статус "ОК" Проч.Вставить("СТАТСООБЩ_ПРЕДУПР", 2); // Статус "Предупреждение" Проч.Вставить("СТАТСООБЩ_ОШИБКА", 3); // Статус "Ошибка" Возврат Проч КонецФункции // Формирует набор картинок, на основании изображений, сохраненных в двоичных // макетах обработки. // // Параметры: // НЕТ. // // Возврат: // Структура с полями - объектами типа Картинка с загруженными картинками // Функция СформироватьКоллекциюКартинок() КоллекцияКартинок = Новый Структура; КоллекцияКартинок.Вставить("СообщениеСтатусОК", Новый Картинка(ПолучитьМакет("КартинкаСообщениеСтатусОК"))); КоллекцияКартинок.Вставить("СообщениеСтатусПредупреждение", Новый Картинка(ПолучитьМакет("КартинкаСообщениеСтатусПредупреждение"))); КоллекцияКартинок.Вставить("СообщениеСтатусОшибка", Новый Картинка(ПолучитьМакет("КартинкаСообщениеСтатусОшибка"))); Возврат КоллекцияКартинок КонецФункции /////////////////////////////////////////////////////////////////////////////////////////////////// // // П О Д П Р О Г Р А М М Ы И Н И Ц И А Л И З А Ц И И Г Л О Б А Л Ь Н Ы Х П Е Р Е М Е Н Н Ы Х // /////////////////////////////////////////////////////////////////////////////////////////////////// // Создает объект JavaScript и возвращает его в качестве значения // Формирует набор картинок, на основании изображений, сохраненных в двоичных // макетах обработки. // // Параметры: // НЕТ. // // Возврат: // Ссылка на машину JavaScript или Неопределено, если машина не создана // Функция СоздатьОбъектJavaScript() Рез = Неопределено; Попытка Рез = Новый COMОбъект("MSScriptControl.ScriptControl"); Рез.Language = "javascript"; Исключение КонецПопытки; Возврат Рез КонецФункции /////////////////////////////////////////////////////////////////////////////////////////////////// // // П О Д П Р О Г Р А М М Ы О Б Щ Е Г О Н А З Н А Ч Е Н И Я // /////////////////////////////////////////////////////////////////////////////////////////////////// // Расширяет строку до заданной ширины пробелами, добавляя их слева // // Параметры: // Стр - исходная строка // Ширина - кол-во символов до которого нужно расширить строку // Возврат: // Полученная строка // Функция РасширСтрЛев(Стр, Ширина) Рез = Стр; Инд = Ширина - СтрДлина(Рез); Пока Инд > 0 Цикл Рез = " " + Рез; Инд = Инд - 1 КонецЦикла; Возврат Рез КонецФункции // Расширяет строку до заданной ширины пробелами, добавляя их справа // // Параметры: // Стр - исходная строка // Ширина - кол-во символов до которого нужно расширить строку // Возврат: // Полученная строка // Функция РасширСтрПрав(Стр, Ширина) Рез = Стр; Инд = Ширина - СтрДлина(Рез); Пока Инд > 0 Цикл Рез = Рез + " "; Инд = Инд - 1 КонецЦикла; Возврат Рез КонецФункции // Возвращает текущую дату и время в миллисекундах // // Параметры: // НЕТ // Возврат: // Числовое значение или 0, если значение определить не удалось // Функция ПолучитьТекущееВремяВМиллисекундах() Экспорт Время = 0; Если JavaScript <> Неопределено Тогда Время = JavaScript.Eval("new Date().getTime()"); КонецЕсли; Возврат Время; КонецФункции // Преобразует массив чисел из объекта COMSafeArray в строку символов // // Параметры: // ОбъектCOMSafeArray - ссылка на массив чисел типа COMSafeArray // (размерность массива должна быть 1) // Возврат: // Полученная строка или "", если операцию выполнить не удалось // Функция COMSafeArrayВСтроку(ОбъектCOMSafeArray) Экспорт Рез = ""; Если ТипЗнч(ОбъектCOMSafeArray) = Тип("COMSafeArray") И (ОбъектCOMSafeArray.GetDimensions() = 1) И (ОбъектCOMSafeArray.GetLength(0) > 0) Тогда Массив = ОбъектCOMSafeArray.Выгрузить(); Для Каждого КодСимв Из Массив Цикл Если ТипЗнч(КодСимв) = Тип("Число") Тогда // Перекодировка символа из Windows-1251 в UTF-8 Если (КодСимв >= 192) И (КодСимв <= 223) Тогда // А - Я КодСимв = КодСимв + 848 ИначеЕсли (КодСимв >= 224) И (КодСимв <= 239) Тогда // а - п КодСимв = КодСимв + 848 ИначеЕсли (КодСимв >= 240) И (КодСимв <= 255) Тогда // р - я КодСимв = КодСимв + 848 ИначеЕсли (КодСимв = 184) Тогда // ё КодСимв = 1105 ИначеЕсли (КодСимв = 168) Тогда // Ё КодСимв = 1025 ИначеЕсли (КодСимв = 185) Тогда // № КодСимв = 8470 КонецЕсли; // Добавить символ к строке Рез = Рез + Символ(КодСимв) КонецЕсли КонецЦикла КонецЕсли; Возврат Рез КонецФункции /////////////////////////////////////////////////////////////////////////////////////////////////// // // К Л А С С Ы О Б Щ Е Г О Н А З Н А Ч Е Н И Я // /////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Реализация класса СообщенияПрограммы (Сообщения) // Создание полей объекта в виде структуры. // // Параметры: // Нет. // Возврат: // Возвращает созданную структуру // Функция Сообщения_СоздатьОбъект() Экспорт Сообщ = Новый Структура; // Общие параметры сущности Сообщ.Вставить("Форма", Неопределено); // Форма с табличным полем Сообщ.Вставить("ТабПоле", Неопределено); // Табличное поле на форме Сообщ.Вставить("ТабЗнач", Неопределено); // Таблица значений с сообщениями Сообщ.Вставить("ВремяПредОбновл", Неопределено); // Время последн. обновл. формы, мс (INT) Возврат Сообщ; КонецФункции // Имитирует конструктор объекта. // // Параметры: // НЕТ // Возврат: // (Структура) - Структура с полями объекта // Функция Сообщения_Конструктор() Экспорт Сообщ = Сообщения_СоздатьОбъект(); Возврат Сообщ; КонецФункции // Имитирует деструктор объекта - освобождает память. // // Параметры: // Сообщ - ссылка на объект // Процедура Сообщения_Деструктор(Сообщ) Экспорт КонецПроцедуры // Устанавливает основные атрибуты для вывода сообщений. // // Параметры: // Сообщ - ссылка на объект // Форма - ссылка на форму, на которой расположено табличное поле списка // ТабПоле - ссылка табличное поле списка для вывода сообщений // ТабЗнач - таблица значений с сообщениями // Процедура Сообщения_УстАтриб(Сообщ, Форма, ТабПоле, ТабЗнач) Экспорт Сообщ.Форма = Форма; Сообщ.ТабПоле = ТабПоле; Сообщ.ТабЗнач = ТабЗнач КонецПроцедуры // Инициализирует таблицу сообщений. Добавляет недостающие колонки // // Параметры: // Сообщ - ссылка на объект // Процедура Сообщения_Инициализация(Сообщ) Экспорт Если Сообщ.ТабЗнач <> Неопределено Тогда КЧ = Новый КвалификаторыЧисла(10, 0); КС = Новый КвалификаторыСтроки(); МассивТипов = Новый Массив; МассивТипов.Добавить(Тип("Число")); ОписаниеТиповЧисла = Новый ОписаниеТипов(МассивТипов, , ,КЧ); МассивТипов.Очистить(); МассивТипов.Добавить(Тип("Строка")); ОписаниеТиповСтроки = Новый ОписаниеТипов(МассивТипов, , КС); // Колонка "ТипСообщ" Если Сообщ.ТабЗнач.Колонки.Найти("ТипСообщ") = Неопределено Тогда Сообщ.ТабЗнач.Колонки.Добавить("ТипСообщ", ОписаниеТиповЧисла, "Тип", 30) КонецЕсли; // Колонка "СтатусСообщ" Если Сообщ.ТабЗнач.Колонки.Найти("СтатусСообщ") = Неопределено Тогда Сообщ.ТабЗнач.Колонки.Добавить("СтатусСообщ", ОписаниеТиповЧисла, "Статус", 30) КонецЕсли; // Колонка "ТекстСообщ" Если Сообщ.ТабЗнач.Колонки.Найти("ТекстСообщ") = Неопределено Тогда Сообщ.ТабЗнач.Колонки.Добавить("ТекстСообщ", ОписаниеТиповСтроки, "Текст", 200) КонецЕсли; // Колонка "КомментарийСообщ" Если Сообщ.ТабЗнач.Колонки.Найти("КомментарийСообщ") = Неопределено Тогда Сообщ.ТабЗнач.Колонки.Добавить("КомментарийСообщ", ОписаниеТиповСтроки, "Комментарий", 200) КонецЕсли КонецЕсли КонецПроцедуры // Выполняет овновление формы, на которой размещено табличное поле // Тем самым вызывает прорисовку сообщений. // Выполняет это не чаще заданного интервала, мс // // Параметры: // Сообщ - ссылка на объект // Процедура Сообщения_Обновить(Сообщ) Экспорт Если Сообщ.Форма <> Неопределено Тогда Интервал = 500; // Интервал обновлений (не чаще), мс ВремяТекущОбновл = ПолучитьТекущееВремяВМиллисекундах(); Если (Сообщ.ВремяПредОбновл = Неопределено) ИЛИ (Сообщ.ВремяПредОбновл = 0) ИЛИ (ВремяТекущОбновл - Сообщ.ВремяПредОбновл >= Интервал) Тогда // Выполняем обновление Сообщ.ВремяПредОбновл = ВремяТекущОбновл; Сообщ.Форма.Обновить() КонецЕсли КонецЕсли КонецПроцедуры // Добавляет новое сообщение в таблицу значений // // Параметры: // Сообщ - ссылка на объект; // ТипСообщ - тип добавляемого сообщения; // ТекстСообщ - текст добавляемого сообщения; // СтатусСообщ - статус добавляемого сообщения; // КомментСообщ - комментарий к добавляемому сообщению // Возврат: // Уникальный идентификатор добавленного сообщения. Используется для дальнейшего обращения к сообщению с целью // его редактирования. По сути представляет собой порядковый номер строки в таблице Сообщ.ТабЗнач // Функция Сообщения_Добавить(Сообщ, ТипСообщ, ТекстСообщ, СтатусСообщ=Неопределено, КомментСообщ=Неопределено) Экспорт ИдСообщ = Неопределено; Если (Сообщ.ТабЗнач <> Неопределено) И (ТипСообщ <> Неопределено) И (ТипСообщ >= Проч.ТИПСООБЩ_УРОВЕНЬ1) И (ТипСообщ <= Проч.ТИПСООБЩ_УРОВЕНЬ3) И (ТекстСообщ <> Неопределено) Тогда НоваяСтрока = Сообщ.ТабЗнач.Добавить(); НоваяСтрока.ТипСообщ = ТипСообщ; // Преобразовать текст Если ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ1 Тогда ИначеЕсли ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ2 Тогда ТекстСообщ = " " + ТекстСообщ ИначеЕсли ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ3 Тогда ТекстСообщ = " " + ТекстСообщ КонецЕсли; // Установить текст НоваяСтрока.ТекстСообщ = ТекстСообщ; Если (СтатусСообщ <> Неопределено) И (СтатусСообщ >= Проч.СТАТСООБЩ_ОК) И (СтатусСообщ <= Проч.СТАТСООБЩ_ОШИБКА) Тогда НоваяСтрока.СтатусСообщ = СтатусСообщ; Иначе НоваяСтрока.СтатусСообщ = Проч.СТАТСООБЩ_НЕОПРЕД; КонецЕсли; НоваяСтрока.КомментарийСообщ = КомментСообщ; Если Сообщ.ТабПоле <> Неопределено Тогда // Изменение текущей строки табличного поля на добавляемую, // чтобы пользователь видел, что добавляется в список в данный момент Сообщ.ТабПоле.ТекущаяСтрока = НоваяСтрока; КонецЕсли; Сообщения_Обновить(Сообщ); ИдСообщ = Сообщ.ТабЗнач.Индекс(НоваяСтрока); КонецЕсли; Возврат ИдСообщ КонецФункции // Изменяет атрибуты сообщения с идентификатором ИдСообщ. Те, значение которых не "Неопределено" // // Параметры: // Сообщ - ссылка на объект; // ИдСообщ - идентификатор сообщения (порядковый номер строки); // ТипСообщ - тип добавляемого сообщения; // ТекстСообщ - текст добавляемого сообщения; // СтатусСообщ - статус добавляемого сообщения; // КомментСообщ - комментарий к добавляемому сообщению // Процедура Сообщения_Изменить(Сообщ, ИдСообщ, ТипСообщ=Неопределено, ТекстСообщ=Неопределено, СтатусСообщ=Неопределено, КомментСообщ=Неопределено) Экспорт Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) Тогда ФлагИзменения = Ложь; // Признак того, что что-то изменили (хоть один атрибут) Если ТипСообщ <> Неопределено Тогда Если (ТипСообщ <> Сообщ.ТабЗнач[ИдСообщ].ТипСообщ) И (ТипСообщ >= Проч.ТИПСООБЩ_УРОВЕНЬ1) И (ТипСообщ <= Проч.ТИПСООБЩ_УРОВЕНЬ3) Тогда Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = ТипСообщ; ФлагИзменения = Истина КонецЕсли КонецЕсли; Если ТекстСообщ <> Неопределено Тогда Если ТекстСообщ <> Сообщ.ТабЗнач[ИдСообщ].ТекстСообщ Тогда // Преобразовать текст Если Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ1 Тогда ИначеЕсли Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ2 Тогда ТекстСообщ = " " + ТекстСообщ ИначеЕсли Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ3 Тогда ТекстСообщ = " " + ТекстСообщ КонецЕсли; // Установить текст Сообщ.ТабЗнач[ИдСообщ].ТекстСообщ = ТекстСообщ; ФлагИзменения = Истина КонецЕсли КонецЕсли; Если СтатусСообщ <> Неопределено Тогда Если СтатусСообщ <> Сообщ.ТабЗнач[ИдСообщ].СтатусСообщ Тогда Если (СтатусСообщ <> Неопределено) И (СтатусСообщ >= Проч.СТАТСООБЩ_ОК) И (СтатусСообщ <= Проч.СТАТСООБЩ_ОШИБКА) Тогда Сообщ.ТабЗнач[ИдСообщ].СтатусСообщ = СтатусСообщ; Иначе Сообщ.ТабЗнач[ИдСообщ].СтатусСообщ = Проч.СТАТСООБЩ_НЕОПРЕД; КонецЕсли; ФлагИзменения = Истина КонецЕсли КонецЕсли; Если КомментСообщ <> Неопределено Тогда Если КомментСообщ <> Сообщ.ТабЗнач[ИдСообщ].КомментарийСообщ Тогда Сообщ.ТабЗнач[ИдСообщ].КомментарийСообщ = КомментСообщ; ФлагИзменения = Истина КонецЕсли КонецЕсли; Если ФлагИзменения = Истина Тогда // Прорисовка, если реально було обновление Сообщения_Обновить(Сообщ) КонецЕсли КонецЕсли КонецПроцедуры // Удаляет сообщение с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // Процедура Сообщения_Удалить(Сообщ, ИдСообщ) Экспорт Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) Тогда Сообщ.ТабЗнач.Удалить(ИдСообщ); Сообщения_Обновить(Сообщ) КонецЕсли КонецПроцедуры // Удаляет все сообщения из таблицы // // Параметры: // Сообщ - ссылка на объект // Процедура Сообщения_Очистить(Сообщ) Экспорт Если (Сообщ.ТабЗнач <> Неопределено) И (Сообщ.ТабЗнач.Количество() > 0) Тогда Сообщ.ТабЗнач.Очистить(); Сообщения_Обновить(Сообщ) КонецЕсли КонецПроцедуры // Возвращает тип сообщения с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // Возврат: // Тип сообщения или Неопределено, если сообщение не найдено // Функция Сообщения_ПолучитьТип(Сообщ, ИдСообщ) Экспорт Рез = Неопределено; Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) Тогда Рез = Сообщ.ТабЗнач[ИдСообщ].ТипСообщ КонецЕсли; Возврат Рез КонецФункции // Устанавливает тип сообщения с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // ТипСообщ - тип сообщения // Процедура Сообщения_УстановитьТип(Сообщ, ИдСообщ, ТипСообщ) Экспорт Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) И (ТипСообщ <> Сообщ.ТабЗнач[ИдСообщ].ТекстСообщ) И (ТипСообщ >= Проч.ТИПСООБЩ_УРОВЕНЬ1) И (ТипСообщ <= Проч.ТИПСООБЩ_УРОВЕНЬ3) Тогда Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = ТипСообщ; Сообщения_Обновить(Сообщ) КонецЕсли; КонецПроцедуры // Возвращает статус сообщения с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // Возврат: // Статус сообщения или Неопределено, если сообщение не найдено // Функция Сообщения_ПолучитьСтатус(Сообщ, ИдСообщ) Экспорт Рез = Неопределено; Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) Тогда Рез = Сообщ.ТабЗнач[ИдСообщ].СтатусСообщ КонецЕсли; Возврат Рез КонецФункции // Устанавливает статус сообщения с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // СтатусСообщ - статус сообщения // Процедура Сообщения_УстановитьСтатус(Сообщ, ИдСообщ, СтатусСообщ) Экспорт Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) И (СтатусСообщ <> Сообщ.ТабЗнач[ИдСообщ].СтатусСообщ) Тогда Если (СтатусСообщ <> Неопределено) И (СтатусСообщ >= Проч.СТАТСООБЩ_ОК) И (СтатусСообщ <= Проч.СТАТСООБЩ_ОШИБКА) Тогда Сообщ.ТабЗнач[ИдСообщ].СтатусСообщ = СтатусСообщ; Иначе Сообщ.ТабЗнач[ИдСообщ].СтатусСообщ = Проч.СТАТСООБЩ_НЕОПРЕД; КонецЕсли; Сообщения_Обновить(Сообщ) КонецЕсли КонецПроцедуры // Возвращает текст сообщения с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // Возврат: // Текст сообщения или Неопределено, если сообщение не найдено // Функция Сообщения_ПолучитьТекст(Сообщ, ИдСообщ) Экспорт Рез = Неопределено; Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) Тогда Рез = Сообщ.ТабЗнач[ИдСообщ].ТекстСообщ КонецЕсли; Возврат Рез КонецФункции // Устанавливает текст сообщения с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // ТекстСообщ - текст добавляемого сообщения; // Процедура Сообщения_УстановитьТекст(Сообщ, ИдСообщ, ТекстСообщ) Экспорт Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) И (ТекстСообщ <> Неопределено) И (ТекстСообщ <> Сообщ.ТабЗнач[ИдСообщ].ТипСообщ) Тогда // Преобразовать текст Если Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ1 Тогда ИначеЕсли Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ2 Тогда ТекстСообщ = " " + ТекстСообщ ИначеЕсли Сообщ.ТабЗнач[ИдСообщ].ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ3 Тогда ТекстСообщ = " " + ТекстСообщ КонецЕсли; // Установить текст Сообщ.ТабЗнач[ИдСообщ].ТекстСообщ = ТекстСообщ; Сообщения_Обновить(Сообщ) КонецЕсли КонецПроцедуры // Возвращает комментарий к сообщению с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // Возврат: // Комментарий сообщения или Неопределено, если сообщение не найдено // Функция Сообщения_ПолучитьКоммент(Сообщ, ИдСообщ) Экспорт Рез = Неопределено; Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) Тогда Рез = Сообщ.ТабЗнач[ИдСообщ].КомментарийСообщ КонецЕсли; Возврат Рез КонецФункции // Устанавливает комментарий к сообщению с идентификатором ИдСообщ // // Параметры: // Сообщ - ссылка на объект // ИдСообщ - идентификатор сообщения (порядковый номер строки) // КомментСообщ - комментарий к сообщению // Процедура Сообщения_УстановитьКоммент(Сообщ, ИдСообщ, КомментСообщ) Экспорт Если (Сообщ.ТабЗнач <> Неопределено) И (ИдСообщ <> Неопределено) И (ИдСообщ >= 0) И (ИдСообщ < Сообщ.ТабЗнач.Количество()) И (КомментСообщ <> Сообщ.ТабЗнач[ИдСообщ].КомментарийСообщ) Тогда Сообщ.ТабЗнач[ИдСообщ].КомментарийСообщ = КомментСообщ; Сообщения_Обновить(Сообщ) КонецЕсли КонецПроцедуры // В зависимости от типа сообщения и статуса определяет цвет текста сообщения // // Параметры: // ТипСообщ - тип сообщения // СтатусСообщ - статус сообщения // Возврат: // Цвет текста сообщения // Функция Сообщения_ЦветТекста(ТипСообщ, СтатусСообщ) Экспорт Рез = Неопределено; Если ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ1 Тогда Рез = WebЦвета.Черный ИначеЕсли ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ2 Тогда Рез = WebЦвета.Черный ИначеЕсли ТипСообщ = Проч.ТИПСООБЩ_УРОВЕНЬ3 Тогда Рез = WebЦвета.Черный КонецЕсли; Возврат Рез КонецФункции // В зависимости от типа сообщения и статуса определяет цвет текста комментария // // Параметры: // ТипСообщ - тип сообщения // СтатусСообщ - статус сообщения // Возврат: // Цвет текста комментария // Функция Сообщения_ЦветКомментария(ТипСообщ, СтатусСообщ) Экспорт Рез = Неопределено; Если СтатусСообщ = Проч.СТАТСООБЩ_ОК Тогда Рез = WebЦвета.Зеленый ИначеЕсли СтатусСообщ = Проч.СТАТСООБЩ_ПРЕДУПР Тогда Рез = WebЦвета.Оранжевый ИначеЕсли СтатусСообщ = Проч.СТАТСООБЩ_ОШИБКА Тогда Рез = WebЦвета.Красный КонецЕсли; Возврат Рез КонецФункции // В зависимости от типа сообщения и статуса определяет картинку статуса сообщения // // Параметры: // ТипСообщ - тип сообщения // СтатусСообщ - статус сообщения // Возврат: // Картинка статуса сообщения // Функция Сообщения_Картинка(ТипСообщ, СтатусСообщ) Экспорт Рез = Неопределено; Если СтатусСообщ = Проч.СТАТСООБЩ_ОК Тогда Рез = КоллекцияКартинок.СообщениеСтатусОК ИначеЕсли СтатусСообщ = Проч.СТАТСООБЩ_ПРЕДУПР Тогда Рез = КоллекцияКартинок.СообщениеСтатусПредупреждение ИначеЕсли СтатусСообщ = Проч.СТАТСООБЩ_ОШИБКА Тогда Рез = КоллекцияКартинок.СообщениеСтатусОшибка КонецЕсли; Возврат Рез КонецФункции // Запрашивает имя файла и сохраняет набор сообщений в текстовый файл // // Параметры: // Сообщ - ссылка на объект // Возврат: // Истина, если сохранение выполнено и Ложь - в противном случае // Функция Сообщения_СохранитьВФайл(Сообщ) Экспорт Рез = Ложь; // Еще ничего не сохранили Если Сообщ.ТабЗнач = Неопределено Тогда // Неверные параметры Возврат Рез КонецЕсли; // Создать диалог и выбрать файл для сохранения ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение); ДиалогВыбораФайла.Фильтр = "Текстовые файлы (*.txt)|*.txt" + "|Все файлы (*.*)|*.*"; ДиалогВыбораФайла.Заголовок = "Сохранение сообщений"; ДиалогВыбораФайла.МножественныйВыбор = Ложь; ДиалогВыбораФайла.ПредварительныйПросмотр = Ложь; ДиалогВыбораФайла.Расширение = "txt"; ДиалогВыбораФайла.ИндексФильтра = 0; ДиалогВыбораФайла.ПроверятьСуществованиеФайла = Истина; Если ДиалогВыбораФайла.Выбрать() Тогда // Выполнить сохранение Попытка // Открыть файл ЗаписьТекста = Новый ЗаписьТекста(ДиалогВыбораФайла.ПолноеИмяФайла, КодировкаТекста.ANSI); // Сформировать и записать в файл строки сообщений // Выяснить максимальные длины строк в сообщениях ДлинаТекстаМакс = 0; ДлинаКомментМакс = 0; Для Каждого СтрокаТЗ Из Сообщ.ТабЗнач Цикл ДлинаТекстаТек = СтрДлина(СтрокаТЗ.ТекстСообщ); Если ДлинаТекстаТек > ДлинаТекстаМакс Тогда ДлинаТекстаМакс = ДлинаТекстаТек; КонецЕсли; ДлинаКомментТек = СтрДлина(СтрокаТЗ.КомментарийСообщ); Если ДлинаКомментТек > ДлинаКомментМакс Тогда ДлинаКомментМакс = ДлинаКомментТек; КонецЕсли; КонецЦикла; // Выполнить запись строк Для Каждого СтрокаТЗ Из Сообщ.ТабЗнач Цикл Дист = " "; // Разделитель элементов строки Стр = ""; // Записать в строку статус сообщения Если (СтрокаТЗ.СтатусСообщ <> Неопределено) И (СтрокаТЗ.СтатусСообщ <> Проч.СТАТСООБЩ_НЕОПРЕД) Тогда СтатусСтр = ""; Если СтрокаТЗ.СтатусСообщ = Проч.СТАТСООБЩ_ОК Тогда СтатусСтр = "ОК"; ИначеЕсли СтрокаТЗ.СтатусСообщ = Проч.СТАТСООБЩ_ПРЕДУПР Тогда СтатусСтр = "ПРЕДУПРЕЖДЕНИЕ"; ИначеЕсли СтрокаТЗ.СтатусСообщ = Проч.СТАТСООБЩ_ОШИБКА Тогда СтатусСтр = "ОШИБКА"; КонецЕсли; Если Стр <> "" Тогда Стр = Стр + Дист КонецЕсли; Стр = Стр + РасширСтрПрав(СтатусСтр, 14); КонецЕсли; // Записать в строку текст сообщения Если (СтрокаТЗ.ТекстСообщ <> Неопределено) И (СтрокаТЗ.ТекстСообщ <> "") Тогда Если Стр <> "" Тогда Стр = Стр + Дист КонецЕсли; Стр = Стр + РасширСтрПрав(СтрокаТЗ.ТекстСообщ, ДлинаТекстаМакс); КонецЕсли; // Записать в строку текст комментария Если (СтрокаТЗ.КомментарийСообщ <> Неопределено) И (СтрокаТЗ.КомментарийСообщ <> "") Тогда Если Стр <> "" Тогда Стр = Стр + Дист КонецЕсли; Стр = Стр + РасширСтрПрав(СтрокаТЗ.КомментарийСообщ, ДлинаКомментМакс); КонецЕсли; // Записать строку в файл Если Стр <> "" Тогда ЗаписьТекста.ЗаписатьСтроку(Стр) КонецЕсли КонецЦикла; // Закрыть файл ЗаписьТекста.Закрыть(); Рез = Истина // Операция выполнена успешно Исключение // Сообщение об ошибке Предупреждение("Ошибка сохранения сообщений:" + Символы.ПС + ИнформацияОбОшибке().Описание); КонецПопытки КонецЕсли; Возврат Рез КонецФункции //////////////////////////////////////////////////////////////////////////////// // ОПЕРАТОРЫ ОСНОВНОЙ ПРОГРАММЫ - НАЧАЛЬНАЯ ИНИЦИАЛИЗАЦИЯ // ИНИЦИАЛИЗАЦИЯ КОНСТАНТ // Константы общего назначения Проч = ИнициализацияПрочихКонстант(); // Коллекция картинок КоллекцияКартинок = СформироватьКоллекциюКартинок(); // ИНИЦИАЛИЗАЦИЯ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ // Создать машину JavaScript JavaScript = СоздатьОбъектJavaScript(); 

Обратите внимание на то, что при выполнении длительного процесса в цикле, интерфейс пользователя блокируется, и поэтому факт добавления или изменения сообщений не будет виден пользователю – прорисовки не будет. Для принудительного выполнения прорисовки, нужно вызвать метод Обновить() формы, отображающей сообщения. Но частый вызов этого метода затормозит работу, поэтому вызывать его нужно лишь иногда (хоть и с небольшим интервалом, чтобы пользователь не заметил лагов – в данной реализации интервал обновления не чаще чем 1 раз в 0,5сек). Для реализации обновления служит метод класса Сообщения_Обновить.

Пример использования.

В данном примере форма, отображающая набор сообщений выглядит вот так:

Вместо кнопки «Запустить процесс», в реальности должно быть что-то более полезное – какой-то пользовательский интерфейс. Кнопка «Запустить процесс» имитирует заполнение списка сообщений, демонстрируя то, как могли бы выводиться сообщения в процессе реального выполнения какого-либо длительного действия.
В качестве реквизитов формы создана ТаблицаЗначений — «Сообщения». Мы ее никак не инициализируем, а просто передаем в качестве параметров соответствующим методам класса «Сообщения_», а они уже создают нужные колонки:

ОбъектСообщ = Сообщения_Конструктор();
Сообщения_УстАтриб(ОбъектСообщ, ЭтаФорма, ЭлементыФормы.ТабличноеПолеСообщения, Сообщения);
Сообщения_Инициализация(ОбъектСообщ);

Для отображения ТаблицыЗначений «Сообщения», на форме размещен визуальный компонент типа «ТабличноеПоле» — «ТабличноеПолеСообщения». При создании колонок в ТабличномПоле, учитываем следующие колонки ТаблицыЗначений (описывались ранее):
ТипСообщ;
СтатусСообщ;
ТекстСообщ;
КомментарийСообщ.

Текст модуля главной формы обработки (отображающей сообщения):

Текст модуля формы

//////////////////////////////////////////////////////////////////////////////// // ОСНОВНОЕ ОКНО ПРОГРАММЫ Перем ОбъектСообщ; // Объект для вывода сообщений в таблицу знач. и отобр. в табл. поле //////////////////////////////////////////////////////////////////////////////// // ОБРАБОТЧИКИ СОБЫТИЙ ФОРМЫ // Процедура - обработчик события "При открытии" формы. Данное событие // возникает при открытии формы, до показа окна пользователю. // // Параметры: // Нет. // Процедура ПриОткрытии() // Создание объекта сообщений и инициализация таблицы сообщений ОбъектСообщ = Сообщения_Конструктор(); Сообщения_УстАтриб(ОбъектСообщ, ЭтаФорма, ЭлементыФормы.ТабличноеПолеСообщения, Сообщения); Сообщения_Инициализация(ОбъектСообщ); КонецПроцедуры // Процедура - обработчик события "При закрытии" формы. Данное событие // возникает после закрытия формы. // Освобождение занятых ресурсов. // // Параметры: // Нет. // Процедура ПриЗакрытии() // Завершить работу с объектом вывода сообщений Сообщения_Деструктор(ОбъектСообщ) КонецПроцедуры // Процедура - обработчик события "Нажатие" кнопки КнопкаЗапуститьПроцесс. // Имитирует операцию, в процессе выполнения которой выводятся сообщения. // // Параметры: // Элемент - кнопка. // Процедура КнопкаЗапуститьПроцессНажатие(Элемент) // Подготовка данных к началу операции ГСЧ = Новый ГенераторСлучайныхЧисел(); ВсегоОбъектов = ГСЧ.СлучайноеЧисло(1, 100); // Запросить согласие пользователя на выполнение операции РезВопр = Вопрос("Выполнить обработку данных?", РежимДиалогаВопрос.ДаНет, , , "Обработка данных"); Если РезВопр = КодВозвратаДиалога.Да Тогда // Подготовка интерфейса к запуску процесса ErrMsg = ""; Сообщения_Очистить(ОбъектСообщ); // Сообщение о начале операции Сообщения_Добавить(ОбъектСообщ, Проч.ТИПСООБЩ_УРОВЕНЬ1, "*** НАЧАЛО ОБРАБОТКИ ДАННЫХ ***"); Рез = Ложь; КолОбрОб = 0; Попытка // Выполнять обработку объектов Для Инд=1 По ВсегоОбъектов Цикл // Вывести сообщение о том, что началась обработка очередного объекта ИдСообщУр2 = Сообщения_Добавить(ОбъектСообщ, Проч.ТИПСООБЩ_УРОВЕНЬ2, "Обработка объекта " + Формат(Инд, "ЧГ=0; ЧН=''")); // ****************************** // Здесь собственно выполняется сама операция над текущим объектом Если ГСЧ.СлучайноеЧисло(0, ВсегоОбъектов) > 0 Тогда // Считаем, что операция над текущим объектом выполнена успешно Рез = Истина Иначе // Считаем, что операция над текущим объектом выполнена с ошибкой Рез = Ложь КонецЕсли; // ****************************** // Установить результат операции над текущим объектом в окне сообщений СтатСообщ = Проч.СТАТСООБЩ_НЕОПРЕД; КоммСообщ = ""; Если Рез Тогда // Операция завершена удачно СтатСообщ = Проч.СТАТСООБЩ_ОК; КоммСообщ = "ОК" Иначе // Ошибки при выполнении операции СтатСообщ = Проч.СТАТСООБЩ_ОШИБКА; КоммСообщ = "ОШИБКА" КонецЕсли; Сообщения_Изменить(ОбъектСообщ, ИдСообщУр2, , , СтатСообщ, КоммСообщ); // Анализируем результат текущей итерации Если НЕ Рез Тогда // Ошибка - прервать работу Прервать КонецЕсли; КолОбрОб = КолОбрОб + 1 КонецЦикла Исключение // Возникло исключение - ошибка ErrMsg = ИнформацияОбОшибке().Описание; Рез = Ложь КонецПопытки; // Сообщение об окончании операции Если Рез Тогда // Операция завершена успешно Сообщения_Добавить(ОбъектСообщ, Проч.ТИПСООБЩ_УРОВЕНЬ1, "*** ОБРАБОТКА ДАННЫХ ЗАВЕРШЕНА ***"); Иначе // Операция прервана Сообщения_Добавить(ОбъектСообщ, Проч.ТИПСООБЩ_УРОВЕНЬ1, "*** ОБРАБОТКА ДАННЫХ ПРЕРВАНА ***", Проч.СТАТСООБЩ_ОШИБКА, ErrMsg) КонецЕсли; // Установка интерфейса по окончанию загрузки ТекстСообщ = ""; Если Рез Тогда ТекстСообщ = "Обработка выполнена успешно" Иначе ТекстСообщ = "Обработка выполнена с ошибками" КонецЕсли; ТекстСообщ = ТекстСообщ + Символы.ПС; ТекстСообщ = ТекстСообщ + "Обработано " + Формат(КолОбрОб, "ЧГ=0; ЧН=''") + " объектов."; Предупреждение(ТекстСообщ, , "Обработка данных") КонецЕсли КонецПроцедуры // Процедура - обработчик события "ПриВыводеСтроки" табличного поля // ТабличноеПолеСообщения. Данное событие происходит при выводе каждой строки. // // Параметры: // Элемент - табличное поле; // ОформлениеСтроки - объект настройки визуального оформления строки // ДанныеСтроки - набор данных строки табличного поля. // Процедура ТабличноеПолеСообщенияПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки) // Оформление ячейки "Статус" ОформлениеСтроки.Ячейки.КолонкаСтатусСообщения.ОтображатьТекст = Ложь; КартинкаСтатуса = Сообщения_Картинка(ДанныеСтроки.ТипСообщ, ДанныеСтроки.СтатусСообщ); Если КартинкаСтатуса <> Неопределено Тогда ОформлениеСтроки.Ячейки.КолонкаСтатусСообщения.ОтображатьКартинку = Истина; ОформлениеСтроки.Ячейки.КолонкаСтатусСообщения.Картинка = КартинкаСтатуса; КонецЕсли; // Оформление ячейки "Текст" Если (ДанныеСтроки.ТекстСообщ <> Неопределено) И (ДанныеСтроки.ТекстСообщ <> "") Тогда ЦветТекста = Сообщения_ЦветТекста(ДанныеСтроки.ТипСообщ, ДанныеСтроки.СтатусСообщ); Если ЦветТекста <> Неопределено Тогда ОформлениеСтроки.Ячейки.КолонкаТекстСообщения.ЦветТекста = ЦветТекста КонецЕсли КонецЕсли; // Оформление ячейки "Комментарий" Если (ДанныеСтроки.КомментарийСообщ <> Неопределено) И (ДанныеСтроки.КомментарийСообщ <> "") Тогда ЦветКомментария = Сообщения_ЦветКомментария(ДанныеСтроки.ТипСообщ, ДанныеСтроки.СтатусСообщ); Если ЦветКомментария <> Неопределено Тогда ОформлениеСтроки.Ячейки.КолонкаКомментарийСообщения.ЦветТекста = ЦветКомментария КонецЕсли КонецЕсли КонецПроцедуры // Процедура - обработчик события "Нажатие" кнопки КнопкаСообщенияОткрытьВНовомОкне. // Открывает текущий список сообщений в новом окне. // // Параметры: // Элемент - кнопка. // Процедура КнопкаСообщенияОткрытьВНовомОкнеНажатие(Элемент) ФормаПросмотраСообщений = ЭтотОбъект.ПолучитьФорму("ФормаПросмотраСообщений", ЭтаФорма); ФормаПросмотраСообщений.ShowMessagesDlg(ОбъектСообщ) КонецПроцедуры // Процедура - обработчик события "Нажатие" кнопки КнопкаСообщенияСохранить. // Сохранить текущий набор сообщений в файл. // // Параметры: // Элемент - кнопка. // Процедура КнопкаСообщенияСохранитьНажатие(Элемент) Сообщения_СохранитьВФайл(ОбъектСообщ); КонецПроцедуры 

Форма просмотра сообщений.

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

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

Код модуля формы совсем несложный. Вот такой:

Текст модуля формы

//////////////////////////////////////////////////////////////////////////////// // ДИАЛОГ ДЛЯ ОТОБРАЖЕНИЯ СПИСКА СООБЩЕНИЙ В ОТДЕЛЬНОМ ОКНЕ Перем ОбъектСообщ; // Объект для работы с сообщениями //////////////////////////////////////////////////////////////////////////////// // ОБРАБОТЧИКИ СОБЫТИЙ ФОРМЫ // Процедура - обработчик события "Действие" элемента меню КоманднаяПанельМенюСохранитьВФайл. // Сохранить текущий набор сообщений в файл. // // Параметры: // Кнопка - элемент меню. // Процедура КоманднаяПанельМенюСохранитьВФайл(Кнопка) Сообщения_СохранитьВФайл(ОбъектСообщ); КонецПроцедуры // Процедура - обработчик события "ПриВыводеСтроки" табличного поля // ТабличноеПолеСообщения. Данное событие происходит при выводе каждой строки. // // Параметры: // Элемент - табличное поле; // ОформлениеСтроки - объект настройки визуального оформления строки // ДанныеСтроки - набор данных строки табличного поля. // Процедура ТабличноеПолеСообщенияПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки) // Оформление ячейки "Статус" ОформлениеСтроки.Ячейки.КолонкаСтатусСообщения.ОтображатьТекст = Ложь; КартинкаСтатуса = Сообщения_Картинка(ДанныеСтроки.ТипСообщ, ДанныеСтроки.СтатусСообщ); Если КартинкаСтатуса <> Неопределено Тогда ОформлениеСтроки.Ячейки.КолонкаСтатусСообщения.ОтображатьКартинку = Истина; ОформлениеСтроки.Ячейки.КолонкаСтатусСообщения.Картинка = КартинкаСтатуса; КонецЕсли; // Оформление ячейки "Текст" Если (ДанныеСтроки.ТекстСообщ <> Неопределено) И (ДанныеСтроки.ТекстСообщ <> "") Тогда ЦветТекста = Сообщения_ЦветТекста(ДанныеСтроки.ТипСообщ, ДанныеСтроки.СтатусСообщ); Если ЦветТекста <> Неопределено Тогда ОформлениеСтроки.Ячейки.КолонкаТекстСообщения.ЦветТекста = ЦветТекста КонецЕсли КонецЕсли; // Оформление ячейки "Комментарий" Если (ДанныеСтроки.КомментарийСообщ <> Неопределено) И (ДанныеСтроки.КомментарийСообщ <> "") Тогда ЦветКомментария = Сообщения_ЦветКомментария(ДанныеСтроки.ТипСообщ, ДанныеСтроки.СтатусСообщ); Если ЦветКомментария <> Неопределено Тогда ОформлениеСтроки.Ячейки.КолонкаКомментарийСообщения.ЦветТекста = ЦветКомментария КонецЕсли КонецЕсли КонецПроцедуры //////////////////////////////////////////////////////////////////////////////// // ПОДПРОГРАММЫ ВНЕШНЕГО ИНТЕРФЕЙСА // Вызов диалога просмотра списка сообщений // // Параметры: // ОбъектСообщения - объект для работы с сообщениями. // Процедура ShowMessagesDlg(ОбъектСообщений) Экспорт; Если ОбъектСообщений <> Неопределено Тогда // Выполнить начальную установку параметров ОбъектСообщ = ОбъектСообщений; ЭлементыФормы.ТабличноеПолеСообщения.Значение = ОбъектСообщ.ТабЗнач; // Вызвать диалог ЭтаФорма.ОткрытьМодально() КонецЕсли КонецПроцедуры 

Скачать исходные тексты подсистемы сообщений и рассмотренный пример (обработка для 1С 8.2) можно здесь.

На этом все. Всем удачи. До встречи.

P.S.

В следующей статье я планирую рассказать о работе с сервером (базами) FireBird из 1С.
Мы выясним:
— как корректно подключаться к серверу (базе) и отключаться;
— как выполнять запросы: передавать и получать данные;
— как работать с бинарными полями и строками в различной кодировке;
— как вызывать хранимые процедуры и получать результаты их работы;
— как обойти такую неприятность, как автоматический разрыв клиентского соединения со стороны сервера FireBird (простой и понятный способ, который почему-то нигде не описан в Интернете).

Мы создадим обособленные и переносимые наборы подпрограмм («классов»):
— для подключения к базе (сможем подключаться к серверу, базе и выполнять запросы);
— для работы с параметрами запроса (можно будет задавать и устанавливать параметры почти как в Delphi);
А также:
— для работы с множествами целых чисел и строк (удобно задавать множественные параметры для запросов);
— удобное хранилище профилей подключений к различным базам FireBird, а также интерфейс пользователя (форма для редактирования набора профилей подключений)

В общем, думаю, что будет интересно. Еще раз, удачи…

О чем эта статья

Статья продолжает цикл статей «Первые шаги в разработке на 1С».

В ней мы рассмотрим способы информирования пользователя, которые присутствуют в платформе «1С:Предприятие» 8, а также акцентируем ваше внимание на некоторых особенностях работы этих механизмов, эти особенности связаны с режимом использования модальности.

Применимость

В статье рассматривается функциональность:

  • Интерфейса в варианте «Версии 8.2» для конфигурации, разработанной на платформе «1С:Предприятие» 8.2.19.130
  • Интерфейса «Такси» для конфигурации, разработанной на платформе «1С:Предприятие» 8.3.4.496 до 8.3.9+
  • Интерфейса «Такси» для конфигурации, разработанной на платформе «1С:Предприятие» 8.3.10-8.3.11

Как в 1С вывести сообщение пользователю

  1. Ознакомительные сообщения
  2. Механизм оповещений
  3. Терминирующие сообщения
  4. Особенности использования модальных окон в Платформе 8.3
  5. Класс СообщениеПользователю
  6. Уведомление о состоянии процесса

Вывод сообщений в пользовательском режиме решает ряд задач:

  • отражение хода выполнения текущего процесса (показ стадии выполнения процесса; показ расчетных значений, полученных в ходе работы алгоритма);
  • выдача ошибок пользователю для возможного их исправления;
  • выдача рекомендаций;

Типы сообщений:

  • терминирующие, которые останавливают выполнение программы и не дают продолжить ее, пока пользователь не ознакомится с этим сообщением и не выполнит определенные действия. Например, на экран пользователю будет выдан вопрос, на который нужно будет ответить Да или Нет. Пока пользователь не ответит – программа не выполняет дальнейшие действия;
  • ознакомительные сообщения, которые просто выводятся для пользователя и позволяют работать дальше (т.е. используются в режиме оповещения).

Терминирующими сообщениями должны быть сообщения об ошибках, а ознакомительными: рекомендации, сообщения о текущем этапе процесса и показ расчетных значений (отладочная печать).

Ознакомительные сообщения

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

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

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

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

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

Открепить от формы окно с сообщением нельзя.

Синтаксис функции:

Сообщить (<Текст сообщения>, <Статус>)

Т.е. первым параметром является сам текст.

Второй параметр (статус сообщения) является необязательным. Для статуса можно указывать значения: Обычное, Важное, ОченьВажное и т.д.

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

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

Ознакомительные сообщения

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

Пользователь нажимает на кнопку Записать и закрыть, в этом случае сообщение выводится в соответствующее окно (справа формы).

Но форма моментально закрывается, и пользователь не увидит, что для него выводилась какая-то информация.

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

Неправильное использование функции Сообщить

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

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

Функция Сообщить полностью поддерживается в Платформе 8.3. Ее можно использовать, и она будет работать (и в файловом варианте, и в клиент-серверном).

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

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

Так, программный код в Платформе 8.3 может быть исполнен как на стороне Клиента, так и на стороне Сервера.

При этом клиентский программный код отвечает за взаимодействие с пользователем, т.е. на стороне клиента открываются формы, выводятся отчеты.

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

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

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

В этот момент система запросит данные из буфера и выведет их на экран.

Эта же особенность касается и класса СообщениеПользователю. На рисунке приведен пример использования метода Сообщить на стороне Сервера.

НаСервере Сообщить

В результате использования метода Сообщить на стороне Сервера вывелись сообщения на экран на стороне Клиента.

Сообщения на Клиенте

Механизм оповещений

Механизм оповещений нужен, чтобы информировать пользователя о том, что в системе “что-то” произошло и это “что-то” требует внимания пользователя. Оповещения создаются двумя сценариями:

  1. Самой платформой при интерактивной записи или изменении объекта
  2. Разработчиком при вызове в коде метода ПоказатьОповещениеПользователя().

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

Механизм оповещений

Кроме того, к оповещениям можно обратиться в соответствующей области информационной панели (кнопка “История” слева внизу формы приложения в варианте интерфейса «Версии 8.2»).

Чтобы создавать свои собственные оповещения, необходимо использовать метод глобального контекста ПоказатьОповещениеПользователя(). Его синтаксис до редакции 8.3.10 представлен ниже:

ПоказатьОповещениеПользователя (<Текст>, <НавигационнаяССылка>, <Пояснение>, <Картинка>)

В первом параметре передается текст, который будет выводиться в оповещении.

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

С помощью третьего параметра можно передать пояснение для сообщения, т.е. какое-то расширенное описание.

Также можно присвоить картинку, отображающую статус оповещения.

Следует отметить, что все эти параметры являются необязательными для заполнения. Ниже приведен пример использования данного метода (в конфигураторе и в пользовательском режиме в варианте интерфейса «Версии 8.2»).

Механизм оповещений
Механизм оповещений

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

ПоказатьОповещениеПользователя(<Текст>, <ДействиеПриНажатии>, <Пояснение>, <Картинка>, <СтатусОповещенияПользователя>, <КлючУникальности>)

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

Механизм оповещений

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

Следующий параметр СтатусОповещенияПользователя появился впервые. В нем указывается статус оповещения (Информация или Важное).

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

Механизм оповещений

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

Механизм оповещений

В панели инструментов появилась кнопка с пиктограммой звонка, по которой вызывается упомянутый выше Центр оповещений. В нем накапливаются новые важные оповещения, на которые пользователь пока никак не отреагировал.

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

Механизм оповещений

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

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

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

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

Также к новым возможностям относится и одновременное отображение на экране до трех оповещений.

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

Давайте представим такую простую ситуацию: пользователь установил фильтр в каком-то списке для удобства. Допустим, он сделал это в форме списка справочника Номенклатуры. Потом, через какое-то время, решил ввести новый элемент с наименованием “Стул”, который не соответствует установленному ранее фильтру. Вводит его, записывает и…? И не видит его в списке. Что будет делать среднестатистический пользователь? Конечно, введет его второй раз, но опять не увидит. Дальше может последовать третий, четвертый, пятый раз. Когда ему надоест вводить одно и тоже, он, наконец, спросит у вас: а куда все пропадает?

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

Механизм оповещений

Терминирующие сообщения

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

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

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

В вопросе могут определяться разные наборы вариантов ответов (ДаНет, ДаНетОтмена, ОК, ОКОтмена, ПовторитьОтмена, ПрерватьПовторитьПропустить), которые задаются с помощью параметра.

Выведем какое-нибудь предупреждение с помощью строки (например, в модуле управляемого приложения):

Предупреждение(“Сейчас будет открыта база”);

Чтобы открыть модуль управляемого приложения, следует в дереве конфигурации выбрать объект Конфигурация, вызвать контекстное меню и выбрать пункт Открыть модуль управляемого приложения.

Открытие модуля управляемого приложения

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

Модальное окно

Аналогичным образом работает и функция Вопрос.

Синтаксис:
Вопрос(<ТекстВопроса>,<Кнопки>,<Таймаут>,<КнопкаПоУмолчанию>,<Заголовок>,
<КнопкаТаймаута>);

Обязательными являются только первые два параметра. Для второго параметра тип данных составной (РежимДиалогаВопрос или СписокЗначений). Третий параметр (<Таймаут>) характеризует интервал времени в секундах, в течение которого система будет ожидать ответа пользователя.

По истечении интервала окно вопроса будет закрыто. Аналогичный параметр(<Таймаут>) есть и у функции Предупреждение.

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

Модуль управляемого приложения

Использование функции Вопрос

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

Особенности использования модальных окон в Платформе 8.3

В платформе 8.3 существуют режимы работы с использованием и без использования модальности. По умолчанию стоит настройка Не использовать режим модальности.

В этом случае использование терминирующих сообщений невозможно. В случае необходимости использования терминирующих сообщений (функции Предупреждение и Вопрос) следует изменить значение свойства конфигурации Режим использования модальности на Использовать.

Режим использования модальности

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

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

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

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

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

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

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

Синтаксис функции ПоказатьПредупреждение:

ПоказатьПредупреждение(<ОписаниеОповещенияОЗавершении>, <ТекстПредупреждения>, <Таймаут>, <Заголовок>)

Параметр <ОписаниеОповещенияОЗавершении> (необязательный)

Тип данных: ОписаниеОповещения.

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

Синтаксис функции ПоказатьВопрос:

ПоказатьВопрос(<ОписаниеОповещенияОЗавершении>, <ТекстВопроса>, <Кнопки>, <Таймаут>, <КнопкаПоУмолчанию>, <Заголовок>, <КнопкаТаймаута>)

Обязательными являются первые три параметра.

Ниже приведен пример использования функции.

Процедура ОписаниеОповещения

Пример функции оповещения в 1С

Класс СообщениеПользователю

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

Сообщения могут быть привязаны к конкретному экранному элементу. Этот объект доступен и на Сервере.

Следует обратить внимание, что, во-первых, данный объект нужно создавать. Например: Сообщение = Новый СообщениеПользователю;

Таким образом мы создаем экземпляр данного объекта.

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

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

Внимание! Для привязки к нужному полю формы обратите внимание на инициализацию свойств ПутьКДанным и КлючДанных. Применительно для документа при размещении кода в модуле объекта можно писать:

Сообщение.ПутьКДанным = “Объект”;
Сообщение.КлючДанных = ЭтотОбъект.Ссылка;

Чтобы открыть модуль документа, следует в окне редактирования объекта (документа) на закладке Прочее нажать на кнопку Модуль объекта.

Для эксперимента в модуле объекта какого-либо документа разместим код.

Сообщение пользователю

Ниже представлен полученный в пользовательском режиме результат для Платформы 8.3.

Сообщение пользователю в пользовательском режиме 1С

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

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

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

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

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

Уведомление о состоянии процесса

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

Синтаксис: Состояние(<ТекстСообщения>, <Прогресс>, <Пояснение>, <Картинка>)
Параметры: <ТекстСообщения> и <Пояснение> – не обязательные, тип – Строка.
Текст выводится на специальную панель состояния.
<Прогресс> параметр тоже необязательный, но наглядный.
Тип: Число. Значение индикатора прогресса (от 1 до 100).
<Картинка> тоже необязательный параметр.
При обработке какого-либо события могут использоваться периодические вызовы функции типа:

Вызов функции

При этом могут меняться надписи, а могут изменяться значения параметра Прогресс.

Функция может вызываться как из одной процедуры (функции), так и из нескольких. Таким образом можно отслеживать состояние выполнения процесса.

Состояние выполнения процесса

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

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

Хочется еще раз акцентировать ваше внимание на том факте, что если ваша конфигурация (версии 8.3.3+) предполагает работу с помощью веб-клиента, то:

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

Более подробно об отказе от использования модальных окон в платформе 1С:Предприятие 8.3 можно почитать в финальной статье цикла. А мы идем дальше и, наконец, приступаем к изучению долгожданного интерфейса «Такси», который уже не раз упоминался в наших материалах.

PDF-версия статьи для участников группы ВКонтакте

Мы ведем группу ВКонтакте – http://vk.com/kursypo1c.

Если Вы еще не вступили в группу – сделайте это сейчас и в блоке ниже (на этой странице) появятся ссылка на скачивание материалов.

Статья по программированию - в PDF-формате

Полезные ссылки:

Ссылка доступна для зарегистрированных пользователей)
Ссылка доступна для зарегистрированных пользователей)
Ссылка доступна для зарегистрированных пользователей)

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

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

Здесь система, по сути, сообщает нам, что произошла ошибка. Больше никакой информации из этого текста мы не получим. Разве что узнаем, что где-то в недрах самой системы существует какой-то метод, который что-то не то вернул. Ценность этого знания для обычного пользователя нулевая.

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

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

5 основных правил

В книге Сьюзан Уэйншенк «100 главных принципов дизайна» приведены основные правила написания информативного и полезного сообщения об ошибке:

  1. Расскажите пользователю, что он сделал.
  2. Объясните проблему.
  3. Объясните, как исправить ошибку.
  4. Используйте активный, а не пассивный залог.
  5. Приведите пример.

В книге есть пример того, как не надо писать сообщения об ошибке:

#402: до того как счёт может быть оплачен, необходимо, чтобы дата платежа была позднее, чем дата создания счёта.

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

А ещё хуже можешь?

«Вы хотите отправить разработчикам отчёт об ошибке в приложении?»
«Ok»
«Ну и ябеда!»

Нам не привыкать — у нас за плечами многолетний опыт общения с разномастными продуктами отечественного софтостроения. Давайте уберём конкретику вовсе:

Счёт не может быть оплачен. Введены неправильные данные.

Чтобы ещё больше запутать пользователя, скроем от него причину ошибки:

Счёт не может быть оплачен.

Теперь приблизимся вплотную к нашему почти нулевому варианту:

Ошибка выполнения метода bill_payment.

Дальше для достижения антиидеала нам остаётся только убрать название метода и творчески преобразовать текст:

Неизвестная ошибка.

Интересно, не возникло ли у вас чувство дежавю, когда вы читали примеры в этом подразделе?

Добавляем информацию

В случае возникновения критической ошибки обновления:
1. Установите причину ошибки.
2. Устраните причину ошибки.
Документация Microsoft

Теперь давайте двигаться в другую сторону по нашей шкале — будем постепенно улучшать текст сообщения, руководствуясь правилами Сьюзан Уэйншенк.

Напомню исходный вариант сообщения из книги:


#402: до того как счёт может быть оплачен, необходимо, чтобы дата платежа была позднее, чем дата создания счёта.

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

#402: Для оплаты счёта необходимо, чтобы дата платежа была позднее, чем дата создания счёта.

Теперь расскажем пользователю, что он сделал не так:

#402: Вы ввели неправильную дату платежа — она раньше, чем дата создания счёта. Для оплаты счёта необходимо, чтобы дата платежа была позднее, чем дата создания счёта.

Выделим суть проблемы в отдельное предложение и явно объясним пользователю, что он должен сделать, чтобы её решить.

#402: Ошибка оплаты счёта. Вы ввели неправильную дату платежа — она раньше, чем дата создания счёта. Измените дату платежа так, чтобы она была позднее даты создания счёта.

Наконец, приведём конкретный пример. В данном случае нам известны даты и мы их можем использовать в тексте:

#402: Ошибка оплаты счёта. Вы ввели неправильную дату платежа 25.07.2021 — она раньше, чем дата создания счёта 29.07.2021. Измените дату платежа так, чтобы она была позднее даты создания счёта 29.07.2021. Например, 30.07.2021.

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

Лучшее сообщение об ошибке

Ошибка выполнения метода: «Метод выполнился без ошибок».

Есть фразы, которые сразу хочется выписать на жёлтый стикер и приклеить где-то рядом с монитором. Одна из таких фраз: «Лучшее сообщение об ошибке — отсутствие сообщения об ошибке». Иными словами, часто бывает проще не допустить возможности возникновения ошибки, чем её описывать.

В нашем примере никто не мешает разработчику сделать две вещи:

  1. При смене даты счёта очистить поле с датой платежа.
  2. В интерфейсе запретить ввод даты платежа позже заданной даты счёта.

Эти незначительные изменения в коде позволят избежать ошибки вовсе. И не нужно будет ломать голову над текстом сообщения.

К сожалению, таким простым способом решить проблему удаётся не всегда. Мы не можем полностью обезопасить приложение от возникновения ошибок. Но в наших силах сделать сообщения об ошибках более информативными и полезными.

А с какими загадочными сообщениями об ошибках сталкивались вы?

Поставьте себя на место пользователя, который не знаком с программированием, Вам интересно читать «Client error 404 not found», «Server error 500»?

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

Так вот, предположим пропало соединение с базой данных, тогда пользователю надо вывести сообщение:

«Операция сохранения данных завершилась неудачей в связи с техническими проблемами на нашей стороне. Попробуйте через несколько минут.»

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

«Мы заметили, что у Вас пропало соединение с интернетом. Работа в режиме Offline с нашей программой невозможна, проверьте соединение с интернетом. Если вы не наблюдаете проблем с открытием страниц в браузере, возможно, доступ к сети мог был заблокирован вашим антивирусным ПО для нашей программы и Вам требуется проверить правила»

Также, можно внизу сделать вкладку «Системная информация» и туда сохранить информацию об ошибке, данные запроса и т.д. и написать пользователю:

«Если данная проблема сохраняется в течении длительного времени, обратитесь в техническую поддержку с информацией из вкладки ‘системная информация'»

Понравилась статья? Поделить с друзьями:
  • Создание точки восстановления виндовс 10 ошибка 0x81000203
  • Солярис ошибка датчика коленвала
  • Создание теневой копии томов ошибка
  • Солярис ошибка в1217
  • Создание простого тома ошибка