WPF Listbox Bind to subproperty List of Objects - wpf

I created a class named TestNumber which contains public List<TestGroup> TestGroups {get; set;}. The TestGroup class contains public List<TestPackage> TestPackages {get; set;} and public property string Name {get; set;}. The TestPackage class contains public string Name {get; set;}.
I have two list box. The first listbox which is bound to TestGroups list and it displays the Name of each TestGroup, this works as expected. For the second list box I would like to bind it to TestPackages list and have it display all TestPackage Name from all of the TestGroup with in the TestGroups list.
The data context is set in the code-behind as follow:
this.DataContext = TestNumber;
I tried the following code to bind the second listbox:
<ListBox Grid.Row="1"
ItemsSource="{Binding Path=TestGroups/TestPackages}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This displays all Name within TestPackages of only the first TestGroup within the TestGroups list.
<ListBox ItemsSource="{Binding Path=TestGroups}"
Grid.Column="4"
Grid.Row="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=TestPackages/Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This displays only the first Name within TestPackages of every TestGroup within the TestGroups list.
What am I doing wrong or what am I missing?
Any help is greatly appreciated. Thank you!
Through XAML, is it possible to populate the second list box with all Name within TestPackages within TestGroups? Are nested ListBox the only way (barring other controls) to accomplish this through XAML?

If you want to display all TestPackages in a single ListBox, you must define a property that actually returns all TestPackages somewhere.
Given your current setup, you can display all TestPackages for each TestGroup by using a nested ListBox:
<ListBox ItemsSource="{Binding TestGroups}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding TestPackages}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

How do you set your ItemsControl ItemsSource to see a collection of objects and also the collection of child-objects within one of the parent-objects

Basically what I have is an ItemsControl and I have set the ItemsSource="{Binding =Invoice.Jobs". Within that Jobs collection I have two other collections called "Shipments" and "Fees". I have it set up so that the ItemsControl.Resources is filled with DataTemplates that determine the type of CustomControl I will use for each Job, Shipment, or Fee. They determine this by the DataType of each of those. So the ItemsControl is supposed to pump out all of those controls one after another to kind of look like Line Items in an invoice. One group of Line items would be:
-Job
-Shipment
-Fee
Then if there were more jobs or of the others it could look like
-Job
-Shipment
-Shipment
-Fee
-Job
-Shipment
<ItemsControl ItemsSource="{Binding Invoice.Jobs}">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type preInvoice:BatchInvoiceJobDto }">
<control:JobLineItem>
</control:JobLineItem>
</DataTemplate>
<DataTemplate DataType="{x:Type preInvoice:BatchInvoiceShipmentDto}">
<control:ShipmentLineItem>
</control:ShipmentLineItem>
</DataTemplate>
<DataTemplate DataType="{x:Type preInvoice:BatchInvoiceFeeDto }">
<control:FeeLineItem>
</control:FeeLineItem>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
The Jobs class basically looks like this:
public class Invoice
{
public IEnumerable<BatchInvoiceJobDto> Jobs {get; set;}
}
public class BatchInvoiceJobDto
{
public IEnumerable<BatchInvoiceShipmentDto> Shipments {get; set;}
public IEnumerable<BatchInvoiceFeeDto> Fees{get; set;}
}
In order to traverse the Collections within your Invoice.Jobs Collection I would suggest nested ItemsControl controls.
XAML:
<ItemsControl ItemsSource="{Binding Invoice.Jobs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<ItemsControl ItemsSource="{Binding Shipments}">
<ItemsControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Fees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you were to set the ItemsControl.ItemsPanel to a StackPanel for each ItemsControl then it should display as rows following the Job -> Shipments -> Fees order for each item in Invoice.Jobs.
Note:
You may want to change your property types from IEnumerable to ObservableCollection so that they will update and display correctly.

How to bind data template of a listbox to class fields

