Learning WPF here and I'm trying to get my head around hierarchical data binding.
This is my situation:
public class A
{
public int Id { ... }
public IEnumerable<B> Children { ... }
}
public class B
{
public string SomeValue { ... }
}
I want to use a ItemsControl to display a collection of A and for each occurance of A I want an inner ItemsControl to display A.Children.
I thought this would do the trick but, apparently, I have much to learn yet...
<ItemsControl x:Name="icCollectionOfAs" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="auto" Height="auto">
<TextBlock Text="{Binding Id}" />
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeValue}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then, in code-behind...
icCollectionOfAs.ItemsSource = createSomeAsWithChildBsInThem();
The result of all this is nothing gets shown. Why?
Thanks
You should not specify the ItemsSource both in XAML (ItemsSource="{Binding}") and code behind (icCollectionOfAs.ItemsSource = createSomeAsWithChildBsInThem();) the XAML might have been called later.
The rest seems fine to me except that the TextBlock for the ID will probably be hidden behind the ItemsControl for the children as you use a Grid without rows or columns rather than a StackPanel.
Related
I am using the Microsoft.Toolkit.Mvvm library as an MVVM library. My C# code is the following:
class SampleViewModel : ObservableObject
{
private List<int> _sampleList;
public List<int> sampleList
{
get => _sampleList;
set
{
SetProperty(ref _sampleList, value);
}
}
}
The XAML code I am attempting to use to display all the values of the List<int> is:
<ListView x:Name="mylistview" ItemsSource="{x:Bind ViewModel.sampleList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="x:Int32">
<TextBlock Text="{x:Bind}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
When the list is updated, the elements are created, but the value of the int is not displayed, only an empty row is present. If I use the following, I can display the value:
<TextBlock Text="{x:Bind ViewModel.sampleList[0], Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
How should I change the ListView, so that it displays the values as well?
i think you have over complicated what you want to do
<ListView x:Name="mylistview" ItemsSource="{x:Bind ViewModel.sampleList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:Int32">
<TextBlock Text="{x:Bind}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
should do the trick
this reads as create an text box for every item in the sampleList
if this doesn't work then the issue is most likely that your datacontext hasn't been set and the binding can't trace the relative address
also you should try using
an Observable collection
ie
public ObservableCollection<int> sampleList { get ;} = new ObservableCollection<int>();
this is because you should be changing the content of the list not the list itself so you need to INotifyCollectionChanged Event to inform the binding the content has changed
Try to use ObservableCollection<int> instead of List<int>
Your binding in XAML is very compicated. Try the following:
<ListView x:Name="mylistview" ItemsSource="{x:Bind ViewModel.sampleList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<Label Content="{x:Bind}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Can you tell me why this code isn't working?
I have a viewmodel with an observablecollection of searchresults which has an observablecollection of resultproperties. I cannot seem to display the nested collection of result properties like I want.
Here are the objects (abstracted for readability):
class ViewModel
{
public ObservableCollection<SearchResults<TDomain>> SearchResults { get; set; }
}
class SearchResults<TDomain>
{
public TDomain Result { get; set; }
public ObservableCollection<ResultProperty> ResultProperties { get; set; }
}
class ResultProperty
{
public string PropertyValue { get; set; }
}
Here is the xaml I cannot get to work. The DataContext is set to the ViewModel:
<StackPanel>
<ItemsControl ItemsSource={Binding SearchResults}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text={Binding Result.Id}/>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource={Binding ResultProperties}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PropertyValue}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="PLACEHOLDER /////"/>
</StackPanel>
The outcome I am looking for is something like this:
[Stack panel to keep things orderly]
1
property1property2property3...
2
property1property2property3...
3
property1property2property3...
PLACEHOLDER /////
The results I am getting are these
[Stack panel to keep things orderly]
1
2
3
PLACEHOLDER /////
In otherwords, the binding isn't picking up the string. I have verified that the collections are populating like expected. But I can't get the xaml to work.
**ADDITION INFORMATION
Ok, so I tried some of the solutions, but they aren't working so I am going to add more details because maybe I am missing something about how the collections are getting updated.
The viewmodel has a button on it called "Search" which uses an ICommand that calls a the view model's TrySearch method which is below:
public void TrySearch()
{
var results = _model.GetAll();
foreach(var result in results)
this.SearchResults.Add(new SearchResults<TDomain>(result));
}
Could there be a reason this doesn't work because of the way the collection is updated? SearchResults is a dependency property (I know I know, it should be INPC, but its not, its dependency), but the other collections are not. Could this be a problem?
I would create the data templates as resources and refer to them elsewhere in the XAML. For example:
<Window ....>
<Window.Resources>
<DataTemplate x:Key="SearchResultTemplate" TargetType="SearchResults">
<TextBlock Text={Binding PropertyValue}"
</DataTemplate>
<DataTemplate x:Key="ViewModelTemplate" TartgetType="ViewModel">
<StackPanel>
<TextBlock Text={Binding Result.Id}/>
<StackPanel Orientation="Horizontal">
<ItemsControl ItemsSource={Binding ResultProperties} ItemTemplate="{StaticResource SearchResultTemplate}" />
</StackPanel>
</StackPanel
</DataTemplate>
</Window.Resources>
<StackPanel>
<ItemsControl ItemsSource={Binding SearchResults} ItemTemplate="{StaticResource ViewModelTemplate}" />
</Window>
I'm not sure, but I think that the bindings you're using are telling the XAML parser to look for properties of the ViewModel class called ResultProperties and PropertyValue. The XAML parser doesn't see that they're properties of the object bound to that instance of the collection. Splitting it up like this should make it plain that the properties belong to the instance that the template is being applied to.
Your code is somehow correct but according to what you want it has a flow. The StackPanel has to be the ItemsPanel. Change it like this:
<StackPanel>
<ItemsControl ItemsSource="{Binding SearchResults}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Result.Id}"/>
<ItemsControl ItemsSource="{Binding ResultProperties}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PropertyValue}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="PLACEHOLDER /////"/>
</StackPanel>
The answer is that my SearchResults class was not hooked up properly. I made it a dependency object with dependency properties and it works fine. I am assuming that would translate similarly if it was INotifyPropertyChanged. Thank you for the responses.
I need to be able to bind to a parent ItemsControl's properties from inside of a child ItemsControl data template:
<ItemsControl ItemsSource="{Binding Path=MyParentCollection, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=MySubCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=MyParentCollection.Value, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Let's assume that MyParentCollection (the outer collection) is of the following type:
public class MyObject
{
public String Value { get; set; }
public List<MyChildObject> MySubCollection { get; set;
}
And let's assume that MyChildObject from the above class is of the following type:
public class MyChildObject
{
public String Name { get; set; }
}
How can I bind to MyParentCollection.Value from inside of the inner data template? I can't really use a FindAncestor by type because they both all levels use the same types. I thought maybe I could put a name on the outer collection and use an ElementName tag in the inner binding, but that still couldn't resolve the property.
Any thoughts?
saving the parent item in the tag of the child itemscontrol could work
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=MySubCollection}" Tag="{Binding .}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Tag.Value, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
its not tested but give you a hint in the right direction :)
Binding for Tag, as suggested in the other answer, is not required. All data can be obtained from DataContext of ItemControl (and this markup Tag="{Binding}" just copies DataContext into Tag property, which is redundant).
<ItemsControl ItemsSource="{Binding Path=MyParentCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=MySubCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.Value, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I'm kind of shooting in the dark on this one and have been looking around but couldn't find much of anything related. Pretty much I am trying to create an ItemsControl on a current Window that I've got, so when the user clicks an "Add Product" button on the window, it will add a UserControl to the screen in a horizontal matter.
For starters I am using an MVVM pattern and I have a PricingViewModel which is my ViewModel for the MAIN window. I have a second view model named ComparisonViewModel, which is the ViewModel for the View of the UserControl that I would like to show everytime the user hits the "Add Product" button on the PricingView. Jumping into my code, I've got a declared ObservableCollection and my AddComparison method. The Collection is instantiated in the constructor of the VM.
public ObservableCollection<ComparisonViewModel> Products { get { return _products; } }
public void AddComparison()
{
var products = IoC.Get<ComparisonViewModel>();
Products.Add(products);
}
Next, I've got and ItemsControl in the PricingView that binds to that collection in the PricingViewModel:
<ItemsControl ItemsSource="{Binding Path=Products}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I run it and after hitting add, it just shows the collection name. How can I actually get it to Pop up with a new Comparison User Control when the user hits Add Comparison? Thanks a ton for the help in advance!
You'll want to set the ItemTemplate so the ItemsControl knows how to render each item in the collection (by default, it is just displaying the result of calling .ToString()).
<ItemsControl ItemsSource="{Binding Path=Products}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type namespace:ComparisonViewModel}">
<!-- EXAMPLE -->
<Border BorderBrush="Black"
BorderThickness="2">
<DockPanel Orientation="Horizontal">
<TextBlock Text="{Binding ComparisonResult}"
DockPanel.Dock="Right" />
<TextBlock Text="{Binding Name}"
DockPanel.Dock="Left" />
</DockPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have found that I needed to tell the ItemsControl two things... First is what type of "thing" the ItemsControl is:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
And the second is how to display the Control:
<ItemsControl.ItemTemplate>
<DataTemplate>
<c:Widget Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
The final code looks like:
<ItemsControl ItemsSource="{Binding Path=DynamicItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<c:Widget Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You also need to add a reference to your control namespace in the window declaration stuff:
xmlns:c="clr-namespace:IHateEverything.Controls"
I'm trying to make a budget program. Where I need to have groupboxes with a list of textblocks inside.
<ItemsControl DataContext="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding}">
<ItemsControl DataContext="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I need somehow to databind a list (perhaps?) with groupboxes so I'd create a list of group boxes, with some lines inside that would be a text with a currency value. So that I could create a group called "Apartment", with two lines "Rent $3000" and "Maintenance $150". Then I could have a second group called "Car" with lines "Insurance", "Loan" and "Maintenance" for instance.
But how would I databind this? And how would I need in C# to perform this. I'm at a loss.
Building off of Jay's comment, you would want to create a Hierarchical data model. Note I have left implementing INotifyPropertyChanged on the properties to you
public class BudgetLineItem : INotifyPropertyChanged
{
public string Name { get; set; }
public decimal Cost { get; set; }
}
public class BudgetGroup : INotifyPropertyChanged
{
public string GroupName { get; set; }
public ObservableCollection<BudgetLineItem> LineItems { get; set; }
}
public class BudgetViewModel : INotifyPropertyChanged
{
public ObservableCollection<BudgetGroup> BudgetGroups { get; set; }
}
Then your data-template would look like this:
<ItemsControl DataContext="{Binding ViewModel}"
ItemsSource="{Binding BudgetGroups}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<GroupBox Header="{Binding GroupName}">
<ItemsControl ItemsSource="{Binding LineItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Cost}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I could be off base here, but it sounds like you want to change the DataTemplate based on the type of object that is being bound from a list of heterogeneous objects.
If that's the case, you want to look into DataTemplateSelectors or create DataTemplates for each of the types you want to support in the list.
For example, for an Apartment you might have:
<DataTemplate DataType="local:ApartmentBudget">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Text}" />
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
a Car may look like:
<DataTemplate DataType="local:CarBudget">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Insurance}" />
<TextBlock Text="{Binding Loan}" />
<TextBlock Text="{Binding Maintenance}" />
</StackPanel>
</DataTemplate>
Then your ItemsControl can be set like:
<ItemsControl ItemSource="{Binding BudgetItems}">
The correct DataTemplate will be picked based on the data type. You can have even more control by creating a custom DataTemplateSelector.
See https://msdn.microsoft.com/en-us/library/ms742521(v=vs.100).aspx for more information.