WPF XPath Binding With Multiple Results Converted to Comma-Delimited String - wpf

I have an XML file similar in structure to the following:
<Parent>
<Child>5</Child>
<Child>3</Child>
<Child>5</Child>
<Child>1</Child>
</Parent>
In my XAML, I have a ListView bound to the XML file and have set the DataTemplate of a ListViewItem to be bound as follows:
<TextBlock Text="{Binding XPath=Parent/Child}"/>
Obviously, I'm expecting 4 results for this XPath query, but I can't seem to find a way to convert the results to a comma-delimited string, and right now, the TextBlock is just displaying the first value.
If I use the same XPath query for setting the ItemsSource of a ListBox, I get all the results in the ListBox, so I think I should be able to get all the values passed to a Converter class...

There's no way to have an XPath query that returns multiple nodes, such as yours, aggregate them into a single value for you. What's happening is that a nodeset is being returned and, since you're binding to a single string property, the infrastructure is simply coercing that nodeset by grabbing the first node from the set and then grabbing its #text node.
Honestly I have not tried this myself and don't have time at the moment, but the only way I'd expect this to ever work is if you wrote a custom IValueConverter. I assume that it will hand an XmlNodeList as the value to be converted and then you can enumerate those nodes and concatenate a comma separated string yourself.
Update
Since the IValueConverter suggestion did not work due to the XPath engine doing a pre-coercion, here's what I suggest you do: instead of binding to a single TextBlock, bind to an ItemsControl instead and define the ItemTemplate for the ItemsControl to be as follows:
<DataTemplate>
<TextBlock Text="{Binding}"/>,
</DataTemplate>
Note: in all honesty I'm taking the lazy approach in the DataTemplate and you will end up with a comma even after the last item right now. That said you should be able to define a DataTemplate with a trigger that determines that it's the last item and doesn't show the comma.
Finally, depending on how you want the data to layout also set the ItemsControl's ItemsPanel. I'm assuming you want horizontal flow with wrapping here:
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>

