По итогам прошлой статьи у нас есть форма 1С (внешней обработки) с полем HTML документа и Silverlight-приложение, работающее в нем самостоятельно. Разумеется, нам хотелось бы каким-то образом воздействовать на него и обмениваться данными, по крайней мере, в одностороннем порядке, если мы хотим средствами Silverlight визуализировать какую-либо информацию.
Изучая HTML документ, созданный отладчиком Visual Studio, мы обратили внимание на вставку JavaScript когда, который обрабатывал ошибки запуска Silverlight. Более того, технология Silverlight предполагает, что приложение будет вставлено в какую-то область страницы и будет взаимодействовать с ней. Стало быть, Silverlight-приложение умеет общаться с родителем - страницей, на которой оно расположено. Значит, нам следует изучить возможности 1С по взаимодействию с полем HTML. Получается следующая схема:
Начнем с первой части схемы. Допустим, будем выводить средствами JavaScript произвольную строку, вводимую пользователем в нашей форме. Создадим новый реквизит типа Строка неограниченной длины и добавим на форму соответствующее поле ввода. Для поля создадим обработчик события "ПриИзменении", в котором пока ничего писать не будем, а откроем макет HTML страницы. Здесь внутрь тега, отвечающего за описание скриптов () поместим код нашей простой функции, выводящей переданный ей параметр.
Мы немного преобразуем полученную строку, отобразим на экране и возвратим. Стандартная функция alert() работает аналогично Сообщить() в 1С. Вообще говоря, параметр не обязательно может быть строковым, как и в 1С, в JavaScript мягкая типизация, alert() попытается преобразовать значение в строку. Теперь вызовем эту функцию из нашего обработчика, есть два способа, запишем их оба:
&НаКлиенте Процедура СообщениеПриИзменении(Элемент) Сообщить(Элементы.ДокументHTML.Документ.parentWindow.ShowMessage(Сообщение)); Сообщить(Элементы.ДокументHTML.Документ.parentWindow.eval("ShowMessage(""" + Сообщение + """)")); КонецПроцедуры
Таким образом, мы продублируем вывод строки еще и в среде 1С, чтобы убедиться, что возврат значений работает верно. Запустим обработку, введем текст в поле и убедимся, что оно выведется два раза в виде отдельных окошек сообщений и в виде сообщений 1С.
В чем же разница в вызовах? Увы, достоверной информации по этому поводу найти трудно, но, очевидно, что второй вариант более ограничен в параметрах - они могут быть лишь строковыми (хотя число передать можно, но придется затем приводить тип в JS). В тоже время, если вы попытаетесь возвратить массив, созданный в JS, то "eval" приведет его к виду, понятному для 1С, а обычный вызов нет. Что же делать, если нам нужно передать что-то не строковое, а получить массив? Передавать его как один из параметров! Добавим еще одну JS функцию:
Она принимает два параметра - входящий массив и тот, в который мы запишем результат. Результатом будет простое добавление "JS: " перед каждым элементом массива (предположим, что элементы строковые). Да, JS спокойно работает с массивами 1С, позволяя оперировать знакомыми методами "Добавить()", "Количество()" и т.д. Увы, не будет работать индексация, поэтому пользуемся функцией "Получить()". В 1С добавим на форму кнопку, привяжем к команде, содержащей следующий код:
&НаКлиенте Процедура test(Команда) мсJS = Новый Массив; мсOut = Новый Массив; мсJS.Добавить("First element"); мсJS.Добавить("Second element"); Элементы.ДокументHTML.Документ.parentWindow.ChangeArray(мсJS, мсOut); Для Каждого элOut Из мсOut Цикл Сообщить(элOut); КонецЦикла; КонецПроцедуры
Здесь мы создаем два массива, добавляем пару элементов, вызываем функцию и выводим полученный массив.
Наконец, еще один способ передачи данных - глобальная переменная-буфер. Мы объявляем одну или несколько глобальных переменных в JS-коде, в 1С устанавливаем им значения и вызываем функции JS, которые эти переменные используют, изменяют. Через эти же переменные получаем данные обратно. Самый простой способ и самый нежелательный, поскольку переменная всегда остается в память и нужно следить за её очисткой, другие функции могут использовать переменную в которой содержится некорректное значение и прочее, использовать только в крайних случаях.
Итак, передавать и получать значения мы умеем, но пока у нас инициатором этого обмена был лишь 1С. Хотелось бы и наоборот. Разумеется, просто вызывать функции 1С и получать от данные мы в JS не можем, хотя бы из соображений безопасности (не забываем, что мы имеем дело с кодом, который может быть расположен на любой странице в интернете). Но оповестить 1С о своих действиях JS может.
Те, кто работал с полем HTML документа в обычных формах, видел, что у него обширный список обработчиков событий, фактически, можно было отловить любое событие HTML, от onclick до onhelp. Что же видим в форме управляемой? "ПриИзменении", "ДокументСформирован" и "Нажатие"... Негусто, странное решение 1С, но что поделать. Будем использовать "Нажатие". Давайте оповестим 1С о том, что формирование массива нашей функцией закончено. Вот здесь нам понадобится буфер. Итак, заведем глобальную переменную и в нашу функцию ChangeArray добавим код:
Теперь создадим обработчик события HTML поля "Нажатие" и в нем выведем содержимое буфера:
&НаКлиенте Процедура ДокументHTMLПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка) Если Элементы.ДокументHTML.Документ.parentWindow.outBuffer Неопределено Тогда Сообщить(Элементы.ДокументHTML.Документ.parentWindow.outBuffer); КонецЕсли; КонецПроцедуры
Запустим обработку и убедимся, что событие действительно перехватывается и сообщение выводится.
В нашем случае "Нажатие" удобно тем, что, поскольку, всю рабочую область поля у нас занимает Silverlight-приложение, вызвать данное событие можно только через JS, остальные нажатия перехватит Silverlight. Функция fireEvent() может иметь два параметра, второй содержит EventObject, информация в котором могла бы нам помочь узнать, кто вызвал событие. Доступен он будет в параметре обработчика "ДанныеСобытия".
Таким образом, мы научили сообщаться 1C и JS в поле HTML. Это важный момент, поскольку уже средства JS позволяют значительно расширить функционал 1С. JS (особенно вкупе с SVG) замечательно управляется с графикой, имеет множество библиотек. В чем же преимущества Silverlight? Во-первых, в удобстве. Здесь и отличный отладчик Visual Studio, и возможность программирования на языках .NET, и удобная разметка с помощью XAML. Во-вторых, скомпилированный код Silverlight работает быстрее (хотя здесь нужно учитывать, что посредником все равно выступает JS). Выбор конкретного средства зависит от разработчика.
Теперь пора приступить ко второй части нашей связки, мы будем работать с JS и Silverlight. Хотя сначала мы рассмотрим способ вызова функций Silverlight из 1С напрямую. Итак, первое, что нам нужно сделать - как-то идентифицировать наше приложение на странице, для чего откроем наш макет и найдем объявление приложения (мы разбирали его параметры) и присвоим ему идентификатор silverlightControl:
Он может быть любым, но по этому названию мы будем искать приложение на странице. Теперь снова будем работать в Visual Studio, отрываем наш проект. Изначально объекты и методы приложения Silverlight закрыты от посторонних и вызвать их нельзя, исправим это, для чего сделаем следующее:
На скриншоте видны все действия:
Компилируем (F6), внешне ничего не изменилось, так что запуск нас не интересует. Не забываем, что теперь нужно загрузить новый xap-файл в макет нашей обработки. Теперь в 1С добавим еще одну кнопку, команда которой будет содержать следующий код:
&НаКлиенте Процедура test2(Команда) SilvElement = Элементы.ДокументHTML.Документ.getElementById("silverlightControl"); Если SilvElement Неопределено Тогда Сообщить(SilvElement.content.SilvApp.SayHello()); КонецЕсли; КонецПроцедуры
Как видно, сначала мы ищем объект-приложение по заданному идентификатору, затем, если оно найдено, через его содержимое обращаемся к приложению по зарегистрированному имени и вызываем нашу функцию, результат выводим. Запускаем и убеждаемся, что все работает.
Данный способ вызова подходит, если нет необходимости в передаче параметров, при попытке передачи любого параметра в функцию возникнет "Неизвестная ошибка". Вот здесь и поможет посредник в лице JavaScript.
Вызов функции Silverlight из JS происходит аналогично рассмотренному только что, методы тоже должны быть "Scriptable", а приложение иметь зарегистрированное имя. Код вызова:
var SilvElement = document.getElementById("silverlightControl"); if (SilvElement != null) { SilvElement.content.SilvApp.SayHello(); }
Аналогично обращаемся к методу документа, чтобы найти объект, а затем по имени приложения вызываем функцию. В данном случае можно передавать параметры, ошибок не будет. Думаю, здесь уже нет необходимости приводить примеры, можете сами доработать функцию SayHello, чтобы она устанавливала переданное пользователем значение и возвращала какое-то оповещение, а затем из 1С вызвать код JS, с переданным ему сообщением, который обратится к данной функции.
Теперь о вызове JavaScript кода из Silverlight. По щелчку мыши на кнопку в Silverlight будем вызывать нашу функцию ShowMessage из JS. Все очень просто:
private void Button_Click_1(object sender, RoutedEventArgs e) { TextBlock1.Text = "Hello World!"; HtmlPage.Window.Invoke("ShowMessage", "Оповещение от Silverlight!"); }
Метод Invoke первым параметром принимает имя функции, а последующими - её аргументы.
И, напоследок, еще один пример. Попробуем передать файл-картинку, выбираемый в 1С, а в Silverlight установим его в качестве фона. На форме добавим третью кнопку, укажем ей новую команду, которая будет вызывать диалог открытия файла.
&НаКлиенте Процедура ОткрытьИзобр(Команда) ДиалогОткрытияФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие); ДиалогОткрытияФайла.Фильтр = "Изображения (*.jpg)|*.jpg"; ДиалогОткрытияФайла.Заголовок = "Выберите файлы"; Если ДиалогОткрытияФайла.Выбрать() Тогда ДанныеФайла = Новый ДвоичныеДанные(ДиалогОткрытияФайла.ВыбранныеФайлы[0]); Сообщить(Элементы.ДокументHTML.Документ.parentWindow.SendImg(Base64Строка(ДанныеФайла))); КонецЕсли; КонецПроцедуры
Сделать из файла пригодный для передачи параметр помогает функция "Base64Строка", которая позволяет передать двоичные данные в виде строки. Код вызываемой функции "SendImg" на JS:
function SendImg (base64str) { var SilvElement = document.getElementById("silverlightControl"); if (SilvElement != null) { ans = SilvElement.content.SilvApp.SetImg(base64str); } return ans; }
Здесь мы просто передаем строку "дальше" - в Silverlight-приложение и получаем ответ, который в 1С выведет функция "Сообщить". И, наконец, код функции "SetImg" в Silverlight:
//добавления в раздел "using" using System.Windows.Media.Imaging; using System.IO; //Сам метод [ScriptableMember()] public String SetImg(String base64str) { byte[] buffer = Convert.FromBase64String(base64str); MemoryStream ms = new MemoryStream(buffer, 0, buffer.Length); BitmapImage image = new BitmapImage(); image.SetSource(ms); ImageBrush imgBrush = new ImageBrush(); imgBrush.ImageSource = image; LayoutRoot.Background = imgBrush; HtmlPage.Window.Invoke("ShowMessage", "Изображение установлено"); return "Оповещение 1С"; }
С помощью стандартного метода Silverlight конвертируем строку в массив байт, сохраняем в MemoryStream и устанавливаем в качестве Source у созданного BitmapImage. Затем создаем кисть с иточником в виде нашего изображения и устанавливаем её свойству Background. В JS вызываем функцию-оповещение с сообщением об успешной установке изображения, а возвращаемый параметр "уйдет", опять-таки через JS, в 1С. Собственно, всё!
Надеюсь, что 1С и дальше будет развивать компоненту WebBrowser, что когда-нибудь она не будет жестко привязана к IE и мы сможем ожидать, например, поддержку HTML5. Это позволит расширить не только возможности локального приложения, но и полностью позволит взаимодействовать с различными API в интернете (а об этом еще надеюсь поговорить). В любом случае, теперь вы будете к этому подготовлены. Разумеется, некоторые моменты в статье были опущены, например, передача сложных объектных данных, вроде элементов справочников - это достигается с помощью xml-сериализации и не имеет прямого отношения к взаимодействию с JS или Silverlight, т.к. касается общих (стандартных) механизмов передачи.