How to set/get the properties Grial Repeater? - xamarin

refer to http://docs.grialkit.com/repeater.html, it got a property
call 'SelectedItem', i want to know the code in detail how to set or get it ?
Please help, thanks!

This is how you will get the SelectedItem, I am assuming you are following the MVVM approach.
XAML:-
<grial:Repeater Spacing="8"
Padding="10"
ScrollPadding="10"
ItemsSource="{Binding Items}"
SelectedItem= "{Binding SelectedItemObject}">
<grial:Repeater.ItemTemplate>
<!--DEFAULT ITEM TEMPLATE-->
</grial:Repeater.ItemTemplate>
</grial:Repeater>
ViewModel:
private ModelClass _selectedItemObject;
public ModelClass SelectedItemObject
{
get { return _selectedItemObject; }
set
{
_selectedItemObject = value;
OnPropertyChanged("SelectedItemObject");
OnItemSelected(SelectedItemObject);
}
}
private List<ModelClass> _items;
public List<ModelClass> Items
{
get{ return _items;}
set
{
_items = value;
OnPropertyChanged("Items");
}
}
private void OnItemSelected(ModelClass selectedItem)
{
if (selectedItem == null)
return;
// Perform your logic her on selection
}

Related

Xamarin forms understanding ObservableCollection binding context