If you want to set the XML data as source to your ListView you can do like this:
MyListView.ItemsSource = XElement.Load(#"XMLFile1.xml").Elements("Child");
and you bind to the Value property in the TextBlock:
<TextBlock Text="{Binding Path=Value}" />
If you need to modify the query you can extract the content of your XML file to a var that you can use as ItemsSource on your ListView.
XElement xmlData = XElement.Load(#"XMLFile1.xml");
var query = from x in xmlData.Elements("Child")
select x;

Related

WPF: How to bind to only one item in a collection, not using ItemsControl since I don't want to display all of them

I have this requirement, that I have a collection of items (ObservableCollection), but I only want to display the first item. The requirement comes from the fact that in most of the case, the collection only contains one item. And due to the space limit, even if there is more than one items in the collection, we'd like to display the number of the items, details of the first one (same presentation as prior situation) and a ... symbol to indicate to the user that there is more items. And when the mouse is over the UI element a popup will eventually display all items.
The first solution I can think of (please suggest others if they are better) is to bind to this collection (but not using an ItemsControl) and define a DataTemplateSelector derived class (which return either the DataTemplate to display the only one item, or the DateTemplate which has the ... and the popup for more details, based on the number of items in the collection) and use it as ContentTemplateSelector.
But now my question: how both of my DataTemplate would look like in XAML, so that they can display only the first item in the collection? Obviously I can't have a ItemsControl.
UPDATE:
Now I have managed to make it work and agree this question can be closed (I can't delete it anymore since there is already some answers).
I actually knew how to bind to one certain item in the collection, but this was not where I am confused. I felt I should use ContentControl as one answer suggests. But I thought since I need to bind to the whole collection (not to single indexed item), and use a DataTemplateSelector to select the proper DataTemplate based on the number of items in the collection. The code would look like this:
<ContentControl Content="{Binding MyCollection}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
And in MyTemplateSelector I wasn't sure how to use it since there is no reference to my collection because it is defined as resource and it doesn't have the information of MyCollection. However, it turned out to be very simple, the DataTemplate can refer to an indexed item without knowing the name or any other reference. Simply like this:
<DataTemplate>
<TextBlock Text="{Binding [0].PropertyName}" />
<DataTemplate />
To bind to just one item from a collection, you can use the following syntax:
{Binding Items[0]}
Or to bind to a property of a single item from the collection:
{Binding Items[0].Property}
You can find out more about property path syntax from the Binding.Path Property page at MSDN... from the linked page:
• Indexers of a property can be specified within square brackets following the property name where the indexer is applied. For instance, the clause Path=ShoppingCart[0] sets the binding to the index that corresponds to how your property's internal indexing handles the literal string "0". Multiple indexers are also supported.
Try this
<ContentControl Content="{Binding YourCollection[0]}">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Ok, late to the party but I thought I'd share my 2 cents anyway: I'd better go with a dumber (XAML-)view and a view-model closer to your presentation needs.
Translated: instead of mapping your existing view-model (or raw data) and its collection of items directly to the view, I suggest to map that to an appropriate view-model showing something like a YourItemViewModel FirstItem property and a bool HasMore property. That second view-model would be easily unit-testable to make sure it behaves propertly, and would be easily mapped to a view with less logic, so to avoid possible hard-to-test problems in view.
{Binding Items[0].SomeProperty}
{Binding [0].SomeProperty}
{Path=/SomeProperty}

WPF - expose binding methods for inherited column

A reoccurring issue I have is needing to create enhanced text columns for datagrids. By that I mean columns that act just like normal text columns, but with an additional graphic or feature, like an image displayed next to the text. So I'm using template columns, but apparently this means having to "start from scratch" in generating a lot of the features expected of a normal text column, such as the textbox editing template:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=[binded text], Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
I want to define a column that's inherited from DataGridTemplateColumn, then dump all this code into it, so I can reuse these columns with any datagrid I wish. But as shown above, I can't declare the binding in the class definition because that obviously depends upon usage.
How can I define an inherited datagrid column that makes use of child controls (specifically the cell editing textbox in this case), but still allows binding to be set for these controls when the column has been declared with xaml inside some actual datagrid?
So far I've tried to expose a method to do this, but it's not working:
Public Class MyTextColumn
Inherits DataGridTemplateColumn
....
Public Property EditorBinding As String
Get....
Set(value As String)
Dim b As New Binding(value)
b.Mode = BindingMode.TwoWay
b.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus
Dim tb = DirectCast(Me.CellEditingTemplate.LoadContent, TextBox)
tb.SetBinding(TextBox.TextProperty, b)
End Set
End Property
Not working, my best guess is I'm not setting the Binding.Source, but I have no idea what I should be setting it to. It's getting pretty frustrating.
So if I understand you correctly, you want to be able to bind the text property of the TextBox to something on the parent control which will hold this child control of yours. You can't do that using the normal property (I'm guessing you got the "Can't bind because it's not the dependency property" exception or something similar).
This is how I usually do it without any problems. First you need to define a dependency property in the code behind. This should show you how to do it in the VB.net (I really really suck at VB.net so I won't pretend to give you any advice on that). Check the first example in VB.net. What you need to change first is from Boolean to String, you will also probably want to change the property name. Be careful to leave the "Property" part of the name where it stands in the example. GetType(MyCode) should be changed to the name of the class where you are implementing the dependency property (the name of your MyTextColumn class)
In the MyTextColumn xaml, it should look something like this:
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=IsSpinning, RelativeSource={RelativeSource AncestorType=DataGridTemplateColumn}, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
I've put the original property name IsSpinning, you should put there your chosen name. Also, you might have to fix the relative source if the base class is not DataGridTemplateColumn. This should pick up anything comming to your custom control.
The final step is to use your control:
<controls:MyTextColumn IsSpinning="{binding PropName}"/>
You basically bind it to whatever string you want. Feel free to write up any problems that you might have with my explanation or code and I'll fix my answer accordingly.

How to set silverlight comboboxitem value in xaml

I create a datatemplate for a combobox as follows:
<DataTemplate x:Key="AircraftTypeTemplate">
<StackPanel Orientation="Horizontal" Width="340">
<ComboBox>
<ComboBoxItem>CJ1</ComboBoxItem>
<ComboBoxItem>CJ3</ComboBoxItem>
<ComboBoxItem>Bravo</ComboBoxItem>
<ComboBoxItem>Excel</ComboBoxItem>
<ComboBoxItem>Sovereign</ComboBoxItem>
</ComboBox>
</StackPanel>
</DataTemplate>
It renders fine, but I would like to be able to associate a value with each of the items without having to bind it to some data context. For example I would like the CJ1 comboboxitem to have a value of 5. How would I set those in XAML?
Like:
<ComboBoxItem Value="5">CJ1</ComboBoxItem>
Thanks!
You can set the Name property to be any arbitrary string and use that. For more flexibility, you can use the Tag property, which according to MSDN:
Gets or sets an arbitrary object value that can be used to store custom information about this object.
You can read more about Tag here. I'd say Tag is probably better as opposed to bending Name to your will, and you can stick a string into Tag just as easily as Name.

WPF Treeview with HierarchicaldataTemplate - don't show lowest item

I have a WPF TreeView with a HierarchicalDataTemplate. As I descend the hierarchy, expanding the nodes, I will eventually get to the bottom and data displayed via a normal DataTemplate.
I'd like not to show those nodes - if I set the DataTemplate's containing TextBlock to Visible Hidden (or similar) I just get allocated space in the treeview. I'd like not to display those items so assume I need to remove them somehow. I cannot use a Filter on a CollectionView as there may be other nodes with children at this level. So basically, at any level I want to remove those nodes that have no children. The actual data is being loaded from an Xml file via an XmlDataProvider, so there are no class objects.
Can anyone suggest how
thanks
John
Presumably you ask the question because the last nodes in the tree are of the same type so you use only the one HierarchicalDataTemplate:
<HierarchicalDataTemplate DataType="{x:Type src:MyNodeClass}" ItemsSource = "{Binding Path=Items}">
<TextBlock Text="{Binding Path=PropertyToDisplay}"/>
</HierarchicalDataTemplate>
If you could change the type of your last nodes they will not automatically use the template (you could also inherit from the normal type so the collection allows them even though the new class is actually empty).

WPF: Listbox, valueconverter

What is the easiest way to use a valueconverter with a listbox?
I'm setting the ItemSource to a List<> of objects at runtime, and it displays a textstring from the ToString() method. What I would like, though, is to pass the object through a valueconverter to get a completely different string value.
All the examples I have found makes a big deal of binding the list to something in xaml, and defining styles and templates to redesign the whole box, but I just want my values converted...
Use a data template with something like:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter=....}" />
</...>
That's it. When you don't specify a path in your binding, it simply binds to the current object.

Resources