Several TreeViewItem in one DataTemplate - windows

I'm struggling with a TreeView in my WinUI app where I would like to have several TreeViewItems in a single DataTemplate.
I have tried several things but I would imagine I could do something like in my example.
But in my running code I can only see the TextBlocks and the TreeViewItem headers but with now arrow at the TreeViewItems.
<Window
x:Class="SimpleTreeViewExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SimpleTreeViewExample"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="PersonTemplate" x:DataType="local:Person">
<StackPanel>
<TextBlock Text="{x:Bind FirstName}" />
<TextBlock Text="{x:Bind LastName}" />
<TreeViewItem ItemsSource="{x:Bind Books}" IsExpanded="False" Content="Books"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BookTemplate" x:DataType="local:Book">
<StackPanel>
<TextBlock Text="{x:Bind Writer}" />
<TextBlock Text="{x:Bind Title}" />
</StackPanel>
</DataTemplate>
<local:TemplateSelector x:Key="TemplateSelector"
PersonTemplate="{StaticResource PersonTemplate}"
BookTemplate="{StaticResource BookTemplate}">
</local:TemplateSelector>
</Grid.Resources>
<StackPanel>
<TreeView x:Name="PackageReferenceTree"
ItemsSource="{x:Bind Persons}"
ItemTemplateSelector="{StaticResource TemplateSelector}" />
</StackPanel>
</Grid>
</Window>
Here is my code behind:
public sealed partial class MainWindow : Window
{
public ObservableCollection<Person> Persons = new();
public MainWindow()
{
this.InitializeComponent();
Person person1 = new Person("John", "Doe");
person1.Books.Add(new Book("Stephen King", "The Shining"));
Persons.Add(person1);
}
}
public partial class Person : ObservableObject
{
[ObservableProperty]
private string firstName;
[ObservableProperty]
private string lastName;
public ObservableCollection<Book> Books = new();
public Person(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
}
public partial class Book : ObservableObject
{
[ObservableProperty]
private string writer;
[ObservableProperty]
private string title;
public Book(string writer, string title)
{
this.writer = writer;
this.title = title;
}
}
public class TemplateSelector : DataTemplateSelector
{
public DataTemplate PersonTemplate { get; set; }
public DataTemplate BookTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
if (item.GetType() == typeof(Person))
{
return PersonTemplate;
}
else if (item.GetType() == typeof(Book))
{
return BookTemplate;
}
throw new NotSupportedException($"The item type: {item.GetType()} wasn't known ");
}
}
If I remove the StackPanel and only keep a single TreeViewItem it works fine.
I'm having a template selector which isn't hit when I'm having a StackPanel but it is hit without the StackPanel so I assume the issue is related to that.
So what I would like to obtain is that I have a list of persons and each of them can be expanded. When expanded they contain a firstname and lastname and a list with books and a list with movies.
Books and Movies lists can also be expanded and are not the same types.
So it looks something like:

