Русские документы
Ежедневные компьютерные новости RSS rusdoc.ru  Найти :
http://www.rusdoc.ru. Версия для печати.

Работа с буфером обмена

Раздел: Programming / .NET Framework @ 05.05.2008 | Ключевые слова: .NET буфер обмена clipboard

Источник: habrahabr

Прочитав заголовок, Вы, наверное, очень удивились. Ведь казалось бы, все предельно просто — есть объект 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> интервал между строками просто убийственный.


Вернуться в раздел: Programming / .NET Framework
© Copyright 1998-2012 Александр Томов. All rights reserved.