I'm trying to use a single Label to display one of the two data fields alternately in Xamarin Forms. Only Label 1 Display the binding field and second Label which I am trying to use a variable "DisplayField" is not displaying either 'Contact_Address' or 'Contact_eMail'
Model class
public class Contacts
{
[PrimaryKey][Autoincrement]
public int Contact_ID { get; set; }
public string Contact_Name { get; set; }
public string Contact_Address { get; set; }
public string Contact_eMail { get; set; }
public string DisplayField { get; set; }
}
XAML page
<StackLayout>
<Button Text="Display Address" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_Address" />
<Button Text="Display Email" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_eMail" />
<Entry HorizontalOptions="FillAndExpand" Text="{Binding DisplayField}" />
<ListView x:Name="listView" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand" >
<Frame >
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding DisplayField}" LineBreakMode="WordWrap" />
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code behind
public partial class FieldSwap : ContentPage
{
readonly FieldViewModel _fieldViewModel;
readonly SQLiteAsyncConnection _connection = DependencyService.Get<ISQLite>().GetConnection();
public ObservableCollection<Contacts> CList { get; set; }
public static string DisplayField { get; private set; }
public static int caseSwitch { get; private set; }
public FieldSwap()
{
InitializeComponent();
_fieldViewModel = new FieldViewModel();
_fieldViewModel.Field = "Contact_Address";
this.BindingContext = _fieldViewModel;
}
public static void SelectField()
{
switch (caseSwitch)
{
case 1:
DisplayField = "Contact_Address";
break;
case 2:
DisplayField = "Contact_eMail";
break;
default:
DisplayField = ("Contact_Address");
break;
}
}
private void Display_Address(object sender, EventArgs e)
{
caseSwitch = 1;
SelectField();
ReadData();
}
private void Display_eMail(object sender, EventArgs e)
{
caseSwitch = 2;
SelectField();
ReadData();
}
public void ReadData()
{
var list = _connection.Table<Contacts>().ToListAsync().Result;
CList = new ObservableCollection<Contacts>(list);
listView.ItemsSource = CList;
}
}
View model class
public class FieldViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
String _field;
public string Field
{
set
{
if (!value.Equals(_field, StringComparison.Ordinal))
{
_field = value;
OnPropertyChanged("DisplayField");
}
}
get
{
return _field;
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
Screen Shot
Screen Shot 2
If you want to display different value in ListView by user selected, I suggest you can use Picker to choose, I do one sample that you can take a look.
<ContentPage.Content>
<StackLayout>
<Picker x:Name="choose" SelectedIndexChanged="choose_SelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Contact_Address</x:String>
<x:String>Contact_eMail</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<ListView
x:Name="listview1"
HasUnevenRows="True"
ItemsSource="{Binding items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label
FontSize="Medium"
LineBreakMode="WordWrap"
Text="{Binding Contact_Name}" />
<Label
IsVisible="{Binding Source={x:Reference root}, Path=BindingContext.selectedm}"
LineBreakMode="WordWrap"
Text="{Binding Contact_eMail}" />
<Label
IsVisible="{Binding Source={x:Reference root}, Path=BindingContext.selecteda}"
LineBreakMode="WordWrap"
Text="{Binding Contact_Address}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
public partial class Page31 : ContentPage, INotifyPropertyChanged
{
public ObservableCollection<Contacts> items { get; set; }
private Boolean _selecteda;
public Boolean selecteda
{
get { return _selecteda; }
set
{
_selecteda = value;
RaisePropertyChanged("selecteda");
}
}
private Boolean _selectedm;
public Boolean selectedm
{
get { return _selectedm; }
set
{
_selectedm = value;
RaisePropertyChanged("selectedm");
}
}
public Page31()
{
InitializeComponent();
items = new ObservableCollection<Contacts>();
for(int i=0;i<20;i++)
{
Contacts contact = new Contacts()
{
Contact_ID = i, Contact_Name = "cherry " + i, Contact_Address = "the street " + i, Contact_eMail = "cherry#outlook.com "+i
};
items.Add(contact);
}
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void choose_SelectedIndexChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
int selectedIndex = picker.SelectedIndex;
if (selectedIndex ==0)
{
selecteda = true;
selectedm = false;
}
else
{
selectedm = true;
selecteda = false;
}
}
}
Related
I have this listview and in the listview I have an ItemTemplate with a DataTemplate and a ViewCell in which I have a checkbox named "box1". I want to make it stay checked when i switch pages, but i can't acess it via name because it is in a DataTemplate and in a ViewCell. I have tried to name all the controls down to the checkbox and get access to it like that, but it does not seem to work...
This is my xaml:
<ListView SeparatorVisibility="None"
BackgroundColor="Transparent"
VerticalOptions="Center"
x:Name="listView"
HasUnevenRows="True"
>
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Meal"
x:Name="mydt"
>
<ViewCell
x:Name="myvc"
>
<Grid BackgroundColor="Transparent"
x:Name="mygrid"
>
<Frame BackgroundColor="Transparent"
CornerRadius="20"
x:Name="myframe"
>
<StackLayout Orientation="Horizontal"
>
<Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
<StackLayout Orientation="Vertical" WidthRequest="300">
<Label VerticalOptions="Start"
FontSize="20"
Text="{Binding Name}"
FontAttributes="Bold"/>
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Ingredients}"/>
<StackLayout Orientation="Horizontal">
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Calories}"
TextColor="OrangeRed"/>
<Label Text="kcal"
FontSize="15"
TextColor="OrangeRed"/>
</StackLayout>
</StackLayout>
<CheckBox
x:Name="box1"
IsChecked="{Binding Checked}"
Color="Green"
Margin="60, 0, 0, 0"
CheckedChanged="box1_CheckedChanged"
BindingContext="{Binding ., Mode=TwoWay}"
/>
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
This is my event handler from the Content Page in cs:
private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var meal = listView.SelectedItem as Meal;
if (listView.SelectedItem != null)
{
if (e.Value == true)
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed + cal;
ch = true;
}
else
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed - cal;
ch = false;
}
}
label_cal.Text = calories_consumed.ToString();
}
This is my updated Meal class using INotifyPropertyChanged:
public class Meal : INotifyPropertyChanged
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string ingredients;
public string Ingredients
{
get { return ingredients; }
set
{
ingredients = value;
OnPropertyChanged(nameof(Ingredients));
}
}
private string calories;
public string Calories
{
get { return calories; }
set
{
calories = value;
OnPropertyChanged(nameof(Calories));
}
}
private bool isChecked;
public bool IsChecked
{
get
{
return isChecked;
}
set
{
isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And this is my code behind for the ContentPage:
protected override async void OnAppearing()
{
base.OnAppearing();
listView.ItemsSource = new ObservableCollection<Meal>(await App.Database.GetMealAsync());
}
public static bool ch;
Event handler for the checkbox in the code behind:
private void box1_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var meal = listView.SelectedItem as Meal;
if (listView.SelectedItem != null)
{
if (e.Value == true)
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed + cal;
ch = true;
}
else
{
long cal = long.Parse(meal.Calories);
calories_consumed = calories_consumed - cal;
ch = false;
}
}
meal.IsChecked = ch;
label_cal.Text = calories_consumed.ToString();
}
To be short you can just save the value in Preferences or make a variable in a model that is static and bind that values OnAppearing
There are mainly two ways that you could store the checkbox's state.
1.You could use Json to Serialize and deserialize the model that has a IsChecked property that binding with the checkbox.For more details, you could refer to this thread.
Code in checkbox_CheckedChanged event:
private void checkbox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
var checkbox = (CheckBox)sender;
var selectMeal = checkbox.BindingContext as Meal;
selectMeal.Checked = e.Value;
//save the data and checkbox state,you could save the data as a json string
string json = JsonConvert.SerializeObject(blistView);
Preferences.Set("listmeals", json);
}
2.You could store the state of the checkbox using sqlite-net-pcl.Please refer to below MS official docs for more details.
And then retrieve the check state of the checkbox via OnAppearing Method.
protected override void OnAppearing()
{
// retrieve the check state of the checkbox in your sqlite database.
base.OnAppearing();
TodoItemDatabase database = await TodoItemDatabase.Instance;
listView.ItemsSource = await database.GetItemsAsync();
}
MS official docs:https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
The way you are connecting your class your view/xaml is incorrect. If you want to do it without MVVM you can go about it by creating a model that implements INotifyPropertyChanged and an ObservableCollection as listView itemssource.
Model class Meal could look like this:
public class Meal : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
private string ingredients;
public string Ingredients
{
get { return ingredients; }
set
{
ingredients = value;
OnPropertyChanged(nameof(Ingredients));
}
}
private string calories;
public string Calories
{
get { return calories; }
set
{
calories = value;
OnPropertyChanged(nameof(Calories));
}
}
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
OnPropertyChanged(nameof(IsChecked));
}
}
#region INotify property changed
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
The MainPage.xaml like this:
<StackLayout>
<ListView SeparatorVisibility="None"
BackgroundColor="Transparent"
VerticalOptions="Center"
x:Name="listView"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:Meal" x:Name="mydt" >
<ViewCell x:Name="myvc" >
<Grid BackgroundColor="Transparent"
x:Name="mygrid" >
<Frame BackgroundColor="Transparent"
CornerRadius="20"
x:Name="myframe">
<StackLayout Orientation="Horizontal">
<Image Source="meal.png" WidthRequest="59" Margin="0, 0, 15, 0"/>
<StackLayout Orientation="Vertical" WidthRequest="300">
<Label VerticalOptions="Start"
FontSize="20"
Text="{Binding Name}"
FontAttributes="Bold"/>
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Ingredients}"/>
<StackLayout Orientation="Horizontal">
<Label VerticalOptions="Start"
FontSize="15"
Text="{Binding Calories}"
TextColor="OrangeRed"/>
<Label Text="kcal" FontSize="15" TextColor="OrangeRed"/>
</StackLayout>
</StackLayout>
<CheckBox x:Name="box1" IsChecked="{Binding IsChecked}" Color="Green" Margin="60, 0, 0, 0" />
</StackLayout>
</Frame>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Clicked="Button_Clicked" Text="Check status items"/>
</StackLayout>
And your MainPage.xaml.cs code behind like this:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
listView.ItemsSource = new ObservableCollection<Meal>(new List<Meal>
{
new Meal { Name = "Meal 01", Ingredients = "Ingredients 01", Calories = "250" },
new Meal { Name = "Meal 02", Ingredients = "Ingredients 02", Calories = "350" },
new Meal { Name = "Meal 03", Ingredients = "Ingredients 03", Calories = "450" }
});
}
/// <summary>
/// check items ischecked status
/// </summary>
private void Button_Clicked(object sender, EventArgs e)
{
foreach (var item in listView.ItemsSource)
{
if (item is Meal meal && meal.IsChecked)
System.Diagnostics.Debug.WriteLine($"{meal.Name} is checked");
}
}
}
Look at the other answer for saving the model and/or list. When saved the list can be retrieved in the OnAppearing and set instead of recreated as shown in this example. I've added a button on the bottom that prints out which Meals are selected in the output window. See screenshot:
I have a Content page, which contain CollectionView inside CarouselView.
First time when the page is loading the double embedded binding is work fine. Show everithing correctly.
But when i try to change embedded property value nothing happen. How update these properties?
formChooseElement.formViewerElements[0].formViewerElementAnswares[0].color=Color.Green;
Like:
<CarouselView ItemsSource="{Binding formViewerElements}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding text}" />
<CollectionView ItemsSource="{Binding formViewerElements}" >
<CollectionView.ItemTemplate>
<DataTemplate >
<StackLayout BackgroundColor="{Binding color}">
<Label Text="{Binding text}" >
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
BindingContext in code behind:
this.BindingContext = new FormViewerViewModell();
ViewModell:
public class FormViewerViewModell : INotifyPropertyChanged
{
public FormViewerViewModell()
{
GenerateData
}
private FormChooseElement FormChooseElement;
public FormChooseElement formChooseElement
{
get => FormChooseElement;
set
{
FormChooseElement = value;
OnPropertyChanged(nameof(FormChooseElement));
}
}
public ObservableCollection<FormViewerElement> formViewerElements
{
get => formChooseElement.formViewerElements;
set
{ formChooseElement.formViewerElements = value;
OnPropertyChanged(nameof(formChooseElement.formViewerElements));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
FormChooseElement
public class FormChooseElement
{
public ...
public ObservableCollection<FormViewerElement> formViewerElements { get; set; }
}
FormViewerElement
public class FormViewerElement
{
public ...
public ObservableCollection<FormViewerElementAnsware> formViewerElementAnswares { get; set; }
}
FormViewerElementAnsware
public class FormViewerElementAnsware
{
public ...
public Color color { get; set; };
I'm trying to use a single Label to display one of the two data fields alternately in Xamarin Forms. Only Label 1 Displaying the binding field (Contact_Name), while second Label which I am trying to use a variable "DisplayField" is not displaying either 'Contact_Address' or 'Contact_eMail' .
Question posted before and Another user tried to help but it didn't work!
Model Class
public class Contacts
{
[PrimaryKey][Autoincrement]
public int Contact_ID { get; set; }
public string Contact_Name { get; set; }
public string Contact_Address { get; set; }
public string Contact_eMail { get; set; }
}
XAML Page
<StackLayout>
<Button Text="Display Address" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_Address" />
<Button Text="Display Email" FontSize="Large" HorizontalOptions="Center" VerticalOptions="Fill" Clicked="Display_eMail" />
<Entry HorizontalOptions="FillAndExpand" Text="{Binding DisplayField}" />
<ListView x:Name="listView" HasUnevenRows="True" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<StackLayout Orientation="Vertical" VerticalOptions="CenterAndExpand" >
<Frame >
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding DisplayField}" LineBreakMode="WordWrap" />
</StackLayout>
</Frame>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
Code Behind
public partial class FieldSwap : ContentPage
{
readonly FieldViewModel _fieldViewModel;
readonly SQLiteAsyncConnection _connection = DependencyService.Get<ISQLite>().GetConnection();
public ObservableCollection<Contacts> CList { get; set; }
public static string DisplayField { get; private set; }
public static int caseSwitch { get; private set; }
public FieldSwap()
{
InitializeComponent();
_fieldViewModel = new FieldViewModel();
_fieldViewModel.Field = "Contact_Address";
this.BindingContext = _fieldViewModel;
}
public static void SelectField()
{
switch (caseSwitch)
{
case 1:
DisplayField = "Contact_Address";
break;
case 2:
DisplayField = "Contact_eMail";
break;
default:
DisplayField = ("Contact_Address");
break;
}
}
private void Display_Address(object sender, EventArgs e)
{
caseSwitch = 1;
SelectField();
ReadData();
}
private void Display_eMail(object sender, EventArgs e)
{
caseSwitch = 2;
SelectField();
ReadData();
}
public void ReadData()
{
var list = _connection.Table<Contacts>().ToListAsync().Result;
CList = new ObservableCollection<Contacts>(list);
listView.ItemsSource = CList;
}
}
View Model Class
public class FieldViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
String _field;
public string Field
{
set
{
if (!value.Equals(_field, StringComparison.Ordinal))
{
_field = value;
OnPropertyChanged("DisplayField");
}
}
get
{
return _field;
}
}
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
You could use IsVisible property to achieve that, not need to bind only one lable.
Therefore, binding Contact_Address and Contact_eMail with two lables in StackLayout as follows:
<StackLayout Orientation="Vertical" VerticalOptions="Center">
<Label Text="{Binding Contact_Name}" FontSize="Medium" LineBreakMode="WordWrap" />
<Label Text="{Binding Contact_Address}" IsVisible="{Binding AddressVisible}" LineBreakMode="WordWrap" />
<Label Text="{Binding Contact_eMail}" IsVisible="{Binding EMailVisible}" LineBreakMode="WordWrap" />
</StackLayout>
Then in Contacts add two visiable proerty:
public class Contacts: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
...
private bool addressVisible;
public bool AddressVisible
{
set
{
if (addressVisible != value)
{
addressVisible = value;
OnPropertyChanged("AddressVisible");
}
}
get
{
return addressVisible;
}
}
private bool eMailVisible;
public bool EMailVisible
{
set
{
if (eMailVisible != value)
{
eMailVisible = value;
OnPropertyChanged("EMailVisible");
}
}
get
{
return eMailVisible;
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now in Contentpage, you could modify the visiable propery when button be clicked:
private void Display_Address(object sender, EventArgs e)
{
foreach(var item in CList )
{
item.AddressVisible = true;
item.EMailVisible = false;
}
}
private void Display_eMail(object sender, EventArgs e)
{
foreach (var item in CList )
{
item.AddressVisible = false;
item.EMailVisible = true;
}
}
Here is the effect:
Following this example to create a grouping for CollectionView, I notice that none of the properties are INotifyPropertyChanged, nor is the base class an ObservableCollection.
While the latter is easy to fix by changing List to ObservableCollection:
public class AnimalGroup : ObservableCollection<Animal>
{
public string Name { get; private set; }
public AnimalGroup(string name, ObservableCollection<Animal> animals) : base(animals)
{
Name = name;
}
private string _someOtherPropertyIWantToChangeAtRuntime = "hey";
public string SomeOtherPropertyIWantToChangeAtRuntime { get => _someOtherPropertyIWantToChangeAtRuntime, set => SetProperty(ref _someOtherPropertyIWantToChangeAtRuntime, value); }
}
It isn't clear how to make Name, or any other property (e.g. SomeOtherPropertyIWantToChangeAtRuntime), I want to associate with the group as an INotifyPropertyChanged. Treating it is as a normal class by adding the interface to base causes this warning:
Base interface 'INotifyPropertyChanged' is redundant because AnimalGroup inherits 'ObservableCollection'
Yet, there is nothing for the setter to call, such as SetProperty(ref _name, Value) and the existing PropertyChanged object is just for monitoring a group's collection changes. It isn't invokable, just handleable.
If I ignore the warning and implement INotifyPropertyChanged anyway (and name my event PropChanged to avoid colliding with ObservableCollection.PropertyChanged),
protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName]string propertyName = "", Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
PropChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
public event PropertyChangedEventHandler PropChanged;
and let my ViewModel manage the value of SomeOtherPropertyIWantToChangeAtRuntime, the bound <Label> never sees any changes.
<CollectionView ItemsSource="{Binding AnimalGroups}" HorizontalOptions="FillAndExpand">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label
Text="{Binding Name}"
HorizontalOptions="Start"
FontSize="24.44"
TextColor="Black"
FontAttributes="Bold"
Margin="0,0,0,10"/>
<Label
Text="{Binding SomeOtherPropertyIWantToChangeAtRuntime}" FontSize="15"
TextColor="Black"
Margin="0,0,0,0">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding BindingContext.FindGroupAndChangeTextCommand, Source{x:Reference thisPageName}" CommandParameter="{Binding Name}"/>
</Label.GestureRecognizers>
</Label>
...
ViewModel:
public ObservableCollection<AnimalGroup> AnimalGroups {get; private set;}
public ICommand FindGroupAndChangeTextCommand {get; private set;}
public void FindGroupAndChangeText(string name)
{
var group = AnimalGroups.FirstOrDefault(t => t.Name == name);
if (group != null)
group.SomeOtherPropertyIWantToChangeAtRuntime = DateTime.Now.ToString();
}
ViewModel()
{
AnimalGroups = LoadData(); // not shown
FindGroupAndChangeTextCommand = new Command(FindGroupAndChangeText);
}
The result is that the label remains "hey" (which is the default value) and never changes even though I can see that the above command fires and the code finds the group and sets the text.
Agree with Jason, ObservableCollection has inherited INotifyPropertyChanged interface , So you will get the warning
Base interface 'INotifyPropertyChanged' is redundant because AnimalGroup inherits 'ObservableCollection'
And please see following screenshot about ObservableCollection<T>.
If you want to change the item at the runtime like this GIF.
Based on your code. I add two properties in the Animal class. For achieve the change the text of properties at the runtime, we can achieve the INotifyPropertyChanged in Animal class. Here is AnimalGroup.cs
public class AnimalGroup : ObservableCollection<Animal>
{
public string Name { get; private set; }
public AnimalGroup(string name, ObservableCollection<Animal> animals) : base(animals)
{
Name = name;
}
}
public class Animal : INotifyPropertyChanged
{
string animalName;
public string AnimalName
{
set
{
if (animalName != value)
{
animalName = value;
OnPropertyChanged("AnimalName");
}
}
get
{
return animalName;
}
}
string animalArea;
public string AnimalArea
{
set
{
if (animalArea != value)
{
animalArea = value;
OnPropertyChanged("AnimalArea");
}
}
get
{
return animalArea;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
For testing click the command, I achieve the MyAnimalViewModel.cs like following code.
public class MyAnimalViewModel
{
public ObservableCollection<AnimalGroup> AnimalGroups { get; private set; } = new ObservableCollection<AnimalGroup>();
public ICommand FindGroupAndChangeTextCommand { protected set; get; }
public MyAnimalViewModel()
{
ObservableCollection<Animal> ts = new ObservableCollection<Animal>();
ts.Add(new Animal() { AnimalArea = "Asia", AnimalName = "cat" });
ts.Add(new Animal() { AnimalArea = "Asia", AnimalName = "dog" });
ObservableCollection<Animal> ts2 = new ObservableCollection<Animal>();
ts2.Add(new Animal() { AnimalArea = "Eourp", AnimalName = "keep" });
ts2.Add(new Animal() { AnimalArea = "Eourp", AnimalName = "gggg" });
AnimalGroups.Add(new AnimalGroup("Animal1", ts));
AnimalGroups.Add(new AnimalGroup("Animal2", ts2));
FindGroupAndChangeTextCommand = new Command<Animal>((key) =>
{
key.AnimalName = "testggggg";
});
}
}
I notice you want to achieve the group for CollectionView. Here is my edited layout.
<ContentPage.Content>
<CollectionView x:Name="MyCollectionView" ItemsSource="{Binding AnimalGroups}" IsGrouped="True" HorizontalOptions="FillAndExpand">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical"/>
</CollectionView.ItemsLayout>
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding Name}"
BackgroundColor="LightGray"
FontSize="Large"
FontAttributes="Bold" >
</Label>
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label
Text="{Binding AnimalArea}"
HorizontalOptions="Start"
FontSize="24.44"
TextColor="Black"
FontAttributes="Bold"
Margin="0,0,0,10"/>
<Label
Text="{Binding AnimalName}" FontSize="15"
TextColor="Black"
Margin="0,0,0,0">
<Label.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1"
Command="{ Binding BindingContext.FindGroupAndChangeTextCommand, Source={x:Reference Name=MyCollectionView} }" CommandParameter="{Binding .}"
/>
</Label.GestureRecognizers>
</Label>
</StackLayout>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</ContentPage.Content>
Here is layout background code.
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
this.BindingContext = new MyAnimalViewModel();
}
}
I have created dynamic 20 entries and want to focus on next entry after user enter a digit and max length of entry is 1. The focus should be automatically move on next entry.I am sharing my code.Thanks in advance for help.
//model
public class CrossingUIModel
{
public int Id { get; set; }
public string FieldValue { get; set; }
}
//on change property
private ObservableCollection<CrossingUIModel> bindCrossingUIModel;
public ObservableCollection<CrossingUIModel> BindCrossingUIModel
{
get { return bindCrossingUIModel; }
set
{
bindCrossingUIModel = value;
OnPropertyChanged(nameof(BindCrossingUIModel));
}
}
//creating ui
public void CreateUI()
{
UserDialogs.Instance.ShowLoading();
BindCrossingUIModel = new ObservableCollection<CrossingUIModel>();
for (int i = 1; i < 21; i++)
{
CrossingUIModel model = new CrossingUIModel();
model.Id = i;
BindCrossingUIModel.Add(model);
}
UserDialogs.Instance.HideLoading();
}
//xml file
<CollectionView x:Name="CrossingView" ItemsSource="{Binding BindCrossingUIModel, Mode=TwoWay}" SelectionMode="Multiple">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="FillAndExpand">
<Entry x:Name="Fields" Text="{Binding FieldValue, Mode=TwoWay}"
ReturnType="Next" MaxLength="1" Keyboard="Numeric"
TextChanged="Fields_TextChanged" ></Entry>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Since you had used Data-Binding , it would be better to handle all the logic in ViewModel .
in code behind
Define a custom Entry
public class MyEntry:Entry
{
public static readonly BindableProperty IsFocusProperty =BindableProperty.Create("IsFocus", typeof(bool), typeof(MyEntry), false,propertyChanged: OnChanged);
static void OnChanged(BindableObject bindable, object oldValue, object newValue)
{
var entry = bindable as MyEntry;
var focus = (bool)newValue;
if(focus)
{
entry.Focus();
}
else
{
entry.Unfocus();
}
}
public bool IsFocus
{
get { return (bool)GetValue(IsFocusProperty); }
set {
SetValue(IsFocusProperty, value);
}
}
public MyEntry()
{
this.Focused += MyEntry_Focused;
this.Unfocused += MyEntry_Unfocused;
}
private void MyEntry_Unfocused(object sender, FocusEventArgs e)
{
this.IsFocus = false;
}
private void MyEntry_Focused(object sender, FocusEventArgs e)
{
this.IsFocus = true;
}
}
in Model
public class CrossingUIModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int Id { get; set; }
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string fieldValue;
public string FieldValue
{
get
{
return fieldValue;
}
set
{
if (fieldValue != value)
{
fieldValue = value;
OnPropertyChanged("FieldValue");
}
}
}
bool isFocus = false;
public bool IsFocus
{
get
{
return isFocus;
}
set
{
if (isFocus != value)
{
isFocus = value;
OnPropertyChanged("IsFocus");
}
}
}
}
in ViewModel
public class MyViewModel
{
public ObservableCollection<CrossingUIModel> BindCrossingUIModel { get; set; }
public MyViewModel()
{
BindCrossingUIModel = new ObservableCollection<CrossingUIModel>();
for (int i = 1; i < 21; i++)
{
CrossingUIModel model = new CrossingUIModel();
model.Id = i;
BindCrossingUIModel.Add(model);
}
foreach (CrossingUIModel model in BindCrossingUIModel)
{
model.PropertyChanged += Model_PropertyChanged;
}
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName== "FieldValue")
{
var model = sender as CrossingUIModel;
if(model.FieldValue.Length==1)
{
model.FieldValue = model.FieldValue.Substring(0, 1);
model.IsFocus = false;
int id = model.Id;
BindCrossingUIModel[id].IsFocus = true;
}
}
}
}
in xaml
Now we don't need to set MaxLength and TextChanged any more .
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<CollectionView x:Name="CrossingView" ItemsSource="{Binding BindCrossingUIModel, Mode=TwoWay}" SelectionMode="Multiple">
<CollectionView.ItemsLayout>
<GridItemsLayout Orientation="Vertical" Span="10" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout WidthRequest="100" HeightRequest="30" HorizontalOptions="FillAndExpand">
<local:MyEntry WidthRequest="80" BackgroundColor="LightBlue" HeightRequest="30" x:Name="Fields" Text="{Binding FieldValue, Mode=TwoWay}"
IsFocus="{Binding IsFocus, Mode=TwoWay}"
ReturnType="Next" Keyboard="Numeric"
></local:MyEntry>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
By the way , you could use Grid instead of StackLayout as the Parent Layout of the Entry.