Quantcast
Viewing all articles
Browse latest Browse all 13

Copying Datagrid data to Clipboard through Context Menu

In my last post, I talked about adding context menu to Silverlight DataGrid. One thing that always frustrated our users was that they could not copy contents of a datagrid in our application to the clipboard.

Here is the example (clipboard is accessible through javascript only in IE, mind you):



I will make the full code available on Monday. Below, is a runthrough of how this was done.

There are plenty of examples on how to access the clipboard in Silverlight through javascript (for IE, anyways). Here is the code that I used, from Jeff Wilcox's example:


1:          public static void SetText(string text)
2:          {
3:              var clipboardData = (ScriptObject)HtmlPage.Window.GetProperty("clipboardData");
4:              if (clipboardData != null)
5:              {
6:                  bool success = (bool)clipboardData.Invoke("setData", "text", text);
7:                  if (!success)
8:                  {
9:                      HtmlPage.Window.Alert(ClipboardFailure);
10:                  }
11:              }
12:              else
13:              {
14:                  HtmlPage.Window.Alert("clipboard not available");
15:              }
16:          }

You can always ctrl+C to copy contents of a textbox or textblock to clipboard, but DataGrid does not allow that. To do this, I take the cell in context, loop through it's children recursively, and write out the contents of the child if it is TextBlock or TextBox:



1:          private static string GetText(UIElement uie)
2:          {
3:              string ret = string.Empty;
4:              if (uie != null)
5:              {
6:                  if (uie is TextBlock)
7:                      ret = (uie as TextBlock).Text;
8:                  else if (uie is TextBox)
9:                      ret = (uie as TextBox).Text;
10:                  else if (uie is Panel)
11:                  {
12:                      foreach (var element in (uie as Panel).Children)
13:                          ret += GetText(element);
14:                  }
15:              }
16:              return ret;
17:          }


Now, what if we want to have a menu command that copies whole row data? I have a generic serializer that takes the datacontext of the row as object and, using reflection, recursively creates a string representation:



   1:  privatestaticstring Serialize(object data)
   2:          {
   3:  if (data != null)
   4:              {
   5:                  var sb = new StringBuilder();
   6:                  var props = data.GetType().GetProperties();
   7:                  sb.Append(data.GetType().Name);
   8:  foreach (var prop in props)
   9:                  {
  10:                      var o = prop.GetValue(data, null);
  11:                      sb.Append(Environment.NewLine).Append(prop.Name).Append(" = ");
  12:  if (o == null || o isstring || o is DateTime || o.GetType().IsPrimitive)
  13:                          sb.Append(o);
  14:  else
  15:                          sb.Append(Serialize(o).Replace(Environment.NewLine, Environment.NewLine + "\t"));
  16:                  }
  17:  return sb.ToString();
  18:              }
  19:  elsereturnstring.Empty;
  20:          }

How do we get to a reference to the cell? When context menu is requested, the DataGrid finds the cell where the right-click occurred, and keeps a reference to in a private field, to be used when the menu selection occurs. So the GetContextMenuContent method changes like so:



   1:  publicvirtual List<ContextMenuItemInfo> GetContextMenuContent(System.Windows.Browser.HtmlEventArgs e)
   2:          {
   3:              Point curr = new Point(e.OffsetX, e.OffsetY);
   4:              _CellInContext = null;
   5:  for (var i = 0; i < presenter.Children.Count; i++)
   6:              {
   7:                  var row = (DataGridRow)presenter.Children[i];
   8:  if (ContextMenuInterceptor.IsPointWithinBounds(curr, row))
   9:                  {
  10:  foreach (var col in Columns)
  11:                      {
  12:                          var cell = col.GetCellContent(row.DataContext).Parent as DataGridCell;
  13:  if (cell != null&& ContextMenuInterceptor.IsPointWithinBounds(curr, cell))
  14:                          {
  15:                              _CellInContext = cell;
  16:  break;
  17:                          }
  18:                      }
  19:   
  20:  foreach (ContextMenuItemInfo m in ContextMenuList)
  21:                          m.Tag = row;
  22:  return ContextMenuList;
  23:                  }
  24:              }
  25:  returnnull;
  26:          }

And the HandleContextMenu actually handles some stuff instead of just raising an event:



   1:  publicvirtualvoid HandleContextMenuClick(object sender, ContextMenuClickEventArgs e)
   2:          {
   3:  switch (e.ClickedItem.Key)
   4:              {
   5:  case CMD_COPY_ROW:
   6:                      Clipboard.SetText(Serialize(((DataGridRow)e.ClickedItem.Value).DataContext));
   7:  break;
   8:  case CMD_COPY_CELL:
   9:  if (_CellInContext != null)
  10:                          Clipboard.SetText(GetText(_CellInContext.Content as UIElement));
  11:  break;
  12:  default:
  13:  if (ContextMenuClicked != null)
  14:                          ContextMenuClicked(sender, e);
  15:  break;
  16:              }
  17:          }

Both of these have worked very well for our application. Both "Copy Cell Content" and "Copy Row Data" are context menu commands in all our datagrids.

Viewing all articles
Browse latest Browse all 13

Trending Articles