SPARQL results in WPF (binding DataGrid) - wpf

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.
Where is my problem ? Thanks a lot for help :)

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:
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;

Related

Reusing Binding Collections for WPF

I am working on a WPF app using the MVVM patterm, which I am learning. It uses EF4. I am trying to use a similar tabbed document interface style; several combo boxes on these tabs have the same items sources (from a sql db). Since this data almost never changes, it seemed like a good idea to make a repository object to get them when the app starts, and just reuse them for each viewmodel. For whatever reason though, even though I use new in the constructors, the lists are connected.
If I set a bound combo box on one tab, it gets set on another (or set when a new tab is created). I don't want this to happen, but I don't know why does.
The repository object is initialized before anything else, and just holds public lists. The views simply use items source binding onto the ObservableCollection. I am using the ViewModelBase class from the article. Here is the Viewmodel and model code.
ViewModel
TicketModel _ticket;
public TicketViewModel(TableRepository repository)
{
_ticket = new TicketModel(repository);
}
public ObservableCollection<Customer> CustomerList
{
get { return _ticket.CustomerList; }
set
{
if (value == _ticket.CustomerList)
return;
_ticket.CustomerList = value;
//base.OnPropertyChanged("CustomerList");
}
}
Model
public ObservableCollection<Customer> CustomerList { get; set; }
public TicketModel(TableRepository repository)
{
CustomerList = new ObservableCollection<Customer>(repository.Customers);
}
EDIT: I am sure this is the wrong way to do this, I am still working on it. Here is the new model code:
public TicketModel(TableRepository repository)
{
CustomerList = new ObservableCollection<Customer>((from x in repository.Customers
select
new Customer
{
CM_CUSTOMER_ID = x.CM_CUSTOMER_ID,
CM_FULL_NAME = x.CM_FULL_NAME,
CM_COMPANY_ID = x.CM_COMPANY_ID
}).ToList());
}
This causes a new problem. Whenever you change tabs, the selection on the combo box is cleared.
MORE EDITS: This question I ran into when uses Rachels answer indicates that a static repository is bad practice because it leaves the DB connection open for the life of the program. I confirmed a connection remains open, but it looks like one remains open for non-static classes too. Here is the repository code:
using (BT8_Entity db = new BT8_Entity())
{
_companies = (from x in db.Companies where x.CO_INACTIVE == 0 select x).ToList();
_customers = (from x in db.Customers where x.CM_INACTIVE == 0 orderby x.CM_FULL_NAME select x).ToList();
_locations = (from x in db.Locations where x.LC_INACTIVE == 0 select x).ToList();
_departments = (from x in db.Departments where x.DP_INACTIVE == 0 select x).ToList();
_users = (from x in db.Users where x.US_INACTIVE == 0 select x).ToList();
}
_companies.Add(new Company { CO_COMPANY_ID = 0, CO_COMPANY_NAME = "" });
_companies.OrderBy(x => x.CO_COMPANY_NAME);
_departments.Add(new Department { DP_DEPARTMENT_ID = 0, DP_DEPARTMENT_NAME = "" });
_locations.Add(new Location { LC_LOCATION_ID = 0, LC_LOCATION_NAME = "" });
However, now I am back to the ugly code above which does not seem a good solution to copying the collection, as the Customer object needs to be manually recreated property by property in any code that needs its. It seems like this should be a very common thing to do, re-using lists, I feel like it should have a solution.
Custom objects, such as Customer get passed around by reference, not value. So even though you're creating a new ObservableCollection, it is still filled with the Customer objects that exist in your Repository. To make a truly new collection you'll need to create a new copy of each Customer object for your collection.
If you are creating multiple copies of the CustomerList because you want to filter the collection depending on your needs, you can use a CollectionViewSource instead of an ObservableCollection. This allows you to return a filtered view of a collection instead of the full collection itself.
EDIT
If not, have you considered using a static list for your ComboBox items, and just storing the SelectedItem in your model?
For example,
<ComboBox ItemsSource="{Binding Source={x:Static local:Lists.CustomerList}}"
SelectedItem="{Binding Customer}" />
This would fill the ComboBox with the ObservableCollection<Customer> CustomerList property that is found on the Static class Lists, and would bind the SelectedItem to the Model.Customer property
If the SelectedItem does not directly reference an item in the ComboBox's ItemsSource, you need to overwrite the Equals() of the item class to make the two values equal the same if their values are the same. Otherwise, it will compare the hash code of the two objects and decide that the two objects are not equal, even if the data they contain are the same. As an alternative, you can also bind SelectedValue and SelectedValuePath properties on the ComboBox instead of SelectedItem.

PropertyPath and PathParameters in Constructor

