WPF treeview with HierarchicalDataTemplate shows only 1 item - wpf

I am trying to bind to a WPF treeview but I am getting unexpected results. I bind to my viewmodel which appears to work as I get a single item but it does not allow me to expand to child items. I cant see what I'm doing wrong. Does anyone have any ideas?
I have the following model
public class Folder
{
public string Name { get; set; }
public IEnumerable<Folder> Subfolders { get; set; }
}
I have the following view model
public class Vm : INotifyPropertyChanged
{
private IEnumerable<Folder> _items;
public Vm()
{
var x = new List<Folder>();
x.Add(new Folder()
{
Name = "Item1",
Subfolders = new List<Folder>()
{
new Folder()
{
Name = "SubItem1", Subfolders = new List<Folder>()
{
new Folder() { Name = "SubItem2", Subfolders = new List<Folder>()}
}
}
}
});
Items = x;
}
public IEnumerable<Folder> Items
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My Xaml looks like
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TreeView Height="450" Width="800" ItemsSource="{Binding Path=Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Folder}" >
<TextBlock Margin="5,0,0,0" FontWeight="Bold" Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>

Its ok I found it. I needed to add a Items source value to the HierarchicalDataTemplate of Subitems

Related

How to bind textbox value to view-model and update it via button command? [duplicate]

This question already has answers here:
Issue with DependencyProperty binding
(3 answers)
How to pass data from MainWindow to a User Control that's inside the MainWindow?
(1 answer)
Closed 1 year ago.
Alright, so I'm new to MVVM and trying to do some data-binding and can't get it working.
I built a WPF application with two user-controls UC1 and UC2 and a View-Model Class VM1.
UC1 consists of a button whose command is specified in VM1 and UC2 consists of three text boxes whose texts are bound to properties in VM1. So what I want is, when I click on the button the text of each textbox must change. But when I click the button nothing happens. See below for the code.
ViewModel Class - VM1:
namespace TestApp.ViewModels
{
public class VM1 : INotifyPropertyChanged
{
#region Definations
private double heading;
private double attitude;
private double bank;
public double Heading
{
get { return heading; }
set
{
heading = value;
RaisePropertyChanged("Heading");
}
}
public double Attitude
{
get { return attitude; }
set
{
attitude = value;
RaisePropertyChanged("Attitude");
}
}
public double Bank
{
get { return bank; }
set
{
bank = value;
RaisePropertyChanged("Bank");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
//public TrayModel TrayModel { get; set; }
public ICommand UpdateViewAxesToFrontCommand { get; set; }
public VM1()
{
UpdateViewAxesToFrontCommand = new RelayCommand(UpdateViewAxesToFront, canExecuteMethod);
}
public bool canExecuteMethod(object parameter)
{
return true;
}
public void UpdateViewAxesToFront(object param)
{
Attitude = 10;
Heading = 0;
Bank = 0;
}
}
}
UC1:
<UserControl x:Class="TestApp.UserControls.UC1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:VM="clr-namespace:TestApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestApp.UserControls"
mc:Ignorable="d">
<UserControl.DataContext>
<VM: VM1/>
</UserControl.DataContext>
<hc:ButtonGroup>
<Button Style="{StaticResource ButtonIcon}" Command="{Binding Path=UpdateViewAxesToFrontCommand, UpdateSourceTrigger=PropertyChanged}" hc:IconElement.Geometry="{StaticResource CubeFontFaceGeometry}"/>
</hc:ButtonGroup>
</UserControl>
UC2:
<UserControl x:Class="TestApp.UserControls.UC2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:VM="clr-namespace:TestApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:TestApp.UserControls"
mc:Ignorable="d">
<UserControl.DataContext>
<VM: VM1/>
</UserControl.DataContext>
<StackPanel>
<TextBox Text="{Binding Attitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Bank, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Heading, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</UserControl>
I've also set DataContext in code-behind of each user-control as follows.
InitilizeComponent();
DataContext = new ViewModels.VM1();
Upon loading, the values in each text boxes are set to 0, and once I click the button the values remain 0 and don't change to 10, 0, 0 as expected.

Binding IsEnabled to a property of a custom type collection and refresh trigger

I have a window with a listbox containing several items. There is a itemtemplate which defines they should be represented by a checkbox:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:converters="clr-namespace:WpfApplication1.Converters"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Customers}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Path=Item.Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1"
HorizontalAlignment="Right"
Margin="5"
Content="Test"
IsEnabled="{Binding Customers, Converter={converters:CustomersToBoolConverter}}"
/>
</Grid>
</Window>
Next, I declare the binded 'Customers' collection and its items in the code behind like so:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ObservableCollection<CheckedListItem<Customer>> Customers { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
Customers = new ObservableCollection<CheckedListItem<Customer>>();
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Kelly Smith" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Joe Brown" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Herb Dean" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "John Paul" }));
DataContext = this;
}
void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Customers"));
}
}
Here is the Customer class definition:
public class Customer
{
public string Name { get; set; }
}
Here is the definition of the CheckedListItem class:
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
This latter implements my INPC logic.
I bind the status of my button to the Customers list and the output of a converter which returns true if any of the items is checked.
it works well when loading the window, however, if I check any item of the list, nothing happens. Moreover, if I implement the INPC logic in the class MainWindow and explicitly call
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Customers"));
Then, the notification is sent to my window and the status of the button is refreshed just fine.
My question is how can I send this notification from within the CheckedListItem, is it possible?
Code source : http://www.jarloo.com/how-to-create-a-checkedlistbox-in-wpf/
You can bind to CollectionChanged event of ObservableCollection which Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
You can put the event subscription code in MainWindow Constructor
Customers.CollectionChanged += Customers_CollectionChanged;
And handler in code behind :
void Customers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
/// Your code
}

