Пост

Все скопируем и вставим! (Буфер обмена в 1С 8.3.24)

Рассмотрим новую возможность 8.3.24 и как её можно эффективно использовать


Предисловие

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

А тем временем в 1С до сих пор не было встроенной возможности программной работы с этим самым “буфером”. Но вот выходит тестовая версия 8.3.24, которая наконец-то даст 1Сникам СредстваБуфераОбмена. Но, как обычно это бывает, с нюансами…

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

Форматы буфера обмена

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

Допустим, мы копируем статью с Википедии. И вставляет к себе в блокнот. В таком случае вставляется просто текст, который лежит в буфере в своей отдельной “ячейке”. А можем вставить в Word. И тогда мы получим из буфера текст с форматированием, картинками и так далее. Это содержимое формата HTML.

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

Вот ещё пример, который знаком каждому 1Снику. Когда вы в конфигураторе нажимаете Ctrl+C на каком-то реквизите или элементе формы, то помещаете его в буфер обмена. И можете вставить его в блокнот, получив тем самым имя копируемого объекта. А можете вставить в другом объекте, форме и даже конфигураторе другой базы. Как же 1С понимает, что это именно реквизит и перетягивает его свойства?

А просто платформа помещает в буфер не только имя реквизита\элемента, но и полные данные о нём. Специальный формат, описывающий копируемый объект и все его свойства. И когда вы нажимаете Ctrl+V в блокноте, то он читает из буфера только интересующий его формат - Текст. А когда вставляете в конфигураторе, то 1С ищет в буфере свой особый формат.

Содержимое буфера можно посмотреть при помощи специальных приложений

Каждый формат имеет свой строковый ключ. Сколько же их? Вообще, каждое приложение может использовать свой формат (как это происходит с метаданными 1C:MD8 Data), но, так как в платформе из-за универсальности часто появляются ограничения связанные с работой веб-клиента в браузерах, то лучше использовать список MIME-типов. Это такие как “text/html”, “application/json”, “application/zip” и т.д. Но чаще всего нас устроит просто несколько стандартных форматов, которые в 1С выражены отдельным системным перечислением. И вот мы плавно переходим к изменениям платформы ↓

Работа с буфером в 1С

Ранее у программиста 1С не было платформенных методов работы с буфером. Но 1Сники привыкли искать всякие костыльные решения, чтобы обойти ограничения платформы. Поэтому периодически можно встретить использование COMОбъект или ПолеHTMLДокумента.

Но теперь в платформе появилось новое свойство клиентского глобального контекста.

  • СредстваБуфераОбмена с методами:
    • ИспользованиеДоступно() - Доступно ли использование буфера обмена
    • СодержитДанныеАсинх(<Формат>) - Проверяет, содержит ли буфер обмена данные конкретного формата
    • ПолучитьДанныеАсинх(<Формат>) - Возвращает содержимое буфера обмена по переданному формату
    • ПоддерживаетсяФорматДанных(<Формат>) - Поддерживается ли передача в буфер конкретного формата данных
    • ПоместитьДанныеАсинх(<Массив из ЭлементБуфераОбмена>) - Помещает данные в буфер обмена в разрезе форматов
  • Появились новые типы данных для работы с буфером:
    • СтандартныйФорматДанныхБуфераОбмена - специальная коллекция со стандартными форматами
      • Текст
      • HTML
      • Картинка
    • ЭлементБуфераОбмена - Объект для помещения данных в буфер. Содержит свойства:
      • Данные - Строка, ДвоичныеДанные, Картинка
      • ФорматДанных - СтандартныйФорматДанныхБуфераОбмена
  • Добавлено новое клиентское событие ПриВставкеИзБуфераОбмена(). Точнее даже событиЯ, потому что помимо глобального в модуле приложения каждая форма обладает своим одноименным событием.
  • А для типа СсылкаНаФайл добавлены методы
    • ПолучитьКакСтрокуАсинх()
    • ПолучитьКакДвоичныеДанныеАсинх()

Описание нововведений в документации можно найти здесь и здесь.

Рассмотрим же все пункты подробнее, а заодно напишем простые методы по работе с буфером ↓

СредстваБуфераОбмена

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

Так как методы работы с буфером асинхронны и доступны только на клиенте, то делаем метод Асинх

1
2
3
4
5
6
&НаКлиенте
Асинх Функция ТекстИзБуфераОбмена()
	
  Возврат Неопределено;
		
КонецФункции

СредстваБуфераОбмена.ИспользованиеДоступно()

Данный метод необходимо использовать перед тем как начинать работу с буфером обмена. Зачем это нужно?

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

Доработаем нашу функцию:

