2005-08-20

Copying an HTML-fragment to the Clipboard

Last year, I wrote an article for RealDevs, that showed how to Copy an HTML-fragment to the Clipboard in C#. However, when I recently tried to update the article (fixed a bug in the code), I got no response. I wrote several e-mails to the webmaster, but the article didn't get updated. So I decided to publish it here, on my blog.

Introduction

Applications like Word and Excel let you copy some text or tabular data to the clipboard in HTML-format, so that the formatting and visual style doesn't change when pasted. At work, I had to do something similar: our application uses a lot of grid-controls to display data, and we wanted the users to be able to select data from a grid, and copy it to the clipboard, in a format that would keep the tabular structure of the data. After some research, I found out that you can copy data to the clipboard in HTML-format, and I thought: this is it. Piece of cake. But it wasn't. Apparently, you have to pass a bit more information than just the HTML-code. Googling around gave me some hints how it should be done, but nothing really specific. So I started experimenting, until it worked correctly.
Tip: use a StringBuilder to build the HTML-code, instead of concatenating strings. StringBuilders are very efficient for building long strings (hence the name StringBuilder): they are a lot faster than strings, and allocate much less memory.

Possible applications

There are a lot of situations where you could use this:
  • Copying tabular data (and pasting it in Excel, Word or Outlook).
  • Copying formatted, visually styled data.
  • ...

The Code

using System;
using System.Text;
using System.Windows.Forms;

class Test {
public static void CopyHtmlToClipBoard(string html) {
   Encoding enc = Encoding.UTF8;

   string begin = "Version:0.9\r\nStartHTML:{0:000000}\r\nEndHTML:{1:000000}"
      + "\r\nStartFragment:{2:000000}\r\nEndFragment:{3:000000}\r\n";

   string html_begin = "<html>\r\n<head>\r\n"
      + "<meta http-equiv=\"Content-Type\""
      + " content=\"text/html; charset=" + enc.WebName + "\">\r\n"
      + "<title>HTML clipboard</title>\r\n</head>\r\n<body>\r\n"
      + "<!--StartFragment-->";

   string html_end = "<!--EndFragment-->\r\n</body>\r\n</html>\r\n";

   string begin_sample = String.Format(begin, 0, 0, 0, 0);

   int count_begin = enc.GetByteCount(begin_sample);
   int count_html_begin = enc.GetByteCount(html_begin);
   int count_html = enc.GetByteCount(html);
   int count_html_end = enc.GetByteCount(html_end);

   string html_total = String.Format(
      begin
      , count_begin
      , count_begin + count_html_begin + count_html + count_html_end
      , count_begin + count_html_begin
      , count_begin + count_html_begin + count_html
      ) + html_begin + html + html_end;

   DataObject obj = new DataObject();
   obj.SetData(DataFormats.Html, new System.IO.MemoryStream(
      enc.GetBytes(html_total)));
   Clipboard.SetDataObject(obj, true);
}
}

Example: copying a DataTable

StringBuilder html = new StringBuilder();
html.Append("<table>");
foreach ( DataRow row in table.Rows ) {
   html.Append("<tr>");
   foreach ( object o in row )
      html.AppendFormat("<td>{0}</td>", o);
   html.Append("</tr>");
}
html.Append("</table>");
Test.CopyHtmlToClipboard(html.ToString());