Write your own Linq query viewer

Sometimes you just want to see data in a tabular format. It could be stored in a server somewhere in the cloud, in a SQL server, a FoxPro database, an EntityFramework object, or just a local object. You’d like to see all columns, without having to write code to select each column.


Linq allows you to create a query of the data, regardless of where it lives. The resulting query is strongly typed, (i.e. a column each for Name and Age, with the appropriate data types).


However, it’s not easy to write code that works generically with any Linq query: each one can have a different shape, like the number and datatype of the columns.


Below is a sample of C# code (VB code is here) that shows a general purpose class called “Browse”. (FoxPro has a command called Browse, which just presents all the data in a navigable grid format.) Try using the various sample queries, or create your own.


You can just create an instance of Browse, passing in the Linq query as an argument. The Browse figures out the rows and columns from the Linq query using reflection.


The sample also shows how to use a value converter, which will take strings that are too long and truncate them to fit in a browse. The value converter also adds commas to large numbers, like “1,000,000”.


If you want to show real database data, do this: Project->Add New Item-> Ado.Net Entity Model. The Entity Model Data Wizard starts up. Generate from Database. Give it a connection to your data. Select a table. Fiddle with the Entity and table name in the code below and/or the Entity designer (intellisense will work)


Start Visual Studio: File->New->Project->C#->WPF Application. Name it WpfApplication1

Replace the MainWindow.xaml.cs with the code below


See also:

Use DataTemplates and WPF in code to create a general purpose LINQ Query results display

Use LINQ to Foxpro or any SQL data and display it in a WPF form using DataSetExtensions and DataRowExtensions

Path Markup Syntax

WPF: How to create Styles in code/and magical Content


<Sample Code>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.Collections;

using System.Data.Objects.DataClasses;

using System.Reflection;

using System.Diagnostics;

using System.ComponentModel;

