I want to visualize a hierarchical structure of objects in a treeview. I know that there are plenty of tutorials out there describing how to do that. In principle I think I even know what to do, but I am stuck. I hope someone can point out my mistake.
This is "myObject":
private int _id;
public virtual int Id
{
get
{
return this._id;
}
set
{
if(this._id != value)
{
this.OnPropertyChanging("Id");
this._id = value;
this.OnPropertyChanged("Id");
}
}
}
private string _name;
public virtual string name
{
get
{
return this._name;
}
set
{
if(this._name != value)
{
this.OnPropertyChanging("name");
this._name = value;
this.OnPropertyChanged("name");
}
}
}
private int? _parentId;
public virtual int? parentId
{
get
{
return this._parentId;
}
set
{
if(this._parentId != value)
{
this.OnPropertyChanging("parentId");
this._parentId = value;
this.OnPropertyChanged("parentId");
}
}
}
private MyObject _myObject1;
public virtual MyObject MyParentObject
{
get
{
return this._myObject1;
}
set
{
if(this._myObject1 != value)
{
this.OnPropertyChanging("MyParentObject");
this._myObject1 = value;
this.OnPropertyChanged("MyParentObject");
}
}
}
private IList<MyObject> _myObjects = new List<MyObject>();
public virtual IList<MyObject> MyChildObjects
{
get
{
return this._myObjects;
}
}
The important thing here is the list of child objects called "MyChildObjects".
The XAML looks as follows:
<TreeView ItemsSource="{Binding myObjects}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding myObjects/MyChildObjects}">
<TextBlock Text="{Binding name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
My problem now is that the treeview only shows a flat structure of all objects. The mistake most likely is in the XAML file, but I am not able to figure it out. What do I have to change to have the hierarchy in the treeview?
Thank you for your help!
Best regards
Try defining your HierarchicalDataTemplate in TreeView.Resources for DataType of MyObject:
<TreeView ItemsSource="{Binding myObjects}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:MyObject}" ItemsSource="{Binding MyChildObjects}">
<TextBlock Text="{Binding name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
also your ItemsSource path is wrong. When you use myObjects/ it means current item of myObjects. What you need is just ItemsSource="{Binding MyChildObjects}
Binding.Path:
When the source is a collection view, the current item can be specified with a slash (/). For example, the clause Path=/ sets the binding to the current item in the view. When the source is a collection, this syntax specifies the current item of the default collection view.
You've set up the ItemsSource, but I think you will also need to set up an ItemTemplate inside the HierachicalDataTemplate. Take a look here.
Related
So I have a collection with Name, Code, Id and List with node type ServiceTypeDto like this model:
public class ServiceTypeDto
{
public long Id
public string Code
public string Name
public List<ServiceTypeDto> ChildrenList
}
I have a method which returns a list of ServiceTypeDtos, like this:
I have a ChildernList that exposes the ServiceTypeDtos.
This is how I try to do this in the ViewModel:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ServiceTypeService.Dto;
using ServiceTypeService.Interface;
using ShowServiceType.Interfaces;
using ShowServiceType.Utils;
namespace ShowServiceType.ViewModel
{
class MainWindowViewModel : ViewModelBase
{
public string _name, _code;
public long _id;
public List<ServiceTypeDto> _childrenList = new List<ServiceTypeDto>();
/// <summary>
/// Create Services for work
/// </summary>
ILogService Log => Service.CreateLog();
IExceptionHandler ExceptionHandler => Service.CreateExeptionHandler();
IServiceType ServiceType => Service.CreateGetServiceType();
public ObservableCollection<ServiceTypeDto> _servicesCollection;
public MainWindowViewModel()
{
ServiceConfig.Initialization();
var _services = ServiceType.GetServiceTypesTree();
_servicesCollection = new ObservableCollection<ServiceTypeDto>();
//This is convert to ObservableCollection my List<> =)
foreach (var item in _services)
_servicesCollection.Add(item);
}
public long ID
{
get => _id;
set
{
_id = value;
OnPropertyChanged("ID");
}
}
public string Code
{
get => _code;
set
{
_code = value;
OnPropertyChanged("Code");
}
}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public List<ServiceTypeDto> Children
{
get => _childrenList;
set
{
_childrenList = value;
OnPropertyChanged("Children");
}
}
}
}
My ViewModelBase type:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged( string propname ) => PropertyChanged?.Invoke(this , new PropertyChangedEventArgs(propname));
}
Code-behind of my main window:
public MainWindow()
{
InitializeComponent();
ServiceConfig.Initialization();
DataContext = new MainWindowViewModel();
}
Finally this is the XAML of the view.
<Grid Grid.Row="1">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible">
<TreeView>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<StackPanel FlowDirection="LeftToRight" Orientation="Horizontal">
<TextBlock Text="{Binding ID}" />
<TextBlock Text="{Binding Code}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</ScrollViewer>
</Grid>
The ObservableCollection dosen't appear in the TreeView. What is wrong?
There are multiple issues in your view and view model:
The TreeView is not bound to anything, bind its ItemSource to Children, otherwise it will not show any items
The ItemsSource of the HierarchicalDataTemplate must be bound to the child collection within your ServiceTypeDto, which is ChildrenList, not Children
You do not populate the Children collection (or its backing collection _childrenList ), so it is empty.
You add items to _servicesCollection, but it is not used either
The ServiceTypeDto does not implement INotifyPropertyChanged, so changes of properties will not be reflected in the user interface
ChildrenList in the ServiceTypeDto is not an ObservableCollection, so adding or removing items will also not be reflected in the user interface
You should consider using a naming convention like Children for your property and _children for the backing field to improve readability of your code, look here for reference on naming in C#.
1) Ok I bound on View my TreeView.ItemSource to ChildrenList
2) I don't understand how I bound ChildList in collection ServiceTypeDto to my ObserveCollection, How I understand wich index I select on UI ?
public List<ServiceTypeDto> ChildrenList
{
get => _servicesCollection[0].ChildrenList; // ? index ?
set
{
_servicesCollection[0].ChildrenList = value; // ? 0 ?
OnPropertyChanged("ChildrenList");
}
}
public string Name
{
get => _servicesCollection[0].Name; // ? 0 ?
set
{
_servicesCollection[0].Name = value; // ?
OnPropertyChanged("Name");
}
}
And other properties must know index to request from _servicesCollection[index].Name correct data?
3) I cant change ChildrenList in ServiceTypeDto to ObserveCollection out off dll, by condition of task.
This is just Reference.
After this, I dont know how but its work with 0 index !
But work only first Nodes don't appear other list in subList
<Grid Grid.Row="1">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Visible">
<TreeView ItemsSource="{Binding Path=ChildrenList}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ChildrenList}">
<StackPanel FlowDirection="LeftToRight" Orientation="Horizontal">
<TextBlock Text="{Binding ID}" />
<TextBlock Text="{Binding Code}" />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</ScrollViewer>
</Grid>
I have a TreeView which contains items of the type TPDItem, each TPDItem has a ObservableCollection of TPDItems which are displayed in the following manner:
TPDItem Hierarchy
The level shows which items are parents of which children, 1.1, 1.2 and 1.3 are children of the item with Level 1.
If i tick the checkbox Export, I want set the export value of that item, and it's children (and it's children children) recursively.
This is my TPDItem class:
public class TPDItem : INotifyPropertyChanged
{
public List<string> LevelArr { get; }
public string Level { get; }
public string _12NC { get; }
private string pn;
public string Description { get; }
private ObservableCollection<TPDItem> children = new ObservableCollection<TPDItem>();
private bool isExported = true;
public bool IsExported
{
get { return isExported; }
set
{
SetExported(value);
OnPropertyChanged("IsExported");
}
}
public string PN
{
get { return pn; }
set { pn = value; }
}
public ObservableCollection<TPDItem> Children
{
get
{
return children;
}
}
public void SetExported(bool exported)
{
isExported = exported;
foreach (TPDItem item in Children)
{
item.SetExported(exported);
}
}
}
And this is my relevant TreeView XAML code:
<TreeView ItemsSource="{Binding Hierarchy}" Margin="10,0,10,0" Height="243" >
<TreeView.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type models:TPDItem}">
<Grid >
<TextBlock Text="{Binding Level}"/>
<TextBlock Text="{Binding _12NC}" Margin="{Binding Margins._12NC}"/>
<TextBlock Text="{Binding PN}" Margin="{Binding Margins.PN}"/>
<TextBlock Text="{Binding Description}" Margin="{Binding Margins.Description}"/>
<CheckBox Content="Export" Margin="{Binding Margins.CheckBox}" IsChecked="{Binding IsExported, Mode=TwoWay}" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
However, the Checkbox in the children only gets updated to their parent's value if that child has not been expanded yet. After creating the tree, If I untick the top item's checkbox, the whole list gets unticked. However, If I expand and close a child, and then tick their parent's checkbox, they don't get updated visually.
Please let me know if you need more information.
Because you directly call SetExported on the children, you are skipping the part of the setter that calls OnPropertyChanged. Note that SetExported sets the backing variable isExported, but never uses the setter on the public property IsExported, which is what would trigger the visual update.
Try this:
public void SetExported(bool exported)
{
isExported = exported;
foreach (TPDItem item in Children)
{
// this will call the SetExported method, but will also trigger OnPropertyChanged
item.IsExported = exported
}
}
Also, making the SetExported method private instead of public would avoid this type of bug.
I have a type called "MyType" and my Pivot's ItemsSource is bound to an ObservableCollection property called "DataSource" inside the "myFirstVM" ViewModel. Inside "MyType" i have the property Title. As you can see from my XAML the TextBlock is bound to MyProperty. How to make it return the current item Title?
So for example, If i am on the second PivotItem, I need the Title of the second item in the DataSource collection
XAML:
<Grid x:Name="LayoutRoot">
<Pivot Name="myPivot"
SelectedItem="{Binding myFirstVM.SelItem, Mode=TwoWay}"
ItemsSource="{Binding myFirstVM.DataSource}"
ItemTemplate="{Binding myFirstVM.OtherTemplate}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.myFirstVM.MyProperty, ElementName=myPivot}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
</Pivot>
</Grid>
myFirstVM code:
private ObservableCollection<MyType> _dataSource;
public ObservableCollection<MyType> DataSource
{
get
{
if (this._dataSource == null)
{
this._dataSource = new ObservableCollection<MyType>();
}
return this._dataSource;
}
set { }
}
public string MyProperty
{
get
{
if (null != this.SelItem)
{
return this.SelItem.Title;
}
return "no title";
}
set { }
}
private MyType _selItem;
public MyType SelItem
{
get
{
return _selItem;
}
set
{
_selItem = value;
RaisePropertyChanged("SelItem");
RaisePropertyChanged("MyProperty");
}
}
public ObservableCollection<MyOtherType> OtherDataSource
{
get
{
if (null != this.SelItem)
{
return this.SelItem.OtherCollection;
}
else
{
return new ObservableCollection<MyOtherType>();
}
}
set { }
}
private MyOtherType _selOtherItem;
public MyOtherType SelOtherItem
{
get
{
return _selSegment;
}
set
{
_selSegment = value;
RaisePropertyChanged("SelOtherItem");
RaisePropertyChanged("PartsDataSource");
}
}
public ObservableCollection<MyThirdType> ThirdDataSource
{
get
{
if (null != this.SelOtherItem)
{
return this.SelOtherItem.ThirdCollection;
}
else
{
return new ObservableCollection<MyThirdType>();
}
}
set { }
}
And these are my DataTemplates for the inner collections "OtherDataSource" and "ThirdDataSource", that are ListBoxes:
<DataTemplate x:Key="OtherTemplate">
<ListBox DataContext="{Binding Source={StaticResource Locator}}"
ItemsSource="{Binding myFirstVM.OtherDataSource}"
ItemTemplate="{StaticResource ThirdTemplate}"
SelectedItem="{Binding myFirstVM.SelOtherItem, Mode=TwoWay}">
</ListBox>
</DataTemplate>
<DataTemplate x:Key="ThirdTemplate">
<ListBox DataContext="{Binding Source={StaticResource Locator}}"
ItemsSource="{Binding myFirstVM.ThirdDataSource}"
ItemTemplate="{StaticResource FourthTemplate}">
</ListBox>
</DataTemplate>
EDIT: I updated the question with the full ViewModel, and the DataTemplates, as sugested by #olitee. The problem with this approach as you can see is that in the second, and third dataTemplate I have ListBoxes. I am using one ViewModel for all the things. Any ideas?
You need to do a little extra work. Your ViewModel is not currently aware of which item is selected. You could create a new property called 'SelectedItem', and bind the Pivots' SelectedItem value.
Then you can access the Selected Item in code.
<Grid x:Name="LayoutRoot">
<Pivot Name="myPivot"
Tag="{Binding}"
SelectedItem="{Binding myFirstVM.SelectedItem}"
ItemsSource="{Binding myFirstVM.DataSource}"
ItemTemplate="{Binding myFirstVM.ViewDataTemplate}">
<Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.myFirstVM.MyProperty, ElementName=myPivot}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
</Pivot>
</Grid>
Then your VM would look something like:
private ObservableCollection<MyType> _dataSource;
public ObservableCollection<MyType> DataSource
{
get
{
if (this._dataSource == null)
{
this._dataSource = new ObservableCollection<MyType>();
}
return this._dataSource;
}
set { }
}
public string MyProperty
{
get
{
if (this.SelectedItem != null)
{
return this.SelectedItem.Title;
}
else
{
return null;
}
}
}
private MyType _selectedItem;
public MyType SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
OnNotifyPropertyChanged("SelectedItem");
OnNotifyPropertyChanged("MyProperty");
}
}
Alternatively, if you're just wanting to fix up the text for presentation, and don't really require the SelectedItem in your VM, you could go with #Jehof's approach - but implement an IValueConvertor that performs the fix.
This should do the trick
<TextBlock Text="{Binding SelectedItem.Title, ElementName=myPivot}"/>
Bind the Text-Property to the SelectedItem property of the Pivot element. When the selected item of the Pivot changes the TextBlock should display the Title of the item.
This is the right XAML implementation:
SelectedItem="{Binding myFirstVM.SelectedItem, Mode=TwoWay}"
and in the code behind instead of OnNotifyPropertyChanged the ViewModel needs to inherit ViewModelBase, part of MVVM Light then in the setter of SelectedItem property:
RaisePropertyChanged("SelectedItem");
Im new on wpf, and ive been having this problem...
I want to show a "Complex" object in my view
the complex object named WeeklySchedule:
that have a list of "Shifts"
public class WeeklySchedule
{
public virtual IEnumerable<Shift> Shifts { get; set; }
.....
}
public class Shift
{
public virtual String EntryTime { get; set; }
public virtual String ExitTime { get; set; }
.....
}
Im using two Data Templates to try and show the content in these objects:
<DataTemplate x:Key="ShiftlistViewTemplate" DataType="viewModel:WorkScheduleViewModel">
<TextBox Text="{Binding EntryTime}"/>
<TextBox Text="{Binding ExitTime}"/>
</DataTemplate>
<DataTemplate x:Key="WeeklySchedulelistViewTemplate"
DataType="viewModel:WorkScheduleViewModel">
<ListView x:Name="ShiftListView"
Grid.Column="0"
ItemTemplate="{StaticResource ShiftlistViewTemplate}"
ItemsSource="{Binding Shifts}"
SelectedItem="{Binding SelectedShift, Mode=TwoWay}"/>
</DataTemplate>
In the viewModel:
public class ViewModel : WorkspaceViewModel
{
public Shift SelectedShift
{
get
{
return _selectedShift;
}
set
{
if (_selectedShift == value)
{
return;
}
_selectedShift = value;
RaisePropertyChanged(SelectedShiftPropertyName);
}
}
public ObservableCollection<WorkSchedule> WorkSchedules
{
get
{
return _workSchedules;
}
set
{
if (_workSchedules == value)
{
return;
}
_workSchedules = value;
RaisePropertyChanged(WorkSchedulePropertyName);
}
}
public ObservableCollection<Shift> Shifts
{
get
{
return _shifts;
}
set
{
if (_shifts == value)
{
return;
}
_shifts = value;
RaisePropertyChanged(ShiftPropertyName);
}
}
When i run it i get this binding errors:
System.Windows.Data Error: 40 : BindingExpression path error: 'SelectedShift' property
not found on 'object' ''WeeklySchedule' (HashCode=7843366)'.
BindingExpression:Path=SelectedShift; DataItem='WeeklySchedule' (HashCode=7843366);
target element is 'ListView' (Name=''); target property is 'SelectedItem' (type 'Object')
I really dont understand that much of the error, is it trying to find the property SelectedShift inside the WeeklySchedule class??
i tried to make it as clear as possible...
Any ideas?, Thanks in advance
Your DataTemplate DataContext is of type WorkScheduleViewModel, and SelectedShift does not exist in WorkScheduleViewModel.
So you will have to set the ListViews DataContext to your ViewModel
Something like this should work
<ListView x:Name="ShiftListView"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ViewModel}}}"
Grid.Column="0"
ItemTemplate="{StaticResource ShiftlistViewTemplate}"
ItemsSource="{Binding Shifts}"
SelectedItem="{Binding SelectedShift, Mode=TwoWay}"/>
I've been having some trouble getting a listbox to correctly bind to a collection.
I'll give the framework code, then explain what I want it to do.
XAML Markup:
<ListBox DataContext="{Binding Foos, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding Main.SelectedFoo, Mode=TwoWay,
Source={StaticResource Locator},
UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding Main.SelectedFoo, Source={StaticResource Locator}}"/>
<ListBox ItemsSource="{Binding Main.SelectedFoo.Bars}" SelectedItem="{Binding Main.SelectedBar}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Right">
<!-- The binding requires "{Binding .}" because a path must be explicitly set for Two-Way binding,
even though {Binding .} is supposed to be identical to {Binding} -->
<TextBox Text="{Binding Path=. , UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
C# ViewModel:
private ObservableCollection<Foo> _barList = new ObservableCollection<Foo>();
private const string BardListPN = "FooList";
public ObservableCollection<Foo> FooList
{
get { return _fooList; }
set
{
if (_fooList == value)
{
return;
}
var oldValue = _fooList;
_fooList = value;
RaisePropertyChanged(FooListPN);
}
}
private Foo _selectedFoo;
private const string SelectedFooPN = "SelectedFoo";
public Foo SelectedFoo
{
get { return _selectedFoo; }
set
{
if (_selectedFoo == value)
{
return;
}
var oldValue = _selectedFoo;
_selectedFoo = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedFooPN);
}
}
public const string SelectedBarPN = "SelectedBar";
private string _selectedBar = "";
public string SelectedBar
{
get
{
return _selectedBar;
}
set
{
if (_selectedBar == value)
{
return;
}
var oldValue = _selectedBar;
_selectedBar = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedBarPN);
}
}
C# Model:
public class Foo
{
public ICollection<string> Bars
{
get { return _bars; }
set
{
_bars= value;
NotifyPropertyChanged("Bars");
// snipped obvious INotifyPropertyChanged boilerplate code
}
}
}
My problem is that any changes to the textboxes for the strings in the Bar collection aren't set. When the selected Foo changes to a different Foo and back, the original Bars are displayed.
Could someone tell me what I'm doing wrong? This seems like it should be much more simple. Thanks!
Update: I've changed the code as per Tri Q's suggestion, but the changes made to the textbox aren't reflected in the property itself. Any ideas?
Your Foo model class I take has been simplified for this example, but the omitted code could be the culprit of your problem. Let me explain.
Foo also needs to implement INotifyPropertyChanged to let the Listbox know when you have initialized the Bars collection and this most definitely depends on when you are initializing it.
Say you initialize Bars in Foo's constructor will cause the Listbox ItemsSource to bind to a valid Bars collection.
public Foo()
{
Bars = new ObservableCollection<string>();
...
}
Buut if you did something like this, the Listbox will not know that the Bars collection has been initialized and will not update it's source...
public Foo SelectedFoo
{
get { return _selectedFoo; }
set
{
if (_selectedFoo == value)
{
return;
}
var oldValue = _selectedFoo;
_selectedFoo = value;
// Update bindings, no broadcast
RaisePropertyChanged(SelectedFooPN);
if(_selectedFoo.Bars == null)
{
_selectedFoo.Bars = new ObservableCollection<string>();
// ...
}
}
}
Also here are a few things you might want to revise in your XAML.
Firstly, binding of the Textbox is TwoWay by default, so you do not need to set the Mode or the Path.
<TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged}" />
Secondly, it makes no sense to set Mode="TwoWay" for ItemsSource. ItemsSource="{Binding Main.SelectedFoo.Bars, Mode=TwoWay}"
Finally, you don't need to set the DataType for your DataTemplate. DataType="{x:Type System:String}"