Xamarin forms List in List (navigating using prism) - mvvm

The issue is that i cant seem to pass through the NavigationService to ModulesModel to use to Navigate to AnotherView items that i can only pass it to SectionModel. but i can only pass the NavigationService to the Parent ListView(Which i dont want) it needs to pass through the child ListView. HELP!!!
UI - CODE
<ListView
SeparatorColor="Transparent"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
ItemsSource="{Binding Data}"
HasUnevenRows="True">
<ListView.Header>
<local:ModuleCourseHeaderViewControl />
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame
HasShadow="False"
BackgroundColor="{StaticResource BiancaBackgroundColour}">
<Frame.CornerRadius>
<OnIdiom
x:TypeArguments="x:Single"
Phone="0"
Tablet="15" />
</Frame.CornerRadius>
<Frame.Margin>
<OnIdiom
x:TypeArguments="Thickness"
Phone="0,10"
Tablet="20,10" />
</Frame.Margin>
<StackLayout>
<Label
FontFamily="{StaticResource LatoRegularFont}"
TextColor="{StaticResource MediumGold}"
Text="{Binding Title}">
<Label.FontSize>
<OnIdiom
x:TypeArguments="x:Double"
Phone="15"
Tablet="17" />
</Label.FontSize>
</Label>
<flv:FlowListView
BackgroundColor="Transparent"
SeparatorVisibility="None"
HasUnevenRows="False"
FlowColumnCount="{DynamicResource ModuleFlowColumnCount}"
RowHeight="{DynamicResource RiseCardHeight}"
FlowItemsSource="{Binding Modules}">
<flv:FlowListView.FlowColumnTemplate>
<OnIdiom
x:TypeArguments="DataTemplate">
<OnIdiom.Phone>
<DataTemplate>
<local:ModuleCourseCardPhoneControl>
<local:ModuleCourseCardPhoneControl.GestureRecognizers>
<TapGestureRecognizer
CommandParameter="{Binding}"
Command="{Binding ItemSelectedCommand}" />
</local:ModuleCourseCardPhoneControl.GestureRecognizers>
</local:ModuleCourseCardPhoneControl>
</DataTemplate>
</OnIdiom.Phone>
<OnIdiom.Tablet>
<DataTemplate>
<local:ModuleCourseCardControl>
<local:ModuleCourseCardControl.GestureRecognizers>
<TapGestureRecognizer
CommandParameter="{Binding}"
Command="{Binding ItemSelectedCommand}" />
</local:ModuleCourseCardControl.GestureRecognizers>
</local:ModuleCourseCardControl>
</DataTemplate>
</OnIdiom.Tablet>
</OnIdiom>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
</StackLayout>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Models - CODE
public class NewModuleModel
{
public IList<SectionModel> Sections { get; set; }
public int ActiviyId { get; set; }
public string CourseTitle { get; set; }
}
public class SectionModel
{
public string Title { get; set; }
public IList<ModulesModel> Modules { get; set; }
public INavigationService NavigationService { get; set; }
}
public class ModulesModel
{
public int ModuleId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string ModuleImageUrl { get; set; }
public double Progress { get; set; }
public double RoundedProgress
{
get
{
var p = Math.Round(Progress * 100, 0);
return p;
}
}
public DateTime? LastActivityDTUtc { get; set; }
public DateTime? CompletedDate { get; set; }
public string EstimatedDuration { get; set; }
public int AssetCount { get; set; }
public int? NavigationAssetID { get; set; }
public int ActiviyId { get; set; }
public string CourseTitle { get; set; }
public int? NavigationAssetContentType { get; set; }
public string Status
{
get
{
var result = string.Empty;
if (CompletedDate != null)
{
var c = String.Format("{0:d MMMM yyyy}", CompletedDate);
result = "Completed on " + c;
}
else if (LastActivityDTUtc != null)
{
var l = String.Format("{0:d MMMM yyyy}", LastActivityDTUtc);
result = "Last Activity on " + l;
}
else
{
result = "New";
}
return result;
}
}
public ModulesModel()
{
ItemSelectedCommand = new DelegateCommand<ModulesModel>(this.OnCardItemSelectedCommand);
}
private void OnCardItemSelectedCommand(ModulesModel module)
{
if (module.NavigationAssetContentType == 28 && module.AssetCount == 1)
{
var navigationParams = new NavigationParameters();
navigationParams.Add("AssetContentId", module.NavigationAssetID);
navigationParams.Add("AssetActivityId", module.ActiviyId);
navigationParams.Add("AssetModuleId", module.ModuleId);
navigationParams.Add("ParamCourseTitle", module.CourseTitle);
navigationParams.Add("ParamModuleTitle", module.Title);
Preferences.Set("NavFromModulePage", true);
NavigationService.NavigateAsync("AssetWebViewPage", navigationParams, true, false).Forget();
}
else
{
var navigationParams = new NavigationParameters();
navigationParams.Add("ParamModuleId", module.ModuleId);
navigationParams.Add("ParamActivityId", module.ActiviyId);
navigationParams.Add("ParamCourseTitle", module.CourseTitle);
Preferences.Set("NavFromModulePage", false);
//NavigationService.NavigateAsync("ModuleAssetPage", navigationParams, true, false);
}
}
public DelegateCommand<ModulesModel> ItemSelectedCommand { get; set; }
}
}
ViewModel - CODE
private async void LoadData()
{
this.ExecuteAsyncTask(async () =>
{
var result = await this.ModuleService.GetNewModuleAsync(ActivityID);
if (result != null)
{
CourseTitle = result.CourseTitle;
CourseDescription = result.CourseDescription;
CourseImageUrl = result.CourseImageUrl;
DurationInSeconds = result.DurationInSeconds;
DurationAsReadableString = result.DurationAsReadableString;
DisplayDescription = result.DisplayDescription;
Progress = Math.Round(result.Progress * 100, 0);
StartDateUTC = result.StartDateUTC;
EnrolmentDate = result.EnrolmentDate;
LastActivityDate = result.LastActivityDate;
foreach (var item in result.Sections)
{
var itemToAdd = new SectionModel
{
Title = item.Title,
Modules = item.Modules,
NavigationService = navigationService
};
Device.BeginInvokeOnMainThread(() =>
{
this.Data.Add(itemToAdd);
});
}
}
else
{
var exception = new Exception($"Api Error");
}
});
}
}

