I am trying to create an MVVM application which pulls data from an API and puts the data into lists which are used to navigate the program.
The issue I'm having is that the list produces this error and I cannot figure out why:
Binding: Error in binding to "Avalonia.Controls.TextBlock"."Text":
"Could not find CLR property 'name' on 'attributes'"
For context, the 'attributes' class contains the 'name' variable and I have confirmed that the name variable is populated beforehand (the program printed the name variable before I moved onto trying to form a list).
XAML code (MainWindow.xaml):
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:RT_Stream_App.ViewModels;assembly=RT_Stream_App"
Icon="resm:RT_Stream_App.Assets.avalonia-logo.ico"
Title="RT Stream App">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<ListBox Items="{Binding CompanyList}" HorizontalAlignment="Left" Width="512" Height="512" Margin="20,20,0,10" VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel DataContext="attributes">
<TextBlock Text="{Binding name}" TextAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/> -->
</Window>
MainWindowViewModel.cs
namespace RT_Stream_App.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
// use => instead of = for assigning
// public string Greeting => "I am testing!";
public companies.APIData siteList => loadCompanies();
//public string Greeting => TestLoop(siteList);
public ObservableCollection<companies.companyData> CompanyList => siteList.data;
public companies.APIData loadCompanies()
{
// This takes the API data for companies and converts it into a useable class
companies.APIData toReturn = JsonConvert.DeserializeObject<companies.APIData>(new WebClient().DownloadString("https://svod-be.roosterteeth.com/api/v1/channels"));
return toReturn;
}
}
}
Class data (companies.cs):
namespace RT_Stream_App.Classes
{
public class companies
{
/// <summary>
/// Root of the JSON
/// </summary>
public class APIData
{
public ObservableCollection<companyData> data = new ObservableCollection<companyData>();
}
/// <summary>
/// A class that holds the data for each company (Name and link mostly)
/// </summary>
public class companyData
{
public attributeData attributes = new attributeData();
public linkData links = new linkData();
}
/// <summary>
/// Contains the company name
/// </summary>
public class attributeData
{
public string name { get; set; }
}
/// <summary>
/// Contains link data for the next step
/// </summary>
public class linkData
{
public string shows { get; set; }
}
}
}
What am I doing wrong and what needs to be changed?
Update: I have tried changing the DataTemplate in the XAML to the following:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding attributes.name}" TextAlignment="Center" />
</StackPanel>
</DataTemplate>
Which produces this error:
Binding: Error in binding to "Avalonia.Controls.TextBlock"."Text":
"Could not find CLR property 'attributes'
<DataTemplate>
<StackPanel DataContext="{Binding attributes}">
<TextBlock Text="{Binding name}" TextAlignment="Center" />
</StackPanel>
</DataTemplate>
Produces this error:
Binding: Error in binding to
"Avalonia.Controls.StackPanel"."DataContext": "Could not find CLR
property 'attributes'
Fix update: From Kekekeks answer, I figured out why my program wasn't working and now lists load. Due to using JSON.NET, I was worried about using constructors but I made the following change to all of my classes and the program displays the intended list
Class data (companies.cs) Updated:
public class APIData
{
public APIData()
{
this.data = new ObservableCollection<companyData>();
}
public ObservableCollection<companyData> data {
get;
set;
}
}
You are setting a string "attributes" as your DataContext. Then Binding can't find the property "name" on System.String.
Remove DataContext="attributes" and replace your binding with Text="{Binding attributes.name}"
Related
I have a Listbox inside a UserControl to manage different entities with different properties. The UserControl is the same for all entities.
I use MVVM and i bind a generic ViewModel as DataContext for the UserControl.
I wish to set the ItemTemplate for the Listbox in the container xaml for the UserControl in order to display the entity properties.
For the entity "Emlployee" I need to display FullName, for the entity Certifying I need to display CertifyingAuthor and CertifyingDate and so on.
What I need is something similar to that
<StackPanel Grid.Row="0" Orientation="Vertical">
<uc:SearchableFromListTextBox ItemTemplateForTheInsideListBox="{StaticResource Something}" ></uc:SearchableFromListTextBox>
Should I add a dependencyProperty ItemTemplateForTheInsideListBoxProperty to the UserControl? And how i could pass it as itemtemplate of the Listbox?
Hope the question is well explained considering my italian native language.
thanks
EDIT : I give up. This is a control for keyboard data entry and something similar to autocomplete.
Since i am forced to agree to a compromise with MVVM :( i will choose some dirty way to resolve.
Thanks to all
A DataTemplateSelector can do what I think you want. You can define different templates from your user control's XAML, then have the selector choose among them.
Assume these model classes:
public class Employee
{
public Employee(string fullName)
{
FullName = fullName;
}
public string FullName { get; }
}
public class Certifying
{
public Certifying(string certifyingAuthor, DateTime certifyingDate)
{
CertifyingAuthor = certifyingAuthor;
CertifyingDate = certifyingDate;
}
public string CertifyingAuthor { get; }
public DateTime CertifyingDate { get; }
}
Your user control's XAML has a ListBox, which in turn uses a template selector (that we'll get to in a moment) -- and also defines different templates for the two different model types:
<UserControl
x:Class="WPF.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF"
>
<Grid>
<ListBox x:Name="listBox">
<ListBox.ItemTemplateSelector>
<local:MyTemplateSelector>
<local:MyTemplateSelector.EmployeeTemplate>
<DataTemplate DataType="local:Employee">
<TextBlock
Foreground="Red"
Text="{Binding FullName}"
/>
</DataTemplate>
</local:MyTemplateSelector.EmployeeTemplate>
<local:MyTemplateSelector.CertifyingTemplate>
<DataTemplate DataType="local:Certifying">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Foreground="Blue"
Text="{Binding CertifyingAuthor}"
/>
<TextBlock
Grid.Column="1"
Foreground="Green"
Text="{Binding CertifyingDate}"
/>
</Grid>
</DataTemplate>
</local:MyTemplateSelector.CertifyingTemplate>
</local:MyTemplateSelector>
</ListBox.ItemTemplateSelector>
</ListBox>
</Grid>
</UserControl>
The user control's code-behind, where I just assign a list of model objects to the list box (for simplicity):
public partial class UserControl1
{
public UserControl1()
{
InitializeComponent();
listBox.ItemsSource = new List<object>
{
new Employee("Donald Duck"),
new Certifying("Mickey Mouse", DateTime.Now),
new Employee("Napoleon Bonaparte"),
new Certifying("Homer Simpson", DateTime.Now - TimeSpan.FromDays(2)),
};
}
}
And finally, the template selector. Its two properties were set from the user control's XAML; the logic of SelectTemplate decides which one to apply:
public class MyTemplateSelector : DataTemplateSelector
{
/// <summary>
/// This property is set from XAML.
/// </summary>
public DataTemplate EmployeeTemplate { get; set; }
/// <summary>
/// This property is set from XAML.
/// </summary>
public DataTemplate CertifyingTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is Employee)
{
return EmployeeTemplate;
}
if (item is Certifying)
{
return CertifyingTemplate;
}
return base.SelectTemplate(item, container);
}
}
And, for completeness, usage of the user control:
<Grid>
<wpf:UserControl1 />
</Grid>
The end result, where Napoleon is currently selected, and Homer suffers a mouse-over:
I am trying to bind a hierarchical structure to a TreeView using Data Binding, but nothing is displayed in the TreeView.
XAML code in the Control:
<UserControl x:Class="CQViewer.Views.HierarchyView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:h="clr-namespace:CQViewer.Hierarchy"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TabControl Margin="5, 0, 5, 5" Grid.Column="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TabItem Header="Repository">
<TreeView Margin="5, 5, 5, 5" HorizontalAlignment="Stretch" ItemsSource="{Binding Path=Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type h:HierarchyNode}"
ItemsSource="{Binding ChildNodes}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</TabItem>
<TabItem Header="Libraries">
<ListBox Margin="5, 5, 5, 5" HorizontalAlignment="Stretch" />
</TabItem>
</TabControl>
</Grid>
</UserControl>
The class that I am using:
using System.Collections.Generic;
namespace CQViewer.Hierarchy
{
class HierarchyNode
{
#region Fields
private IList<HierarchyNode> childNodes_;
#endregion
#region Construction/Deconstruction/Initialisation
/// <summary>
/// Constructor
/// </summary>
public HierarchyNode (string Name)
{
this.Name = Name;
childNodes_ = new List<HierarchyNode> ();
}
#endregion
#region Properties
public string Name { get; set; }
public IList<HierarchyNode> ChildNodes
{
get { return childNodes_; }
set { childNodes_ = value; }
}
#endregion
}
}
What I want to do is populate the TreeView using the ChildNodes in the HierarchyNode objects using the HierarchicalDataTemplate, while displaying a TextBlock for the name of the current object. The root HierarchyNode is created in the constructor of the ViewModel. Data Binding in this UserControl works, since binding a List<> to the ListBox works fine.
What is wrong with what I am doing, and why it is not working?
EDIT: The ViewModel used for the View
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CQViewer.Hierarchy;
namespace CQViewer.ViewModels
{
class HierarchyViewModel : ViewModelBase
{
#region Fields
#endregion
#region Construction/Deconstruction/Initialisation
/// <summary>
/// Constructor
/// </summary>
public HierarchyViewModel ()
{
Nodes = new HierarchyNode ("Test1");
HierarchyNode Node2 = new HierarchyNode ("TestX");
Node2.ChildNodes.Add (new HierarchyNode ("TestY"));
Nodes.ChildNodes.Add (Node2);
Nodes.ChildNodes.Add (new HierarchyNode ("Test3"));
}
#endregion
#region Properties
public HierarchyNode Nodes { get; set; }
#endregion
#region Public Access Interface
/// <summary>
/// Creates/Recreates the hierarchy
/// </summary>
public void InitializeHierarchy ()
{
// TODO Clear the tree view
}
/// <summary>
/// Gets the folder currently selected in the tree view hierarchy
/// </summary>
public string GetCurrentlySelected ()
{
return "$/";
}
#endregion
#region Private Functions
#endregion
}
}
The problem is that ItemsSource property of TreeViewItem expects a collection where as HierarchyViewModel.Nodes property is of type HierarchyNode. I suggest to change it to ObservableCollection<HierarchyNode>. i.e.:
public ObservableCollection<HierarchyNode> Nodes { get; set; }
Your solution with ItemsSource="{Binding Nodes.ChildNodes}" works partially because a tree view will not display a root node (Test1 in your case).
I have managed to get this to work by changing the TreeView code to the following:
<TreeView Margin="5, 5, 5, 5" HorizontalAlignment="Stretch">
<TreeViewItem Header="{Binding Nodes.Name}" ItemsSource="{Binding Nodes.ChildNodes}">
<TreeViewItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type h:HierarchyNode}"
ItemsSource="{Binding ChildNodes}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
</TreeView>
But I am sure that there is a better way to handle this.
im building a UserControl MyUserControl that has his own ViewModel MyUserControlViewModel. MyUserControl contains 6 VehicleSelectionBlock (V1, ... V6). VehicleSelectionBlock is a UserControl i've made. it has 3 RadioButton: car, train, bus; all are of enum type Vehicle and of the same GroupName VehicleGroup.
my goal is to represent each of MyUserControl's VehicleSelectionBlocks in MyUserControlViewModel.
to make my self clear: in MyUserControlViewModel i want to be able to know&change what RadioButton is checked in every one of the 6 VehicleSelectionBlock. i think my main problem is not the converter but rather the DataContex - i'm not sure how to set it correctly for each of the controllers.
iv'e tried Binding (which is the obvious solution). i tried reading here, here , and here. unfortunately neither one helped my acheive my goal.
my code is below - im kinda new to wpf and data binding in generally. i've read almost every chapter in this tutorial but still lost sometimes.
please help me get through this and understand better the DataContex concept.
ty
MyUserContlor.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for MyUserContlor.xaml
/// </summary>
public partial class MyUserContlor : UserControl
{
public MyUserContlorViewModel ViewModel { get; set; }
public MyUserContlor()
{
ViewModel = new MyUserContlorViewModel();
InitializeComponent();
this.DataContext = ViewModel;
}
private void BtnImReady_OnClick(object sender, RoutedEventArgs e)
{
//this code is irrelevant to the question
throw NotImplementedException();
}
}
}
MyUserContlor.xaml:
<UserControl x:Class="Project01.MyUserContlor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:loc="clr-namespace:Project01"
mc:Ignorable="d"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<Viewbox Stretch="Uniform">
<StackPanel>
<loc:VehicleSelectionBlock Name="V1"/>
<loc:VehicleSelectionBlock Name="V2"/>
<loc:VehicleSelectionBlock Name="V3"/>
<loc:VehicleSelectionBlock Name="V4"/>
<loc:VehicleSelectionBlock Name="V5"/>
<loc:VehicleSelectionBlock Name="V6"/>
<Button x:Name="BtnImReady" Click="BtnImReady_OnClick">Im Ready!</Button>
</StackPanel>
</Viewbox>
</UserControl>
MyUserContlorViewModel.cs:
namespace Project01
{
public class MyUserContlorViewModel : INotifyPropertyChanged
{
public MyUserContlorViewModel()
{
VehicleArr = new MyViewModel_Vehicle[6];
PropertyChanged+=MyUserControlViewModel_PropertyChanged;
}
public MyViewModel_Vehicle[] VehicleArr;
public event PropertyChangedEventHandler PropertyChanged;
public PropertyChangedEventHandler GetPropertyChangedEventHandler() { return PropertyChanged; }
private void MyUserControlViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//might be useful
throw NotImplementedException();
}
}
//this class should represent a VehicleSelectionBlock
public class MyViewModel_Vehicle
{
public Vehicle VehicleSelected {get; set;}
MyViewModel_Vehicle(){}
MyViewModel_Vehicle(Vehicle v){ VehicleSelected = v;}
}
}
VehicleSelectionBlock.xaml:
<UserControl x:Class="Project01.VehicleSelectionBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Project01"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Border VerticalAlignment="Center" HorizontalAlignment="Center" Background="GhostWhite"
BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel >
<Label Content="{Binding Name}"
FontWeight="Bold" HorizontalContentAlignment="Center"></Label>
<RadioButton GroupName="VehicleGroup" >car</RadioButton>
<RadioButton GroupName="VehicleGroup">train</RadioButton>
<RadioButton GroupName="VehicleGroup" IsChecked="True">bus</RadioButton>
</StackPanel>
</Border>
</Grid>
</UserControl>
VehicleSelectionBlock.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for VehicleSelectionBlock.xaml
/// </summary>
public partial class VehicleSelectionBlock : UserControl
{
public VehicleSelectionBlock()
{
InitializeComponent();
}
public VehicleSelectionBlock(String name)
{
name = Name;
InitializeComponent();
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
"Name", typeof (String), typeof (VehicleSelectionBlock), new PropertyMetadata(default(String)));
public String Name
{
get { return (String) GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
public enum Vehicle { Car, Train, Bus}
}
here is a quick solution. keep in mind that the code needs to change if you want to add more values to your Vehicle enum.
the MyUserControlViewModel.cs file
public class MyUserControlViewModel
{
public MyUserControlViewModel()
{
VehicleArr = new VehicleViewModel[6];
for (int i = 0; i < 6;i++ )
VehicleArr[i] = new VehicleViewModel();
}
public VehicleViewModel[] VehicleArr { get; set; }
}
this will expose your 6 items. They could be more. As a result they will be displayed in an ItemsControl, as you will see later.
public class VehicleViewModel:ViewModelBase
{
private bool isCar, isTrain, isBus;
public bool IsCar
{
get { return isCar; }
set
{
if (isCar == value) return;
isCar = value;
OnChanged("IsCar");
}
}
public bool IsTrain
{
get { return isTrain; }
set
{
if (isTrain == value) return;
isTrain = value;
OnChanged("IsTrain");
}
}
public bool IsBus
{
get { return isBus; }
set
{
if (isBus == value) return;
isBus = value;
OnChanged("IsBus");
}
}
}
instances of VehicleViewModel will contain your radio selection using 3 bool properties. this is the solution disadvantage. If you want more values you'll have to add more properties. you can see this inherits ViewModelBase. ViewModelBase just implements INPC so i'm not going to put it here. ViewModelBase also exposes the OnChange method that triggers the INPC event.
displaying the list can be done in your MyUserControl by using an ItemsControl like below.
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<loc:VehicleControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
each item is also a UserControl. The VehicleControl user control is just a StackPanel that displays the RadioButons. This can be seen below.
<StackPanel Orientation="Horizontal">
<RadioButton Content="Car" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsCar, Mode=TwoWay}"/>
<RadioButton Content="Train" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsTrain, Mode=TwoWay}"/>
<RadioButton Content="Bus" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsBus, Mode=TwoWay}"/>
</StackPanel>
please notice that each RadioButton is bound to one of the 3 properties in the VehicleViewModel instance.
Once you press your button you should have all the selections recorded. if you want you could have a function that returns an enum value by analysing the 3 bool properties if that is what you need.
the best solution will be to get rid of the radio buttons and replace them with combo boxes. in this way you can change the enum members and everything will continue to work without changing anything else. this might look as below.
public class VehicleViewModel:ViewModelBase
{
private Vehicle selOption;
private readonly Vehicle[] options;
public VehicleViewModel()
{
this.options = (Vehicle[])Enum.GetValues(typeof(Vehicle));
}
public Vehicle[] Options { get { return options; } }
public Vehicle SelectedOption
{
get { return selOption; }
set
{
if (selOption == value) return;
selOption = value;
OnChanged("SelectedOption");
}
}
}
and for the view:
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can do directly in the code-behind of your control (in the default constructor)
public VehicleSelectionBlock()
{
InitializeComponent();
this.DataContext = new MyUserContlorViewModel ();
}
You can also do that in XAML (http://msdn.microsoft.com/en-us/library/ms746695(v=vs.110).aspx) declaration, as you wish.
I have searched the web for the last few days but can't seem to find something that I would have thought was quite a simple task. I would like to add a resource in my XAML page of my windows phone application which will reference a complex object but I can't find the correct method. Is this possible? Object is made up something similar to:
Public class ComplexClass
{
Public string name { get; set; }
Public int ID { get; set; }
Public observablecollection<SimpleClass> simpleObjects { get; set; }
Public addSimpleObject(SimpleClass newSimpleObject)
{
if (simpleObjects == null)
simpleObjects = new ObservableCollection<SimpleClass>();
simpleObjects.Add(newSimpleObject);
}
}
Public Class SimpleClass
{
Public String Name { get; set; }
Public String Disc { get; set; }
}
You could use MVVM do achieve this. There are already heaps of tutorials available that you can access to show you how to follow this design pattern, so I won't go into that.
Instead I'll just show you a simple way of getting the data to your view.
In the constructor of your UserControl (or Page or whatever), set up the DataContext to an instance of your ComplexClass:
ComplexClass complexClass;
public MyUserControl1()
{
complexClass = new ComplexClass();
complexClass.AddSimpleObject(new SimpleClass { Name = "Bob" });
this.DataContext = complexClass;
this.InitializeComponent();
}
Then in your XAML you can bind to it like this:
<StackPanel>
<!-- Binding to properties on ComplexClass -->
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding ID}" />
<ListView ItemsSource="{Binding SimpleObjects}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Binding to properties on SimpleClass -->
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Disc}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
Without knowing specifics of your code, it's hard for me to suggest a method that is most suitable for you. I'd read up on MVVM and view models.
Current Setup
I have a custom class representing an installer file and some properties about that file, conforming to the following interface
public interface IInstallerObject
{
string FileName { get; set; }
string FileExtension { get; set; }
string Path { get; set; }
int Build { get; set; }
ProductType ProductType { get; set; }
Architecture ArchType { get; set; }
bool Configurable { get; set; }
int AverageInstallTime { get; set; }
bool IsSelected { get; set; }
}
My ViewModel has a ReadOnlyObservableCollection<IInstallerObject> property named AvailableInstallerObjects.
My View has a GroupBox containing the ItemsControl which binds to the aforementioned property.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
The binding works correctly, except it's not user friendly. 100+ items are shown.
Need Help Here
I'd like to be able to use my collection of IInstallerObjects but have the View present them with the following ItemTemplate structure.
<GroupBox Header="Products">
<ItemsControl ItemsSource="{Binding Path=AvailableInstallerObjects}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=ProductType}" Margin="5" />
<ComboBox ItemsSource="{Binding Path=Build}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
Basically I want to be able to group by the ProductType property, showing a list of the available products, with the ComboBox representing the available Build property values for IInstallerObjects of the ProductType.
I can use LINQ in the ViewModel to extract the groupings, but I have no idea how I'd bind to what I've extracted.
My research also turned up the possibility of using a CollectionViewSource but I'm not certain on how I can apply that to my current setup.
I appreciate your help in advance. I'm willing to learn so if I've overlooked something obvious please direct me to the information and I'll gladly educate myself.
If Build should be a collection type.
so your class should be structured like this as an example.
Public Class Customer
Public Property FirstName as string
Public Property LastName as string
Public Property CustomerOrders as observableCollection(OF Orders)
End Class
This should give you the expected results. Each item in the main items presenter will show first name last name and combobox bound to that customers orders.
I know it's simple but this should do.
All you have to do is declare a CollectionViewSource in your view and bind it to the ObservableCollection. Within this object you declare one or more GroupDescriptions which will split up the source into several groups.
Bind this source to the listbox, create a Template for the group description and you are done.
An example can be found here: WPF Sample Series – ListBox Grouping, Sorting, Subtotals and Collapsible Regions. More about CollectionViewSource can be found here: WPF’s CollectionViewSource
The description of your problem lead me to believe you are looking for some kind of colapsing / expanding / grouped / tree-view sort of thing.
XAML for the tree-view
<Window x:Class="WPFLab12.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WPFLab12"
Title="MainWindow" Height="350" Width="525">
<Grid>
<GroupBox Header="Products">
<TreeView ItemsSource="{Binding Path=ProductTypes}">
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type loc:ProductType}"
ItemsSource="{Binding AvailableInstallerObjects}">
<TextBlock Text="{Binding Description}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type loc:InstallerObject}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"
VerticalAlignment="Center" Margin="5"/>
<TextBlock Text="{Binding Path=FileName}" Margin="5" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</GroupBox>
</Grid>
</Window>
What does that do? Well, it establishes a hierarchy of controls in the tree based on the type of data found. The first HierarchicalDataTemplate handles how to display the data for each class, and how they are related in the hierarchy. The second HierarchicalDataTemplate handles how to display each InstallerObject.
Code behind for the Main Window:
public partial class MainWindow : Window
{
public ReadOnlyObservableCollection<ProductType> ProductTypes
{
get { return (ReadOnlyObservableCollection<ProductType>)GetValue(ProductTypesProperty); }
set { SetValue(ProductTypesProperty, value); }
}
// Using a DependencyProperty as the backing store for ProductTypes. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProductTypesProperty =
DependencyProperty.Register("ProductTypes", typeof(ReadOnlyObservableCollection<ProductType>), typeof(MainWindow), new UIPropertyMetadata(null));
public MainWindow()
{
this.InitializeComponent();
this.ProductTypes = new ReadOnlyObservableCollection<ProductType>(
new ObservableCollection<ProductType>()
{
new ProductType()
{
Description = "Type A",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "B" },
new InstallerObject() { FileName = "C" },
})
},
new ProductType()
{
Description = "Type B",
AvailableInstallerObjects = new ReadOnlyObservableCollection<InstallerObject>(
new ObservableCollection<InstallerObject>()
{
new InstallerObject() { FileName = "A" },
new InstallerObject() { FileName = "D" },
})
}
});
this.DataContext = this;
}
}
This is totally cheating, though - normally the MainWindow.cs would not serve as the DataContext and have all this stuff. But for this example I just had it make a list of ProductTypes and populate each ProductType class with the InstallerObject instances.
Classes I used, note I made some assumptions and modified your class to suit this View Model better:
public class InstallerObject
{
public string FileName { get; set; }
public string FileExtension { get; set; }
public string Path { get; set; }
public int Build { get; set; }
public bool Configurable { get; set; }
public int AverageInstallTime { get; set; }
public bool IsSelected { get; set; }
}
public class ProductType
{
public string Description { get; set; }
public ReadOnlyObservableCollection<InstallerObject> AvailableInstallerObjects
{
get;
set;
}
public override string ToString()
{
return this.Description;
}
}
So, in MVVM, it seems to me that your current InstallerObject class is more of a Model layer sort of thing. You might consider transforming it in your ViewModel to a set of collection classes that are easier to manage in your View. The idea in the ViewModel is to model things similarly to how they are going to be viewed and interracted with. Transform your flat list of InstallerObjects to a new collection of hierarchical data for easier binding to the View.
More info on various ways to use and customize your TreeView: http://www.codeproject.com/Articles/124644/Basic-Understanding-of-Tree-View-in-WPF