Возможно, вашему проекту на BPMonline понадобится возможность использования буфера обмена операционной системы. Немного погуглив можно обнаружить, что сделать это напрямую со страницы HTML средствами javascript невозможно в связи с политикой безопасности, конкретно, запись в него может быть использована в нехороших целях злоумышленниками. Обходных путей несколько
1) Каждый браузер имеет своё контекстное меню, которое имеет доступ к странице и может быть вызвано поверх логики страницы, и при случае скопировать выделенный текст элемента страницы. В Firefox это реализовано через плагин, так что вы можете взаимодействовать с ним. В IE имеется прямой доступ через window.clipboardData , что является нарушением безопасности. В остальных браузерах конкретного решения нет.
Недостатки: под каждый браузер необходимо писать свою реализацию, и не все браузеры вообще имеют такую возможность.
2) Использовать Flash (старый отработанный метод), который позволяет работать с ClipBoard .Для этого даже создан javascript код ZeroClipBoard (один js и один swf файлы), используемый многими в качестве готового решения.
Недостатки:
- Flash постепенно становится непопулярной технологией и возможно перестанет быть стандартом из-за прихода HTML5
- по умолчанию не позволяет работать с локальным сайтом, нужно настраивать Flash Player для этого
- необходимо использовать "crossdomain.xml" для разрешения кросс-доменнного доступа, т.е. иметь прямой доступ к шаблонам страниц, чего в BPmonline для проектных решений нет.
3) Использовать Silverlight, который аналогично Flash позволяет получать доступ к ClipBoard через свой контекст.
Недостатки:
- Silverlight - это тоже не чистый HTML, поэтому нелюбители этой технологии могут отказаться от данного решения, но он набирает свои обороты и становится популярным. К тому же для BPMonline - это родная технология, и её наличие обязательно на машине клиента.
В итоге была выбрана 3 технология в виду наименьших недостатков.
Сразу скажу.Статья была сделана на основании конкретной задачи в рамках проекта Zyxel - добавить в каждый TreeGrid возможность копирования в буфер обмена выделенной ячейки реестра . Код иэ той задачи специфичен конкретно для данной реализации под TreeGrid, поэтому в статье будут приведены только основные моменты.
Создаём silverlight-проект типа application, где имеются два объекта
App.xaml - инициализация
Ваш класс ClipBoard>.xaml - класс, реализующий доступ к ClipBoard
На странице вашего класса создаём два контрола - кнопку, которая будет нажимать для копирования в буфер обмена и невидимое поле. Пример разметки
Почему так? Почему кнопка, а не Ctrl+C ? Дело в том, что на нажатие клавиш Ctrl+C контейнер silverlight будет реагировать только, если это нажатие будет внутри контейнера(silverlight в фокусе), а у нас нажатие будет на странице. Во-вторых, silverlight имеет одно, но существенное ограничение при доступе к ClipBoard, это можно сделать, но только из контекста Silverlight и только с подачи пользователя, т.е. должна быть нажата кнопка или любое другое событие, недоступное из javascript (private), иначе это ничем бы не отличалось от доступа напрямую из javascript (только через вызов silverlight метода).
Кнопка будет записывать текст в ClipBoard. Но как этому событию передать заданный текст. Для этого сначала надо этот текст получить. Можно вызвать javascript метод, который вернёт в качестве результата выделенный текст. Из silverlight можно вызвать javascript-метод, и передать ему параметры-значения через HtmlPage.Window.Invoke, но нельзя напрямую получить результат, поэтому я использовал текстовое поле, в которое вызванный javascript может записать результат.
В итоге получился следующий класс
[ScriptableType] и [ScriptableMember] - обязательно для класса и метода, если хотите иметь доступ к ним со страницы
HtmlPage.RegisterScriptableObject("silverlightClipBoard", this) - под указанным именем регистрируется объект на странице и к нему можно будет обращаться(см. ниже)
JavascriptFunctionName - имя функции, которая вызывается для записи значения в текстовое поле, для дальнейшего копирования в буфер обмена.
CopyToTextEdit - будет вызвана со страницы для записи результата в текстовое поле
Copy - метод по нажатию кнопки, в конце копирует в буфер обмена
Это всё, что нужно со стороны silverlight, в результате вы получаете xap файл - контейнер silverlight, который обычно кладут в ClientBin папку сервера.
Теперь что же нужно сделать со стороны страницы
- подготовить и создать объект silverlight на странице
- подготовить и создать функцию, которая будет вычитывать копируемое в буфер значение со страницы и записывать его в текстовое поле silverlight
Всё это делается одним скриптом на PageLoadComplete с помощью Page.AddScript(script):
для добавление используется файл Silverlight.js - поставляется Microsoft, в BPMonline уже добавлен
var silverlightparent//родительский объект для silverlight; var silverlightdiv = document.createElement('div');
b =new Object();
b.source= document.location.href.substring(0,document.location.href.lastIndexOf('/'))+'/ClientBin/ClipBoard.xap';
b.properties={};
b.properties.version='4.0.50303';
b.properties.width=20;b.properties.height=20;
b.parentElement= silverlightparent;
b.id='silverlightClipBoardElement';
b.events=null;
b.initParams='JavascriptFunctionName=silverlightClipBoardCopyToEdit';
b.context=null;
silverlightdiv.innerHTML= Silverlight.createObjectEx(b);
silverlightClipBoardElement = document.getElementById('silverlightClipBoardElement');
Silverlight.createObjectEx - cоздаёт объект на странице
b.source = относительный путь к xap файлу
b.properties = {};
b.properties.version - минимальная требуемая silverlight версия
b.properties.width- размеры
b.parentElement - родительский DOM-элемент
b.id - по нему можно найти объект, в котором находится контейнер
b.initParams - начальные параметры, будут переданы в конструктор класса, задаются в виде 'key1=value1;key2=value2', их можно вычитать, я таким образом передаю название функции, которая будет работать на стороне javascript
silverlightClipBoardElement - глобальная переменная для доступа к объекту
а теперь сама функция
silverlightClipBoardCopyToEdit =function(){ // получить значение текста для буфера обмена
silverlightClipBoardElement.content.silverlightClipBoard.CopyToTextEdit(CopyValue); }
посмотрите, как можно обращаться к методу , для этого у DOM-объекта silverlightClipBoardElement обращаемся к объекту, зарегистрированному под именем silverlightClipBoard (см. конструктор класса silverlight), и далее к методу, передавая заветное CopyValue.
В общем порядок работы такой
Нажатие кнопки -> Вызов javascript-метода из метода кнопки-> Javascript вычитывает копируемое значение -> Вызова silverlight-метода и запись в текстовое поле значения -> Возврат в Javascript-метод-> Возврат в метод кнопки silverlight -> Чтение значения из текстового поля и запись его в буфер обмена.
Столкнулся с ситуацией: требуется вставить в ImageDataСontrol на карточке картинку из буфера обмена. Не важно, напрямую или через временный файл. Я испытал три различных способа, но все три - безуспешно:
Но толку никакого. Мне кажется, что картинка не отрисовывается не из-за отсутствия данных, а именно из-за того, что двоичный Stream не содержит правильного заголовка картинки. Контрол просто не знает, что от него ожидают. Сунули, мол, каких-то каракатиц, а к чему они - неизвестно.
По идее, все дело в том, как сохраняется в буфер обмена само изображение. Думаю, нужно ждать совета разработчиков ядра, которые бы объяснили происходящее с точки зрения контрола.
Столкнулся с ситуацией: требовалось вставить в ImageDataСontrol на карточке картинку из буфера обмена. К примеру, нажал пользователь PrintScreen, щёлкнул на карточке кнопку, картинка и вставилась. Казалось бы, чего проще... А не тут-то было!
Самый очевидный способ не сработал. Пишет в ответ: "Разрушительный сбой":
var Stream = System.CopyClipboardToStream();
edtScreenShot.DataField.SetValAsBlob(Stream);
Stream этот какой-то странный, ни свойств, ни методов, просто кусок двоичного кода. Я этот код увидел, когда сделал так:
System.StreamToMime(Stream);
На восемь мегабайт каракатиц. Но где наша не пропадала! Есть ведь путь посложнее, взять ImageList, и через Image прочитать картинку из Stream, сохранить в файл, а потом этот файл автоматически открыть методом поля типа Blob.
Написал так:
var Stream = System.CopyClipboardToStream(); var ImageList = Services.CreateItem('ImageList'); var Image = ImageList.CreateImage();
Image.ImageType=1;
Image.LoadFromStream(Stream);
Image.SaveToFile('c:\Temp.bmp');
Система всё это лихо выполнила, ни разу не кашлянула. Но файл получился нулевой длины. Так что вставлять в Blob после этой операции уже было нечего...
Наконец (верно от отчаяния) я решил подсунуть вместо этого таинственного Stream известный и понятный ADODB.Stream. Только как бы в него засунуть то, что в буфере обмена? Я написал такое:
var Stream = System.CopyClipboardToStream(); var Mime = System.StreamToMime(Stream); var ADODBStream =new ActiveXObject('ADODB.Stream');
ADODBStream.Mode=3;//Кстати, кто знает, что означают эти числа?
ADODBStream.Type=1;//Расскажите, а то я просто скопировал из другого примера...
ADODBStream.Open();
System.MimeToStream(Mime, ADODBStream);
ADODBStream.SaveToFile('c:\Temp1.bmp');
Способ сработал, все весемь мегабайт каракатиц выгрузились в Temp1.bmp, только файл был не в формате BMP, а именно в виде строки Mime. Естественно, картинку прочитать невозможно.
Итак, у меня ничего не получилось. Но, может быть, я просто пошёл неправильным путём? Кто подскажет, вдруг эта задача уже решалась? Кто что знает о сохранении картинок из буфера обмена, либо об их вставке в Blob-поле? Использовал версию 3.2.1.58.
Спасибо, Саша, что обратили моё внимание на этот момент! Действительно, здесь стоит посмотреть внимательно. Вот, что я выяснил. Если ставить в контроле по умолчанию ImageType = itBMP, то сообщение о разрушительном сбое пропадает. Его нет и при использовании itJPG (хотя скриншот явно не в виде jpg попадает в Stream). Сообщение о разрушительном сбое бывает только при itPNG. Однако вставки картинки всё равно не происходит, хотя сообщение об ошибке я убрал, принудительно устанавливая ему itBMP перед вставкой. Так что мы на правильном пути!
Датасет, по-видимому, подключен корректно. Рядом с кнопкой "Вставить из буфера" размещена кнопка "Вставить из файла". Там стоит такая конструкция для того же поля того же датасета:
edtScreenShot.DataField.LoadFromFile(FilePath);
Работает безупречно, картинка загружается.
Но ни в чём нельзя быть уверенным до конца. С моим-то склерозом :) Для эксперимента попробовал и такое обращение:
var Dataset = dlData.Dataset;
Dataset.DataFields('ScreenShot').LoadFromFile(FilePath);
Тоже отлично работает.
Ну, и чем чёрт не шутит:
var Dataset = dlData.Dataset;
var Stream = System.CopyClipboardToStream();
Dataset.DataFields('ScreenShot').SetValAsBlob(Stream);
Увы, хоть сообщения об ошибке нет, картинка не видна...
Однако самое поразительное - вот что! Хоть картинки в контроле и не видно, если сделать
то по указанному пути появится восьмимегабайтный файл, со структурой, похожей на bmp (куча каракатиц стройными рядами), но без характерного bmp-заголовка. Который, увы, ни одна графическая программа не считает bmp-файлом. Удивительно ещё и то, что запись сохраняется, и в blob двоичные данные попадают, это видно как в таблице, так и при открытии карточки (ощутимо замедленном из-за такого объёма картинки). Но саму картинку по-прежнему не видно.
Мне кажется, что добавив в Stream правильный заголовок bmp проблема бы решилась. Он явно идёт без него, хотя структура bmp-шная. Но как бы его добавить?