I was trying to bind my DataGrid columns to a list where the item for a column could be retrieved using an indexer. The indexer type is DateTime.
I am creating the DataGrid columns using code and wanted to create a binding to retrieve the value from the list. In XAML the path would be written as:
{ Binding Path=Values[01/01/2011] }
But since I am doing it in code behind I need to define the path using a PropertyPath, like so:
new Binding{
Path = new PropertyPath("Values[01/01/2011]")
}
There is another overload for the constructor that takes a path and an array of parameters. According to the documentation the parameters are used for indexers. But when I write my binding as
new Binding {
Path = new PropertyPath("Values", new DateTime(2011, 01, 01))
}
the binding cannot resolve the path. Fair enough, I'm not stating that it should look for an indexer. But if I write it as:
new Binding{
Path = new PropertyPath("Values[]", new DateTime(2011, 01, 01))
}
then DateTime.MinValue is passed to the indexer.
Can someone explain to me how I use the PathParameters in the constructor and how I can bind to indexers without having to do a ToString on my value in the actual path?
Based on this MSDN article, you'd need to include "(0)" to indicate where the parameter should be placed. So the following should work:
new Binding {
Path = new PropertyPath("Values[(0)]", new DateTime(2011, 01, 01))
}

Entity Framework 4 Binding a related field to a DataGridView column

I have a DataGridView that is bound - via a binding source - to a list of entities:
VehicleRepository:
private IObjectSet<Vehicles> _objectSet;
public VehicleRepository(VPEntities context)
{
_context = context;
_objectSet = context.Vehicles;
}
List<Vehicle> IVehicleRepository.GetVehicles(Model model)
{
return _objectSet
.Where(e => e.ModelId == model.ModelId)
.ToList();
}
In my presenter
private List<Vehicle> _vehicles;
...
_vehicles = _vehicleRepository.GetVehicles(_model);
_screen.BindTo(_vehicles);
in my view
public void BindTo(List<Vehicle> vehicles)
{
_vehicles = vehicles;
if (_vehicles != null)
{
VehicleBindingSource.DataSource = _vehicles;
}
}
This works fine - my grid displays the data as it should. However, in the grid I am wanting to replace the ModelId column with a description field from the Model table. I've tried changing the binding for the column from ModelId to Model.ModelDescription but the column just appears blank.
I'm pretty sure that the data is being loaded, as I can see it when I debug, and when the same list is passed to a details screen I can successfully bind the related data to text fields and see the data.
Am I doing something obviously wrong?
It's a bit manual, but it 'works on my machine'.
Add a column to your DataGridView for the description field and then after you set your DataSource iterate through like so.
Dim row As Integer = 0
foreach (entity In (List<Entity>)MyBindingSource.DataSource)
{
string description = entity.Description;
MyDataGridView.Rows[row].Cells["MyDescriptionCell"].Value = description;
row ++;
}
You get a readonly view of your lookup. I make the new column readonly, but you could write something to handle the user changing the field if you wanted updates to run back to the server. Might be messy though.
The answer involves adding unbound read only columns and setting their value in the DataGridView's DataBindingComplete event
as described here
You can just add a column to your DataGridView, and in the DataPropertyName you must set the [entity].[Field name you need] in your case you could do: VehiclesType.Description
then you must add another binding source for the VehiclesTypes to the form, fill it using your context, and your good to go ;)

Can we bind dynamic data (dataset or list) to a control in WPF

I have a user control...and the base page(s) which uses this user control has a dataset which will be used by the user control.
My dataset is dynamic...means..it can have different number of columns depending upon which page my usercontrol is implemented. Is there any control in wpf which i can use to bind this dataset (without knowing the column information) ...i mean similar to datagrid/gridview in 2.0 ?
Take a look at the WPF toolkit, this contains a Grid which meets your requirements.
This toolkit is built by Microsoft, and part of the Microsoft Shared Source Initiative (see the link I provided).
It's not supported my Microsoft though, so if you get a bug you can use their forums but not call MS support.
If you do want to do it yourself, you can for example write some code that takes a List<T>, you get the generic type, get the properties, iterate over them, write the column headers, iterate over all the items in the list and write all the properties.
I wrote this code a while back to write a List to an HTML table, I hope it's useful:
public void PrintList<T>(List<T> list)
{
if(null!=list.FirstOrDefault())
{
Type t = typeof(list[0]);
PropertyInfo[] properties = t.GetProperties();
// properties = list of all properties.
print("<table><tr>");
foreach(var property in properties)
{
// print property title
print(string.Format("<th>{0}</th>", property.Name));
}
print("</tr>");
foreach(var item in list)
{
print("<tr>");
foreach(var property in properties)
{
var propertyValue = t.InvokeMember(property.Name, BindingFlags.GetProperty, null, item, new object[] {});
print(string.Format("<td>{0}</td>", propertyValue));
}
print("</tr>");
}
print("</table>");
}
}