I have a Class Person that consists of FirstName and LastName. I created an object of type ObservableCollection and filled it with some data, bounded it to Listbox.ItemsSource via code-behind. Now I want that data to be displayed on the Window inside a listbox, but via data template, so I can choose which fields of a class to appear..
So, one item would represent FirstName and Lastname in two separate textblocks.
<Window x:Class="PlayList.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:PlayList"
Title="Media Player PlayList"
Height="300"
Width="300" >
<Grid Height="224" Name="grid1" Width="261" >
<ListBox Height="100" x:Name="listBox1" Margin="12,0,12,124" MouseDoubleClick="listBox1_MouseDoubleClick" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=FirstName}" />
<TextBlock Text="{Binding Path=Surname}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Edit:
personae = new ObservableCollection<Person> { per1, per2, per3, per5, per4 }; listBox1.ItemsSource = personae;
Make sure FirstName and LastName are properties, not fields.
Setting the Items Source properly and your example template should be enough
If the ListBoxItem's data context object contains properties string FirstName and string Surname, having the following in the markup would suffice:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding Surname}"/>
</StackPanel>
</DataTemplate>
You cannot bind to fields, only to properties (which preferably are in a class implementing INPC if you need changes to reflect in the view).

How can I bind a control into a WPF listview column

I'm new to WPF so forgive me if I've missed something obvious
I have the following list view control (non relevent details removed for brevity)
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Type}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Details}"/>
</GridView>
</ListView.View>
</ListView>
I add items to this list view in code behind
public void Add(LogEntry entry)
{
ListViewItem item = new ListViewItem();
item.Content = entry;
listView.Items.Add(item);
}
where LogEntry has public string properties for "Type" and "Details"
So far, so good, all works lovely but..
I want my details property to be an element itself (TextBox or DockPanel containing various types of content)
How can I bind this Element to the list column?
Using the code above, Changing "Details" to an element I simply get the class name in the list box (e.g. System.Windows.Control.TextBox) as by default the cell displays the ToString() value of the property.
I have googled examples which use a DataTemplate but I can't find an example of binding an element to the content of a panel.
The control cannot be defined in xaml as its structure and contents are not known until runtime
The binding is one way (I only need to display the list, not update the underlying data structure)
To make my problem clearer, I want to bind the following list item
class LogEntry
{
public string Type {get;}
public DockPanel Details {get;} // This dock panel was created by code and contains
// various elements not predictable at compile time
}
Your Model, the LogEntry class, should not reference a UI control. Instead it should contain the data needed by the UI, and the XAML should define a DataTemplate that uses that data. For example,
public class LogEntry
{
public string Type {get;}
public ObservableCollection<IDetail> Details {get;}
}
<DataGridTemplateColumn Header="Details">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DockPanel>
<ItemsControl ItemsSource="{Binding Details}" />
</DockPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
You mentioned that the DockPanel in the LogEntry created items that were not known at runtime. Can you give an example of that? In most cases, you should have some kind of pattern in the data, and you can use DataTemplates to define how to draw each Detail piece.
<DataTemplate DataType="{x:Type local:LoginDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding MachineLoggedIn}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:LogoutDetail}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CreatedDate}" />
<TextBlock Text="{Binding UserName}" />
<TextBlock Text="{Binding LoggedInTime}" />
</StackPanel>
</DataTemplate>
If you REALLY need to store a control in the Model, then you can use a ContentControl in your DataGridTemplateColumn and set it's Content equal to the Details
<ContentControl Content="{Binding Details}" />
Sorry I cant give you an exact answer as I dont have VS where I am right now here but a few pointers.
First instead of using your method to add Listview items you want to create an ObservableCollection with your data. Then you can bind the itemssource of your listview to the observableCollection.
Next you can create an itemtemplate for the listview containing the control you want, something quite simple would be like a stack panel with horizontal orientation and two textboxes.
Once you have done that because you have set the itemsource of the listview to the ObservableCollection you can just bind the textbox to the String property within your collection.
Note that ObservableCollection is better to bind to than List as ObservableCollection supports NotifyPropertyChanged().
I have copied your code into a new project and created the same list view here is the code. And this one worked and displayed to data correctly
XAML:
<ListView x:Name="MyList" ItemsSource="{Binding MyListDetails}">
<ListView.View>
<GridView>
<GridViewColumn Header="Type" DisplayMemberBinding="{Binding Firstname}"/>
<GridViewColumn Header="Details" DisplayMemberBinding="{Binding Lastname}"/>
</GridView>
</ListView.View>
ViewModel:
private List<Contact> _details= new List<Contact>();;
public List<Contact> MyListDetails
{
get
{
return _details;
}
set
{
_details = value;
this.RaisePropertyChanged("MyListDetails");
}
}
public void AddEntry(LogEntry entry)
{
MyListDetails.Add(entry);
}