1
2
3
4
5
6
7
8
9
10
&НаКлиенте
Асинх Функция ТекстИзБуфераОбмена()
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        <...>
    КонецЕсли;
	
    Возврат Неопределено;
		
КонецФункции

СредстваБуфераОбмена.СодержитДанныеАсинх(<Формат>)

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
&НаКлиенте
Асинх Функция ТекстИзБуфераОбмена()
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        ФорматДанных = СтандартныйФорматДанныхБуфераОбмена.Текст;
        Если Ждать СредстваБуфераОбмена.СодержитДанныеАсинх(ФорматДанных) Тогда
            <...>
        КонецЕсли;
    КонецЕсли;
	
    Возврат Неопределено;
		
КонецФункции

СредстваБуфераОбмена.ПолучитьДанныеАсинх(<Формат>)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
&НаКлиенте
Асинх Функция ТекстИзБуфераОбмена()
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        ФорматДанных = СтандартныйФорматДанныхБуфераОбмена.Текст;
        Если Ждать СредстваБуфераОбмена.СодержитДанныеАсинх(ФорматДанных) Тогда
            Возврат Ждать СредстваБуфераОбмена.ПолучитьДанныеАсинх(ФорматДанных);
        КонецЕсли;
    КонецЕсли;
	
    Возврат Неопределено;
		
КонецФункции

Всё, наша функция по извлечению текста готова!

А теперь сделаем такую же функцию, но которая поместит текст в буфер обмена и вернет признак успеха. Если поместить не удалось, то вернет Ложь. При успехе же Истину.

1
2
3
4
5
6
7
8
9
10
&НаКлиенте
Асинх Функция ПоместитьТекстВБуфераОбмена(Текст)
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        <...>
    КонецЕсли;
	
    Возврат Ложь;
		
КонецФункции

СредстваБуфераОбмена.ПоддерживаетсяФорматДанных(<Формат>)

Прежде чем поместить данные буфера обмена, необходимо проверить поддержку нужного формата. Работает метод по аналогии СодержитДанныеАсинх, но проверяет можно ли вообще данный формат поместить в буфер. И снова всё это из-за веба)

1
2
3
4
5
6
7
8
9
10
11
12
13
&НаКлиенте
Асинх Функция ПоместитьТекстВБуфераОбмена(Текст)
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        ФорматДанных = СтандартныйФорматДанныхБуфераОбмена.Текст;
        Если Ждать СредстваБуфераОбмена.ПоддерживаетсяФорматДанных(ФорматДанных) Тогда
            <...>
        КонецЕсли;
    КонецЕсли;
	
    Возврат Ложь;
		
КонецФункции

СредстваБуфераОбмена.ПоместитьДанныеАсинх(<ЭлементБуфераОбмена>)

Все проверки выполнены и теперь можем просто поместить текст в буфер. Для этого в метод нужно передать объект ЭлементБуфераОбмена, который содержит в себе формат данных и сами данные (наш текст)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
&НаКлиенте
Асинх Функция ПоместитьТекстВБуфераОбмена(Текст)
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        ФорматДанных = СтандартныйФорматДанныхБуфераОбмена.Текст;
        Если Ждать СредстваБуфераОбмена.ПоддерживаетсяФорматДанных(ФорматДанных) Тогда
            ПомещаемыеДанные = Новый ЭлементБуфераОбмена(ФорматДанных, Текст);
            Возврат Ждать СредстваБуфераОбмена.ПоместитьДанныеАсинх(ПомещаемыеДанные);
        КонецЕсли;
    КонецЕсли;
	
    Возврат Ложь;
		
КонецФункции

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//Примеры использования:

УдалосьПоместить = Ждать ПоместитьВБуфераОбмена(
    СтандартныйФорматДанныхБуфераОбмена.Текст, "Мой текст");
		
ТекстИзБуфера = Ждать СодержимоеБуфераОбмена(
    СтандартныйФорматДанныхБуфераОбмена.Текст);
	
	
&НаКлиенте
Асинх Функция СодержимоеБуфераОбмена(ФорматДанных, ЗначениеПоУмолчанию = Неопределено)
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        Если Ждать СредстваБуфераОбмена.СодержитДанныеАсинх(ФорматДанных) Тогда
            Возврат Ждать СредстваБуфераОбмена.ПолучитьДанныеАсинх(ФорматДанных);
        КонецЕсли;
    КонецЕсли;
	
    Возврат ЗначениеПоУмолчанию;
		
КонецФункции