Remember that MVVM is Model-View-ViewModel. Very often we see people confusing Models and ViewModel's. I'll more indirectly answer your question by showing you the proper architecture. To start we'll look at a ToDoItem model.
public class ToDoItem
{
public string Name { get; set; }
public DateTime Created { get; set; }
public DateTime Due { get; set; }
}
With this note that we can make this easier to Bind by having our model inherit from something like Prism's BindableBase or ReactiveUI's ReactiveObject. I will make the assumption here that you already understand how to make those properties implement INotifyPropertyChanged.
The next thing we need is our ViewModel, we'll say that this is the ToDoItemsPageViewModel
public class ToDoItemsPageViewModel : BindableBase
{
public ObservableCollection<ToDoItem> Items { get; set; }
public DelegateCommand<ToDoItem> ItemSelectedCommand { get; }
private async void ItemSelectedCommandExecuted(ToDoItem item)
{
// do your navigation here..
}
}
Finally we have our ListView or CollectionView (which is what you should use as ListView is being deprecated). For simplicity let's look at the following.
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
<Button Text="Show"
Command="{Binding ItemSelectedCommand}"
CommandParameter="{Binding .}" />
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The core idea to understand here is that the ListView shares the BindingContext of its parent Page. Within our DataTemplate our BindingContext is a ToDoItem and not the ToDoItemsPageViewModel. As a result our binding for the value of Name will return the name of the individual ToDoItem. While our Binding for the CommandParameter will pass in the entire ToDoItem, our Binding for the Command will not work because the ToDoItem has no such command.
Now let's look at how we can access the Command from our ViewModel.
<ListView ItemsSource="{Binding Items}" x:Name="list">
<ListView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding Name}" />
<Button Text="Show"
Command="{Binding BindingContext.ItemSelectedCommand,Source={x:Reference list}}"
CommandParameter="{Binding .}" />
</StackLayout>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What we've done here are three small changes.
We've added an x:Name to the ListView. This could also be put on the parent Page.
We've changed the Command Binding from ItemSelectedCommand to BindingContext.ItemSelectedCommand
We've changed the Command Binding to include a Source which references the parent item (the list in this case)
What this accomplishes is to update that one specific binding to look at the parent. This effectively sets the BindingContext (for this single property) to the actual parent. In this case you can think of it as the BindingContext being set to the instance of the ListView. This means that our Binding must reference the BindingContext, and then we can dot into access a property of our BindingContext on the ListView.