I'm having some issue getting my ObservableCollection to bind to alexrainman CarouselView.
After reading some basic articles I created my view model:
public class PostObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string postOwner = string.Empty;
string id = string.Empty;
string profileimage = string.Empty;
string post = string.Empty;
List<string> postimages = null;
public string PostOwner
{
set
{
if (postOwner != value)
{
postOwner = value;
OnPropertyChanged("PostOwner");
}
}
get
{
return postOwner;
}
}
public string Id {
set
{
if (id != value)
{
id = value;
OnPropertyChanged("Id");
}
}
get
{
return id;
}
}
public string Post
{
set
{
if (post != value)
{
post = value;
OnPropertyChanged("Post");
}
}
get
{
return post;
}
}
public string ProfileImage
{
set
{
if (profileimage != value)
{
profileimage = value;
OnPropertyChanged("ProfileImage") ;
}
}
get
{
return profileimage;
}
}
public List<string> PostImages
{
set
{
if (postimages != value)
{
postimages = value;
OnPropertyChanged("PostImages");
}
}
get
{
return postimages;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I retrieve my data via a REST call to my server:
public static bool GetMyPostData(ref ObservableCollection<PostObject> myPosts, string groupid, string apikey)
{
try
{
string newURL = URL + "GetPosts";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
MultipartFormDataContent formdata = new MultipartFormDataContent
{
{ new StringContent(apikey), "apikey" },
{ new StringContent(groupid), "groupid" }
};
HttpResponseMessage response = client.PostAsync(newURL, formdata).Result; // Blocking call! Program will wait here until a response is received or a timeout occurs.
if (response.IsSuccessStatusCode)
{
try
{
myPosts = response.Content.ReadAsAsync<ObservableCollection<PostObject>>().Result;
}
catch (Exception e)
{
Debug.WriteLine(e);
return false;
}
}
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine(ex);
return false;
}
}
Which works I get my data correctly, now I set up my Binding context like so:
ObservableCollection<PostObject> GroupPosts = new ObservableCollection<PostObject>();
public Posts (GroupInfo ginfo)
{
InitializeComponent ();
GroupTitle.Text = ginfo.Title;
CurrentGroupInfo = ginfo;
GetDataPosts();
BindingContext = GroupPosts;
}
public void GetDataPosts()
{
try
{
GroupPosts.Clear();
if (RestController.GetMyPostData(ref GroupPosts, CurrentGroupInfo.Id.ToString(), apikey))
{
Debug.WriteLine("Data downloaded");
}
}
catch(Exception e)
{
Debug.WriteLine(e.Message);
}
And finally I have my XAML set up like this:
<?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:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin"
xmlns:cv="clr-namespace:CarouselView.FormsPlugin.Abstractions;assembly=CarouselView.FormsPlugin.Abstractions"
NavigationPage.HasNavigationBar="True"
NavigationPage.HasBackButton="False"
NavigationPage.BackButtonTitle="Back"
x:Class="forms.Posts">
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" VerticalOptions="Center" Spacing="10" >
<Label x:Name="GroupTitle" TextColor="White" FontSize="Medium"/>
</StackLayout>
</NavigationPage.TitleView>
<ContentPage.ToolbarItems>
<ToolbarItem Name="iconexample" Icon="settings.png" Priority="0" Order="Primary" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
<cv:CarouselViewControl x:Name="carousel"
ItemsSource="{Binding PostImages}"
ShowArrows="true"
ShowIndicators="true"
Orientation="Horizontal">
</cv:CarouselViewControl>
</ContentPage.Content>
</ContentPage>
However I get an error
Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object.
So I'm not sure what I'm missing or I need to read up on this a little more? any help would be great.
You want to do a few changes here:
Change the field definition to a property, you won't be able to bind to the field:
public ObservableCollection<PostObject> GroupPosts { get; } = new ObservableCollection<PostObject>();
If you updating the reference then you have to raise property changed event, so your property definition should look like that:
private ObservableCollection<PostObject> _groupPosts = new ObservableCollection<PostObject>();
public ObservableCollection<PostObject> GroupPosts
{
get { return _groupPosts; }
set
{
_groupPosts = value;
RaisePropertyChanged(.....); // here you should notify your binding that value has changed
}
}
Because you are trying to pass this list by reference (ref parameter), you won't be able to compile it with a property so it's better just to return value from your data provider and then apply it:
GroupPosts.Clear();
var newData = RestController.GetMyPostData(CurrentGroupInfo.Id.ToString(), apikey);
GroupPosts = newData;
it's a bad practice to pass the observable collection to an underlying data provider because it will limit it to operate on UI thread only (otherwise after updating the collection on non-ui thread you can crash the app). But this is a top for another post :)

Button not disabled on page load in Xamarin.Forms

I have a button in my page. I want to disable this button on page load, but its not disabled.
XAML:
<Button IsEnabled="{Binding IsEnabledSaveBtn,Mode=TwoWay}" Text="Save" Command="{Binding SaveItem}" />
ViewModel:
bool _isEnabledSaveBtn = false;
public bool IsEnabledSaveBtn
{
get { return _isEnabledSaveBtn; }
set
{
_isEnabledSaveBtn = value;
OnPropertyChanged();
}
}
Inside ViewModel Constructor:
public CreateDiscountViewModel(INavigation navigation)
{
Navigation = navigation;
IsEnabledSaveBtn=False;
}
I'm also validating data on TextChange event and it's working fine. But I want to disable this button on page load.
How can I solve this?
You need the following changes:
In your XAML you do not need two-way binding :
<Button IsEnabled="{Binding IsEnabledSaveBtn}" Text="Save" Command="{Binding SaveItem}" />
Your Property should look something like this:
private bool _isEnabledSaveBtn;
public bool IsEnabledSaveBtn
{
get { return _isEnabledSaveBtn; }
set
{
_isEnabledSaveBtn = value;
OnPropertyChanged(nameof(IsEnabledSaveBtn));
}
}
And in your ViewModel constructor set the value:
public CreateDiscountViewModel(INavigation navigation)
{
Navigation = navigation;
IsEnabledSaveBtn = false;
}
Something seems to be wrong with your OnPropertyChanged event.
Try the following, it will work.
is Button Enabled:
bool _isEnabledSaveBtn = false;
public bool IsEnabledSaveBtn
{
get { return _isEnabledSaveBtn; }
set
{
_isEnabledSaveBtn = value;
OnPropertyChanged("IsEnabledSaveBtn");
}
}
OnPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
and in your contructor
IsEnabledSaveBtn = false;

Listview not refresh when any property of a list item changes

Listview not refresh when any changes on a listview items property
here is my PageModel:
Public MyPageModel()
{
// populating listview data
ListData=PopulateData();
ObservableCollection<MyClass> _listData = new ObservableCollection<MyClass>();
public ObservableCollection<MyClass> ListData
{
get { return _listData; }
set
{
_listData = value;
RaisePropertyChanged();
}
}
}
foreach(MyClass item in ListData)
{
if(item.id==2)
{
item.Name="UpdatedName"
}
}
Inside XAML:
<ListView ItemSource="{Bindind ListData}">
..............
</ListView>
after changing item details it will not update the listview itemsource
Did you added the property notify change in 'MyClass' as well?
ex:
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged();
}
}

Where to set bindings for a custom cell with customer renderer if I am using a DataTemplateSelector?

I have a DataTemplateSelector that selects between two different cells. On Android, this template picks cells that are defined as Android xml files. I can confirm that the template selector is working because I have two different color circles showing, and the colors are correct. But my data is not being bound and I am not sure why. I think I am not setting the binding somewhere, but I am not sure where/how to do that.
Here is my page that includes the ListViewwith the DataTemplateSelector. I set the ItemsSourcehere, but I never set the bindings for the different parts of the list items. That is where I do not know what to do.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Pages.Routines.TopLevelRoutinesPage"
xmlns:statics="clr-namespace:MyApp.Statics;assembly=MyApp"
xmlns:controls="clr-namespace:MyApp.Controls;assembly=MyApp">
<ContentPage.Resources>
<ResourceDictionary>
<controls:RoutinesDataTemplateSelector x:Key="RoutinesDataTemplateSelector"></controls:RoutinesDataTemplateSelector>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Orientation="Vertical"
Spacing="0">
<ListView ItemsSource="{Binding SelectedRoutineTree}"
ItemTemplate="{StaticResource RoutinesDataTemplateSelector}"
x:Name="RoutinesView"
ItemSelected="RoutineClicked"
Margin ="0, 8, 0, 0">
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
The code-behind:
using MyApp.ViewModels;
using MyCloudContracts.DTOs;
using System;
using System.Linq;
using Xamarin.Forms;
namespace MyApp.Pages.Routines
{
public partial class TopLevelRoutinesPage : ContentPage
{
private TopLevelRoutinesViewModel _viewModel;
private string _projCompName;
public TopLevelRoutinesPage(Guid docId, bool fromCompany, string projCompName)
{
InitializeComponent();
_projCompName = projCompName;
Title = _projCompName;
_viewModel = new TopLevelRoutinesViewModel(docId, fromCompany);
BindingContext = _viewModel;
if (Device.OS == TargetPlatform.Android)
RoutinesView.SeparatorVisibility = SeparatorVisibility.None;
}
private async void RoutineClicked(object sender, SelectedItemChangedEventArgs e)
{
//since this is also called when an item is deselected, return if set to null
if (e.SelectedItem == null)
return;
var selectedRoutine = (PublishedDocumentFragmentDTO)e.SelectedItem;
var fragId = selectedRoutine.FragmentId;
var title = selectedRoutine.Title;
var blobIdStr = selectedRoutine.BlobId;
var blobId = new Guid(blobIdStr);
if (selectedRoutine.Children.Any())
{
var routineTree = _viewModel.SelectedRoutineTree;
var subroutinesPage = new SubroutinesPage(routineTree, fragId, title, blobId, _projCompName);
await Navigation.PushAsync(subroutinesPage);
}
else
{
var routinePage = new RoutinePage(title, blobId);
await Navigation.PushAsync(routinePage);
}
//take away selected background
((ListView)sender).SelectedItem = null;
}
}
}
The DataTemplateSelector
using MyApp.Pages.Routines.CustomCells;
using MyCloudContracts.DTOs;
using Xamarin.Forms;
namespace MyApp.Controls
{
class RoutinesDataTemplateSelector : DataTemplateSelector
{
private readonly DataTemplate _folderDataTemplate;
private readonly DataTemplate _routineDataTemplate;
public RoutinesDataTemplateSelector()
{
_folderDataTemplate = new DataTemplate(typeof(FolderViewCell));
_routineDataTemplate = new DataTemplate(typeof(RoutineViewCell));
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
var chooser = item as PublishedDocumentFragmentDTO;
if (chooser == null)
return null;
else if (chooser.Children.Length == 0)
{
return _routineDataTemplate;
}
else
{
return _folderDataTemplate;
}
}
}
}
And an example of one of my custom ViewCells. I think this is where I am wrong, but I am not sure why. I make the properties, but I do not know how to set them properly.
using Xamarin.Forms;
namespace MyApp.Pages.Routines.CustomCells
{
public class RoutineViewCell : ViewCell
{
public static readonly BindableProperty TitleProperty =
BindableProperty.Create("Title", typeof(string), typeof(RoutineViewCell), "");
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
}
}
Thanks for the help :)
I found the answer. I needed to override OnBindingContextChanged() in the custom cell file. My working code looks like this now:
using Xamarin.Forms;
namespace MyApp.Pages.Routines.CustomCells
{
public class RoutineViewCell : ViewCell
{
public static readonly BindableProperty TitleProperty =
BindableProperty.Create("Title", typeof(string), typeof(RoutineViewCell), "");
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
protected override void OnBindingContextChanged()
{
this.SetBinding(TitleProperty, "Title");
base.OnBindingContextChanged();
}
}
}

