I have the following task:
create Tree which user can modify through app UI - add new Items, delete existing one. TreeView control should be binded to appropriate List in code behind.
Items in tree are CriteriaItem objects.
public class Subcriteria
{
public Subcriteria(string header)
{
Title = header;
subcriterias = new ObservableCollection<Subcriteria>();
}
public string Title { get; set; }
public ObservableCollection<Subcriteria> subcriterias { get; set; }
}
public class Criteria
{
public Criteria(string header)
{
Title = header;
criterias = new ObservableCollection<Subcriteria>();
}
public string Title { get; set; }
public ObservableCollection<Subcriteria> criterias { get; set; }
}
public MainWindow()
{
InitializeComponent();
public ObservableCollection<Alternative> _alt = new ObservableCollection<Alternative>();
Criteria root = new Criteria("root");
criteriaBundle.Add(root);
trvMenu.DataContext = _alt;
}
XAML:
<TreeView Name="trvMenu" Grid.Row="2" ItemsSource="{Binding criteriaBundle}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding criterias}">
<TextBlock Text="{Binding Title}" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding subcriterias}">
<TextBlock Text="{Binding Title}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Title}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
But it doesn't work. Could you please assist me with binding?
You should change your code-behind like this:
1) You should set DataContext, if you use binding
2) You can use only Properties in binding, not fields
My personal advice that You should read about binding basic and MVVM
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
criteriaBundle = new ObservableCollection<CriteriaItem> {new CriteriaItem("root")};
}
public ObservableCollection<CriteriaItem> criteriaBundle { get; set; }
}
EDIT:
Related
I have a treeView defined in XAML as:
<UserControl.Resources>
<models:TreeLines x:Key="myLines" x:Name="myLinesData"/>
</UserControl.Resources>
<TreeView
x:Name="treeData"
Grid.Column="1"
Padding="0,5,0,0"
Background="#282828"
BorderThickness="0"
SelectedValuePath="Uid">
<TreeViewItem
x:Name="tLines"
Uid="tabLines"
Header="Lines"
ItemsSource="{Binding Source={StaticResource myLines}, Path=MyLines}"
Style="{StaticResource custTVItem}">
<TreeViewItem.Resources>
<HierarchicalDataTemplate DataType="{x:Type models:Lines}" ItemsSource="{Binding lineSet}">
<TextBlock Text="{Binding productName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:LineSets}" ItemsSource="{Binding lineName}">
<TextBlock Text="{Binding setName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:LineNames}" ItemsSource="{Binding dataTypes}">
<TextBlock Text="{Binding lineName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type models:LineData}" ItemsSource="{Binding dataVals}">
<TextBlock Text="{Binding dataType}" />
</HierarchicalDataTemplate>
</TreeViewItem.Resources>
</TreeViewItem>
</TreeView>
The UserControl.Resources is pointing towards a class:
public partial class TreeLines : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(MainWindow.treeData.ItemsSource))]
private List<Lines>? myLines;
}
The error I get here is:
The target(s) of [NotifyPropertyChangedFor] must be a (different) accessible property
The object myLines I'm trying to bind to has the classes behind, as seen in the TreeView `HierarchicalDataTemplates:
public class Lines
{
public string productName { get; set; }
public List<LineSets> lineSet { get; set; }
}
public class LineSets
{
public string setName { get; set; }
public List<LineNames> lineName { get; set; }
}
public class LineNames
{
public string lineName { get; set; }
public List<LineData> dataTypes { get; set; }
}
public class LineData
{
public string dataType { get; set; }
public List<double> dataVals { get; set; }
}
If I remove all the CommunityToolkit.MVVM aspects and set my variable:
private List<Lines>? myLines; manually by changing it to public and assigning data to it on loading, then it populates on load only.
I need to modify myLines on the fly within my C# code which in-turn should update the treeView. You can see I'm trying to achieve this automatically with the data binding but something isn't right.
I think the mistakes could possibly be in the line:
[NotifyPropertyChangedFor(nameof(MainWindow.treeData.ItemsSource))]
and/or possibly the StaticResource usage in XAML:
<TreeViewItem
x:Name="tLines"
Uid="tabLines"
Header="Lines"
ItemsSource="{Binding Source={StaticResource myLines}, Path=linesCollection}"
Style="{StaticResource custTVItem}">
Please advise if you can help
Replace all List<T> properties with ObservableCollection<T>. Then the view will be updated whenever you add or remove items from these collections.
For the view to also update when you change a property of an individual item in a collection, the class of the property that you change should implement the INotifyPropertyChanged interface and raise change notifications.
Here is an example of how you should implement the Lines class:
public class Lines : ObservableObject
{
[ObservableProperty]
private string productName { get; set; }
[ObservableProperty]
private ObservableCollection<LineSets> lineSet { get; set; }
}
Bind to the generated properties (starting with an uppercase letter):
<HierarchicalDataTemplate DataType="{x:Type models:Lines}"
ItemsSource="{Binding LineSet}">
<TextBlock Text="{Binding ProductName}"/>
</HierarchicalDataTemplate>
[NotifyPropertyChangedFor(nameof(MainWindow.treeData.ItemsSource))] does not need to be added.
There is no need to implement additional notifications. Because [ObservableProperty] is already implementing the notification function.
Check out the auto-generated sources.
[NotifyPropertyChangedFor(parameter)]'s parameter should be the name of property inside the class.
public partial class TreeLines : ObservableObject
{
[ObservableProperty]
private List<Lines>? myLines;
public string OtherProperty1 { get; set; }
public string OtherProperty2 { get; set; }
}
In this case, the possible Arguments of [NotifyPropertyChangedFor] are only MyLines, OtherProperty1 , and OtherProperty2.
[NotifyPropertyChangedFor] is an attribute indicating that other properties connected within the class have changed
Here's an example.
public partial class GetSum : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Sum))]
private int num1;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Sum))]
private int num2;
public int Sum { get => num1 + num2; }
}
When calling the setter of Num1 Property,
simultaneously update the Num1 value and Sum value bound to the screen.
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp2"
Height="450" Width="800">
<Window.DataContext>
<local:GetSum/>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Num1}"/>
<TextBox Text="{Binding Num2}"/>
<TextBlock Text="{Binding Sum}"/>
</StackPanel>
</Window>
i'm new to WPF and MVVM pattern. I'm trying to bind recursively a Treeview to ObservableCollections.
I have searched so many times on this site, but I found no answers to my problem.
Here it is my Model class:
public class CategoryCounter
{
public int ID { get; set; }
public string Supplier { get; set; }
public string Name { get; set; }
public Nullable<int> Parent { get; set; }
public ObservableCollection<CategoryCounter> Children => new ObservableCollection<CategoryCounter>(/*some linq code here*/);
And the ViewModel class:
public class CategoriesViewModel : BaseViewModel
{
private string supplier;
private ObservableCollection<CategoryCounter> categories;
public ObservableCollection<CategoryCounter> Categories
{
get { return categories; }
set
{
if (value != categories)
{
categories = value;
}
}
}
public void SetSupplier(string supplier)
{
this.supplier = supplier;
Categories = new ObservableCollection<CategoryCounter>(CategoryContatori.GetRootBySupplier(supplier));
NotifyPropertyChanged();
}
}
Now, when i call "SetSupplier()" the collection is filled and it is all ok, but the view does not show me anything.
This is the XAML code:
<StackPanel>
<TreeView ItemsSource="{Binding Categories}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="250">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type dbModel:CategoryCounter}" ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</StackPanel>
How can I bind the children items even if they are the same object? Is this the problem?
Thank you for your patience.
Try setting the template directly
<TreeView ItemsSource="{Binding Categories}" HorizontalAlignment="Left" Height="200" VerticalAlignment="Top" Width="250">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Resources generally need to be in the parent of the item that needs to access them.
EDIT: Although having just done a quick search this may not be the case for TreeViews, so it's likely that INPC is your problem as already noted in the comments.
Raise the PropertyChanged in the setter of your Categories property:
private ObservableCollection<CategoryCounter> categories;
public ObservableCollection<CategoryCounter> Categories
{
get { return categories; }
set
{
categories = value;
NotifyPropertyChanged(nameof(Categories));
}
}
Also make sure that the Categories property and the Children property return materialized collections of CategoryCounter objects.
I've been trying to get my first TreeView to work, at first without the ViewModel. But no matter what I do, it doesn't show any children, even though the binding is correct.
I'm using two Item templates, one for hierarchical and another for data. You can see the important parts for both below:
class GrupoTag
{
public string Nome { get; set; }
public GrupoTag Pai { get; set; }
public List<Tag> ListaFilhos { get; set; }
public List<GrupoTag> SubGrupos { get; set; }
public GrupoTag(string nome)
{
Nome = nome;
ListaFilhos = new List<Tag>();
SubGrupos = new List<GrupoTag>();
}
public List<object> Filhos
{
get
{
List<object> lista = new List<object>();
foreach (GrupoTag subGrupo in SubGrupos)
lista.Add(subGrupo);
foreach (Tag filho in ListaFilhos)
lista.Add(filho);
}
}
}
class Tag
{
public GrupoTag Pai { get; set; }
public string Nome { get; set; }
public Tag(string nome)
{
Nome = nome;
}
public Tag(GrupoTag pai, string nome)
{
Pai = pai;
Nome = nome;
}
}
The XAML binding to all of this:
<TreeView Name="MenuTags">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="xml2Excel2:GrupoTag" ItemsSource="{Binding Filhos}">
<TextBlock Text="{Binding Nome}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="xml2Excel2:Tag">
<TextBlock Text="{Binding Nome}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
But the property "Filhos" on the GrupoTag class is never accessed. I've tried putting a breakpoint in there, throwing an exception, but it's simply never called. And the TreeView only displays the names of the collection of GrupoTags I assigned to it as its ItemSource in code-behind.
MenuTags.ItemsSource = arvoreTeste.SubGrupos;
I've read all the related questions and corrected the code for the respective errors, but I'm still lost here.
EDIT 1: So I modified the code of the classes to conform to the simple interface below:
class ITag
{
public string Nome { get; set; }
public ObservableCollection<ITag> Filhos { get; set; }
}
As per Benin comment, GrupoTag now uses a single property stored in a ObservableCollection to represent its children. And, as per Adnan answer removed the DataType from the XAML TreeView. Now it looks like this:
<TreeView Name="ArvoreTags">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Nome}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
It works, the TreeViewis functional. But I don't know why.
You need to give your TreeView ItemSource and DataType. And I would agree to Berins comment , you should avoid making new list to each access to Filhos property. TreeView works very good with Polymorphis, I mean with data types.
<TreeView DockPanel.Dock="Top"
DataContext="{Binding ProjectNodes_DataContext}"
ItemsSource="{Binding Nodes}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:ProjectNode}" ItemsSource="{Binding SubItems}">
<StackPanel>
<TextBlock Text="{Binding Header}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
My Property is:
private ProjectNodesVM mProjectNodes_DataContext;
public ProjectNodesVM ProjectNodes_DataContext
{
get { return mProjectNodes_DataContext; }
protected set
{
SetProperty(ref mProjectNodes_DataContext, value);
}
}
and class ProjectNodesVM has:
public ObservableCollection<ProjectNode> Nodes {
get { return mNodes; }
protected set { SetProperty(ref mNodes, value); }
}
inside ProjectNode class i have:
private string mHeader;
public string Header
{
get { return mHeader; }
set { SetProperty(ref mHeader, value); }
}
I found a simple answer to my specific case, not yours it seems
The children must be a "property".
So:
public ObservableCollection<Tag> ListaFilhos ; //doesn't work
public ObservableCollection<Tag> ListaFilhos {get; set;} //works
I want to create the hierarchical tree. I followed every step from here, but for some reason it shows me only one level deep.
Here's what I get:
What could be the reason?
XAML
<TreeView SelectedItemChanged="TreeView_SelectedItemChanged" ItemsSource="{Binding Items}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type local:MyClass}">
<TextBlock Foreground="Red" Text="{Binding Name}" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" MouseLeftButtonUp="TreeViewItem_MouseLeftButtonUp"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
VIEWMODEL
public class MyClass
{
public string Name { get; set; }
public List<MyClass> Children { get; set; }
}
public class MyViewModel
{
private List<MyClass> _items;
public List<MyClass> Items
{
get
{
return _items;
}
}
public MyViewModel()
{
_items = new List<MyClass>();
_items.Add(new MyClass
{
Name = "1",
Children = new List<MyClass>
{
new MyClass
{
Name = "1_1",
Children = new List<MyClass>
{
new MyClass
{
Name = "1_1_1"
},
new MyClass
{
Name = "1_1_2"
}
}
},
new MyClass
{
Name = "1_2"
}
}
});
}
}
You are defining your template as exactly two levels deep, so you are going to get a two level tree view. This is because you explicitly state that the ItemTemplate of the HeirarchicalDataTemplate should be a DataTemplate with no children.
I am not sure what your intended template is supposed to be, but if you remove that inner template you should get all of the items to display. Alternatively, you could make the inner template also a HeirarchicalDataTemplate.
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