Related

Changing Property of ObservableCollection does nothing

In my Xamarin project I have a ListView, which gets populated by an ObservableCollection, which holds "Item" objects with some properties. If I add items to the collection the UI gets updated, but if I change only a property, it won't it does nothing. Even after an UI update through adding an item does nothing, although the property gets correctly changed. How can I make the UI refresh if a property gets changed?
BindableBase is a class from PRISM that implements INotifyPropertyChanged and DelegateCommand implements ICommand, btw.
Here's my XAML:
<ListView ItemsSource="{Binding ListItems}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell x:Name="viewCell">
<ContentView Padding="0,0,0,5"
HeightRequest="50">
<ContentView.GestureRecognizers>
<TapGestureRecognizer BindingContext="{Binding Source={x:Reference listView}, Path=BindingContext}"
Command="{Binding ItemTappedCommand}"
CommandParameter="{Binding Source={x:Reference viewCell}, Path=BindingContext}" />
</ContentView.GestureRecognizers>
<Frame OutlineColor="{Binding Color}" Padding="8">
<StackLayout Orientation="Horizontal" >
<Image x:Name="checkedImage"
HeightRequest="30"
WidthRequest="30"
BackgroundColor="{Binding Color}"
/>
<Label Text="{Binding Text}"
TextColor="{Binding Color}"
Margin="20,0,0,0"
VerticalTextAlignment="Center"
HorizontalOptions="FillAndExpand"/>
<Image Source="{Binding Image}" />
</StackLayout>
</Frame>
</ContentView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Here's my ViewModel:
public class DetailPageViewModel : BindableBase
{
public DetailPageViewModel()
{
_listItems.Add(new ViewModels.Item("#123456", "Test1", "XamarinForms.Assets.Yellow.png"));
_listItems.Add(new ViewModels.Item("#654321", "Test3", "XamarinForms.Assets.close.png"));
}
private ObservableCollection<Item> _listItems = new ObservableCollection<Item>();
public ObservableCollection<Item> ListItems
{
get { return _listItems; }
set { SetProperty(ref _listItems, value); }
}
public DelegateCommand<Item> ItemTappedCommand => new DelegateCommand<Item>(ItemTapped);
private void ItemTapped(Item listItem)
{
// Adding an item refreshes the UI.
_listItems.Add(new ViewModels.Item("#654321", "Test3", "XamarinForms.Assets.close.png"));
// But changing the color of an item does nothing. Not even after an UI refresh.
_listItems.ElementAt(_listItems.IndexOf(listItem)).Color="#987654";
}
}
And my Item class:
public class Item
{
public string Color { set; get; }
public ImageSource Check { set; get; }
public string Text { private set; get; }
public ImageSource Image { private set; get; }
public Item(string color, string text, string imageSource)
{
Check = ImageSource.FromResource("XamarinForms.Assets.checkmark-outlined-verification-sign.png");
Color = color;
Text = text;
Image = ImageSource.FromResource(imageSource);
}
}
This is because also your item class needs to implement INotifyPropertyChanged. In your case as you are using Prism you just need to make your item class extend BindableBase (Prism base class which already implements INotifyPropertyChanged for you).
Link: https://github.com/PrismLibrary/Prism/blob/a60d38013c02b60807e9287db9ba7f7506af0e84/Source/Prism/Mvvm/BindableBase.cs
That should make it work.
Also I see in you are doing this:
public ObservableCollection<Item> ListItems
{
get { return _listItems; }
set { SetProperty(ref _listItems, value); }
}
With ObservableCollections you don't need to raise the event manually as they already do it internally. They can be defined as regular properties.
public ObservableCollection<Item> ListItems {get; set;}

How to code a UWP MVVM ComboBox within an ItemTemplate?

