Прочитав заголовок, Вы, наверное, очень удивились. Ведь казалось бы, все предельно просто — есть объект Clipboard, есть его статические методы (вроде SetText/SetData и GetText/GetData), чего еще для счастья нужно?
Однако, на практике все просто лишь до тех пор, пока Вы копируете или вставляете только базовые объекты, вроде текста или bitmap-картинки. Что же случается, когда нужно оперировать более сложной структурой?
Лично я недавно столкнулся с необходимостью копировать "гиперссылки", которые потом должны легко вставляться в Word/Outlook/любую другую программу. Причем, не полагаясь на то, что программа-получатель сама определит во вставленном тексте ссылку и не преобразует в нужный формат. Поэтому и рассмотрим работу на примере гиперссылки (алгоритм действий для любого другого формата будет аналогичным).
Итак, с чего же начать?
Для начала, необходимо выяснить, в каком формате должны быть те или иные данные. Выяснить это можно разными способами, но самый наглядный, пожалуй, — это небольшая утилита
ClipSpy. Достаточно просто скопировать откуда угодно нужный объект и определить, какие форматы при этом создаются в буфере обмена и что они в себе содержат.
Как показывает эксперимент, для гиперссылки обычно создаются форматы TEXT, UNICODETEXT и HTML. Первый и второй содержат в себе текстовое представление гиперссылки (то, что будет вставлено, например, в блокнот). Формат HTML же представляет для нас наибольший интерес — в нем содержится html-фрагмент, который и будет вставлен в целевую программу как гиперссылка. Выглядит он примерно так:
Version:1.0
StartHTML:XXXXXXXX
EndHTML:YYYYYYYY
StartFragment:ZZZZZZZZ
EndFragment:TTTTTTTT
Some target
...
где XXXXXXXX — смещение начала html-содержимого (фактически, длина заголовка),
YYYYYYYY — соответственно, смещение конца html-содержимого (фактически, длина всего контента),
ZZZZZZZZ и TTTTTTTT — начало и конец фрагмента с гиперссылкой.
Что ж, с форматом определились. Теперь сформировать нужный контент — дело техники. А мы пока что рассмотрим, как отправить нужное содержимое (в нескольких форматах) в буфер обмена.
Гугл (любимый всеми советчик) дает, как правило, следующее решение:
void CopyLink(Uri target, string title)
{
var htmlContent = MakeLink(target, title);
var data = new DataObject();
data.SetData(DataFormats.Text, true, target.ToString());
data.SetData(DataFormats.Unicode, true, html_content);
data.SetData(DataFormats.Html, true, formatted_buffer);
Clipboard.SetDataObject(data, true);
}
И все бы было хорошо, если бы не одно "но" — такое решение совершенно не позволяет управлять кодировкой при задании html-содержимого. Поэтому ссылки, например, с русским текстом в названии вставляются совершенно неправильно, что приводит к разным интересным эффектам, вплоть до «вылетания» программы-получателя.
На этой ноте идея написания чисто управляемого кода накрылась медным тазом, посему пришлось обращаться к WinAPI. В итоге вышло не так красиво, зато весьма работоспособно:
[DllImport("user32.dll")]
private static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);
[DllImport("user32.dll")]
private static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll")]
private static extern bool EmptyClipboard();
[DllImport("user32.dll")]
private static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
private static extern uint RegisterClipboardFormat(string lpszFormat);
void CopyLink(Uri target, string title)
{
var htmlContent = MakeLink(target, title, Encoding.UTF8);
if (!OpenClipboard(IntPtr.Zero))
throw new Exception("Failed to open clipboard");
EmptyClipboard();
var pText = IntPtr.Zero;
var pHtml = IntPtr.Zero;
try
{
pText = Marshal.StringToHGlobalAnsi(target.ToString());
SetClipboardData(1 /* CF_TEXT */, pText); // Для TEXT и UNICODETEXT
var bytes = Encoding.UTF8.GetBytes(htmlContent);
pHtml = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, pHtml, bytes.Length);
SetClipboardData(RegisterClipboardFormat(DataFormats.Html), pHtml);
}
finally
{
CloseClipboard();
if (pText != IntPtr.Zero)
Marshal.FreeHGlobal(pText);
if (pHtml != IntPtr.Zero)
Marshal.FreeHGlobal(pHtml);
}
}
Ну, и для полноты картины исходный текст MakeLink:
string MakeLink(Uri target, string title, Encoding encoding)
{
const int numberLengthWithCr = 11;
var htmlIntro = "\n\n\n\n\n";
var htmlOutro = "\n\n";
var htmlLink = string.Format("{1}", target, title);
var startHtmlIndex = 57 + 4 * numberLengthWithCr;
var startFragmentIndex = startHtmlIndex + encoding.GetByteCount(htmlIntro);
var endFragmentIndex = startFragmentIndex + encoding.GetByteCount(htmlLink);
var endHtmlIndex = endFragmentIndex + encoding.GetByteCount(htmlOutro);
var buff = new StringBuilder();
buff.AppendFormat("Version:1.0\n");
buff.AppendFormat("StartHTML:{0:0000000000}\n", startHtmlIndex);
buff.AppendFormat("EndHTML:{0:0000000000}\n", endHtmlIndex);
buff.AppendFormat("StartFragment:{0:0000000000}\n", startFragmentIndex);
buff.AppendFormat("EndFragment:{0:0000000000}\n", endFragmentIndex);
buff.Append(htmlIntro).Append(htmlLink).Append(htmlOutro);
return buff.ToString();
}
P.S. Кстати, вопрос: есть ли возможность писать код нормально, без отключения автоформатирования и ручной расстановки разрывов строк? А то с автоформатированием в < pre> интервал между строками просто убийственный.