This code below works. The key point is to use TreeViews inside the template.
TreeViewDataTemplateSelector.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
namespace TreeViewTests;
public class TreeViewDataTemplateSelector : DataTemplateSelector
{
public DataTemplate? PersonCollectionTemplate { get; set; }
public DataTemplate? PersonTemplate { get; set; }
public DataTemplate? BookCollectionTemplate { get; set; }
public DataTemplate? MovieCollectionTemplate { get; set; }
public DataTemplate? BookTemplate { get; set; }
public DataTemplate? MovieTemplate { get; set; }
public TreeViewDataTemplateSelector()
{
}
protected override DataTemplate? SelectTemplateCore(object item)
{
return item switch
{
PersonCollection => PersonCollectionTemplate,
Person => PersonTemplate,
BookCollection => BookCollectionTemplate,
Book => BookTemplate,
MovieCollection => MovieCollectionTemplate,
Movie => MovieTemplate,
_ => throw new NotSupportedException(),
};
}
}
MainPageViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace TreeViewTests;
public partial class PersonCollection : ObservableObject
{
[ObservableProperty]
private ObservableCollection<Person> persons = new();
}
public partial class Person : ObservableObject
{
[ObservableProperty]
private string firstName = string.Empty;
[ObservableProperty]
private string lastName = string.Empty;
[ObservableProperty]
private ObservableCollection<BookCollection> bookCollections = new();
[ObservableProperty]
private ObservableCollection<MovieCollection> movieCollections = new();
}
public partial class ItemCollection<T> : ObservableObject where T : class
{
[ObservableProperty]
private string name = string.Empty;
[ObservableProperty]
private ObservableCollection<T> items = new();
}
public class BookCollection : ItemCollection<Book>
{
}
public class MovieCollection : ItemCollection<Movie>
{
}
public record Book(string Title, string Writer);
public record Movie(string Production, int Year, double Score);
public partial class MainPageViewModel : ObservableObject
{
[ObservableProperty]
private ObservableCollection<PersonCollection> personCollections = new();
public MainPageViewModel()
{
this.PersonCollections.Add(new PersonCollection()
{
Persons = new ObservableCollection<Person>()
{
new Person()
{
FirstName = "First A",
LastName = "Last A",
BookCollections = new ObservableCollection<BookCollection>()
{
new BookCollection()
{
Name = "Books",
Items = new ObservableCollection<Book>()
{
new Book(
Title: "Book A-1",
Writer: "Writer A-1"),
}
}
},
MovieCollections = new ObservableCollection<MovieCollection>()
{
new MovieCollection()
{
Name = "Movies",
Items = new ObservableCollection<Movie>()
{
new Movie(
Production: "Production A-1",
Year: 2018,
Score: 10.0),
new Movie(
Production: "Production A-2",
Year: 2019,
Score: 10.0),
new Movie(
Production: "Production A-3",
Year: 2020,
Score: 10.0),
}
}
},
},
new Person()
{
FirstName = "First B",
LastName = "Last B",
BookCollections = new ObservableCollection<BookCollection>()
{
new BookCollection()
{
Name = "Books",
Items = new ObservableCollection<Book>()
{
new Book(
Title: "Book B-1",
Writer: "Writer B-1"),
new Book(
Title: "Book B-2",
Writer: "Writer B-2"),
}
}
},
MovieCollections = new ObservableCollection<MovieCollection>()
{
new MovieCollection()
{
Name = "Movies",
Items = new ObservableCollection<Movie>()
{
new Movie(
Production: "Production B-1",
Year: 2021,
Score: 10.0),
new Movie(
Production: "Production B-2",
Year: 2022,
Score: 10.0),
}
}
},
},
new Person()
{
FirstName = "First C",
LastName = "Last C",
BookCollections = new ObservableCollection<BookCollection>()
{
new BookCollection()
{
Name = "Books",
Items = new ObservableCollection<Book>()
{
new Book(
Title: "Book C-1",
Writer : "Writer C-1"),
new Book(
Title: "Book C-2",
Writer: "Writer C-2"),
new Book(
Title: "Book C-3",
Writer: "Writer C-3"),
}
}
},
MovieCollections = new ObservableCollection<MovieCollection>()
{
new MovieCollection()
{
Name = "Movies",
Items = new ObservableCollection<Movie>()
{
new Movie(
Production: "Production C-1",
Year: 2023,
Score: 10.0),
}
}
},
},
}
});
}
}
MainPage.xaml
<Page
x:Class="TreeViewTests.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:local="using:TreeViewTests"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate
x:Key="PersonCollectionTemplate"
x:DataType="local:PersonCollection">
<TreeViewItem
HasUnrealizedChildren="True"
ItemsSource="{x:Bind Persons, Mode=OneWay}">
<TextBlock Text="Persons" />
</TreeViewItem>
</DataTemplate>
<DataTemplate
x:Key="PersonTemplate"
x:DataType="local:Person">
<StackPanel>
<TextBlock Text="{x:Bind FirstName, Mode=OneWay}" />
<TextBlock Text="{x:Bind LastName, Mode=OneWay}" />
<TreeView
ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"
ItemsSource="{x:Bind BookCollections, Mode=OneWay}" />
<TreeView
ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"
ItemsSource="{x:Bind MovieCollections, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
<DataTemplate
x:Key="BookCollectionTemplate"
x:DataType="local:BookCollection">
<TreeViewItem
HasUnrealizedChildren="True"
ItemsSource="{x:Bind Items, Mode=OneWay}">
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</TreeViewItem>
</DataTemplate>
<DataTemplate
x:Key="BookTemplate"
x:DataType="local:Book">
<StackPanel>
<TextBlock Text="{x:Bind Title, Mode=OneWay}" />
<TextBlock Text="{x:Bind Writer, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
<DataTemplate
x:Key="MovieCollectionTemplate"
x:DataType="local:MovieCollection">
<TreeViewItem
HasUnrealizedChildren="True"
ItemsSource="{x:Bind Items, Mode=OneWay}">
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
</TreeViewItem>
</DataTemplate>
<DataTemplate
x:Key="MovieTemplate"
x:DataType="local:Movie">
<StackPanel>
<TextBlock Text="{x:Bind Production, Mode=OneWay}" />
<TextBlock Text="{x:Bind Year, Mode=OneWay}" />
<TextBlock Text="{x:Bind Score, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
<local:TreeViewDataTemplateSelector
x:Key="TreeViewDataTemplateSelector"
BookTemplate="{StaticResource BookTemplate}"
MovieCollectionTemplate="{StaticResource MovieCollectionTemplate}"
BookCollectionTemplate="{StaticResource BookCollectionTemplate}"
MovieTemplate="{StaticResource MovieTemplate}"
PersonCollectionTemplate="{StaticResource PersonCollectionTemplate}"
PersonTemplate="{StaticResource PersonTemplate}" />
</Page.Resources>
<Grid>
<TreeView
x:Name="TreeViewControl"
ItemTemplateSelector="{StaticResource TreeViewDataTemplateSelector}"
ItemsSource="{x:Bind ViewModel.PersonCollections, Mode=OneWay}" />
</Grid>
</Page>