INotifyPropertyChanged Xamarin string value just disappears

I have a Xamarin.Forms project with a ListView populated with an ObservableCollection. The class (object) that is each item in the ObservableCollection implements INotifyPropertyChanged. A Color property toggles fine in the UI but a string property disappears and never returns.
I get the initial values from a webservice but then just do a completely static change of the values, for debugging and I still can't figure it out.
At the top of the my ContentPage class I have this:
public ObservableCollection<GroceryItem> oc;
After my webservice has returned with the data I put the data in the ObservableCollection and make that, the ItemsSource for the listview. Like this:
lvGroceries.ItemsSource = oc;
All that works great.
The XAML
<ListView x:Name="lvGroceries" ItemTapped="GroceryPdsItemTapped" >
<ListView.ItemTemplate >
<DataTemplate>
<ViewCell>
<AbsoluteLayout VerticalOptions="Fill">
<Label Text="{Binding GroceryName}" AbsoluteLayout.LayoutBounds="0,0,200,40" ></Label>
<Label Text="{Binding strHomeLoc}" AbsoluteLayout.LayoutBounds="200,0,100,40" ></Label>
<Label Text="{Binding isNeeded}" AbsoluteLayout.LayoutBounds="300,0,50,40" ></Label>
<Label Text="someText" BackgroundColor="{Binding myBackgroundColor}" AbsoluteLayout.LayoutBounds="350,0,50,40" ></Label>
</AbsoluteLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The class - GroceryItem
public class GroceryItem : INotifyPropertyChanged
{
public GroceryItem() { }
public event PropertyChangedEventHandler PropertyChanged;
private string privateIsNeeded;
public string isNeeded
{
get { return privateIsNeeded; }
set
{
privateIsNeeded = value;
OnPropertyChanged();
}
}
private Color theColor;
public Color myBackgroundColor
{
get { return theColor; }
set
{
theColor = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The click handler. I grab an item from the ObservableCollection and change the two properties.
public void GroceryPdsItemTapped(object obj, ItemTappedEventArgs e)
{
if (e.Item == null)
{
return;
}
var g = ((GroceryItem)e.Item);
foreach (var gr in oc)
{
if (gr.GroceryId == "27769")
{ // the UI changes because the myBackgroundColor property in the GroceryItem class is watching for a value change
gr.myBackgroundColor = (gr.myBackgroundColor == Color.Yellow) ? Color.Blue : Color.Yellow;
gr.isNeeded = (gr.isNeeded == "true" || gr.isNeeded == "blah" || gr.isNeeded == "false") ? "notblah" : "blah";
}
}
}
The Color toggles fine in the UI but the isNeeded string value disappears on the first tap and never re-appears
Ideas?
A couple issues here. First is that you need to make your ObservableCollection run OnPropertyChanged() when it has been changed like so:
private ObservableCollection<GroceryItem> _oc;
public ObservableCollection<GroceryItem> oc {
get { return _oc ?? (_oc = new ObservableCollection<GroceryItem>()); }
set {
if(_oc != value) {
_oc = value;
OnPropertyChanged();
}
}
}
Now you should really have all of this in a ViewModel but since you do not, you need to set your ContentPage as the BindingContext from within your code-behind like this:
public partial class MyGroceryPage : ContentPage {
public MyGroceryPage() { BindingContext = this; }
}
You also need to be binding your ObservableCollection to your ListView.ItemSource instead of assigning it. That looks like this:
<ListView ItemSource="{Binding oc}"/>
If you do the above and then you go into your code behind and execute lvGroceries.ItemsSource = oc; then that would overwrite the binding that you did in your XAML, so do not do that. Instead, when you get data from your web service, you would just assign it to your existing ObservableCollection:
public async Task GetGroceryData() {
List<GroceryItem> myData = await GroceryService.GetGroceriesAsync();
oc = new ObservableCollection<GroceryItem>(myData);
}
Try all of that first and if your items are still not updating you might want to try removing them from your ObservableCollection, changing the properties, then adding them back in:
public void GroceryPdsItemTapped(object obj, ItemTappedEventArgs e) {
if (e.Item == null) { return; }
var g = ((GroceryItem)e.Item);
foreach (var gr in oc.ToList()) {
if (gr.GroceryId == "27769") { // the UI changes because the myBackgroundColor property in the GroceryItem class is watching for a value change
oc.Remove(gr);
gr.myBackgroundColor = (gr.myBackgroundColor == Color.Yellow) ? Color.Blue : Color.Yellow;
gr.isNeeded = (gr.isNeeded == "true" || gr.isNeeded == "blah" || gr.isNeeded == "false") ? "notblah" : "blah";
oc.Add(gr);
}
}
}

Resources