Custom Visualizers

A lot of people have asked for the ability to write custom visualizers for LINQPad. There are plenty of obvious uses:

Well, I'm pleased to announce you can now do just that. Writing a visualizer is easy to do: simply write an extension method that accepts the kind of object that you want to visualize, and in that method, .Dump() a WPF or Windows Forms control that displays what you want! LINQPad will display your UI control in an output panel and users can interact with it as they would normally.

And if you write your extension method in My Extensions, you'll be able to call it from any query.

PanelManager

Calling .Dump() on a WPF or Windows Forms control is a easy shortcut for displaying custom UI content in LINQPad. However, you can get more control over the process by calling the following static methods on LINQPad's PanelManager class:

public static OutputPanel DisplayControl    (Control c,   string panelTitle = null);
public static OutputPanel DisplayWpfElement (UIElement e, string panelTitle = null);

The advantage of these methods over calling .Dump() is that you get back an OutputPanel object (more on this later). Here's a simple example of displaying a button:

(You'll need to import System.Windows.Forms.dll - press F4 or more simply use the smart-tag if you have autocompletion.)

Here's the same thing done with WPF:

var button = new System.Windows.Controls.Button { Content = "Hello, world!" };
PanelManager.DisplayWpfElement (button, "My Button");

These controls are 'live', meaning you can interact with them after the query completes. For instance:

var button = new System.Windows.Controls.Button { Content = "Hello, world!" };
button.Click += (sender, args) => button.Content = "I've just been clicked!";
PanelManager.DisplayWpfElement (button, "My Button");

Putting this practical use, the following extension method uses a Windows Forms DataGridView to visualize a DataTable, allowing limitless rows (rather like LINQPad's results-to-grid mode):

public static class Extensions
{
   public static void DisplayInGrid (this DataTable dt)
   {
      var grid = new DataGridView { DataSource = dt };
      PanelManager.DisplayControl (grid);
   }
}

StackWpfElement

Controls that you display with DisplayControl or DisplayWpfElement fill and monopolize the entire panel. To display multiple elements on a single panel, there's another method in PanelManager called StackWpfElement.

public static OutputPanel StackWpfElement (UIElement e, string panelTitle = null);

StackWpfElement docks the UIElement to the top of the panel. Calling this repeatedly results in a Dump-like effect:

PanelManager.StackWpfElement (new Label  { Content = "Hello" }, "WPF Control Gallery");
PanelManager.StackWpfElement (new Button { Content = "Click" }, "WPF Control Gallery");
PanelManager.StackWpfElement (new Expander { Header = "More" }, "WPF Control Gallery");
PanelManager.StackWpfElement (new GroupBox { Header = "Data" }, "WPF Control Gallery");

Again, each element can respond to events after the query has finished executing.

To give a practical example, here's how you could write a method to dump IObservable objects, showing each observable change in-place:

public static IObservable<T> DumpLive<T> (this IObservable<T> obs)
{
   var presenter = new ContentPresenter ();
   OutputPanel outPanel = PanelManager.StackWpfElement (presenter, "Live &Observables");
       
   var subscription = obs
       .ObserveOn (presenter.Dispatcher)
       .Subscribe (val => presenter.Content = val, ex => presenter.Content = ex.Message);

   outPanel.PanelClosed += delegate { subscription.Dispose (); };
   return obs;
}            

(LINQPad in fact includes a DumpLive extension method for observables that works very much like this.)

Note that if you're looking for a Windows Forms equivalent of StackWpfElement, there is none: docking lots of WinForms controls gets slow pretty quickly! (This is why calling .Dump() on a Windows Forms control is equivalent to calling PanelManager.DisplayControl whereas calling .Dump() on a WPF element is equivalent to calling PanelManager.StackWpfElement.)

OutputPanel and GetOutputPanel

The PanelManager methods return an OutputPanel object which exposes properties relating to the output panel. It also exposes events that you can hook into, such as QueryEnded and PanelClosed (we'll see later how these can be useful). Calling GetControl or GetWpfElement on the OutputPanel returns the control or WPF element that you created it with.

You can obtain an OutputPanel instance for a panel that you previously created - simply call PanelManager.GetOutputPanel. With this method, we could reimplement the StackWpfElement method ourselves:

public static OutputPanel StackWpfElement (System.Windows.UIElement e, string panelTitle = null)
{
   if (string.IsNullOrEmpty (panelTitle)) panelTitle = "&Custom";

   StackPanel stackContainer;
   var panel = PanelManager.GetOutputPanels (panelTitle)
                           .FirstOrDefault (c => c.GetWpfElement() is StackPanel);
   if (panel == null)
   {
      stackContainer = new StackPanel();
      panel = PanelManager.DisplayWpfElement (stackContainer, panelTitle);
   }
   else
      stackContainer = (StackPanel) panel.GetWpfElement();

   stackContainer.Children.Add (e);
   return panel;
}		

(LINQPad's StackWpfElement method is slightly more sophisticated in that it wraps the StackPanel in a ScrollViewer to enable vertical scrolling).

Packaging and Deployment

Using PanelManager from within LINQPad is great for testing ideas. For more sophisticated visualizers, however, you'll probably want to create a Visual Studio project. The good news is that you can create a project in VS that references LINQPad.exe, build a .DLL, and just drop it in to LINQPad's plug-in folder. Your visualizer - along with any extensions that you define - will be automatically visible to all queries!

By default, LINQPad's plugins folder is My Documents\LINQPad Plugins . You can change this in Edit | Preferences. Any DLLs or EXEs that you put this folder are automatically referenced by all queries.

So, here are the steps to creating and deploying a visualizer:

  1. Create a class library project in Visual Studio, and add a reference to Any-CPU build of LINQPad 4.x*.
  2. Write the desired extension methods that call PanelManager's methods.
  3. Copy the DLL and any dependencies into LINQPad's plugins folder  (My Documents\LINQPad Plugins by default)

*Your plug-in will work with both the X86 version and the Any-CPU version of LINQPad if you do this.

Advanced Features

Life Extension Tokens

Sometimes it's useful to keep a query in a "running" state for a while after the user's code has finished executing. An example is when dumping observables - you want the query to show as running until all observables have disengorged. To achieve this, there's a new method in the Util class - Util.GetQueryLifeExtensionToken. Just dispose the object that it returns when you no longer need the query's life to be extended.

Note that the user may cancel a query extended on a life extension token. You can respond to this by handling the OutputPanel's QueryEnded event, and checking the OutputPanel's IsCanceled property.

Over 1 million downloads

LINQPad

LINQPad

Delicious Bookmark this on Delicious

Follow LINQPad on Facebook

More

LINQPad Forum

LINQPad + Mindscape LightSpeed

LINQPad + DevForce

LINQPad + DevArt

AWS with LINQPad

Method Chaining and Debugging

LINQBugging WinForms/WPF

Terry's LINQPad Extensions

Videos

Writing queries with LINQPad
   - Hi-res QuickTime

LINQPad + EF

V2 and Beyond

Dimecasts LINQPad Intro

LINQPad + OData


LINQPad was written by Joseph Albahari
© 2007-2014. Site hosted in Windows Azure