Here is a sample for your reference, you need change the value of Name in code behind. The code is from the official sample, you need to change the model (Persons, Books, Movies) according to your needs.
MainWindow.xaml
<Grid>
<TreeView ItemsSource="{x:Bind DataSource}">
<TreeView.ItemTemplate>
<DataTemplate x:DataType="local:ExplorerItem">
<TreeViewItem ItemsSource="{x:Bind Children}" Content="{x:Bind Name}">
</TreeViewItem>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
MainWindow.xaml.cs
public sealed partial class MainWindow : Window
{
TreeViewNode personalFolder;
TreeViewNode personalFolder2;
private ObservableCollection<ExplorerItem> DataSource;
public MainWindow()
{
this.InitializeComponent();
DataSource = GetData();
}
private ObservableCollection<ExplorerItem> GetData()
{
var list = new ObservableCollection<ExplorerItem>();
ExplorerItem folder1 = new ExplorerItem()
{
Name = "Persons",
Type = ExplorerItem.ExplorerItemType.Folder,
Children =
{
new ExplorerItem()
{
Name = "John",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Doe",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Books",
Type = ExplorerItem.ExplorerItemType.Folder,
Children =
{
new ExplorerItem()
{
Name = "Title",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Writer",
Type = ExplorerItem.ExplorerItemType.File,
}
}
},
new ExplorerItem()
{
Name = "Movies",
Type = ExplorerItem.ExplorerItemType.Folder,
Children =
{
new ExplorerItem()
{
Name = "Production",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Year",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Score",
Type = ExplorerItem.ExplorerItemType.File,
}
}
}
}
};
ExplorerItem folder2 = new ExplorerItem()
{
Name = "Personal Folder",
Type = ExplorerItem.ExplorerItemType.Folder,
Children =
{
new ExplorerItem()
{
Name = "Home Remodel Folder",
Type = ExplorerItem.ExplorerItemType.Folder,
Children =
{
new ExplorerItem()
{
Name = "Contractor Contact Info",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Paint Color Scheme",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Flooring Woodgrain type",
Type = ExplorerItem.ExplorerItemType.File,
},
new ExplorerItem()
{
Name = "Kitchen Cabinet Style",
Type = ExplorerItem.ExplorerItemType.File,
}
}
}
}
};
list.Add(folder1);
list.Add(folder2);
return list;
}
}
public class ExplorerItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public enum ExplorerItemType { Folder, File };
public string Name { get; set; }
public ExplorerItemType Type { get; set; }
private ObservableCollection<ExplorerItem> m_children;
public ObservableCollection<ExplorerItem> Children
{
get
{
if (m_children == null)
{
m_children = new ObservableCollection<ExplorerItem>();
}
return m_children;
}
set
{
m_children = value;
}
}
private bool m_isExpanded;
public bool IsExpanded
{
get { return m_isExpanded; }
set
{
if (m_isExpanded != value)
{
m_isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
}
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class ExplorerItemTemplateSelector : DataTemplateSelector
{
public DataTemplate FolderTemplate { get; set; }
public DataTemplate FileTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
var explorerItem = (ExplorerItem)item;
return explorerItem.Type == ExplorerItem.ExplorerItemType.Folder ? FolderTemplate : FileTemplate;
}
}

Related

Xamarin: Bind picker selected item with FreshMvvM ViewModel

I'm creating a form where a product status needs to be selected with a dropdown menu.
I've created a picker for this. The data is loaded from a list in my ViewModel, but it doesn't get sent back.
I've tried the same using entry fields and that works fine. I just have no idea how to link the picker with the view model.
Here's my code.
Xaml
</Grid>
<Label Text="Current status" Style="{StaticResource MainLabel}"/>
<Label Style="{StaticResource MainLabel}" Text="{Binding ProductionStatus, Mode=TwoWay}"/>
<!-- gets send when saved-->
<Entry Style="{StaticResource MainEntry}" Text="{Binding ProductionStatus, Mode=TwoWay}" Keyboard="Text" />
<Label Text="Remark" Style="{StaticResource MainLabel} "/>
<!-- gets send when saved-->
<Entry Text="{Binding Remark}" Keyboard="Text" Style="{StaticResource MainEntry}"/>
<!-- Does not bind with anything.-->
<Picker x:Name="statusPicker" ItemsSource="{Binding ProductionOrderStatusName}" ItemDisplayBinding="{Binding Name}" SelectedItem="{Binding ProductionStatusName}"/>
<Button Style="{StaticResource PrimaryButton}" Text="Save" Command="{Binding SaveCommand}"></Button>
Code-behind ViewModel
public ICommand SaveCommand
{
get
{
return new Command(ExecuteSaveCommand);
}
}
private async void ExecuteSaveCommand()
{
int statusId = FindProductionOrderStatusId(ProductionStatus); //the production status should be the result of the selected value in the picker
Guid id = _CurrentProductionOrder.Id;
string remark = Remark; // value from the remark entery in the xaml
await __productionOrderService.UpdateAsync(id, remark,statusId);
}
Properties
public ObservableCollection<ProductionOrderStatus> productionOrderStatusName;
public ObservableCollection<ProductionOrderStatus> ProductionOrderStatusName
{
get { return productionOrderStatusName; }
set
{
productionOrderStatusName = value;
}
}
public int amount;
public int Amount
{
get { return amount; }
set
{
amount = value;
}
}
public DateTime finishDate;
public DateTime FinishDate
{
get { return finishDate; }
set
{
finishDate = value;
}
}
public int ordernumber;
public int OrderNumber
{
get { return ordernumber; }
set
{
ordernumber = value;
}
}
public string remark;
public string Remark
{
get { return remark; }
set
{
remark = value;
}
}
public string productionStatus;
public string ProductionStatus
{
get
{
return productionStatus;
}
set
{
productionStatus = value;
}
}
private string materialNumber;
public string MaterialNumber
{
get { return materialNumber; }
set
{
materialNumber = value;
}
}
private string materialDescription;
public string MaterialDescription
{
get { return materialDescription; }
set
{
materialDescription = value;
}
}
Code behind loading my data
public OrderViewModel()
{
_productionOrderStepService = new MockingProductionOrderStepService();
_materialService = new MockingMaterialService();
__productionOrderService = new MockingProductionOrderService();
__productionOrderStatusService = new MockingProductionOrderStatusService();
_seederService = new Seeder(__productionOrderService, _productionOrderStepService, __productionOrderStatusService, _materialService);
_seederService.EnsureSeeded();
}
public override void Init(object initData)
{
_CurrentProductionOrder = initData as ProductionOrder;
LoadItemState();
base.Init(initData);
}
private void LoadItemState()
{
ObservableCollection<ProductionOrderStatus> ProductionStatusName = new ObservableCollection<ProductionOrderStatus>(__productionOrderStatusService.GetListAllAsync().Result);
this.ProductionOrderStatusName = ProductionStatusName;
this.materialDescription = FindMaterialDescription(_CurrentProductionOrder.MaterialId);
this.materialNumber = FindMaterialNumber(_CurrentProductionOrder.MaterialId);
this.productionStatus = FindProductionOrderStatus(_CurrentProductionOrder.StatusId);
this.remark = _CurrentProductionOrder.Remark;
this.amount=_CurrentProductionOrder.Amount;
this.finishDate = _CurrentProductionOrder.FinishDate;
this.ordernumber = _CurrentProductionOrder.OrderNumber;
}
Thx for the help!
you are making this more complicated than it needs to be
<Picker x:Name="statusPicker"
// this is the List of items X to display
ItemsSource="{Binding ProductionOrderStatusName}"
// this tells the picker which property of X to display to the user
ItemDisplayBinding="{Binding Name}"
// this is the specific X the user has selected
SelectedItem="{Binding SelectedStatus}" />
then in your VM
public ObservableCollection<ProductionOrderStatus> ProductionOrderStatusName { get; set; }
public ProductionOrderStatus SelectedStatus { get; set; }

Xamarin Forms CollectionView is empty

I have a Xamarin Forms CollectionView, contained within a RefreshView. The binding source is populated but its not showing anything in the CollectionView. I know the binding source is populated as I show a count in the toolbar. Can anyone spot why the list is empty?
Here is my Content Page with my RefreshView and CollectionView:
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddDogCommand}" />
<ToolbarItem Text="{Binding Dogs.Count}"></ToolbarItem>
</ContentPage.ToolbarItems>
<RefreshView x:DataType="local:MyDogsViewModel" Command="{Binding LoadDogsCommand}" IsRefreshing="{Binding IsBusy, Mode=TwoWay}">
<CollectionView ItemsLayout="HorizontalList" ItemsSource="{Binding Dogs}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid WidthRequest="100">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="1"
Text="{Binding DogName}"
FontAttributes="Bold" TextColor="Red"/>
<Label Grid.Row="1"
Grid.Column="1"
Text="{Binding Nickname}"
FontAttributes="Italic" TextColor="Green"
VerticalOptions="End" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</RefreshView>
Here is my ViewModel
public class MyDogsViewModel : BaseViewModel
{
private DogModel _selectedItem;
private DogService dogService = new DogService();
private string _dogName;
private string _nickname;
public ObservableCollection<DogModel> Dogs { get; }
public Command LoadDogsCommand { get; }
public Command<DogModel> ItemTapped { get; }
public Command AddDogCommand { get; }
public Command SaveDogCommand { get; }
public string DogName
{
get => _dogName;
set => SetProperty(ref _dogName, value);
}
public string Nickname
{
get => _nickname;
set => SetProperty(ref _nickname, value);
}
public MyDogsViewModel()
{
Title = "My Dogs";
Dogs = new ObservableCollection<DogModel>();
LoadDogsCommand = new Command(async () => await ExecuteLoadDogsCommand());
ItemTapped = new Command<DogModel>(OnItemSelected);
AddDogCommand = new Command(OnAddDog);
SaveDogCommand = new Command(OnSaveDog);
}
async Task ExecuteLoadDogsCommand()
{
IsBusy = true;
try
{
Dogs.Clear();
var dogs = await dogService.GetDogsAsync();
foreach (var d in dogs)
{
Dogs.Add(d);
}
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
}
private async void OnAddDog(object obj)
{
await Shell.Current.GoToAsync(nameof(AddDogPage));
}
private async void OnSaveDog(object obj)
{
AddDogModel model = new AddDogModel
{
DogName = DogName,
Nickname = Nickname
};
await dogService.AddDog(model);
await Shell.Current.GoToAsync("..");
}
async void OnItemSelected(DogModel dog)
{
if (dog == null)
return;
}
}
DogModel class
public class DogModel
{
public int Id { get; set; }
public string DogName { get; set; }
public string Nickname { get; set; }
}
keep your xaml the same and i will edit your ViewModel and take out the properties that is causing you the issue ;
public class MyDogsViewModel : BaseViewModel
{
private DogModel _selectedItem;
private DogService dogService = new DogService();
private string _dogName;
private string _nickname;
private ObservableCollection<DogModel> dogs;
public ObservableCollection<DogModel> Dogs
{
get{return dogs;}
set{dogs=value;}
}
public Command LoadDogsCommand { get; set; }
public Command<DogModel> ItemTapped { get; set;}
public Command AddDogCommand { get; set;}
public Command SaveDogCommand { get; set;}
public MyDogsViewModel()
{
Title = "My Dogs";
Dogs = new ObservableCollection<DogModel>();
LoadDogsCommand = new Command(async () => await ExecuteLoadDogsCommand());
ItemTapped = new Command<DogModel>(OnItemSelected);
AddDogCommand = new Command(OnAddDog);
SaveDogCommand = new Command<object>(OnSaveDog);
}
async Task ExecuteLoadDogsCommand()
{
IsBusy = true;
try
{
Dogs.Clear();
var dogs = await dogService.GetDogsAsync();
foreach (var d in dogs)
{
Dogs.Add(d);
}
}
catch (Exception ex)
{
Debug.WriteLine("Exception: " + ex);
}
finally
{
IsBusy = false;
}
}
public void OnAppearing()
{
IsBusy = true;
}
private async void OnAddDog(object obj)
{
await Shell.Current.GoToAsync(nameof(AddDogPage));
}
private async void OnSaveDog(object obj)
{
DogModel newdog = obj as DogModel;
await dogService.AddDog(newdog);
await Shell.Current.GoToAsync("..");
}
async void OnItemSelected(DogModel dog)
{
if (dog == null)
return;
}
}
dont forget to bind to the viewmodel. in the xaml.cs add this in the constuctor.
this.BindingContext = new MyDogsViewModel();

How to mark the checkbox in repeater in Xamarin.Forms?

I am using checkbox control under repeater to do a radio button functionality, everything seems to be fine but now stuck on how to bind the checkbox when the page loads. I have saved the radio button text whichever was selected and once user come back to page again I want to bin what he has selected last time. Not getting any hint here how to proceed.
<grial:Repeater
x:Name="PP"
SelectionMode="Single"
InitialSelection="Empty"
ItemSize="100"
HorizontalOptions="Start"
ItemsSource="{Binding BlowerPostions}">
<grial:Repeater.ItemTemplate>
<DataTemplate>
<grial:Checkbox
IsChecked="false"
UncheckedBorderColor="Black">
<Label
TextColor="Black"
Text="{ Binding . }"
Margin="8,0" />
</grial:Checkbox>
</DataTemplate>
</grial:Repeater.ItemTemplate>
<grial:Repeater.SelectedItemTemplate>
<DataTemplate>
<grial:Checkbox
IsChecked="true"
UncheckedBorderColor="Black"
InputTransparent="true">
<Label
TextColor="Black"
Text="{ Binding . }"
Margin="8,0" />
</grial:Checkbox>
</DataTemplate>
</grial:Repeater.SelectedItemTemplate>
</grial:Repeater>
View Model :
public class ProductionViewModel : INotifyPropertyChanged
{
public ObservableCollection<BlowerPostion> _blowerPostions;
public ObservableCollection<BlowerPostion> BlowerPostions
{
get => _blowerPostions;
set
{
_blowerPostions = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs("BlowerPostions"));
}
}
}
public void LoadData()
{
BlowerPostions = new ObservableCollection<BlowerPostion>();
BlowerPostions.Add(new BlowerPostion("Left", 1));
BlowerPostions.Add(new BlowerPostion("Standard", 1));
}
}
public class BlowerPostion
{
public string Text { get; set; }
public int Id { get; set; }
public BlowerPostion(string _text, int _id)
{
Text = _text;
Id = _id;
}
}
I don't use grial:Repeater,but you can refer to the following code which use CheckBox in ListView item.
Item.cs
public class Item
{
public string Name { get; set; }
public string Type { get; set; }
public string Image { get; set; }
//This field indicates whether or not it is selected
public bool isChecked { get; set; }
}
MyViewModel.cs
public class MyViewModel
{
public ObservableCollection<Item> items { get; private set; }
public MyViewModel() {
items = new ObservableCollection<Item>();
items.Add(new Item { Name = "Tomato", Type = "Fruit", Image = "tomato.png", isChecked = true });
items.Add(new Item { Name = "Romaine Lettuce", Type = "Vegetable", Image = "lettuce.png", isChecked = false });
items.Add(new Item { Name = "Zucchini", Type = "Vegetable", Image = "zucchini.png", isChecked = false });
}
}
TestPage1.xaml
<ContentPage.Content>
<ListView x:Name="listview" ItemsSource="{Binding items}" VerticalOptions="FillAndExpand">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Padding="5,0,5,0">
<Label Text="{Binding Name}" HorizontalOptions="StartAndExpand" FontSize="30"/>
<input:CheckBox IsChecked="{Binding isChecked}" Type="Check" Color="White" BoxBackgroundColor="Green" TextColor="White" HeightRequest="40"
CheckChanged="CheckBox_CheckChanged" BindingContext="{Binding .}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
TestPage1.xaml.cs
public partial class TestPage1 : ContentPage
{
public List<Item> selectedItems; // define `selectedItems` as the list of selected items.
public MyViewModel viewModel;
public TestPage1 ()
{
InitializeComponent ();
selectedItems = new List<Item>(); // init the `selectedItems`
viewModel = new MyViewModel();
BindingContext = viewModel;
}
private void CheckBox_CheckChanged(object sender, EventArgs e)
{
var checkbox = (Plugin.InputKit.Shared.Controls.CheckBox)sender;
var ob = checkbox.BindingContext as Item;
if (ob != null)
{
System.Diagnostics.Debug.WriteLine("isChecked = " + ob.isChecked + "<---> Name = " + ob.Name +"<---> Type = " + ob.Type );
if (ob.isChecked)
{
selectedItems.Add(ob);
}
else {
// remove the item
}
}
}
}
Note:
1.add new field isChecked in item model
public bool isChecked { get; set; }
2.Add event CheckChanged for the item.And when we check the CheckBox,we can get the corresponding value isChecked of the CheckBox.
<input:CheckBox IsChecked="{Binding isChecked}" Type="Check" Color="White" BoxBackgroundColor="Green" TextColor="White" HeightRequest="40"
CheckChanged="CheckBox_CheckChanged" BindingContext="{Binding .}" />