I'm trying to figure out how to display a category for a selected item in a ComboBox which is itself within a GridView.ItemsTemplate. Nothing that I have tried so far has worked. I've tried using SelectedValue and SelectedValuePath.
I can display the Item's Category in a textbox from within the GridView.ItemsTemplate, but not the collection in a ComboBox.
// FiledNotesPageViewModel ViewModel
public class FiledNotesPageViewModel : ViewModelBase
{
public ObservableCollection<MemoryItem> NoteItems { get; set; } = new ObservableCollection<MemoryItem>();
}
// Public Properties
public int FiledNotesId
{
get
{
return _filedNotesId;
}
set
{
_filedNotesId = value;
OnPropertyChanged("FiledNotesId");
}
}
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public string Note
{
get
{
return _note;
}
set
{
_note = value;
OnPropertyChanged("Note");
}
}
public string Category
{
get
{
return _category;
}
set
{
_category = value;
OnPropertyChanged("Category");
}
}
// MemoryItem Model
public int FiledNotesId { get; set; }
public string Note { get; set; }
public string Name { get; set; }
public string Category { get; set; }
FiledNotesPage View
<GridView x:Name="FiledNotesGrid"
RelativePanel.Below="FilterNoteGrid"
ItemsSource="{x:Bind ViewModel.NoteItems}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsWrapGrid Orientation="Horizontal"
Margin="5"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:MemoryItem">
<StackPanel Orientation="Vertical"
BorderBrush="{ThemeResource AppBarBorderThemeBrush}"
BorderThickness="2,2,2,4"
Margin="20"
Background="#FFF7F713" >
<TextBox x:Name="NoteNameTextBox"
Text="{x:Bind Name, Mode=TwoWay}"/>
<TextBox x:Name="NoteTextBox"
TextWrapping="Wrap"
AcceptsReturn="True"
Text="{x:Bind Note, Mode=TwoWay}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5,0">Category:</TextBlock>
<ComboBox x:Name="NoteCategoryComboBox"
SelectedItem="{Binding FiledNotesId, Mode=TwoWay}"
MinWidth="125">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Category}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Your combobox doesn't have an ItemsSource
You need to do something like this...
In your CS File create an ObservableCollection property to bind to.
public ObservableCollection<int> FieldNoteIds {get; set;}
In your view, bind to your collection
<ComboBox ItemsSource="{Binding FieldNoteIds}">
Right now you're providing a template for how to display each item that is populated into the combobox but you're not giving it anything to populate.
MSDN Tutorial on ComboBoxes and Binding
https://code.msdn.microsoft.com/windowsdesktop/Best-ComboBox-Tutorial-5cc27f82#content

ViewCell not binding

I have the following listView with binding on my xaml page. I am binding data to it from my viewModel. But the values are not binding to the cell.
<ListView x:Name="historyList" ItemsSource="{Binding History}" HasUnevenRows="true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<ViewCell.View>
<StackLayout Orientation="Horizontal" Padding="10, 5, 10, 5" Spacing="0" HorizontalOptions="FillAndExpand" >
<Label Text="{Binding History.DayNumber}" HorizontalOptions="Center" FontSize="15" TextColor="Red" VerticalOptions="Center"></Label>
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My History object has the property DayNumber, but the value is not binding to the label?
Any suggestion? or am i missing something?
Thank you.
Edit:
Here is how I get my History List:
void GetAllHistory()
{
foreach(CheckInTimeHistory h in App.Database.GetHistoryCheckins(null))
{
CheckInHistoryItem hi = new CheckInHistoryItem();
hi.CheckedIn = (h.AutoCheckIn ? false : true);
hi.CheckInTime = h.CheckInTime.AddSeconds(-h.CheckInTime.Second).ToString("HH:mm-ss tt");
hi.DayNumber = h.CheckInTime.Day.ToString();
hi.DayText = h.CheckInTime.DayOfWeek.ToString().Substring(0, 2);
History.Add(hi);
}
}
public class CheckInHistoryItem
{
public String DayText, DayNumber, CheckInTime;
public Boolean CheckedIn;
}
You will see warnings like this in your debug output:
Binding: 'DayNumber' property not found on '....CheckInHistoryItem', target property: 'Xamarin.Forms.Label.Text'
The properties have to be C# properties. Just change your class to
public class CheckInHistoryItem
{
public String DayText { get; set; }
public String DayNumber { get; set; }
public String CheckInTime { get; set; }
public Boolean CheckedIn { get; set; }
}
and use DayNumber instead of History.DayNumber
<Label Text="{Binding DayNumber}" HorizontalOptions="Center" FontSize="15" TextColor="Red" VerticalOptions="Center"></Label>
Assuming History is a static var:
ItemsSource="{x:Static local:History}">
And change:
{Binding History.DayNumber}
to:
{Binding DayNumber}
Binding Basics:
https://developer.xamarin.com/guides/xamarin-forms/user-interface/xaml-basics/data_binding_basics/#Bindings_and_Collections