&НаКлиенте
Асинх Функция ПоместитьВБуфераОбмена(ФорматДанных, Значение)
	
    Если СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        Если Ждать СредстваБуфераОбмена.ПоддерживаетсяФорматДанных(ФорматДанных) Тогда
            ПомещаемыеДанные = Новый ЭлементБуфераОбмена(ФорматДанных, Значение);
            Возврат Ждать СредстваБуфераОбмена.ПоместитьДанныеАсинх(ПомещаемыеДанные);
        КонецЕсли;
    КонецЕсли;
	
    Возврат Ложь;
		
КонецФункции

Работа с несколькими форматами

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

  • СтандартныйФорматДанныхБуфераОбмена
    • Текст
    • HTML
    • Картинка

И мы можем получить или поместить одновременно несколько форматов. Используем наши функции, чтобы получить из буфера и текст и HTML.

1
2
    Текст = Ждать СодержимоеБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.Текст);
    HTML = Ждать СодержимоеБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.HTML);

Попробуем скопировать это слово

Как видим, первая строка просто получила слово без форматирования. А уже вторая получала текст HTML, который можно преобразовать, например, в форматированный документ.

Но как нам в буфер обмена передать одновременно и строку и HTML? Используя наши методы, поместим в буфер строку и HTML, а потом попробуем извлечь их же:

1
2
3
4
5
    Ждать ПоместитьВБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.Текст, "слово");
    Ждать ПоместитьВБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.HTML, "<h1>слово<h1>");
	
    Текст = Ждать СодержимоеБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.Текст);
    HTML = Ждать СодержимоеБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.HTML);

Результат нас может удивить. Мы и в тексте и в HTML получили только текст:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&НаКлиенте
Асинх Функция ПоместитьВБуфераОбмена(Данные)
	
    Если НЕ СредстваБуфераОбмена.ИспользованиеДоступно() Тогда
        Возврат Ложь;
    КонецЕсли;
	
    Если ТипЗнч(Данные) = Тип("ЭлементБуфераОбмена") Тогда
        ПомещаемыеДанные = Новый Массив;
        ПомещаемыеДанные.Добавить(Данные);
    Иначе
        ПомещаемыеДанные = Данные;
    КонецЕсли;
		
    Для Каждого ТекущиеДанные Из ПомещаемыеДанные Цикл
			
        Если НЕ Ждать СредстваБуфераОбмена.ПоддерживаетсяФорматДанных(ТекущиеДанные.ФорматДанных) Тогда
            Возврат Ложь;
        КонецЕсли;
			
    КонецЦикла;
		
    Возврат Ждать СредстваБуфераОбмена.ПоместитьДанныеАсинх(ПомещаемыеДанные);
		
КонецФункции

Поместим же теперь и HTML и строку:

1
2
3
4
5
6
7
8
    Данные = Новый Массив;
    Данные.Добавить(Новый ЭлементБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.HTML, "<h1>слово<h1>"));
    Данные.Добавить(Новый ЭлементБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.Текст, "слово"));
		
    Ждать ПоместитьВБуфераОбмена(Данные);
		
    Текст = Ждать СодержимоеБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.Текст);
    HTML = Ждать СодержимоеБуфераОбмена(СтандартныйФорматДанныхБуфераОбмена.HTML);

Ну вот, совсем другое дело:

Собственные форматы

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

Например, мы можем положить в буфер обмена табличную часть документа одновременно и в виде простого текста с отступами, и окрашенного HTML, а ещё и дополнительно в виде массива структур со ссылками (сериализовать в XML).

И тогда, если пользователь попытается вставить содержимое буфера в блокнот, то увидит красиво отформатированный текст. Если вставит в Word, то получит раскрашенную таблицу. А если же нажмет нашу кнопку “Вставить” в форме документа, то мы извлечем содержимое XML и поместим в табличную часть.

Главное понимать, что использовать можно и свои придуманные ключи формата буфера. Хоть “МойКлюч”, хоть “blabla”. И в тонком клиенте это будет работать. Но если приложение планируется запускать в браузере, то необходимо придерживаться списком MIME-форматов. Их на самом деле очень много и для любой задачи их будет достаточно. Например, для сериализации в XML можно использовать “text/xml”. А что туда уже помещать - наше личное дело.

ПриВставкеИзБуфераОбмена(Значение, СтандартнаяОбработка)

Данное событие есть у каждой управляемой формы + глобальное в модуле приложения

Событие срабатывает, если пользователь, находясь на форме, делает вставку из буфера (Ctrl+V) картинки или файлов. К сожалению только такие данные приводят событие в действие (но об этом мы ещё поговорим ниже).

В Значение попадает картинка или массив в элементами типа “СсылкаНаФайл”. Здесь мы можем поместить данные в реквизит формы, прикрепить файл к карточке номенклатуры или же просто установить картинку.

