Xamarin.Forms How to change UI label text inside DataTemplate - xamarin

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();
}
}
}
}

Related

Several TreeViewItem in one DataTemplate

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;
}
}

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 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; }
}

How to Bind Xamarin Forms Checkbox isChecked to a dynamic bool variable?

I am newbie to xamarin forms. I have a Listview containing checkboxes. I bind the checkbox "isChecked" to one of the listview's itemsource bool property. the problem is, everytime i change the bool value where the checkbox is bind, checkbox appearance has'nt change. How can i achieve that kind of approach?enter image description here
[1]: https://i.stack.imgur.com/4KcT2.png
Hi #Weggie Villarante. Please try this.It's work for me
<ViewCell>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Orientation="Horizontal">
<Label Text="{Binding Title}" HorizontalOptions="StartAndExpand"></Label>
<CheckBox IsChecked="{Binding IsCheck}" HorizontalOptions="End" HeightRequest="50"></CheckBox>
</StackLayout>
</ViewCell>
NotificationModel.cs
public class NotificationModel : INotifyPropertyChanged
{
public string Message { get; set; }
public string Title { get; set; }
public bool _IsCheck = false;
public bool IsCheck
{
get
{
return _IsCheck;
}
set
{
_IsCheck = value;
this.OnPropertyChanged("IsCheck");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
NotificationViewModel.cs
class NotificationViewModel : INotifyPropertyChanged
{
ObservableCollection<NotificationModel> _Items;
public ObservableCollection<NotificationModel> Items
{
get
{
return _Items;
}
set
{
_Items = value;
OnPropertyChanged();
}
}
public NotificationViewModel()
{
Items = new ObservableCollection<NotificationModel>();
AddItems();
}
void AddItems()
{
_Items.Add(new NotificationModel { Title = "Info", Message = "This is only information message please ignor this one." ,IsCheck = false});
_Items.Add(new NotificationModel { Title = "Alert", Message = "This is only Alert message please ignor this one." , IsCheck = false });
_Items.Add(new NotificationModel { Title = "Suggesstion", Message = "This is only Suggesstion message please ignor this one." , IsCheck = false});
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Xamarin Forms - remove extra space / embedded ListView

I am trying to figure out how to remove the white space you see in the image below (surrounded by a red rectangle). Notice I have a ListView embedded in a parent ListView.
XAML
<ListView x:Name="___listview" HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Button Image="{Binding ImageName}" Command="{Binding ShowDetailsCommand}" />
<ListView ItemsSource="{Binding Notes}">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding Note}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This probably isn't needed, but here is the model...
MODEL
namespace ViewCellClick
{
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Model : ModelBase
{
public Model()
{
_imageName = "ellipses_vertical.png";
_showDetails = true;
ShowDetailsCommand = new Command(() =>
{
ShowDetails = !_showDetails;
ImageName = (_imageName == "ellipses_vertical.png")
? "ellipses_horizontal.png"
: "ellipses_vertical.png";
});
}
bool _showDetails;
public bool ShowDetails
{
get { return _showDetails; }
set { if (_showDetails != value) { _showDetails = value; OnPropertyChanged("ShowDetails"); } }
}
string _imageName;
public string ImageName
{
get { return _imageName; }
set { if (_imageName != value) { _imageName = value; OnPropertyChanged("ImageName"); } }
}
public ICommand ShowDetailsCommand { get; set; }
List<ChildModel> _notes;
public List<ChildModel> Notes { get { return _notes; } set { _notes = value; } }
}
public class ChildModel : ModelBase
{
public ChildModel(string note) { _note = note; }
string _note;
public string Note
{
get { return _note; }
set { if (_note != value) { _note = value; OnPropertyChanged("Note"); } }
}
}
}
You can't do this with Xamarin.Forms.ListView and nesting them is not supported. Really on iOS this would be very difficult and I'm not sure you could get it working without some weird gesture behavior.

Resources