namespace WpfApplication1


    /// <summary>

    /// Interaction logic for MainWindow.xaml

    /// </summary>

    public partial class MainWindow : Window


        public MainWindow()



            this.Loaded += new RoutedEventHandler(this.Window_Loaded);


        private void Window_Loaded(object sender, RoutedEventArgs e)


            var dockPanel = new DockPanel();

            var btnRefresh = new Button()


                Content = "_Refresh",

      HorizontalAlignment = System.Windows.HorizontalAlignment.Left,

                MaxWidth = 100


            DockPanel.SetDock(btnRefresh, Dock.Top);

            this.Content = dockPanel;

            btnRefresh.Click += delegate




                //var ent = new MyEntities();

                //var br = new Browse(from logs in ent.Customers.ToArray() // ToArray so not readonly

                // //where logs.TimeStamp > DateTime.Now.AddDays(-4)

                // orderby logs.ID descending

                // select logs

                // );

                var br = new Browse(from proc in System.Diagnostics.Process.GetProcesses() select proc);

                //var br = new Browse(from file in System.IO.Directory.GetFiles(

                // Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

                // )

                // select new System.IO.FileInfo(file)

                // );

                //var br = new Browse(from typ in this.GetType().GetMembers() select typ);

                //var br = new Browse(from ProcessThread thrd in System.Diagnostics.Process.GetCurrentProcess().Threads select thrd );

  //var br = new Browse(from ProcessModule mod in Process.GetCurrentProcess().Modules select mod);



            btnRefresh.RaiseEvent(new RoutedEventArgs(Button.ClickEvent, this));



    public class Browse : ListView


        public Browse(IEnumerable query)


            this.Margin = new System.Windows.Thickness(8);

            this.ItemsSource = query;

            var gridvw = new GridView();

            this.View = gridvw;

            var ienum = query.GetType().GetInterface(typeof(IEnumerable<>).FullName);

            var members = ienum.GetGenericArguments()[0].GetMembers().Where(m => m.MemberType == System.Reflection.MemberTypes.Property);

            foreach (var mbr in members)


                if (mbr.DeclaringType == typeof(EntityObject)) // if using Entity framework, filter out EntityKey, etc.




              var gridcol = new GridViewColumn();

                var colheader = new GridViewColumnHeader() { Content = mbr.Name };

                gridcol.Header = colheader;

                colheader.Click += new RoutedEventHandler(colheader_Click);


                // now we make a dataTemplate with a Stackpanel containing a TextBlock

                // The template must create many instances, so factories are used.

                var dataTemplate = new DataTemplate();

                gridcol.CellTemplate = dataTemplate;

                var stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));

                stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);

             var txtBlkFactory = new FrameworkElementFactory(typeof(TextBlock));

                var binder = new Binding(mbr.Name)


                    Converter = new MyValueConverter() // truncate things that are too long, add commas for numbers


                txtBlkFactory.SetBinding(TextBlock.TextProperty, binder);


                txtBlkFactory.SetBinding(TextBlock.ToolTipProperty, new Binding(mbr.Name)); // the tip will have the non-truncated content

                txtBlkFactory.SetValue(TextBlock.FontFamilyProperty, new FontFamily("courier new"));

                txtBlkFactory.SetValue(TextBlock.FontSizeProperty, 10.0);

                dataTemplate.VisualTree = stackPanelFactory;


            // now create a style for the items

            var style = new Style(typeof(ListViewItem));

            style.Setters.Add(new Setter(ForegroundProperty, Brushes.Blue));

            var trig = new Trigger()


                Property = IsSelectedProperty,// if Selected, use a different color

                Value = true


            trig.Setters.Add(new Setter(ForegroundProperty, Brushes.Red));

            trig.Setters.Add(new Setter(BackgroundProperty, Brushes.Cyan));


            this.ItemContainerStyle = style;


        private ListSortDirection _LastSortDir = ListSortDirection.Ascending;

        private GridViewColumnHeader _LastHeaderClicked = null;

        void colheader_Click(object sender, RoutedEventArgs e)


            GridViewColumnHeader gvh = sender as GridViewColumnHeader;

            if (gvh != null)


                var dir = ListSortDirection.Ascending;

                if (gvh == _LastHeaderClicked) // if clicking on already sorted col, reverse dir


                    dir = 1 - _LastSortDir;




                    var dataView = CollectionViewSource.GetDefaultView(this.ItemsSource);


                    var sortDesc = new SortDescription(gvh.Content.ToString(), dir);



                    if (_LastHeaderClicked != null)


                        _LastHeaderClicked.Column.HeaderTemplate = null; // clear arrow of prior column



                    _LastHeaderClicked = gvh;

                    _LastSortDir = dir;


                catch (Exception)


                    // some types aren't sortable




        void SetHeaderTemplate(GridViewColumnHeader gvh)


            // now we'll create a header template that will show a little Up or Down indicator

            var hdrTemplate = new DataTemplate();

  var dockPanelFactory = new FrameworkElementFactory(typeof(DockPanel));

            var textBlockFactory = new FrameworkElementFactory(typeof(TextBlock));

            var binder = new Binding();

            binder.Source = gvh.Content; // the column name

            textBlockFactory.SetBinding(TextBlock.TextProperty, binder);

            textBlockFactory.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center);


       // a lot of code for a little arrow

            var pathFactory = new FrameworkElementFactory(typeof(Path));

            pathFactory.SetValue(Path.FillProperty, Brushes.DarkGray);

            var pathGeometry = new PathGeometry();

            pathGeometry.Figures = new PathFigureCollection();

            var pathFigure = new PathFigure();

            pathFigure.Segments = new PathSegmentCollection();

            if (_LastSortDir != ListSortDirection.Ascending)

            {//"M 4,4 L 12,4 L 8,2"

                pathFigure.StartPoint = new Point(4, 4);

                pathFigure.Segments.Add(new LineSegment() { Point = new Point(12, 4) });

                pathFigure.Segments.Add(new LineSegment() { Point = new Point(8, 2) });



            {//"M 4,2 L 8,4 L 12,2"

                pathFigure.StartPoint = new Point(4, 2);

                pathFigure.Segments.Add(new LineSegment() { Point = new Point(8, 4) });

                pathFigure.Segments.Add(new LineSegment() { Point = new Point(12, 2) });



            pathFactory.SetValue(Path.DataProperty, pathGeometry);


            hdrTemplate.VisualTree = dockPanelFactory;

            gvh.Column.HeaderTemplate = hdrTemplate;



    public class MyValueConverter : IValueConverter


        private const int maxwidth = 70;

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)


            if (null != value)


                Type type = value.GetType();

                //trim len of long strings. Doesn't work if type has ToString() override

                if (type == typeof(string))


                    var str = value.ToString().Trim();

                    var ndx = str.IndexOfAny(new[] { '\r', '\n' });

                    var lenlimit = maxwidth;

                    if (ndx >= 0)


                        lenlimit = ndx - 1;


                    if (ndx >= 0 || str.Length > lenlimit)


                        value = str.Substring(0, lenlimit);




                        value = str;



                else if (type == typeof(Int32))


                    value = ((int)value).ToString("n0"); // Add commas, like 1,000,000


                else if (type == typeof(Int64))


                    value = ((Int64)value).ToString("n0"); // Add commas, like 1,000,000



            return value;


        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)


            throw new NotImplementedException();




</Sample Code>