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: private static string 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 is string || 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: else return string.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: public virtual 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: return null;
26: }
And the HandleContextMenu actually handles some stuff instead of just raising an event:
1: public virtual void 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.
No comments:
Post a Comment