PropertyPath and PathParameters in Constructor - wpf

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))
}

Related

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

Providing the WPF designer with an image at DesignTime

This is a restatement of my question, the revision history contains the original mess.
What it boils down to is "How do I get the application's directory from my WPF application, at design time?"
Which duplicates the question here so if you happen to be passing by please vote to close, thanks.
Do you need the image to be "Content - Copy if newer"? If you switch it to "Resource" you can use the following path to reference the file:
"/MyImage.JPG"
or a longer version
"pack://application:,,,/MyImage.JPG"
given that the image is in the root of the project, otherwise just change the URI to
"/Some/Path/MyImage.JPG"
UPDATE 1:
For me, the longer pack uri syntax works with an image marked as "Content - Copy if newer" as well. However, the shorter syntax does not work. I.e:
This works:
"pack://application:,,,/MyImage.JPG"
This does NOT work:
"/MyImage.JPG"
I my example I added the image to the root of the project, and marked it as "Content". I then bound the design time data context to a view model with a property returning the longer pack URI above. Doing that results in the Content image being shown correctly at design time.
UPDATE 2:
If you want to load a bitmap source from a pack uri, you can do so by using another overload of the BitmapFrame.Create which takes an URI as the first parameter.
If I understand your problem correctly you get the string with the pack uri as the first item in the object array that is passed to your converter. From this string you want to load a BitmapSource.
Since the string contains a pack URI, you can create an actual URI from the string and then use that URI to load the BitmapSource:
var imagePath = values[0] as string;
// ...
try
{
var packUri = new Uri(imagePath);
BitmapSource bitmap = BitmapFrame.Create(packUri, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
{
// ...
}
}
}
Just return the exact path of the image from your entity in ImagePath property, such as ..
"C:\MyImage.JPG"
..
OR
..
"C:\MyApp\bin\Debug\MyImage.JPG"
Then your binding (i.e. <Image Source="{Binding ImagePath}" />) in .xaml will start working..
I solved it by leveraging the clevers found in this stackoverflow answer.
public class DMyViewModel : PhotoViewModelBase
{
public override string ImagePath
{
get
{
string applicationDirectory =
(from assembly in AppDomain.CurrentDomain.GetAssemblies()
where assembly.CodeBase.EndsWith(".exe")
select System.IO.Path.GetDirectoryName(assembly.CodeBase.Replace("file:///", ""))
).FirstOrDefault();
return applicationDirectory + "\\MyImage.JPG";
}
}
}

How to detect the sender(button) of dynamical created bindings

I create some RibbonButtons dynamically and add them to a group according to an xml file. The follwoing function is carried out as often as entries found in the xml file.
private void ExtAppsWalk(ExternalAppsXml p, AppsWalkEventArgs args)
{
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
Binding tagBinding = new Binding("UrlTag");
tagBinding.Mode = BindingMode.OneWayToSource;
rBtn.SetBinding(RibbonButton.TagProperty, tagBinding);
rBtn.Label = args.Haed;
rBtn.Tag = args.Url;
rBtn.Margin = new Thickness(15, 0, 0, 0);
MyHost.ribGrpExtern.Items.Add(rBtn);
}
I tried to use the Tag property to store the Url's to be started when the respective button is clicked. Unfortunately the binding to the Tag property gives me the last inserted Url only.
What would be the best way to figure out which button is hit or to update the Tag property.
The datacontext is by default the context of the Viewmodel. The RibbonGroup to which the Buttons are added is created in the xaml file at designtime. I use that construct:
MyHost.ribGrpExtern.Items.Add(rBtn);
to add the buttons. It maight not really be conform with the mvvm pattern. May be someone else has a better idea to carry that out.
I foud a solution for my problem here and use the RelayCommand class. So I can pass objects (my Url) to the CommandHandler.
RibbonButton rBtn = new RibbonButton();
rBtn.Name = args.Name;
Binding cmdBinding = new Binding("ExtAppCommand");
rBtn.SetBinding(RibbonButton.CommandProperty, cmdBinding);
rBtn.CommandParameter = (object)args.Url;
private void ExtAppFuncExecute(object parameter)
{
if (parameter.ToString().....//myUrl

Silverlight InlineCollection.Add(InlineUIContainer) missing?

I'm having difficulty adding the inline of specific type InlineUIContainer into the InlineCollection (Content property) of a TextBlock. It appears the .Add() method of InlineCollection doesn't accept this type, however you can clearly set it through XAML without explicitly marking the content as a InlineContainer, as demonstrated in many examples:
http://msdn.microsoft.com/en-us/library/system.windows.documents.inlineuicontainer.aspx
Is it possible to programatically add one of these as in the following?
Target.Inlines.Add(new Run() { Text = "Test" });
Target.Inlines.Add(new InlineUIContainer() {
Child = new Image() { Source = new BitmapImage(new Uri("http://example.com/someimage.jpg")) } });
Target.Inlines.Add(new Run() { Text = "TestEnd" });
I have a feeling what's going on is that Silverlight is using a value converter to create the runs when specified in XAML as in the example which doesn't use InlineContainer, but I'm not sure where to look to find out.
The specific error I'm getting is as follows:
Cannot add value of type 'System.Windows.Documents.InlineUIContainer' to a 'InlineCollection' in a 'System.Windows.Controls.TextBlock'.
As pointed out by Jedidja, we need to use RichTextBox to do this in Silverlight.
You can't Add() Runs directly, but you can add Spans containing Runs.
Interestingly, you can also do this:
textBlock.Inlines.Clear();
textBlock.Inlines.Add(new Span());
textBlock.Inlines[0] = new Run();
Not that it's a good idea to hack around what the framework is actively trying to prevent you from doing.
P.S. If you can't figure out what XAML is doing, inspect the visual tree.

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