Xamarin CollectionView Observable Collection not updating with Searchbar

My Accounts CollectionView is not updating with the Searchbar. Xaml below.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:viewmodels="clr-namespace:Pricing051721.ViewModels"
x:Class="Pricing051721.MainPage" Title="KR Pricing"
x:Name="This">
<ContentPage.BindingContext>
<viewmodels:MainPageViewModel/>
</ContentPage.BindingContext>
<StackLayout>
<Button Text="Logout" Command="{Binding LogoutCommand}" Margin="0,5,0,5"/>
<SearchBar x:Name="searchBar"
SearchCommand="{Binding PerformSearch}"
SearchCommandParameter="{Binding Text, Source={x:Reference searchBar}}"/>
<CollectionView ItemsSource="{Binding Accounts}" Margin="5">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Margin="5" >
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.AccountSelected, Source={x:Reference This}}" CommandParameter="{Binding .}"/>
</StackLayout.GestureRecognizers>
<StackLayout >
<Label FontSize="Medium" Text="{Binding Name}" ></Label>
<Label Text="{Binding Address}"></Label>
</StackLayout>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>
I am trying to search through the Accounts already queried in the view model so I don't have to hit the database again. The search works, but Accounts is not updated.
namespace Pricing051721.ViewModels
{
public class MainPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<Account> Accounts { get; set; }
public INavigation Navigation { get; set; }
public ICommand LogoutCommand { get; set; }
AdAuthenticationService authService;
public ObservableCollection<Account> baseAccountList;
public MainPageViewModel()
{
Accounts = new ObservableCollection<Account> { new Account { AllowUpdate = true, Address = "Wait", Name = "Loading" } };
authService = new AdAuthenticationService();
Task.Run(async () =>
{
if (!authService.IsAuthenticated)
{
var response = authService.Authenticate();
await Update(response.AccessToken, "");
}
else await Update(authService.AccessToken, "");
});
AccountSelected = new Command<Account>(async (a) =>
{
if (!a.AllowUpdate)
return;
await Navigation.PushAsync(new UpdateAccountView(a));
return;
var result = await UserDialogs.Instance.PromptAsync(new PromptConfig
{
InputType = InputType.Name,
OkText = "Change",
Title = "Enter New Column Break",
Text = a.ColumnBreak
});
if (result.Ok && result.Text != null && !result.Text.Trim().Equals(""))
{
a.ColumnBreak = result.Text;
isUpdating = true;
var ok = await crm.Update(a);
var message = ok ? "Account Updated!" : "Unable to update!";
await UserDialogs.Instance.AlertAsync(new AlertConfig
{
Title = "Message",
Message = message,
OkText = "Ok"
});
isUpdating = false;
}
}, _ => !isUpdating);
LogoutCommand = new Command(new Action(() => {
authService.Logout();
Environment.Exit(Environment.ExitCode);
}));
}
//search
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ICommand PerformSearch => new Command<string>((string query) =>
{
Accounts = SearchAccounts(query);
});
private bool isUpdating = false;
private Crm crm;
public ObservableCollection<Account> accounts;
public async Task Update(string accessToken, string query)
{
Crm.Setup(accessToken);
crm = Crm.AuthenticatedCrmService;
var accounts = await crm.GetAccounts();
Accounts.RemoveAt(0);
accounts.ForEach(a => Accounts.Add(a));
}
public ObservableCollection<Account> SearchAccounts(string query)
{
Task.Run(async () =>
{
if (!authService.IsAuthenticated)
{
var response = authService.Authenticate();
await Update(response.AccessToken, "");
}
else await Update(authService.AccessToken, "");
});
baseAccountList = Accounts;
if (!(query == ""))
{
var normalizedQuery = query?.ToLower() ?? "";
List<Account> accountsList = (List<Account>)Accounts.Where(f => f.Name.ToLowerInvariant().Contains(normalizedQuery)).ToList();
ObservableCollection<Account> accounts = new ObservableCollection<Account>(accountsList);
Accounts.Clear();
return accounts;
}
else
{
accounts = Accounts;
return accounts;
}
}
public ICommand AccountSelected { get; set; }
}
}
I don't need a neat solution (as you can tell by my code so far), just something that will work Thanks in advance!
My Accounts CollectionView is not updating with the Searchbar
From your code, you don't post some code about PerformSearch command, I don't know how do you search data by searchbar. I do one sample about search some data by searchbar, display in collectionview, you can modify your code according to the following code.
<SearchBar
x:Name="searchBar"
SearchCommand="{Binding PerformSearch}"
SearchCommandParameter="{Binding Text, Source={x:Reference searchBar}}" />
<CollectionView Margin="5" ItemsSource="{Binding Accounts}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Margin="5">
<!--<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.AccountSelected, Source={x:Reference This}}" CommandParameter="{Binding .}" />
</StackLayout.GestureRecognizers>-->
<StackLayout>
<Label FontSize="Medium" Text="{Binding Name}" />
<Label Text="{Binding Address}" />
</StackLayout>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
public partial class Page15 : ContentPage
{
public Page15()
{
InitializeComponent();
this.BindingContext = new AccountViewModel();
}
}
public class AccountViewModel
{
public ObservableCollection<Account> AccountList { get; set; }
public ObservableCollection<Account> Accounts { get; set; }
public ICommand PerformSearch { get; set; }
public AccountViewModel()
{
AccountList = new ObservableCollection<Account>();
Accounts = new ObservableCollection<Account>();
for(int i=0;i<30;i++)
{
Account a = new Account();
a.Name = "account" + i;
a.Address = "address " + i;
AccountList.Add(a);
Accounts.Add(a);
}
PerformSearch = new Command(search => {
if(search!=null)
{
string searchtext = (string)search;
if (!string.IsNullOrEmpty(searchtext))
{
Accounts.Clear();
List<Account> list= AccountList.Where((account) => account.Name.ToLower().Contains(searchtext) || account.Address.ToLower().Contains(searchtext)).ToList();
foreach(Account a in list)
{
Accounts.Add(a);
}
}
Accounts = AccountList;
}
else
{
Accounts = AccountList;
}
});
}
}
public class Account
{
public string Name { get; set; }
public string Address { get; set; }
}

