Используя Delphi FireMonkey, мне нужно скопировать строку в формате HTML в буфер обмена в Delphi, чтобы другое приложение, вставившее ее, увидело ее как HTML.
Я уже пытаюсь скопировать простой текст через скрытый компонент Memo, и он работает, но копируется как простой текст. Мне нужна копия в формате HTML. Имея его в виде текста, при вставке в любую программу с форматированным текстом отображаются все теги HTML вместо форматированного текста.
Мой текущий подход к копированию текста в буфер обмена:
procedure TForm1.Button1Click(Sender: TObject);
var
SourceText: string;
begin
SourceText := 'This is a <b>bold</b> html line';
//I can use TMemo to copy it to clipboard like:
Memo1.lines.Text := SourceText;
Memo1.SelectAll;
memo1.CopyToClipboard;
end;
Но проблема в том, что если я вставлю текст, скопированный в буфер обмена, в Microsoft Word, он будет вставлен как:
«Это <b>bold</b>
html-строка». Я хочу, и это должно быть так:
Это жирная строка html
Примечание. Я читал другие обсуждения о том, как это можно сделать в Windows, но мне нужно решение Firemonkey для кросс-платформенного приложения. Любая помощь приветствуется.
Спасибо за руководство. Я обновил вопрос соответственно с примером.
Здесь не будет простого ответа. В Windows вы, вероятно, захотите поместить rtf и html в буфер обмена. Я ожидаю, что это потребует некоторого индивидуального кодирования с вашей стороны.
У меня есть решение для вас. Как только вопрос будет открыт, я опубликую. Это единственная функция CopyHtmlToClipboard для выполнения этой работы.
@fpiette Спасибо. Да, пожалуйста. Я отчаянно нуждаюсь в этом. Я не закрыл вопрос и не знаю, как открыть его снова.
@HilalSoftware Прочитайте комментарии ... Ваш вопрос был закрыт по причине «нужны подробности или ясность». Чтобы его снова открыть, вы должны отредактировать его и добавить деталей и ясности (я уже отредактировал и добавил немного ясности). Закрытие и повторное открытие вопросов в StackOverflow осуществляется с помощью голосования. Вы найдете, кто проголосовал за закрытие вашего вопроса, в синей области над вашим вопросом. Теперь вам нужно дождаться повторного открытия вашего вопроса. Я уже проголосовал за это. На StackOverflow вам абсолютно необходимо следовать правилам, иначе ваши вопросы будут отклонены. Посмотрите это: stackoverflow.com/help/how-to-ask
@fpiette Спасибо за руководство. Я уже редактировал и добавлял детали после того, как он был закрыт. Область блюза не показывает мне, кто закрыл вопрос. Я программист Delphi со времен Delphi 1, и мое эмпирическое правило заключается в том, что если проблема достаточно общая, то какой-нибудь умный разработчик уже знает ее решение; надо поискать и правильно спросить. Спасибо за редактирование. Теперь вопрос кажется достаточно очевидным и ясным. Правила и структура StackOverflow очень хороши, и я жду, когда вопрос будет открыт.
@fpiette Спасибо. Ждем ваших указаний по этому поводу.
Копирование данных в буфер обмена кроссплатформенным способом с помощью FireMonkey осуществляется через интерфейс IFMXExtendedClipboardService
, который можно получить, вызвав сервис платформы:
var
Svc : IFMXExtendedClipboardService;
begin
if not TPlatformServices.Current.SupportsPlatformService(IFMXExtendedClipboardService, Svc) then
Exit; // Not clipboard supported
// Code using the interface
Этот интерфейс имеет методы для копирования/получения текста/изображений в/из буфера обмена: SetText
, GetText
, SetImage
, GetImage
.
Для других типов данных пользователь должен зарегистрировать формат данных, а затем записывать/считывать данные в/из буфера обмена: RegisterCustomFormat
, IsCustomFormatRegistered
, UnregisterCustomFormat
, HasCustomFormat
, GetCustomFormat
, SetCustomFormat
.
В своем вопросе вы говорите, что хотите скопировать данные в формате HTML в буфер обмена. Это можно перевести, чтобы зарегистрировать формат HTML, создать поток с вашими данными и вызвать SetCustomFormat, передав формат и поток.
Любой формат может использоваться и передаваться в/из буфера обмена с использованием вышеперечисленных методов интерфейса. Код ниже берет поток stream
и копирует его содержимое в буфер обмена с помощью ClipFormat
:
if TPlatformServices.Current.SupportsPlatformService(
IFMXExtendedClipboardService, Svc) then begin
if not Svc.IsCustomFormatRegistered(ClipFormat) then
Svc.RegisterCustomFormat(ClipFormat);
Svc.SetCustomFormat(ClipFormat, Stream);
end;
Формат должен быть зарегистрирован только один раз, поэтому вызов IsCustomFormatRegistered
предотвращает вызов RegisterCustomFormat
более одного раза.
Имя формата представляет собой простую строку и может быть любым. Приложения для копирования и вставки должны согласовать имя формата и формат данных (способ записи данных в потоке).
Написание данных в формате HTML может быть затруднено из-за стиля. Если взять всего лишь простую копию полного HTML-документа, возможно, он будет отображаться некорректно из-за стилей.
Если вы передаете данные через буфер обмена между двумя вашими приложениями, вы можете делать все, что хотите. Но передача данных между вашим приложением и другим (вы упомянули Microsoft Word в своем вопросе) намного сложнее.
В продуктах Microsoft и всех остальных на платформе Windows формат HTML в буфере обмена описан здесь.
Пример, который вы даете в своем вопросе после правильного форматирования, выглядит следующим образом:
Version:0.9
StartHTML:00000144
EndHTML:00000218
StartFragment:00000167
EndFragment:00000205
StartSelection:00000167
EndSelection:00000205
<!DOCTYPE><HTML><BODY><P>This is a <b>bold</b> html line</P></BODY></HTML>
Фактическая строка такая же, как и выше, с CRLF в конце каждой строки. Имя формата — «формат HTML».
Вы видите, что предложение This is a <b>bold</b> html line
должно быть окружено тегами HTML, чтобы сформировать действительный полный HTML-документ, и ему должен предшествовать заголовок, состоящий из нескольких пар keyword:value
. Значения смещаются, если строка. Ключевые слова говорят сами за себя. Строка представляет собой кодировку UTF8, которую можно сократить до ANSI, если вы используете объекты HTML для представления специальных символов.
Я написал функцию для построения всей строки:
function FormatHtmlForClipboard(const HtmlSrc : UTF8String) : UTF8String;
const
Header = 'Version:0.9' + #13#10 +
'StartHTML:00000000' + #13#10 +
'EndHTML:00000000' + #13#10 +
'StartFragment:00000000' + #13#10 +
'EndFragment:00000000' + #13#10 +
'StartSelection:00000000' + #13#10 +
'EndSelection:00000000' + #13#10;
var
BodyStart : Integer;
BodyEnd : Integer;
HdrLen : Integer;
begin
Result := Header;
BodyStart := Pos('<BODY>', String(HtmlSrc));
if BodyStart <= 0 then
raise Exception.Create('<BODY> tag not found');
Inc(BodyStart, 6);
BodyEnd := Pos('</BODY>', String(HtmlSrc));
if BodyEnd <= 0 then
raise Exception.Create('</BODY> tag not found');
HdrLen := Length(Header) - 1;
WriteNumberIntoString(HdrLen, 'StartHTML:', Result);
WriteNumberIntoString(HdrLen + Length(HtmlSrc), 'EndHTML:', Result);
WriteNumberIntoString(HdrLen + BodyStart, 'StartFragment:', Result);
WriteNumberIntoString(HdrLen + BodyEnd, 'EndFragment:', Result);
WriteNumberIntoString(HdrLen + BodyStart, 'StartSelection:', Result);
WriteNumberIntoString(HdrLen + BodyEnd, 'EndSelection:', Result);
Result := Result + HtmlSrc;
end;
procedure WriteNumberIntoString(
N : Integer;
const At : UTF8String;
var S : UTF8String);
var
I : Integer;
V : UTF8String;
begin
I := Pos(At, S);
if I <= 0 then
Exit;
I := I + Length(At);
V := UTF8String(Format('%08.8d', [N]));
Move(V[1], S[I], Length(V));
end;
При этом функция копирования данных в формате HTML в буфер обмена выглядит так:
procedure CopyHtmlToClipboard(const HtmlSrc : UTF8String);
var
Svc : IFMXExtendedClipboardService;
Stream : TStringStream;
HtmlData : UTF8String;
const
ClipFormat = 'HTML format'; // This is what Windows expect
// Maybe other platform want something else
begin
HtmlData := FormatHtmlForClipboard(HtmlSrc);
Stream := TStringStream.Create(HtmlData);
if TPlatformServices.Current.SupportsPlatformService(
IFMXExtendedClipboardService, Svc) then begin
if not Svc.IsCustomFormatRegistered(ClipFormat) then
Svc.RegisterCustomFormat(ClipFormat);
Svc.SetCustomFormat(ClipFormat, Stream);
end;
end;
Вы должны использовать функцию следующим образом:
procedure TForm1.Button1Click(Sender: TObject);
begin
CopyHtmlToClipboard(
'<!DOCTYPE><HTML><BODY><P>' +
'This is a <b>bold</b> html line' + //<== Your actual HTML text
'</P></BODY></HTML>');
end;
Красиво и замечательно объяснено. Позвольте мне проверить быстрый проект fmx и вернуться за любыми дополнительными указаниями. Большое спасибо.
Большое спасибо. Прекрасно работает. Моя проблема решилась моментально. Пометил это решение как принятый ответ. Просто небольшое дополнительное руководство. Если я использую этот код для символов Unicode, они копируются как ????. Я изменил все «AnsiString» на «String», но все равно не работает. Что еще можно сделать здесь, чтобы сделать его совместимым с Unicode? Большое спасибо.
@HilalSoftware О юникоде: как я уже сказал, HTML должен использовать UTF8. Вы можете использовать UTF8String вместо AnsiString. Вы должны преобразовать свои строки (Unicode UTF16) в UTF8, если они содержат специальные символы. Обратите внимание, что рендеринг HTML в целевом приложении (Word или другом) не обязательно будет принимать какие-либо символы в строке. Вы должны использовать сущности HTML (см. dev.w3.org/html5/html-author/charref).
Решено. Проблема связана с TStringStream, как описано здесь: stackoverflow.com/questions/2557358/… «TStringStream использует внутри системную кодировку ANSI по умолчанию (1 байт на символ)». Таким образом, в этом отношении изменение create следующим образом устраняет проблему Stream := TStringStream.Create(HtmlData,TEncoding.UTF8);// TEncoding.Unicode); Не удалось решить это как TEncoding.Unicode, но TEncoding.UTF8 выполняет свою работу.
Короче говоря, чтобы сделать описанные выше подпрограммы совместимыми с Unicode, мы должны: 1. Объявить все "AnsiString" как "String" 2. Создать поток с помощью TEncoding.UTF8 Большое спасибо за помощь. Нужно ли мне изменить ответ, чтобы сделать его совместимым с Unicode?
@HilalSoftware Вы можете изменить все AnsiString на UTF8String (в любом случае это AnsiString с CodePage 65001). Наиболее важно при вызове функции верхнего уровня CopyHtmlToClipboard передать строку в кодировке UTF8. Это будет работать, но не гарантирует, что символы, отличные от US-ASCII, будут правильно отображаться в целевом приложении. Как я уже сказал, лучше оставаться с AnsiString и использовать HTML Entities. Например, по-французски лето — это «été». Правильный способ представить это в HTML - "été". Я уверен, что вы легко найдете код Delphi для преобразования строки в AnsiString с объектами HTML.
@HilalSoftware Я отредактировал свой ответ, заменив все AnsiString на UTF8String. Если вам нужна дополнительная помощь с UTF8String или объектами HTML, задайте новый вопрос (сначала проведите самостоятельное исследование, чтобы обязательно написать «хороший вопрос» и избежать того, чтобы ваш вопрос был «закрыт как дубликат» или «нужны подробности и ясность»). ").
Спасибо за обновление вашего ответа. Ничего больше не требуется сейчас для проблемы под рукой. Совместимость с UTF8String является бонусом. Будьте благословлены за сотрудничество и помощь.
Мы не знаем, что вы копируете или куда вставляете. У нас нет подробностей о вашем коде. Пожалуйста, пройдите тур, чтобы узнать, как получить максимальную отдачу от этого сайта, а затем соответствующим образом отредактируйте вопрос.