Xamarin List View SelectedItem ViewModel binding - c#

Using Visual Studio 2015 proff,
My LoginViewModel Class (Portable Class Library)
public class LoginViewModel : INotifyPropertyChanged, INotifyCollectionChanged
{
LoginPage page;
private ObservableCollection<Employees> _employeeList;
private string _loginName;
public ObservableCollection<Employees> EmployeeList
{
get { return _employeeList; }
set
{
_employeeList = value;
OnPropertyChanged();
OnCollectionChanged(NotifyCollectionChangedAction.Reset);
}
}
public string LoginName
{
get { return _loginName; }
set
{
_loginName = value;
if (_loginName != null)
{
OnPropertyChanged();
}
}
}
public LoginViewModel(LoginPage parent)
{
page = parent;
}
public async void GetEmployees()
{
var loginService = new LoginService();
EmployeeList = await loginService.GetEmployeesAsync();
}
public event PropertyChangedEventHandler PropertyChanged;
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged( NotifyCollectionChangedAction action)
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(action));
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My LoginPage.xaml (Portable Class Library)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ScannerApp.Views.LoginPage"
xmlns:ViewModels="clr-namespace:ScannerApp.ViewModels;assembly=ScannerApp">
<StackLayout Orientation="Vertical">
<Label Text="Please Login"
VerticalOptions="Start"
HorizontalTextAlignment="Center"
IsVisible="true"
FontSize="Large"
FontAttributes="Bold" />
<ListView x:Name="mylist" ItemsSource="{Binding EmployeeList}"
HasUnevenRows="True" SelectedItem="{Binding LoginName}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Name}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My LoginPage.xaml.cs Class (Portable Class Library)
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
BindingContext = new LoginViewModel(this);
}
protected override void OnAppearing()
{
base.OnAppearing();
LoginViewModel model = new LoginViewModel(this);
model.GetEmployees();
BindingContext = model;
}
public ListView MyList
{
get
{
return mylist;
}
}
}
Question
I get a list of employees, the list on the front end renders this. The user then Selects a name from the list, At this point I would like to detect this and then navigate to my different page.
Currently my property is not hit, I'm wondering if this has anything to do with my Binding on the code behind "OnAppearing"? but I'm not sure.

While what you have may work there are a few tweaks I would suggest.
No need to set your BindingContext in your constructor and in OnAppearing(). Just make your LoginViewModel a class level private property in your code-behind and only assign it to your BindingContext in your constructor. Then call GetEmployees() in OnAppearing().
Also, you should make GetEmployees() return a Task, in order to await as far up the chain as possible.
ViewModel:
....
public async Task GetEmployees()
{
var loginService = new LoginService();
EmployeeList = await loginService.GetEmployeesAsync();
}
....
Code-behind:
public partial class LoginPage : ContentPage
{
private LoginViewModel _model;
public LoginPage()
{
InitializeComponent();
BindingContext = _model = new LoginViewModel(this);
}
protected override async void OnAppearing() //Notice I changed this to async and can now await the GetEmployees() call
{
base.OnAppearing();
await _model.GetEmployees();
}
public ListView MyList
{
get
{
return mylist;
}
}
private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e) {
if (e.SelectedItem == null) return;
await Navigation.PushAsync(new MenuPage());
}
}
XAML:
<!-- Adding OnItemSelected in XAML below -->
<ListView x:Name="mylist"
ItemsSource="{Binding EmployeeList}"
HasUnevenRows="True"
SelectedItem="{Binding LoginName}"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding Name}"/>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

mylist.ItemSelected += async (sender, e) => {
if (e.SelectedItem == null) return;
await Navigation.PushAsync(new MenuPage());
};
This works, had to set the page to a nav wrap in the App.cs, then apply this event handler to the OnAppearing method.

Related

Shell CollectionView is not updating Xamarin Forms