Xceed Datagrid loses SelectedItem when child selected

I have an Xceed datagrid in a WPF MVVM application which is set up to hold master-detail records. When a child row is selected, I want the ViewModel to detect the selected child. I would like to do this preferably with zero code-behind. I have written code which executes an action on the selected item when a contextmenu is clicked. This works correctly when a parent is selected, but always returns null when a child is selected.
I have put together a very simplified version of what I am trying to acheive:
My XAML is:
<Window x:Class="MasterDetailSelection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xcdg="clr-namespace:Xceed.Wpf.DataGrid;assembly=Xceed.Wpf.DataGrid.v4.2"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<xcdg:DataGridCollectionViewSource x:Key="cvs_parents" Source="{Binding Path=Parents}">
<xcdg:DataGridCollectionViewSource.DetailDescriptions>
<xcdg:PropertyDetailDescription RelationName="Children"
AutoCreateDetailDescriptions="False">
</xcdg:PropertyDetailDescription>
</xcdg:DataGridCollectionViewSource.DetailDescriptions>
</xcdg:DataGridCollectionViewSource>
</Grid.Resources>
<xcdg:DataGridControl x:Name="ParentGrid"
NavigationBehavior="RowOnly"
ItemsSource="{Binding Source={StaticResource cvs_parents}}"
SelectedItem="{Binding SelectedItem}"
AutoCreateDetailConfigurations="True"
ReadOnly="True">
<xcdg:DataGridControl.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Execute Command"
CommandParameter="{Binding DataContext.SelectedItem}"
Command="{Binding DataContext.SampleCommand}" />
</ContextMenu>
</xcdg:DataGridControl.ContextMenu>
</xcdg:DataGridControl>
</Grid>
</Window>
The View Model is:
namespace MasterDetailSelection
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Microsoft.Practices.Composite.Presentation.Commands;
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Parent> _parents;
public event PropertyChangedEventHandler PropertyChanged;
private DelegateCommand<Object> _sampleCommand;
private object _selectedItem;
public ObservableCollection<Parent> Parents
{
get { return _parents; }
set
{
_parents = value;
OnPropertyChanged("Parents");
}
}
public DelegateCommand<Object> SampleCommand
{
get
{
if (_sampleCommand == null)
{
_sampleCommand = new DelegateCommand<object>(ExecuteSampleCommand, CanExecuteSampleCommand);
OnPropertyChanged("SampleCommand");
}
return _sampleCommand;
}
}
public bool CanExecuteSampleCommand(Object commandParameter)
{
return true;
}
public void ExecuteSampleCommand(Object commandParameter)
{
Console.WriteLine("ExecuteSampleCommand");
}
public object SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
public void LoadParents()
{
var parents = new ObservableCollection<Parent>()
{
new Parent()
{
Id=1,
Description = "Parent1",
Children = new List<Child>(){new Child() {Id = 1, Description = "Child1"} }
}
};
Parents = parents;
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
There are 2 simple entities:
public class Parent
{
public int Id { get; set;}
public string Description { get; set;}
public IEnumerable<Child> Children { get; set;}
}
public class Child
{
public int Id { get; set; }
public string Description { get; set; }
}
The OnStartup override in App.xaml.cs contains the following:
var viewModel = new ViewModel();
var window = new MainWindow();
window.DataContext = viewModel;
viewModel.LoadParents();
window.Show();
Whenever i select the parent row, the SelectedItem setter is called with a populated object. When I selected a child row, the same setter is called, but with a null value.
Is there any way I can get a reference to the selected item when the context menu is clicked on a child row - and do this without code-behind. If not, is it possible with code-behind?
Perhaps you should set a context menu directly on the cells or row like below. Then you can send the appropriate model to the command. In the example below I use a static locator class that has my VM to bind the actual command too.
<Style TargetType="{x:Type xcdg:DataCell}">
<Setter Property="ContextMenu" Value="{StaticResource CellContextMenu}">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Execute Command"
CommandParameter="{Binding}"
Command="{Binding Source={StaticResource Locator} Path=ViewModel.SampleCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>

How to create DataTemplate in TreeView Silverlight 4.0

I have some complex requirement to create a DataTemplate for a TreeView in Silverlight. This is using MVVM design pattern.
here is what i want:
Package1
Child1
Child2
Package2
Child3
Child4
Code:
Class Package
{
string _Name;
ObservableCollection<Child> _Childs;
public ObservableCollection<Child> Childs{get{return _Childs; } set{_Childs=value;}}
public string PackageName{get{return _Name; } set{_Name=value;}}
}
class Child
{
string ChildName;
public bool IsEnabled{get;set;}
public string Id{get; set;}
}
Using the above data contract i should create a tree view:
Package Name being TreeItem header value.
Each child item is a child box with Content is ChildName and The Enabled property of the checkbox is binded to IsEnabled of Child.
I have tried the following code but it is not showing any tree view at all.
<sdk:TreeView x:Name="SelectedPackagesTV" ItemsSource="{Binding Packages, Mode=TwoWay}"
ItemTemplate="{StaticResource PackageTemplate}">
<sdk:TreeView.Resources>
<DataTemplate x:Key="FormsTemplate" >
<CheckBox Content="{Binding Id, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}" >
</CheckBox>
</DataTemplate>
<sdk:HierarchicalDataTemplate x:Key="PackageTemplate" ItemsSource="{Binding Childs, Mode=TwoWay}" ItemTemplate="{StaticResource FormsTemplate}">
<TextBlock Text="{Binding Name, Mode=TwoWay}" />
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.Resources>
</sdk:TreeView>
NOTE: I am populating values to the PackagesCollection in the view model constructor. Will that be a problem?
Let me know if I am missing anything and also suggest me best way to resolve this.
Thanks in advance.
Edit:
The binding in the "PackageTemplate" is set to "Name" but the public property on the class is "PackageName".
Here is a sample of creating the data and attaching it. I have tested this and it works.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
public class Package
{
public string Name { get; set; }
public ObservableCollection<Child> Childs { get; set; }
}
public class Child
{
public string ChildName { get; set; }
public bool IsEnabled { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Package> _packages;
public event PropertyChangedEventHandler PropertyChanged;
public string TestStr { get { return "testing123"; } }
public ViewModel()
{
List<Package> list = new List<Package>() { new Package() { Name = "pkg1", Childs = new ObservableCollection<Child>(new List<Child>() { new Child() { ChildName = "Child1" } }) } };
this.Packages = new ObservableCollection<Package>(list);
}
public ObservableCollection<Package> Packages
{
get
{
return this._packages;
}
set
{
this._packages = value;
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
A few things:
Your business objects should look like this:
public class Package
{
public string Name { get; set; }
public ObservableCollection<Child> Childs { get; set; }
}
public class Child
{
public string ChildName { get; set; }
public bool IsEnabled { get; set; }
}
Binding can only work on public Properties, not private fields
Your xaml can be corrected to:
<sdk:TreeView ItemTemplate= "{StaticResoruce PackageTemplate}" ItemsSource="{Binding Packages}">
<sdk:TreeView.Resources>
<sdk:HierarchicalDataTemplate x:Key="PackageTemplate"
ItemsSource="{Binding Childs,Mode=TwoWay}"
ItemTemplate="{StaticResource ChildTemplate}">
<TextBlock Text="{Binding Name,Mode=TwoWay}" />
</sdk:HierarchicalDataTemplate>
<sdk:DataTemplate x:Key="ChildTemplate" >
<CheckBox Content="{Binding ChildName,Mode=TwoWay}"
IsEnabled="{Binding IsEnabled}">
</CheckBox>
</sdk:DataTemplate>
</sdk:TreeView.Resources>
The templates need to be in an enclosing "Resources" bracket, either of the TreeView or the UserControl.
Your PackageTemplate should have the ItemsSource "Childs" instead of Packages. You are binding the PackageTemplate to "PackageName" but the Package class states "Name".
Not an error but because your ChildTemplate has no children of its own it only needs to be a DataTemplate, no hierarchy.
Also
ItemTemplate= {"StaticResoruce PackageTemplate"}
Should be:
ItemTemplate= "{StaticResource PackageTemplate}"