Add Usercontrol as ListBoxItem from ViewModel

I'm very new to MVVM (and WPF). I have a main view that has a ListBox with the same usercontrol added to it x times.No problem adding these controls. The UC conatins a ListBox that will hold an unknown number of items. Can these items be another UC ? How do I not break MVVM and add items to the ListBox of each UC added?
Can someone point me in the right direction?
You can add the usercontrol in a Datatemplate in the ItemTemplate of the listbox. refer the below sample.
<Window x:Class="MSDN15Jan2015_Learning.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MSDN15Jan2015_Learning"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding PersonList}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:ListBoxItemControl/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
class MainViewModel
{
private ObservableCollection<Person> perList = new ObservableCollection<Person>();
public ObservableCollection<Person> PersonList
{
get { return perList; }
set { perList = value; }
}
public MainViewModel()
{
perList.Add(new Person() { Age = 1, Name = "Test1"});
perList.Add(new Person() { Age = 2, Name = "Test2" });
perList.Add(new Person() { Age = 3, Name = "Test3" });
perList.Add(new Person() { Age = 4, Name = "Test4" });
}
}
public class Person
{
private int age;
public int Age
{
get { return age; }
set { age = value; }
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
<UserControl x:Class="MSDN15Jan2015_Learning.ListBoxItemControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
</Grid>

MVVM Binding a combobox

I have a very ordinary ViewModel and I am tring to bind a collection of values to a combobox. The problem is nothing is binding. I have checked the ViewModel constructor and the data is being loaded so I suspect its in my XAML but I just cant find out where.
public class OwnerOccupierAccountViewModel : ViewModelBase
{
readonly UserAccountContext _userAccountContext;
readonly LoadOperation<Structure> _loadStructures;
#region Properties
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Structures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
#endregion
public OwnerOccupierAccountViewModel()
{
_userAccountContext = new UserAccountContext();
if (!DesignerProperties.IsInDesignTool)
{
_loadStructures = _userAccountContext.Load(_userAccountContext.GetStructuresQuery());
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
}
}
void _loadStructures_Completed(object sender, EventArgs e)
{
_structures = new ObservableCollection<Structure>();
foreach (var structure in _loadStructures.Entities)
{
Structures.Add(structure);
}
}
}
<UserControl.Resources>
<viewmodel:OwnerOccupierAccountViewModel x:Key='ViewModel'></viewmodel:OwnerOccupierAccountViewModel>
</UserControl.Resources>
<ComboBox x:Name='cboApartments'
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
Width='200' />
Try intializing your ObservableCollection of Structures like that:
void _loadStructures_Completed(object sender, EventArgs e)
{
Structures = new ObservableCollection<Structure>(_loadStructures.Entities);
}
and as it was mentioned earlier i think you should change order here:
if (!DesignerProperties.IsInDesignTool)
{
//other code before
//_loadStructures = ...
_loadStructures.Completed += new EventHandler(_loadStructures_Completed);
//and now start loading
}
I did similar, very simple app to check what could gone wrong, but everything works well. I will show you my code, so you can compare and maybe you will find some bugs in your solution.
Structure.cs
public class Structure
{
public Structure(string name)
{
Name = name;
}
public string Name { get; set; }
}
StructureService.cs
public class StructureService
{
public void GetAllStructures(Action<IList<Structure>> CompleteCallback)
{
var temp = new List<Structure>()
{
new Structure("Str1"),
new Structure("Str2"),
new Structure("Str3"),
new Structure("Str4"),
new Structure("Str5"),
new Structure("Str6"),
new Structure("Str7")
};
CompleteCallback(temp);
}
}
ViewModelBase.cs
public class ViewModelBase : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string prop)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
OwnerOccupierAccountViewModel:
public class OwnerOccupierAccountViewModel : ViewModelBase
{
StructureService service;
public OwnerOccupierAccountViewModel()
{
if (!DesignerProperties.IsInDesignTool)
{
service = new StructureService();
service.GetAllStructures((result) =>
{
Structures = new ObservableCollection<Structure>(result);
});
}
}
private ObservableCollection<Structure> _structures;
public ObservableCollection<Structure> Structures
{
get { return _structures; }
set
{
_structures = value;
RaisePropertyChanged("Stuctures");
}
}
private Structure _selectedStructure;
public Structure SelectedStructure
{
get { return _selectedStructure; }
set
{
_selectedStructure = value;
RaisePropertyChanged("SelectedStructure");
}
}
}
MainPage.xaml:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<vm:OwnerOccupierAccountViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures,Source={StaticResource ViewModel},Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Source={StaticResource ViewModel},Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
If i were in your shoes i wll change xaml to such view:
SuggestedView:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<vm:OwnerOccupierAccountViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox x:Name="cboApartments"
ItemsSource='{Binding Structures, Mode=TwoWay}'
SelectedItem="{Binding SelectedStructure, Mode=TwoWay}"
Width="100" Height="30">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</UserControl>
but i understand that it is somehow impossible in your scenario?
Try replacing this line:
_structures = new ObservableCollection<Structure>();
with this:
Structures = new ObservableCollection<Structure>();
And set the binding of ComboBox to OneWay.
Edited to update solution:
Set DisplayMemberPath property of ComboBox as well:
DisplayMemberPath="StructureName"
The binding will only fire when the property is changed. The line setting the backing variable won't call the RaisePropertyChanged event. Even if it did it would be empty at this point anyway and you'd end up with an empty list.
_structures = new ObservableCollection<Structure>();
When you then add to the collection you aren't changing the property value, you're calling the getter so again the RaisePropertyChanged won't fire.
Structures.Add(structure);
You need to build a local collection then use that as the value for the Structures property. This should cause the binding to be triggered.
var structures = new ObservableCollection<Structure>();
foreach ...
Structures = structures;
You are binding directly to the ViewModel key as a source, but is it set as a DataContext anywhere?

Binding recursively in a WPF TreeView

I am trying to bind recursively to the children of an item in a TreeView. From what I can see on MSDN HierarchicalDataTemplate is the way to go, but thus far I've only been partially successful.
My class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DocumentText test = new DocumentText();
this.DataContext = test;
for (int i = 1; i < 5; i++)
{
test.AddChild();
}
foreach (DocumentText t in test.Children)
{
t.AddChild();
t.AddChild();
}
}
}
partial class DocumentText
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public override string ToString()
{
return Name;
}
public List<DocumentText> _children;
public List<DocumentText> Children
{
get { return this._children; }
}
public DocumentText()
{
_name = "Test";
_children = new List<DocumentText>();
}
public void AddChild()
{
_children.Add(new DocumentText());
}
}
My XAML:
In mainview.xaml:
<Window x:Class="treetest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TreeView Name="binderPanel" DockPanel.Dock="Left"
MinWidth="150" MaxWidth="250" Background="LightGray"
ItemsSource="{Binding Children}">
</TreeView>
</Grid>
</Window>
In app.xaml:
<HierarchicalDataTemplate x:Key="BinderTemplate"
DataType="{x:Type src:DocumentText}" ItemsSource="{Binding Path=/Children}">
<TreeViewItem Header="{Binding}"/>
</HierarchicalDataTemplate>
This code produces a list of the first children, but the nested children are not displayed.
The primary problem in what you posted is that you haven't connected the HierarchicalDataTemplate as the TreeView's ItemTemplate. You need to either set ItemTemplate="{StaticResource BinderTemplate}" or remove the x:Key to apply the template to all DocumentText instances. You should also change the TreeViewItem in the template to a TextBlock - the TreeViewItem is generated for you and what you put in that template is applied to it as a HeaderTemplate.

Resources