I'm trying to make CollectionView in Shell but it's not updating.
I have one view model connected to Page and AppShell but when I update Collection view only page is updationg.
`public class AppShellViewModel : INotifyPropertyChanged
{
public Command Load { get; }
public ObservableCollection<ListData> _lists { get; set; }
public ObservableCollection<ListData> Lists
{
get { return _lists; }
set
{
_lists = value;
OnPropertyChanged();
}
}
public AppShellViewModel()
{
Lists = new ObservableCollection<ListData>()
{
new ListData(){id=0,name="test",UserId=0},
new ListData(){id=1,name="test1",UserId=1},
new ListData(){id=2,name="test2",UserId=2},
new ListData(){id=3,name="test3",UserId=3},
new ListData(){id=4,name="test4",UserId=4}
};
Load = new Command(async () => await GetUserLists());
}
async Task GetUserLists()
{
for (int i = 5; i < 15; i++)
{
Lists.Add(new ListData {id=i, name=$"test{ i }", UserId=i });
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}`
Then i have App Shell Collection View
`<Shell.FlyoutContent>
<StackLayout BackgroundColor="#34495e">
<Label Text="YOUR LISTS" FontSize="50" />
<CollectionView ItemsSource="{Binding Lists}" >
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:ListData">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</Shell.FlyoutContent>`
And There is Page CollectionView
`<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ToDoApp.Views.AboutPage"
xmlns:model="clr-namespace:ToDoApp.Models">
<StackLayout>
<Button Text="Load" Command="{Binding Load}"/>
<Label Text="{Binding error}"/>
<CollectionView ItemsSource="{Binding Lists}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:ListData">
<Label Text="{Binding name}"
LineBreakMode="NoWrap"
FontSize="13" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
</ContentPage>`
before update it looks like this
Page before update
Shell before update
And after update the only what changed is content page and shell is the same as before
Page after update
Shell after update
Related to Jason's comment.
WON'T CHANGE TOGETHER
NOT the same instance - BindingContexts similar to these:
// In AppShell.xaml.cs.
public AppShell()
{
InitializeComponent();
BindingContext = new AppShellViewModel();
}
// In AboutPage.xaml.cs.
public AboutPage()
{
InitializeComponent();
BindingContext = new AppShellViewModel();
}
GOOD (SHARED BETWEEN TWO PLACES)
BindingContexts are SAME instance:
// In AppShellViewModel.cs.
public class AppShellViewModel ...
{
private static AppShellViewModel _it;
public static AppShellViewModel It
{
get {
if (_it == null)
_it = new AppShellViewModel();
return _it;
}
}
}
// In AppShell.xaml.cs.
public AppShell()
{
InitializeComponent();
BindingContext = AppShellViewModel.It;
}
// In AboutPage.xaml.cs.
public AboutPage()
{
InitializeComponent();
BindingContext = AppShellViewModel.It;
}

Custom Picker Xamarin

