Search code examples
wpfdatagridsparqldotnetrdf

SPARQL results in WPF (binding DataGrid)


I use dotnetrdf and I would like to display results from query in WPF. This is my function in ViewModel. I have DataTable which I use next in my view.

        //Results
        SparqlResultSet results;
        DataTable table;
        //Define a remote endpoint
        //Use the DBPedia SPARQL endpoint with the default Graph set to DBPedia
        SparqlRemoteEndpoint endpoint = new SparqlRemoteEndpoint(new Uri("http://dbpedia.org/sparql"), "http://dbpedia.org");

        //Make a SELECT query against the Endpoint
        results = endpoint.QueryWithResultSet("PREFIX dbo: <http://dbpedia.org/ontology/> PREFIX : <http://dbpedia.org/resource/> SELECT ?film ?producerName WHERE {    ?film dbo:director :Andrzej_Wajda .    ?film dbo:producer ?producerName . }");
        foreach (SparqlResult result in results)
        {
           Console.WriteLine(result.ToString());
        }

        table = new DataTable();
        DataRow row;

        switch (results.ResultsType)
        {
            case SparqlResultsType.VariableBindings:
                foreach (String var in results.Variables)
                {
                    table.Columns.Add(new DataColumn(var, typeof(INode)));
                }

                foreach (SparqlResult r in results)
                {
                    row = table.NewRow();

                    foreach (String var in results.Variables)
                    {
                        if (r.HasValue(var))
                        {
                            row[var] = r[var];
                        }
                        else
                        {
                            row[var] = null;
                        }
                    }
                    table.Rows.Add(row);
                }
                break;
            case SparqlResultsType.Boolean:
                table.Columns.Add(new DataColumn("ASK", typeof(bool)));
                row = table.NewRow();
                row["ASK"] = results.Result;
                table.Rows.Add(row);
                break;

            case SparqlResultsType.Unknown:
            default:
                throw new InvalidCastException("Unable to cast a SparqlResultSet to a DataTable as the ResultSet has yet to be filled with data and so has no SparqlResultsType which determines how it is cast to a DataTable");
        }

In WPF I use code:

<DataGrid ItemsSource="{Binding Table}" AutoGenerateColumns="True"/>

Binding work very well and finally I get dynamic created columns and DataGrid, but only header. I don't get value of rows. In this example there are rows, but without values.

enter image description here

Where is my problem ? Thanks a lot for help :)


Solution

  • This question is not really much to do with dotNetRDF other than the starting data is from a SPARQL query but is really about how DataGrid behaves when the ItemsSource is a DataTable and AutoGenerateColumns is used.

    The basic problem is that the DataGrid does not know how to display arbitrary data types and it just generates DataGridTextColumn for the auto-generated columns. Unfortunately this only supports String values or types for which an explicit IValueConverter is applied AFAIK, it doesn't call ToString() because conversions are expected to be two way hence why you see empty columns (thanks to this question for explaining this).

    So actually getting values to be appropriately displayed requires us to create a DataTemplate for our columns to use. However as you want to use AutoGenerateColumns you need to add a handler for the AutoGeneratingColumns event like so:

    <DataGrid ItemsSource="{Binding Table}" AutoGenerateColumns="True"
              AutoGeneratingColumn="AutoGeneratingColumn" />
    

    Next you need to add an implementation of the event handler to apply an appropriate column type for each auto-generated column like so:

    private void AutoGeneratingColumn(object sender, System.Windows.Controls.DataGridAutoGeneratingColumnEventArgs e)
    {
        if (e.PropertyType != typeof (INode)) return;
        DataTableDataGridTemplateColumn column = new DataTableDataGridTemplateColumn();
        column.ColumnName = e.PropertyName;
        column.ClipboardContentBinding = e.Column.ClipboardContentBinding;
        column.Header = e.Column.Header;
        column.SortMemberPath = e.Column.SortMemberPath;
        column.Width = e.Column.Width;
        column.CellTemplate = (DataTemplate) Resources["NodeTemplate"];
    
        e.Column = column;
    }
    

    Note the use of a special DataTableDataGridTemplateColumn type here, this is just the class from an answer to Binding WPF DataGrid to DataTable using TemplateColumns renamed to something more descriptive.

    The reason we can't use DataGridTemplateColumn directly is that when binding a DataTable the template for each column is passed the entire row rather than the specific column value so we need to extend the class in order to bind only the specific column value so our template formats the actual INode value for that column in the row and not the whole row.

    Finally we need to define the template we've referred to in our XAML so that our columns are appropriately formatted:

    <Window.Resources>
        <sparqlResultsDataGridWpf:MethodToValueConverter x:Key="MethodToValueConverter" />
        <DataTemplate x:Key="NodeTemplate" DataType="rdf:INode">
            <TextBlock Text="{Binding Converter={StaticResource MethodToValueConverter}, ConverterParameter='ToString'}"/>
        </DataTemplate>
    </Window.Resources>
    

    Note that I've also defined a value converter here, this MethodToValueConverter is taken from an answer of Bind to a method in WPF? and allows us to simply take the result of a method call on an arbitrary type and use this as our display value. Here the configuration of our template simply calls ToString() on the underlying INode instances.

    With all these things implemented I run your example query and I get the following in my DataGrid:

    enter image description here

    You can find all my code used at https://bitbucket.org/rvesse/so-23711774

    You can use this basic approach to construct a much more robust rendering of INode with as many visual bells and whistles as you see fit.

    Side Notes

    A couple of notes related to this answer, firstly it would have been much easier to produce if you had posted a minimal complete example of your code rather than just partial XAML and code fragments.

    Secondly the dotNetRDF SparqlResultSet class actually already has an explicit cast to DataTable defined so you shouldn't need to manually translate it to a DataTable yourself unless you want to control the structure of the DataTable e.g.

    DataTable table = (DataTable) results;