How to do datatemplate for items in listbox?

I have an XML file (see below) and can display all the Product Names in a listbox.
I want each entry in the listbox to display Product Name followed by Price, not just Product Name.
How do I do the datatemplate in the XAML file? Thanks.
Simplified XML file:
<Product>
<Name>Red Chair</Name>
<Price>29.5</Price>
</Product>
Simplified XAML file:
<DockPanel>
<ListBox Name="listBox1" ItemsSource="{Binding}" Margin="10" >
</ListBox>
</DockPanel>
In my C# file, I use LINQ to collect the products from the XML file and assign var products to listBox1.DataContext and it works fine. Now I just want to add in the Price. Thanks.
You do this the same as any other ItemTemplate.
Make sure that you're binding to the Product, not the Name. You can then select the values from the XML using XPath, something like this.
<DockPanel>
<ListBox Name="listBox1"
ItemsSource="{Binding}"
Margin="10" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text={Binding XPath=./Name} />
<TextBlock Text={Binding XPath=./Price} />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
Assuming your ItemsSource is of type IEnumerable<Product>, with
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
you can set the item template like this:
<ListBox Name="listBox1" ItemsSource="{Binding}" Margin="10" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text = "{Binding Name}" />
<TextBlock Text = "{Binding Price, StringFormat=f2}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF Combobox DisplayMemberPath

Ok, I looked at other questions and didn't seem to get my answer so hopefully someone here can.
Very simple question why does the DisplayMemberPath property not bind to the item?
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" DisplayMemberPath="{Binding Name}" SelectedItem="{Binding Prompt}"/>
The trace output shows that it is trying to bind to the class holding the IEnumerable not the actual item in the IEnumerable. I'm confused as to a simple way to fill a combobox without adding a bunch a lines in xaml.
It simply calls the ToString() for the object in itemssource. I have a work around which is this:
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
But in my opinion it's too much for such a simple task. Can I use a relativesource binding?
DisplayMemberPath specifies the path to the display string property for each item. In your case, you'd set it to "Name", not "{Binding Name}".
You are not binding to the data in the class, you are telling it to get it's data from the class member that is named by the member "name" so, if your instance has item.Name == "steve" it is trying to get the data from item.steve.
For this to work, you should remove the binding from the MemberPath. Change it to MemberPath = "Name" this tells it to get the data from the member "Name". That way it will call item.Name, not item.steve.
You should change the MemberPath="{Binding Name}" to MemberPath="Name". Then it will work.
You could remove DisplayMemberPath and then set the path in the TextBlock.
The DisplayMemberPath is really for when you have no ItemTemplate.
Or you could remove your ItemTemplate and use DisplayMemberPath - in which case it basically creates a TextBlock for you.
Not recomended you do both.
<TextBlock text="{Binding Path=Name, Mode=OneWay}"
Alternatively you don't need to set the DisplayMemberPath. you can just include an override ToString() in your object that is in your PromptList. like this:
class Prompt {
public string Name = "";
public string Value = "";
public override string ToString() {
return Name;
}
}
The ToString() will automatically be called and display the Name parameter from your class. this works for ComboBoxes, ListBoxes, etc.
Trying this :
<ComboBox Grid.Row="1" Grid.Column="2" ItemsSource="{Binding PromptList}" SelectedItem="{Binding Prompt}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
from what i can figure,
"DisplayMemberPath" uses reflection to get the property name in the data context class, if it cant find it nothing will be displayed.
if class
class some_class{
string xxx{ get; }
}
DisplayMemberPath=xxx, will show whatever value "xxx" is
if you want to concatenate properties from the datacontext you need to create an item template, which will show up in the header and the drop down list.
<ComboBox.ItemTemplate>
<DataTemplate DataType="employee">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding first_name}" />
<TextBlock Text="" />
<TextBlock Text="{Binding last_name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
you cannot have "DisplayMemberPath" and "ComboBox.ItemTemplate" set at the same time.

Resources