I'm trying to search how to create a custom picker on Xamarin but I have no idea how to do it.
Here is what I want to do
I don't even know if I need to install a nuget package. Please help and thanks.
As mentioned by #Skalpel02, you need to sub-class the Picker class and implement the corresponding Renderers in each platform. There, you have the ability to interact with native APIs of the platform.
This could be implemented by custom renderer.
First,a custom Picker control can be created by subclassing the Picker control, as shown in the following code:
public class BorderlessPicker : Picker
{
public BorderlessPicker() : base()
{
}
}
Second:Create the Custom Renderer on each Platform,Override the OnElementChanged method and write logic to customize the control,then Add an ExportRenderer attribute to the custom renderer class to specify that it will be used.
In Android:
[assembly: ExportRenderer(typeof(BorderlessPicker), typeof(BordlessPickerRenderer))]
namespace AppPicker01.Droid
{
public class BordlessPickerRenderer : PickerRenderer
{
public BordlessPickerRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control.Background = null;
}
}
}
}
In iOS:
[assembly: ExportRenderer(typeof(BorderlessPicker), typeof(BorderlessPickerRenderer))]
namespace AppPicker01.iOS
{
public class BorderlessPickerRenderer : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (Control == null)
{
return;
}
Control.Layer.BorderWidth = 0;
Control.BorderStyle = UITextBorderStyle.None;
}
}
}
Last but not least, consume the custom picker control in Xaml:
<apppicker01:BorderlessPicker Title="Select a color" ItemsSource="{Binding ColorNames}" SelectedItem="{Binding SelectedColorName, Mode=TwoWay}" />
Screenshot:
MS official docs link:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/
You can easily create your own control that doesn't need a renderer and works on iOS, Android, and UWP. Here my solution.
You have to create a View "PickerCustom" for the control
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="___YOURCLASS"
xmlns:xmleditor="clr-namespace:XmlEditor" HorizontalOptions="FillAndExpand" BackgroundColor="#ddd">
<StackLayout x:Name="stack" Orientation="Horizontal" HorizontalOptions="FillAndExpand" Margin="1" BackgroundColor="#fff" Padding="5">
<Label Text="{Binding TextValue}" Margin="0" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>
<ImageButton BackgroundColor="#ffffff" Source="dropdown.png" x:Name="img" WidthRequest="20"></ImageButton>
<Entry WidthRequest="0"></Entry>
</StackLayout>
</ContentView>
with this code behind
public partial class PickerCustom : ContentView
{
public PickerCustom()
{
InitializeComponent();
Items = new ObservableCollection<CustomItem>();
SelectedIndex = -1;
BindingContext = this;
TapGestureRecognizer tap0 = new TapGestureRecognizer();
tap0.Tapped += (sender, e) =>
{
img.Focus();
PickerCustomList pcl = new PickerCustomList();
pcl.Items = this.Items;
App.Current.MainPage.Navigation.PushModalAsync(pcl);
MessagingCenter.Subscribe<PickerCustomList>(this, "finish", (sender1) =>
{
MessagingCenter.Unsubscribe<PickerCustomList>(this, "finish");
img.Focus();
if(((PickerCustomList)sender1).SelectedIndex != -1)
{
SelectedIndex = ((PickerCustomList)sender1).SelectedIndex;
}
});
};
GestureManager.AddGesture(stack, tap0);
}
string _textvalue = "";
public string TextValue
{
get
{
return _textvalue;
}
set
{
_textvalue = value;
OnPropertyChanged();
}
}
public ObservableCollection<CustomItem> Items { get; set; }
int _selectedIndex = 0;
public int SelectedIndex
{
get
{
return _selectedIndex;
}
set
{
_selectedIndex = value;
if(_selectedIndex>= Items.Count)
{
_selectedIndex = -1;
} else if (_selectedIndex != -1)
{
TextValue = Items[SelectedIndex].Name;
}
else
{
TextValue = "";
}
OnPropertyChanged();
}
}
}
public class CustomItem
{
public CustomItem(string _name)
{
name = _name;
}
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
And a View "PickerCustomList" for the choice
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="___YOURCLASS" BackgroundColor="#66aaaaaa"
x:Name="ContentPage1" Padding="30,100,30,100" >
<ListView x:Name="ContactsList" ItemsSource="{Binding Items}" IsVisible="True"
VerticalOptions="Start" HorizontalOptions="Center"
BackgroundColor="Transparent" HasUnevenRows="True">
<ListView.Header HorizontalOptions="FillAndExpand">
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand" BackgroundColor="#f0f0f0" >
<ImageButton Source="close.png" WidthRequest="20" Clicked="Button_Clicked" Margin="10,5,10,5" BackgroundColor="Transparent"></ImageButton>
</StackLayout>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell Tapped="ViewCell_Tapped" >
<StackLayout BackgroundColor="#ffffff">
<Label Text="{Binding Name}" Padding="10"></Label>
<ContentView HeightRequest="1" BackgroundColor="#666"></ContentView>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
with this code behind
public partial class PickerCustomList : ContentPage
{
public int SelectedIndex = -1;
ObservableCollection<CustomItem> myItems= new ObservableCollection<CustomItem>();
public ObservableCollection<CustomItem> Items
{
get { return myItems; }
set {
myItems = value;
OnPropertyChanged();
}
}
public PickerCustomList()
{
InitializeComponent();
BindingContext = this;
}
private void Button_Clicked(object sender, EventArgs e)
{
SelectedIndex = -1;
App.Current.MainPage.Navigation.PopModalAsync();
MessagingCenter.Send<PickerCustomList>(this, "finish");
}
private void ViewCell_Tapped(object sender, EventArgs e)
{
SelectedIndex = Items.IndexOf(((CustomItem)((ViewCell)sender).BindingContext));
App.Current.MainPage.Navigation.PopModalAsync();
MessagingCenter.Send<PickerCustomList>(this, "finish");
}
}

How can I wtite my code behind for checkbox in MVVM?

I have a code for checkbox. Please tell me how to write it in MVVM?
There is a function that I can choose only one checkbox. In general I understand that I must to write command.
XAML:
<StackLayout>
<!-- Place new controls here -->
<ListView ItemsSource="{Binding Items}" ItemSelected="ListView_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal">
<CheckBox HorizontalOptions="Start" Color="Black" CheckedChanged="CheckBox_CheckedChanged"
IsChecked="{Binding IsSelected}"
/>
<Label Text="meow" TextColor="Gray"></Label>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
CODE BEHIND
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
BindingContext = new MainPageViewModel();
}
Model previousModel;
private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
if (previousModel != null)
{
previousModel.IsSelected = false;
}
Model currentModel = ((CheckBox)sender).BindingContext as Model;
previousModel = currentModel;
if (currentModel.IsSelected)
{
var viewModel = BindingContext as MainPageViewModel;
int index = viewModel.Items.IndexOf(currentModel);
}
}
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (previousModel != null)
{
previousModel.IsSelected = false;
}
Model currentModel = e.SelectedItem as Model;
currentModel.IsSelected = true;
previousModel = currentModel;
}
}
ViewModel
public class MainPageViewModel
{
public List<Model> Items { set; get; }
public MainPageViewModel()
{
List<Model> list = new List<Model>();
for (int i=0; i<10; i++)
{
list.Add(new Model { IsSelected = false });
}
Items = list;
}
}
Model
public class Model : INotifyPropertyChanged
{
bool isSelected;
public bool IsSelected
{
set
{
isSelected = value;
onPropertyChanged();
}
get => isSelected;
}
public event PropertyChangedEventHandler PropertyChanged;
void onPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
For an event to command use Corcav.Behavior nuget
https://github.com/corradocavalli/Corcav.Behaviors
...
xmlns:corcav="clr-namespace:Corcav.Behaviors;assembly=Corcav.Behaviors"
...
<CheckBox>
<corcav:Interaction.Behaviors>
<corcav:BehaviorCollection>
<corcav:EventToCommand EventName="CheckedChanged" Command="{Binding Path=CheckBoxChangedCommand}" Commandparameter="{Binding .}"/>
</corcav:BehaviorCollection>
</corcav:Interaction.Behaviors>
</CheckBox>
Add this command in ViewModel and write your logic
public ICommand CheckBoxChangedCommand{ get; set; }
...
CheckBoxChangedCommand= new Command<object>(CheckBoxChanged);
...
private void CheckBoxChanged(object obj)
{
//set all list/collection element to false with linq
if(obj is Model model)
{
model.IsSelected = true;
}
}
For now, CheckBox do not support Command. This issue has reported on Github and have not fixed. We could follow this enhancement. https://github.com/xamarin/Xamarin.Forms/issues/6606
You could use the InputKit instead. Install Xamarin.Forms.InputKit on NuGet.
It provides CheckChangedCommand.
CheckChangedCommand: (Command) Bindable Command, executed when check changed.
<input:CheckBox HorizontalOptions="Start" Color="Black" CheckChangedCommand="{Binding CheckBoxChangedCommand}">

How to use proper MVVM to pass data in different content page Xamarin

I tried to pass values between two content page - MainPage and Page1 in my Xamarin app but no luck.Here is my MainPage.xaml
<ContentPage.BindingContext>
<local:FindPerimeter/>
</ContentPage.BindingContext>
<StackLayout>
<Label Text="A side here"/>
<Entry Placeholder="A side" Text="{Binding Aside}"/>
<Button Text="Next page" Clicked="Button_Clicked"/>
</StackLayout>
Button code behind
private async void Button_Clicked(object sender, EventArgs e) => await Navigation.PushModalAsync(new Page1());
FindPerimeter.cs
public class FindPerimeter : ViewModelBase
{
string a_side;
public string Aside
{
get => a_side;
set
{
if (a_side == value) return;
a_side = value;
OnPropertyChanged();
Perimeter = Calculate(a_side).ToString();
}
}
string perimeter;
public string Perimeter
{
get => perimeter;
set
{
if (perimeter == value) return;
perimeter = value;
OnPropertyChanged();
}
}
double Calculate(string a)
{
return 2 * double.Parse(a);
}
}
V
iewModelBae.cs
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
and Page1.xaml code
<ContentPage.BindingContext>
<local:FindPerimeter/>
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Label Text="Perimeter is :"/>
<Label Text="{Binding Perimeter}"/>
</StackLayout>
</ContentPage.Content>
So I want pass Perimeter value between MainPage.xaml and Page1.xaml but it is empty every time.I think it is because i create anther BindingContext in Page1.xaml but i dont know how to fix.The only way that i found is to make Perimeter property static.
You could Binding the viewmodel of Page8 to Page9 like below.
MainPage:
private async void Button_Clicked(object sender, EventArgs e)
{
var page1 = new Page1();
page1.BindingContext = this.BindingContext;
await Navigation.PushModalAsync(page1);
}

Xamrin forms Charge a page before going to it

I am learning Xamaring forms , I want to do 4 content pages. One will display my to do listand images.
I would like to know if there is a way to charge my todo list before going to the last page from any of my 3 pages.
Knowing that I am going through pages like this :
var page = new LastPage();
MainView.Content = page.Content;
Thanks for your help
Do you want to achieve the result like following GIF?
If so, you need achieve it by MVVM and INotifyPropertyChanged
First of all, you should create a model to achieve the INotifyPropertyChanged.
public class MyModel: INotifyPropertyChanged
{
string name;
public string Name
{
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Image");
}
}
get
{
return name;
}
}
string count;
public string Count
{
set
{
if (count != value)
{
count = value;
OnPropertyChanged("Count");
}
}
get
{
return count;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then you need the ViewModel to push the data.
public class MyViewModel
{
public ObservableCollection<MyModel> myModels { get; set; }
public MyViewModel() {
myModels = new ObservableCollection<MyModel>();
myModels.Add(new MyModel() { Count = "0", Name = "test1" });
myModels.Add(new MyModel() { Count = "1", Name = "test2" });
myModels.Add(new MyModel() { Count = "2", Name = "test3" });
}
}
In the First page and end page, you should binding same viewmodel that use bindingcontext like following code format.
MainPage.xaml
<StackLayout>
<!-- Place new controls here -->
<Button Text="Next" Clicked="Button_Clicked"></Button>
<ListView x:Name="mylistview" ItemsSource="{Binding myModels}" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout>
<Label Text="{Binding Name}"
FontAttributes="Bold"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Label Text="{Binding Count}"
LineBreakMode="WordWrap"
HorizontalOptions="Center" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
MyViewModel viewModel;
public MainPage()
{
InitializeComponent();
viewModel = new MyViewModel();
BindingContext = viewModel;
}
private void Button_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new Page1(viewModel));
}
}
Here is my demo, you can download it.
https://github.com/851265601/XFormsMvvmChange
Here is a helpful article about it, you can refer to it.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

Categories