Xamarin.Forms How to change UI label text inside DataTemplate

This piece of code is displaying a few rows of icon and text that looks like a simple menu. The code below is perfectly working. But there are times I need to change one or more of the labels' text.
.xaml
<Frame HorizontalOptions="End" VerticalOptions="Start" BackgroundColor="Black" CornerRadius="5" >
<StackLayout Margin="0" Spacing="15" BindableLayout.ItemsSource="{Binding StockItemCollection}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal" Spacing="20">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</StackLayout.GestureRecognizers>
<Image Source="{Binding StockItemIcon}" WidthRequest="15" HeightRequest="15" BackgroundColor="Transparent"
VerticalOptions="Center" HorizontalOptions="Start"/>
<Label Text="{Binding StockItemTitle}" TextColor="White" FontSize="16" FontAttributes="Bold"
HorizontalOptions="Start" VerticalOptions="Center"/>
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</Frame>
The code behind...
public MainPage()
{
InitializeComponent();
.
.
.
LoadStock();
this.BindingContext = this;
}
public ObservableCollection<StockItem> StockItemCollection { get; set; }
private void LoadStock()
{
StockItemCollection = new ObservableCollection<StockItem>
{
new StockItem { StockItemTitle = "ItemTitle 0", StockItemIcon = "Item0.png" },
new StockItem { StockItemTitle = "ItemTitle 1", StockItemIcon = "Item1.png" },
new StockItem { StockItemTitle = "ItemTitle 2", StockItemIcon = "Item2.png" },
new StockItem { StockItemTitle = "ItemTitle 3", StockItemIcon = "Item3.png" },
new StockItem { StockItemTitle = "ItemTitle 4", StockItemIcon = "Item4.png" },
new StockItem { StockItemTitle = "ItemTitle 5", StockItemIcon = "Item5.png" }
};
}
public class StockItem
{
public string StockItemTitle { get; set; }
public string StockItemIcon { get; set; }
}
... Testing Get & Set...
Device.BeginInvokeOnMainThread(() =>
{
.
.
.
string before_StockItem = StockItemCollection[4].StockItemTitle;
// before_StockItem = "ItemTitle 4"
.
StockItemCollection[4].StockItemTitle = "Item 4 title Edited";
.
string after_StockItem = StockItemCollection[4].StockItemTitle;
// after_StockItem = "Item 4 title Edited"
// Problem: value updated here but NOT the label in the UI
});
This is where the problem is. The updated value is Not updated in the UI.
Anyone help please. Thanks
This is where the problem is. The updated value is Not updated in the UI.
As Jason said, to update the view at runtime, please make the model class implement the INotifyPropertyChanged interface to trigger the PropertyChanged. Check the tutorial.
Try the code like below:
public class CustomModel : INotifyPropertyChanged
{
private string content;
public string Content
{
get
{
return content;
}
set
{
if (content != value)
{
content = value;
NotifyPropertyChanged();
}
}
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here is the running gif:
I tried earlier by implementing INotifyPropertyChanged , but did it wrongly that caused PropertyChanged not firing. Thank you to Jason for making me try it again and finally got it done correctly.
So I think I should answer my own question here and I hope it will also help others.
Ref: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/how-to-implement-the-inotifypropertychanged-interface?view=netframeworkdesktop-4.8
Implementing INotifyPropertyChangedby changing this
public class StockItem
{
public string StockItemTitle { get; set; }
public string StockItemIcon { get; set; }
}
to this...
public class StockItem : INotifyPropertyChanged
{
private string _stockItemTitle = string.Empty;
public string StockItemIcon { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string StockItemTitle
{
get
{
return this._stockItemTitle;
}
set
{
if (value != this._stockItemTitle)
{
this._stockItemTitle = value;
NotifyPropertyChanged();
}
}
}
}

Resources