Refreshing BindingSource after insert (Linq To SQL)

I have a grid bound to a BindingSource which is bound to DataContext table, like this:
myBindingSource.DataSource = myDataContext.MyTable;
myGrid.DataSource = myBindingSource;
I couldn't refresh BindingSource after insert. This didn't work:
myDataContext.Refresh(RefreshMode.OverwriteCurrentValues, myBindingSource);
myBindingSource.ResetBinding(false);
Neither this:
myDataContext.Refresh(RefreshMode.OverwriteCurrentValues, myDataContext.MyTable);
myBindingSource.ResetBinding(false);
What should I do?
I have solved the problem but not in a way I wanted.
Turns out that DataContext and Linq To SQL is best for unit-of-work operations. Means you create a DataContext, get your job done, discard it. If you need another operation, create another one.
For this problem only thing I had to do was recreate my DataContext like this.dx = new MyDataContext();. If you don't do this you always get stale/cached data. From what I've read from various blog/forum posts that DataContext is lightweight and doing this A-OK. This was the only way I've found after searching for a day.
And finally one more working solution.
This solution works fine and do not require recreating DataContext.
You need to reset internal Table cache.
for this you need change private property cachedList of Table using reflection.
You can use following utility code:
public static class LinqDataTableExtension
{
public static void ResetTableCache(this ITable table)
{
table.InternalSetNonPublicFieldValue("cachedList", null);
}
public static void ResetTableCache(this IListSource source)
{
source.InternalSetNonPublicFieldValue("cachedList", null);
}
public static void InternalSetNonPublicFieldValue(this object entity, string propertyName, object value)
{
if (entity == null)
throw new ArgumentNullException("entity");
if(string.IsNullOrEmpty(propertyName))
throw new ArgumentNullException("propertyName");
var type = entity.GetType();
var prop = type.GetField(propertyName, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop != null)
prop.SetValue(entity, value);
// add any exception code here if property was not found :)
}
}
using something like:
var dSource = Db.GetTable(...)
dSource.ResetTableCache();
You need to reset your BindingSource using something like:
_BindingSource.DataSource = new List();
_BindingSource.DataSource = dSource;
// hack - refresh binding list
Enjoy :)
Grid Data Source Referesh by new query instead just Contest.Table.
Simple Solution < But Working.
Whre is eg.
!!!!! Thanks - Problem Solved after no of days !!! but with so simple way ..
CrmDemoContext.CrmDemoDataContext Context = new CrmDemoContext.CrmDemoDataContext();
var query = from it in Context.Companies select it;
// initial connection
dataGridView1.DataSource = query;
after changes or add in data
Context.SubmitChanges();
//call here again
dataGridView1.DataSource = query;
I have the same problem. I was using a form to create rows in my table without saving the context each time. Luckily I had multiple forms doing this and one updated the grid properly and one didn't.
The only difference?
I bound one to the entity similarly (not using the bindingSource) to what you did:
myGrid.DataSource = myDataContext.MyTable;
The second I bound:
myGrid.DataSource = myDataContext.MyTable.ToList();
The second way worked.
I think you should also refresh/update datagrid. You need to force redraw of grid.
Not sure how you insert rows. I had same problem when used DataContext.InsertOnSubmit(row), but when I just inserted rows into BindingSource instead BindingSource.Insert(Bindingsource.Count, row)
and used DataContext only to DataContext.SubmitChanges() and DataContext.GetChangeSet(). BindingSource inserts rows into both grid and context.
the answer from Atomosk helped me to solve a similar problem -
thanks a lot Atomosk!
I updated my database by the following two lines of code, but the DataGridView did not show the changes (it did not add a new row):
this.dataContext.MyTable.InsertOnSubmit(newDataset);
this.dataContext.SubmitChanges();
Where this.dataContext.MyTable was set to the DataSource property of a BindingSource object, which was set to the DataSource property of a DataGridView object.
In code it does looks like this:
DataGridView dgv = new DataGridView();
BindingSource bs = new BindingSource();
bs.DataSource = this.dataContext.MyTable; // Table<T> object type
dgv.DataSource = bs;
Setting bs.DataSource equals null and after that back to this.dataContext.MyTable did not help to update the DataGridView either.
The only way to update the DataGridView with the new entry was a complete different approach by adding it to the BindingSource instead of the corresponding table of the DataContext, as Atomosk mentioned.
this.bs.Add(newDataset);
this.dataContext.SubmitChanges();
Without doing so bs.Count; returned a smaller number as this.dataContext.MyTable.Count();
This does not make sense and seems to be a bug in the binding model in my opinion.

Resources