I'm writing a UI app that allows the user to paste a grid of numbers into it that will be sent to a remote server. Client could be WPF or Silverlight.

I've tried a `Grid` of `Textbox` controls using `DataObject.AddPastingHandler` to hook the user pasting input, converting from tab-separated rows in a string to `Textbox` controls. This takes 12 seconds (!) to update itself after the user pastes in just 122x161 cells from Excel which is unacceptably slow. Dissecting the string takes 0.1s, adding the rows and columns takes 1s, constructing and inserting the `TextBox` controls takes 2s and the remainder of the 12s appears to be spent drawing the `TextBox` controls for the first time.

I've also tried `DataGrid` but it doesn't seem to handle 2D arrays well, preferring a 1D array of objects that it dissects for properties using reflection.

Now I'm contemplating just using a `Canvas` and drawing the numbers myself, which seems insane. Is there a simple way to use built-in WPF or Silverlight controls to get this done without painful lag for the user?

**EDIT**

I've since identified `TextBox` as the culprit because it is insanely slow. Here's some F# code that illustrates the problem (replace `TextBox` with `TextBlock` and its 40x faster, which is still orders of magnitude slower than it should be but is acceptable in this instance):

```
open System.Windows

let app = Application()

let readClipboard() =
  let data = (Clipboard.GetData "Text") :?> string
  [|for row in data.Split[|'\n'|] do
    match row.Split[|'\t'|] with
    | [||] | [|""|] -> ()
    | row -> yield row|]

[<System.STAThreadAttribute>]
do
  let grid = Controls.Grid()
  let row = Controls.RowDefinition()
  Controls.RowDefinition() |> grid.RowDefinitions.Add
  Controls.ColumnDefinition() |> grid.ColumnDefinitions.Add
  let add i j ctrl =
    Controls.Grid.SetRow(ctrl, i)
    Controls.Grid.SetColumn(ctrl, j)
    grid.Children.Add ctrl |> ignore
  let paste() =
    let timer = System.Diagnostics.Stopwatch.StartNew()
    let data = readClipboard()
    printfn "Read clipboard %fs" timer.Elapsed.TotalSeconds
    let rows = data.Length
    let cols = data |> Array.fold (fun n xs -> xs.Length |> max n) 0
    printfn "%dx%d" rows cols
    grid.RowDefinitions.Clear()
    grid.ColumnDefinitions.Clear()
    grid.Children.Clear()
    for row in 1..rows do
      Controls.RowDefinition(Height=GridLength 24.0) |> grid.RowDefinitions.Add
    for col in 1..cols do
      Controls.ColumnDefinition(Width=GridLength 64.0) |> grid.ColumnDefinitions.Add
    printfn "Add rows and columns complete %fs" timer.Elapsed.TotalSeconds
    for i in 0..rows-1 do
      for j in 0..data.[i].Length-1 do
        Controls.TextBox(Text=data.[i].[j]) |> add i j
    printfn "Insert complete %fs" timer.Elapsed.TotalSeconds
    Media.CompositionTarget.Rendering.Add(fun _ ->
      printfn "Next Rendering event at %fs" timer.Elapsed.TotalSeconds
      app.Shutdown())
  let scroll = Controls.ScrollViewer(Content=grid)
  scroll.HorizontalScrollBarVisibility <- Controls.ScrollBarVisibility.Visible
  let window = Window(Content=scroll)
  window.Focusable <- true
  window.Focus() |> ignore
  window.PreviewKeyDown.Add(fun e ->
    let ctrl = Input.ModifierKeys.Control
    if Input.Keyboard.Modifiers &&& ctrl = ctrl then
      if e.Key = Input.Key.V then
        paste())
  app.Run window |> ignore

```