Для удобной работы со ссылками на файлы добавлены методы ПолучитьКакСтрокуАсинх() и ПолучитьКакДвоичныеДанныеАсинх().

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

Конфигурация с примерами использования

Для тестирования возможностей работы с буфером обмена сделал обработку и поместил её в конфигурацию на GitHub

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

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

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

Ссылка на проект

Нюансы, ошибки и замечания

В данном пункте будут собираться баги или недостатки, а при выходе нового релиза платформы статья будет обновляться. Все замечания и баги отправляю в 1С.

Что имеем на данный момент?

  • СредстваБуфераОбмена не возвращают файл. Это странно, но нет такого формата данных, который бы позволил извлечь файл из буфера обмена. Да, выше мы упоминали про тип СсылкаНаФайл, но его мы можем получить только в событии формы. Э если пользователь сам нажмёт сочетание клавиш для вставки из буфера. Но не программно в любой момент.
  • СредстваБуфераОбмена не возвращают табличный документ или его область. Когда пользователь копирует в буфер обмена табличный документ или его область, то 1С помещает в буфер не только HTML со строкой, но и специальный внутренний формат. Это позволяет вставить кусок документа в другой документ с сохранением всех 1Сных свойств данного типа. Т.е. платформа помещает, извлекает и обрабатывает такие данные. Но для разработчика нет возможности программно получить из буфера таб.док или его область. Только текст или HTML, который придется обрабатывать.
  • Нет возможности получить все содержимое буфера. Или хотя бы просто посмотреть лежащие там форматы, чтобы потом извлечь циклом. Поэтому для разработки “просмотрщика” буфера мне пришлось добавлять в качестве макета перечисление из всех возможных MIME-типов. И это всё равно может быть не всем возможным содержимым.
  • ПриВставкеИзБуфераОбмена работает только с файлами и картинками. Считаю это странным и крайне неудобным ограничением. В результате, мы получаем отдельно методы по извлечению данных кроме файлов, а ещё и событие, которое срабатывает только на файлах и картинках. И код приходится раскидывать между кнопкой и событием. Хотя если бы событие просто срабатывало всегда, то для ряда случаев и дополнительные кнопки могли бы не понадобиться.
  • Баг. ПриВставкеИзБуфераОбмена не всегда срабатывает. Например, если у формы не будет активного элемента (какого-то редактирования), то срабатывают оба события. И формы и глобальное. Но если попытаться, например, сделать вставку при редактировании поля ТЧ, то вызывается только глобальное событие всего приложения, а форма игнорируется. А вот если пытаться вставить в поле текстового документа, то не срабатывает вообще никакое событие.
  • Значение произвольного формата обязано быть двоичными данными. Не страшный, но неприятный нюанс, не дающий нам просто извлечь или поместить, к примеру, текст XML. Обязательно надо производить сериализацию в двоичные данные. Если столкнулись с этим, то просто используйте методы ПолучитьСтрокуИзДвоичныхДанных() и ПолучитьДвоичныеДанныеИзСтроки()
  • Баг метода помещения в буфер. Если в массиве данных будет сначала текст, а потом HTML, то в оба формата попадет HTML. Поэтому надо сначала HTML, а потом уже текст.
  • Баг. Картинки, помещенные в буфер из 1С, воспринимаются не всеми приложениями. Например, проблемы есть с Word и Outlook. Можно даже скопировать картинку в буфер (скриншотером, например), а потом в 1С извлечь и её же поместить обратно в буфер. И точно такая же картинка, но помещенная при помощи платформы, вставляться в Word не будет.
  • Баг. Если назначить произвольной команде формы сочетание клавиш Ctrl+V, то перестает работать событие ПриВставкеИзБуфераОбмена(). И не только текущей формы, но и глобальное. Разработчик может случайно сломать в своей обработке логику конфигурации.
  • Нашли ещё какой-то нюанс? Пишите в комментариях - добавлю в список.

Итоги

Буфер обмена - базовая возможность, которая позволяет значительно облегчить работу с приложением. И скоро ответственность за реализацию связанных с ним “фишек” упадет на плечи рядовых 1Сников. Поэтому лучше уже сейчас приглядываться к этой возможности и с переходом на 8.3.24 использовать её в своих инструментах. Тем более, что снятие режима совместимости не требуется.

Если находите какой-то баг, нюанс или интересный способ применения, то пишите в комментариях. Я буду дополнять статью так, чтобы сделать её полным источников информации по работе с буфером обмена в 1С.

Авторский пост защищен лицензией CC BY 4.0 .