title: WPF/Silverlight Control for viewing 122x161 grid of numbers
body: <p>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.</p>
  
  <p>I've tried a <code>Grid</code> of <code>Textbox</code> controls using <code>DataObject.AddPastingHandler</code> to hook the user pasting input, converting from tab-separated rows in a string to <code>Textbox</code> 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 <code>TextBox</code> controls takes 2s and the remainder of the 12s appears to be spent drawing the <code>TextBox</code> controls for the first time.</p>
  
  <p>I've also tried <code>DataGrid</code> but it doesn't seem to handle 2D arrays well, preferring a 1D array of objects that it dissects for properties using reflection.</p>
  
  <p>Now I'm contemplating just using a <code>Canvas</code> 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?</p>
  
  <p><strong>EDIT</strong></p>
  
  <p>I've since identified <code>TextBox</code> as the culprit because it is insanely slow. Here's some F# code that illustrates the problem (replace <code>TextBox</code> with <code>TextBlock</code> and its 40x faster, which is still orders of magnitude slower than it should be but is acceptable in this instance):</p>
  
  <pre><code>open System.Windows
  
  let app = Application()
  
  let readClipboard() =
    let data = (Clipboard.GetData "Text") :?&gt; string
    [|for row in data.Split[|'\n'|] do
      match row.Split[|'\t'|] with
      | [||] | [|""|] -&gt; ()
      | row -&gt; yield row|]
  
  [&lt;System.STAThreadAttribute&gt;]
  do
    let grid = Controls.Grid()
    let row = Controls.RowDefinition()
    Controls.RowDefinition() |&gt; grid.RowDefinitions.Add
    Controls.ColumnDefinition() |&gt; grid.ColumnDefinitions.Add
    let add i j ctrl =
      Controls.Grid.SetRow(ctrl, i)
      Controls.Grid.SetColumn(ctrl, j)
      grid.Children.Add ctrl |&gt; 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 |&gt; Array.fold (fun n xs -&gt; xs.Length |&gt; 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) |&gt; grid.RowDefinitions.Add
      for col in 1..cols do
        Controls.ColumnDefinition(Width=GridLength 64.0) |&gt; 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]) |&gt; add i j
      printfn "Insert complete %fs" timer.Elapsed.TotalSeconds
      Media.CompositionTarget.Rendering.Add(fun _ -&gt;
        printfn "Next Rendering event at %fs" timer.Elapsed.TotalSeconds
        app.Shutdown())
    let scroll = Controls.ScrollViewer(Content=grid)
    scroll.HorizontalScrollBarVisibility &lt;- Controls.ScrollBarVisibility.Visible
    let window = Window(Content=scroll)
    window.Focusable &lt;- true
    window.Focus() |&gt; ignore
    window.PreviewKeyDown.Add(fun e -&gt;
      let ctrl = Input.ModifierKeys.Control
      if Input.Keyboard.Modifiers &amp;&amp;&amp; ctrl = ctrl then
        if e.Key = Input.Key.V then
          paste())
    app.Run window |&gt; ignore
  </code></pre>